前言

谈起当前前端最热门的 js 框架,必少不了

Vue、React、Angular,对于大多数人来说,我们更多的是在使用框架,对于框架解决痛点背后使用的基本原理往往关注不多,近期在研读

Vue.js

源码,也在写源码解读的系列文章。和多数源码解读的文章不同的是,我会尝试从一个初级前端的角度入手,由浅入深去讲解源码实现思路和基本的语法知识,通过一些基础事例一步步去实现一些小功能。

本场 Chat 是系列 Chat

的开篇,我会首先讲解一下数据双向绑定的基本原理,介绍对比一下三大框架的不同实现方式,同时会一步步完成一个简单的mvvm示例。读源码不是目的,只是一种学习的方式,目的是在读源码的过程中提升自己,学习基本原理,拓展编码的思维方式。

模板引擎实现原理

对于页面渲染,一般分为服务器端渲染和浏览器端渲染。一般来说服务器端吐html页面的方式渲染速度更快、更利于SEO,但是浏览器端渲染更利于提高开发效率和减少维护成本,是一种相关舒服的前后端协作模式,后端提供接口,前端做视图和交互逻辑。前端通过Ajax请求数据然后拼接html字符串或者使用js模板引擎、数据驱动的框架如Vue进行页面渲染。

在ES6和Vue这类框架出现以前,前端绑定数据的方式是动态拼接html字符串和js模板引擎。模板引擎起到数据和视图分离的作用,模板对应视图,关注如何展示数据,在模板外头准备的数据,

关注那些数据可以被展示。模板引擎的工作原理可以简单地分成两个步骤:模板解析 / 编译(Parse /

Compile)和数据渲染(Render)两部分组成,当今主流的前端模板有三种方式:

String-based templating (基于字符串的parse和compile过程)

Dom-based templating (基于Dom的link或compile过程)

Living templating (基于字符串的parse 和 基于dom的compile过程)

String-based templating

基于字符串的模板引擎,本质上依然是字符串拼接的形式,只是一般的库做了封装和优化,提供了更多方便的语法简化了我们的工作。基本原理如下:

典型的库:

art-template

mustache.js

doT

之前的一篇文章中我介绍了js模板引擎的实现思路,感兴趣的朋友可以看看这里:JavaScript进阶学习(一)——

基于正则表达式的简单js模板引擎实现。这篇文章中我们利用正则表达式实现了一个简单的js模板引擎,利用正则匹配查找出模板中{{}}之间的内容,然后替换为模型中的数据,从而实现视图的渲染。

var template = function(tpl, data) {

var re = /{{(.+?)}}/g,

cursor = 0,

reExp = /(^( )?(var|if|for|else|switch|case|break|{|}|;))(.*)?/g,

code = 'var r=[];\n';

// 解析html

function parsehtml(line) {

// 单双引号转义,换行符替换为空格,去掉前后的空格

line = line.replace(/('|")/g, '\\$1').replace(/\n/g, ' ').replace(/(^\s+)|(\s+$)/g,"");

code +='r.push("' + line + '");\n';

}

// 解析js代码

function parsejs(line) {

// 去掉前后的空格

line = line.replace(/(^\s+)|(\s+$)/g,"");

code += line.match(reExp)? line + '\n' : 'r.push(' + 'this.' + line + ');\n';

}

// 编译模板

while((match = re.exec(tpl))!== null) {

// 开始标签  {{ 前的内容和结束标签 }} 后的内容

parsehtml(tpl.slice(cursor, match.index));

// 开始标签  {{ 和 结束标签 }} 之间的内容

parsejs(match[1]);

// 每一次匹配完成移动指针

cursor = match.index + match[0].length;

}

// 最后一次匹配完的内容

parsehtml(tpl.substr(cursor, tpl.length - cursor));

code += 'return r.join("");';

return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);

}

源代码:http://jsfiddle.net/zhaomenghuan/bw468orv/embedded/

现在ES6支持了模板字符串,我们可以用比较简单的代码就可以实现类似的功能:

const template = data => `

name: ${data.name}

age: ${data.profile.age}

${data.skills.map(skill => `

${skill}

`).join('')}

`

const data = {

name: 'zhaomenghuan',

profile: { age: 24 },

skills: ['html5', 'javascript', 'android']

}

document.body.innerHTML = template(data)

Dom-based templating

Dom-based templating 则是从DOM的角度去实现数据的渲染,我们通过遍历DOM树,提取属性与DOM内容,然后将数据写入到DOM树中,从而实现页面渲染。一个简单的例子如下:

function MVVM(opt) {

this.dom = document.querySelector(opt.el);

this.data = opt.data || {};

this.renderDom(this.dom);

}

MVVM.prototype = {

init: {

sTag: '{{',

eTag: '}}'

},

render: function (node) {

var self = this;

var sTag = self.init.sTag;

var eTag = self.init.eTag;

var matchs = node.textContent.split(sTag);

if (matchs.length){

var ret = '';

for (var i = 0; i

var match = matchs[i].split(eTag);

if (match.length == 1) {

ret += matchs[i];

} else {

ret = self.data[match[0]];

}

node.textContent = ret;

}

}

},

renderDom: function(dom) {

var self = this;

var attrs = dom.attributes;

var nodes = dom.childNodes;

Array.prototype.forEach.call(attrs, function(item) {

self.render(item);

});

Array.prototype.forEach.call(nodes, function(item) {

if (item.nodeType === 1) {

return self.renderDom(item);

}

self.render(item);

});

}

}

var app = new MVVM({

el: '#app',

data: {

name: 'zhaomenghuan',

age: '24',

color: 'red'

}

});

源代码:http://jsfiddle.net/zhaomenghuan/6e3yg6Lq/embedded/

页面渲染的函数 renderDom

是直接遍历DOM树,而不是遍历html字符串。遍历DOM树节点属性(attributes)和子节点(childNodes),然后调用渲染函数render。当DOM树子节点的类型是元素时,递归调用遍历DOM树的方法。根据DOM树节点类型一直遍历子节点,直到文本节点。

render的函数作用是提取{{}}中的关键词,然后使用数据模型中的数据进行替换。我们通过textContent获取Node节点的nodeValue,然后使用字符串的split方法对nodeValue进行分割,提取{{}}中的关键词然后替换为数据模型中的值。

DOM 的相关基础

注:元素类型对应NodeType

元素类型

NodeType

元素

1

属性

2

文本

3

注释

8

文档

9

childNodes 属性返回包含被选节点的子节点的

NodeList。childNodes包含的不仅仅只有html节点,所有属性,文本、注释等节点都包含在childNodes里面。children只返回元素如input,

span, script, div等,不会返回TextNode,注释。

数据双向绑定实现原理

js模板引擎可以认为是一个基于MVC的结构,我们通过建立模板作为视图,然后通过引擎函数作为控制器实现数据和视图的绑定,从而实现实现数据在页面渲染,但是当数据模型发生变化时,视图不能自动更新;当视图数据发生变化时,模型数据不能实现更新,这个时候双向数据绑定应运而生。检测视图数据更新实现数据绑定的方法有很多种,目前主要分为三个流派,Angular使用的是脏检查,只在特定的事件下才会触发视图刷新,Vue使用的是Getter/Setter机制,而React则是通过

Virtual DOM 算法检查DOM的变动的刷新机制。

本文限于篇幅和内容在此只探讨一下 Vue.js 数据绑定的实现,对于 angular 和 react

后续再做说明,读者也可以自行阅读源码。Vue 监听数据变化的机制是把一个普通 JavaScript 对象传给 Vue 实例的 data

选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为

getter/setter。Vue 2.x 对 Virtual DOM 进行了支持,这部分内容后续我们再做探讨。

引子

为了更好的理解Vue中视图和数据更新的机制,我们先看一个简单的例子:

var o = {

a: 0

}

Object.defineProperty(o, "b", {

get: function () {

return this.a + 1;

},

set: function (value) {

this.a = value / 2;

}

});

console.log(o.a); // "0"

console.log(o.b); // "1"

// 更新o.a

o.a = 5;

console.log(o.a); // "5"

console.log(o.b); // "6"

// 更新o.b

o.b = 10;

console.log(o.a); // "5"

console.log(o.b); // "6"

这里我们可以看出对象o的b属性的值依赖于a属性的值,同时b属性值的变化又可以改变a属性的值,这个过程相关的属性值的变化都会影响其他相关的值进行更新。反过来我们看看如果不使用Object.defineProperty()方法,上述的问题通过直接给对象属性赋值的方法实现,代码如下

var o = {

a: 0

}

o.b = o.a + 1;

console.log(o.a); // "0"

console.log(o.b); // "1"

// 更新o.a

o.a = 5;

o.b = o.a + 1;

console.log(o.a); // "5"

console.log(o.b); // "6"

// 更新o.b

o.b = 10;

o.a = o.b / 2;

o.b = o.a + 1;

console.log(o.a); // "5"

console.log(o.b); // "6"

很显然使用Object.defineProperty()方法可以更方便的监听一个对象的变化。当我们的视图和数据任何一方发生变化的时候,我们希望能够通知对方也更新,这就是所谓的数据双向绑定。既然明白这个道理我们就可以看看Vue源码中相关的处理细节。

Object.defineProperty()

Object.defineProperty()方法可以直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

语法:Object.defineProperty(obj, prop, descriptor)

参数:

obj:需要定义属性的对象。

prop:需被定义或修改的属性名。

descriptor:需被定义或修改的属性的描述符。

返回值:返回传入函数的对象,即第一个参数obj

该方法重点是描述,对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。描述符必须是两种形式之一;不能同时是两者。

数据描述符和存取描述符均具有以下可选键值:

configurable:当且仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除。默认为 false。

enumerable:当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。

数据描述符同时具有以下可选键值:

value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。

writable:当且仅当仅当该属性的writable为 true 时,该属性才能被赋值运算符改变。默认为 false。

存取描述符同时具有以下可选键值:

get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为undefined。

set:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为undefined。

我们可以通过Object.defineProperty()方法精确添加或修改对象的属性。比如,直接赋值创建的属性默认情况是可以枚举的,但是我们可以通过Object.defineProperty()方法设置enumerable属性为false为不可枚举。

var obj = {

a: 0,

b: 1

}

for (var prop in obj) {

console.log(`obj.${prop} = ${obj[prop]}`);

}

结果:

"obj.a = 0"

"obj.b = 1"

我们通过Object.defineProperty()修改如下:

var obj = {

a: 0,

b: 1

}

Object.defineProperty(obj, 'b', {

enumerable: false

})

for (var prop in obj) {

console.log(`obj.${prop} = ${obj[prop]}`);

}

结果:

"obj.a = 0"

这里需要说明的是我们使用Object.defineProperty()默认情况下是enumerable属性为false,例如:

var obj = {

a: 0

}

Object.defineProperty(obj, 'b', {

value: 1

})

for (var prop in obj) {

console.log(`obj.${prop} = ${obj[prop]}`);

}

结果:

"obj.a = 0"

其他描述属性使用方法类似,不做赘述。Vue源码core/util/lang.jsS中定义了这样一个方法:

/**

* Define a property.

*/

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {

Object.defineProperty(obj, key, {

value: val,

enumerable: !!enumerable,

writable: true,

configurable: true

})

}

Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor() 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

语法:Object.getOwnPropertyDescriptor(obj, prop)

参数:

obj:在该对象上查看属性

prop:一个属性名称,该属性的属性描述符将被返回

返回值:如果指定的属性存在于对象上,则返回其属性描述符(property descriptor),否则返回 undefined。可以访问“属性描述符”内容,例如前面的例子:

var o = {

a: 0

}

Object.defineProperty(o, "b", {

get: function () {

return this.a + 1;

},

set: function (value) {

this.a = value / 2;

}

});

var des = Object.getOwnPropertyDescriptor(o,'b');

console.log(des);

console.log(des.get);

Vue源码分析

本次我们主要分析一下Vue 数据绑定的源码,这里我直接将 Vue.js 1.0.28 版本的代码稍作删减拿过来进行,2.x 的代码基于

flow 静态类型检查器书写的,代码除了编码风格在整体结构上基本没有太大改动,所以依然基于 1.x 进行分析,对于存在差异的部分加以说明。

监听对象变动

// 观察者构造函数

function Observer (value) {

this.value = value

this.walk(value)

}

// 递归调用,为对象绑定getter/setter

Observer.prototype.walk = function (obj) {

var keys = Object.keys(obj)

for (var i = 0, l = keys.length; i

this.convert(keys[i], obj[keys[i]])

}

}

// 将属性转换为getter/setter

Observer.prototype.convert = function (key, val) {

defineReactive(this.value, key, val)

}

// 创建数据观察者实例

function observe (value) {

// 当值不存在或者不是对象类型时,不需要继续深入监听

if (!value || typeof value !== 'object') {

return

}

return new Observer(value)

}

// 定义对象属性的getter/setter

function defineReactive (obj, key, val) {

var property = Object.getOwnPropertyDescriptor(obj, key)

if (property && property.configurable === false) {

return

}

// 保存对象属性预先定义的getter/setter

var getter = property && property.get

var setter = property && property.set

var childOb = observe(val)

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter () {

var value = getter ? getter.call(obj) : val

console.log("访问:"+key)

return value

},

set: function reactiveSetter (newVal) {

var value = getter ? getter.call(obj) : val

if (newVal === value) {

return

}

if (setter) {

setter.call(obj, newVal)

} else {

val = newVal

}

// 对新值进行监听

childOb = observe(newVal)

console.log('更新:' + key + ' = ' + newVal)

}

})

}

定义一个对象作为数据模型,并监听这个对象。

let data = {

user: {

name: 'zhaomenghuan',

age: '24'

},

address: {

city: 'beijing'

}

}

observe(data)

console.log(data.user.name)

// 访问:user

// 访问:name

data.user.name = 'ZHAO MENGHUAN'

// 访问:user

// 更新:name = ZHAO MENGHUAN

效果如下:

监听数组变动

上面我们通过Object.defineProperty把对象的属性全部转为 getter/setter

从而实现监听对象的变动,但是对于数组对象无法通过Object.defineProperty实现监听。Vue

包含一组观察数组的变异方法,所以它们也将会触发视图更新。

const arrayProto = Array.prototype

const arrayMethods = Object.create(arrayProto)

function def(obj, key, val, enumerable) {

Object.defineProperty(obj, key, {

value: val,

enumerable: !!enumerable,

writable: true,

configurable: true

})

}

// 数组的变异方法

;[

'push',

'pop',

'shift',

'unshift',

'splice',

'sort',

'reverse'

]

.forEach(function (method) {

// 缓存数组原始方法

var original = arrayProto[method]

def(arrayMethods, method, function mutator () {

var i = arguments.length

var args = new Array(i)

while (i--) {

args[i] = arguments[i]

}

console.log('数组变动')

return original.apply(this, args)

})

})

Vue.js 1.x 在Array.prototype原型对象上添加了$set 和 $remove方法,在2.X后移除了,使用全局 API Vue.set 和 Vue.delete代替了,后续我们再分析。

定义一个数组作为数据模型,并对这个数组调用变异的七个方法实现监听。

let skills = ['JavaScript', 'Node.js', 'html5']

// 原型指针指向具有变异方法的数组对象

skills.__proto__ = arrayMethods

skills.push('java')

// 数组变动

skills.pop()

// 数组变动

效果如下:

我们将需要监听的数组的原型指针指向我们定义的数组对象,这样我们的数组在调用上面七个数组的变异方法时,能够监听到变动从而实现对数组进行跟踪。

对于__proto__属性,在ES2015中正式被加入到规范中,标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,所以

Vue

是先进行了判断,当__proto__属性存在时将原型指针__proto__指向具有变异方法的数组对象,不存在时直接将具有变异方法挂在需要追踪的对象上。

我们可以在上面Observer观察者构造函数中添加对数组的监听,源码如下:

const hasProto = '__proto__' in {}

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

// 观察者构造函数

function Observer (value) {

this.value = value

if (Array.isArray(value)) {

var augment = hasProto

? protoAugment

: copyAugment

augment(value, arrayMethods, arrayKeys)

this.observeArray(value)

} else {

this.walk(value)

}

}

// 观察数组的每一项

Observer.prototype.observeArray = function (items) {

for (var i = 0, l = items.length; i

observe(items[i])

}

}

// 将目标对象/数组的原型指针__proto__指向src

function protoAugment (target, src) {

target.__proto__ = src

}

// 将具有变异方法挂在需要追踪的对象上

function copyAugment (target, src, keys) {

for (var i = 0, l = keys.length; i

var key = keys[i]

def(target, key, src[key])

}

}

原型链

对于不了解原型链的朋友可以看一下我这里画的一个基本关系图:

原型对象是构造函数的prototype属性,是所有实例化对象共享属性和方法的原型对象;

实例化对象通过new构造函数得到,都继承了原型对象的属性和方法;

原型对象中有个隐式的constructor,指向了构造函数本身。

Object.create

Object.create 使用指定的原型对象和其属性创建了一个新的对象。

const arrayProto = Array.prototype

const arrayMethods = Object.create(arrayProto)

这一步是通过 Object.create

创建了一个原型对象为Array.prototype的空对象。然后通过Object.defineProperty方法对这个对象定义几个变异的数组方法。有些新手可能会直接修改

Array.prototype 上的方法,这是很危险的行为,这样在引入的时候会全局影响Array

对象的方法,而使用Object.create实质上是完全了一份拷贝,新生成的arrayMethods对象的原型指针__proto__指向了Array.prototype,修改arrayMethods

对象不会影响Array.prototype。

基于这种原理,我们通常会使用Object.create 实现类式继承。

// 实现继承

var extend = function(Child, Parent) {

// 拷贝Parent原型对象

Child.prototype = Object.create(Parent.prototype);

// 将Child构造函数赋值给Child的原型对象

Child.prototype.constructor = Child;

}

// 实例

var Parent = function () {

this.name = 'Parent';

}

Parent.prototype.getName = function () {

return this.name;

}

var Child = function () {

this.name = 'Child';

}

extend(Child, Parent);

var child = new Child();

console.log(child.getName())

发布-订阅模式

在上面一部分我们通过Object.defineProperty把对象的属性全部转为 getter/setter 以及

数组变异方法实现了对数据模型变动的监听,在数据变动的时候,我们通过console.log打印出来提示了,但是对于框架而言,我们相关的逻辑如果直接写在那些地方,自然是不够优雅和灵活的,这个时候就需要引入常用的设计模式去实现,vue.js采用了发布-订阅模式。发布-订阅模式主要是为了达到一种“高内聚、低耦合”的效果。

Vue的Watcher订阅者作为Observer和Compile之间通信的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。

/**

* 观察者对象

*/

function Watcher(vm, expOrFn, cb) {

this.vm = vm

this.cb = cb

this.depIds = {}

if (typeof expOrFn === 'function') {

this.getter = expOrFn

} else {

this.getter = this.parseExpression(expOrFn)

}

this.value = this.get()

}

/**

* 收集依赖

*/

Watcher.prototype.get = function () {

// 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者

Dep.target = this

// 触发getter,将自身添加到dep中

const value = this.getter.call(this.vm, this.vm)

// 依赖收集完成,置空,用于下一个Watcher使用

Dep.target = null

return value

}

Watcher.prototype.addDep = function (dep) {

if (!this.depIds.hasOwnProperty(dep.id)) {

dep.addSub(this)

this.depIds[dep.id] = dep

}

}

/**

* 依赖变动更新

*

* @param {Boolean} shallow

*/

Watcher.prototype.update = function () {

this.run()

}

Watcher.prototype.run = function () {

var value = this.get()

if (value !== this.value) {

var oldValue = this.value

this.value = value

// 将newVal, oldVal挂载到MVVM实例上

this.cb.call(this.vm, value, oldValue)

}

}

Watcher.prototype.parseExpression = function (exp) {

if (/[^\w.$]/.test(exp)) {

return

}

var exps = exp.split('.')

return function(obj) {

for (var i = 0, len = exps.length; i

if (!obj) return

obj = obj[exps[i]]

}

return obj

}

}

Dep 是一个数据结构,其本质是维护了一个watcher队列,负责添加watcher,更新watcher,移除watcher,通知watcher更新。

let uid = 0

function Dep() {

this.id = uid++

this.subs = []

}

Dep.target = null

/**

* 添加一个订阅者

*

* @param {Directive} sub

*/

Dep.prototype.addSub = function (sub) {

this.subs.push(sub)

}

/**

* 移除一个订阅者

*

* @param {Directive} sub

*/

Dep.prototype.removeSub = function (sub) {

let index = this.subs.indexOf(sub);

if (index !== -1) {

this.subs.splice(index, 1);

}

}

/**

* 将自身作为依赖添加到目标watcher

*/

Dep.prototype.depend = function () {

Dep.target.addDep(this)

}

/**

* 通知数据变更

*/

Dep.prototype.notify = function () {

var subs = toArray(this.subs)

// stablize the subscriber list first

for (var i = 0, l = subs.length; i

// 执行订阅者的update更新函数

subs[i].update()

}

}

模板编译

compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

这种实现和我们讲到的Dom-based

templating类似,只是更加完备,具有自定义指令的功能。在遍历节点属性和文本节点的时候,可以编译具备{{}}表达式或v-xxx的属性值的节点,并且通过添加

new Watcher()及绑定事件函数,监听数据的变动从而对视图实现双向绑定。

MVVM实例

在数据绑定初始化的时候,我们需要通过new Observer()来监听数据模型变化,通过new Compile()来解析编译模板指令,并利用Watcher搭起Observer和Compile之间的通信桥梁。

/**

* @class 双向绑定类 MVVM

* @param {[type]} options [description]

*/

function MVVM(options) {

this.$options = options || {}

// 简化了对data的处理

let data = this._data = this.$options.data

// 监听数据

observe(data)

new Compile(options.el || document.body, this)

}

MVVM.prototype.$watch = function (expOrFn, cb) {

new Watcher(this, expOrFn, cb)

}

为了能够直接通过实例化对象操作数据模型,我们需要为MVVM实例添加一个数据模型代理的方法:

MVVM.prototype._proxy = function (key) {

Object.defineProperty(this, key, {

configurable: true,

enumerable: true,

get: () => this._data[key],

set: (val) => {

this._data[key] = val

}

})

}

至此我们可以通过一个小例子来说明本文的内容:

{{user.name}}

{{modelValue}}

let vm = new MVVM({

el: '#app',

data: {

modelValue: '',

user: {

name: 'zhaomenghuan',

age: '24'

},

address: {

city: 'beijing'

},

skills: ['JavaScript', 'Node.js', 'html5']

}

})

vm.$watch('modelValue', val => console.log(`watch modelValue :${val}`))

本文目的不是为了造一个轮子,而是在学习优秀框架实现的过程中去提升自己,搞清楚框架发展的前因后果,由浅及深去学习基础,本文参考了网上很多优秀博主的文章,由于时间关系,有些内容没有做深入探讨,觉得还是有些遗憾,在后续的学习中会更多的独立思考,提出更多自己的想法。

参考文档

前端模板技术面面观

Object.defineProperty()

Vue.js 源码学习笔记

vue早期源码学习系列

解析最简单的observer和watcher

剖析Vue实现原理 – 如何实现双向绑定mvvm

作者:佚名

来源:51CTO

双向绑定 当obj的值修改时_JavaScript进阶之深入理解数据双向绑定相关推荐

  1. angular的数据双向绑定

    以前我们通过jquery操作DOM可以实现数据绑定,但这样会使操作量加大,而在angular中可以轻松的实现数据双向绑定.数据双向绑定指的是数据改变,相应的视图发生改变,而用户操作视图,底层数据会发生 ...

  2. js 取得input绑定的datalist中的值_javascript基础修炼(9)——MVVM中双向数据绑定的基本原理...

    [小宅按] 开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 概述 1.1 MVVM模型 MVVM模型是前端单页面应用中非常重要的模型之一,也是Single Pag ...

  3. input绑定的jedate日期控件的值改变时触发事件问题

    一.input绑定的jedate日期控件的值改变时触发事件问题 一般input中值发生改变,用onchange 就可以触发事件,但我现用jedate日期控,选中日期后,发现onchange无效. 后经 ...

  4. vue数据双向绑定的原理

    vue数据双向绑定的原理 一 复习闭包 1 闭包含义: 当函数嵌套时,内部函数使用了外部函数的变量,就会产生闭包 当函数可以记住并访问自己的作用域时,就会产生闭包 2 闭包注意点 ① 队列里的代码执行 ...

  5. 数据双向绑定_手写 Vue3 数据双向绑定 理解Proxy

    前言 vue3的 Proxy 最近貌似各大网红公众号都有发,我也来蹭蹭热度写一篇吧!我们也可以结合vue2来看看vue3到底发生了些什么变化,又解决了Vue2.x的哪些痛点.接下来我们一起看看~ 目录 ...

  6. Vue源码学习(三)——数据双向绑定

    在Vue中我们经常修改数据,然后视图就直接修改了,那么这些究竟是怎么实现的呢? 其实Vue使用了E5的语法Object.defineProperty来实现的数据驱动. 那么Object.defineP ...

  7. Vue数据双向绑定原理(vue2向vue3的过渡)

    众所周知,Vue的两大重要概念: 数据驱动 组件系统 接下来我们浅析数据双向绑定的原理 一.vue2 1.认识defineProperty vue2中的双向绑定是基于defineProperty的ge ...

  8. vue的数据双向绑定原理

    前言: 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化.这也算是vue的精髓之处了.单项数据绑定是使用状 ...

  9. this指向-作用域、作用域链-预解析 变量提升-Vue组件传值 父子 子父 非父子-Vue数据双向绑定原理

    目录 this指向 作用域.作用域链 预解析 变量提升 Vue组件传值 父子 子父 非父子 Vue数据双向绑定原理 1.this指向 函数的this指向 看调用.不看声明 (1)普通函数调用 ①函数名 ...

最新文章

  1. Google Voice开始发送邀请函
  2. 消息 8101,级别 16,状态 1,第 1 行仅当使用了列列表并且 IDENTITY_INSERT 为 ON 时,才能为表'CUSTOMER_TBL'中的标识列指定显式值。...
  3. i2c的时钟延展问题(转)
  4. Android Studio 生成签名的APK
  5. 聊聊zxing的qrcode
  6. 常用PHP数组函数总结
  7. 新Attention | NAM,一种新的注意力计算方式,无需额外的参数
  8. 中国互联网关于阿里未来预测:这盘大期如何走
  9. 计算机保养与维护论文答辩ppt,北京交通大学毕业答辩ppt模板
  10. 云医院HIS系统—医院挂号模块
  11. 贴片电容COG、NPO、X7R、Y5V、X5R介质的区别
  12. xp计算机用户名和密码忘记了怎么办,xp电脑开机密码忘记了怎么办,xp忘记开机密码的解决方法...
  13. 离散数学4_第5章关系与函数__关系矩阵
  14. CSS实现文本居中和块级元素居中
  15. 计算机1946考试试题,统考计算机考试试题及答案
  16. 瀚高数据库企业版中的权限问题
  17. R语言 数据操作小贴士合集
  18. [读书笔记] 蔡康永的说话之道
  19. Bmob后端云的基本使用
  20. 小米路由器 ping 测试软件,小米路由器3 测试: 自建简单智能家居

热门文章

  1. gis投影中未定义的地理转换_ArcGIS中的坐标系统定义与投影转换(转)
  2. 基于VS2019 C++的跨平台(Linux)开发(1.2.2)——设备管理及文件IO
  3. OSS上传图片并获取相关链接
  4. Cesium billboard 广告牌字体模糊处理方法
  5. 7-1 计算分段函数[1] (20分)
  6. 屏幕距离和坐便转换工具_投影仪的最佳观看距离与计算方法 欢迎围观 - 坚果智能家庭影院官方社区...
  7. 纯CSS简单案例合集(折扇效果,资料卡,定格轮播,菜单三角形)
  8. mac下优酷客户端下载视频路径
  9. 【一】机器学习的动机与应用
  10. 5年Java老鸟,靠这份Java全能手册闭关3个月拿到字节offer!