Vue 双向绑定原理
2016-11-23 17:06
295 查看
Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统。
一、访问器属性:Object.defineProperty
ECMAScript 262v5带来的新东西,FF把它归入为javaScript 1.8.5的功能之一。
语法:Object.defineProperty(obj, prop, descriptor)
参数:obj:目标对象;prop:需要定义的属性或方法的名字;descriptor:目标属性所拥有的特性。
可供定义的特性列表:
value:属性的值
writable:如果为false,属性的值就不能被重写。
get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
set:一旦目标属性被赋值,就会调回此方法。
configurable:如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化。
enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来。
说明:
因为
二、极简绑定实现
三、 DocumentFragment
DocumentFragment 接口表示文档的一部分(或一段)。更确切地说,它表示一个或多个邻接的 Document 节点和它们的所有子孙节点。DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null。
不过它有一种特殊的行为,该行为使得它非常有用,即当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。可以用 Document.createDocumentFragment() 方法创建新的空 DocumentFragment 节点。
DocumentFragment。程序员可以使用DocumentFragment将一批子元素添加到任何类似node的父节点上,对这批子元素的操作不需要一个真正的根节点。可以不依赖可见的DOM来构造一个DOM结构,它比直接操作DOM快70%。
当需要进行大量DOM操作时,尽量使用DocumentFragement,它会让你的应用变的更快! Vue进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持)到DocumentFragment中,经过一番处理后,再将DocumentFragment整体返回插入挂载目标。
四、Vue实现
五、其他绑定方式
1.Angularjs内部会维护一个序列,将所有需要监控的属性放在这个序列中,当发生某些特定事件时(注意,这里并不是定时的而是由某些特殊事件触发的),Angularjs会调用
这种方式的缺点很明显,遍历轮训watcher是非常消耗性能的,特别是当单页的监控数量达到一个数量级的时候。
2.使用ECMAScript7中的
3.封装属性访问器
在php中的
从上面的代码中我们可以管中窥豹,比如
其实,针对上述的代码,更加严格一点的话,不允许直接访问
这种方法当然也有弊端,最突出的就是每添加一个属性监控,都必须为这个属性添加对应访问器方法,否则这个属性的变更就无法捕获。
vue.js和avalon.js实现数据双向绑定的原理就是属性访问器。不过它当然不会像上述示例代码一样原始。它使用了ECMAScript5.1(ECMA-262)中定义的标准属性
参考:
http://www.cnblogs.com/kidney/p/6052935.html?utm_source=gold_browser_extension
一、访问器属性:Object.defineProperty
ECMAScript 262v5带来的新东西,FF把它归入为javaScript 1.8.5的功能之一。
语法:Object.defineProperty(obj, prop, descriptor)
参数:obj:目标对象;prop:需要定义的属性或方法的名字;descriptor:目标属性所拥有的特性。
可供定义的特性列表:
value:属性的值
writable:如果为false,属性的值就不能被重写。
get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
set:一旦目标属性被赋值,就会调回此方法。
configurable:如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化。
enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来。
说明:
Object.defineProperty()方法设置属性时,属性不能同时声明访问器属性(
set和
get)和
writable或者
value属性。 意思就是,某个属性设置了
writable或者
value属性,那么这个属性就不能声明
get和
set了,反之亦然。
因为
Object.defineProperty()在声明一个属性时,不允许同一个属性出现两种以上存取访问控制。
二、极简绑定实现
<input type="text" id="a”> <span id="b"></span> <script> var obj = {}; Object.defineProperty(obj,'hello',{ set: function(newVal){ document.getElementById('a').val = newVal; document.getElementById('b').innerHTML = newVal; }, get: function(){ return document.getElementById('b').innerHTML; } }); document.addEventListener('keyup',function(e){ obj.hello = e.target.value; }); </script>
三、 DocumentFragment
DocumentFragment 接口表示文档的一部分(或一段)。更确切地说,它表示一个或多个邻接的 Document 节点和它们的所有子孙节点。DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null。
不过它有一种特殊的行为,该行为使得它非常有用,即当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。可以用 Document.createDocumentFragment() 方法创建新的空 DocumentFragment 节点。
DocumentFragment。程序员可以使用DocumentFragment将一批子元素添加到任何类似node的父节点上,对这批子元素的操作不需要一个真正的根节点。可以不依赖可见的DOM来构造一个DOM结构,它比直接操作DOM快70%。
<ul id="list"></ul> // Create the fragment var frag = document.createDocumentFragment(); // Create numerous list items, add to fragment for(var x = 0; x < 10; x++) { var li = document.createElement("li"); li.innerHTML = "List item " + x; frag.appendChild(li); } // Mass-add the fragment nodes to the list listNode.appendChild(frag);
当需要进行大量DOM操作时,尽量使用DocumentFragement,它会让你的应用变的更快! Vue进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持)到DocumentFragment中,经过一番处理后,再将DocumentFragment整体返回插入挂载目标。
function nodeToFragment (node, vm) { var flag = document.createDocumentFragment(); var child; while (child = node.firstChild) { compile(child, vm); flag.append(child); // 将子节点劫持到文档片段中 } return flag; }
四、Vue实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Two-way data-binding</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script>
function observe (obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
});
}
function defineReactive (obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function () {
// 添加订阅者watcher到主题对象Dep
if (Dep.target) dep.addSub(Dep.target);
return val
},
set: function (newVal) {
if (newVal === val) return
val = newVal;
// 作为发布者发出通知
dep.notify();
}
});
}
function nodeToFragment (node, vm) { var flag = document.createDocumentFragment(); var child; while (child = node.firstChild) { compile(child, vm); flag.append(child); // 将子节点劫持到文档片段中 } return flag; }
function compile (node, vm) {
var reg = /\{\{(.*)\}\}/;
// 节点类型为元素
if (node.nodeType === 1) {
var attr = node.attributes;
// 解析属性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue; // 获取v-model绑定的属性名
node.addEventListener('input', function (e) {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name] = e.target.value;
});
node.value = vm[name]; // 将data的值赋给该node
node.removeAttribute('v-model');
}
};
}
// 节点类型为text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 获取匹配到的字符串
name = name.trim();
// node.nodeValue = vm[name]; // 将data的值赋给该node
new Watcher(vm, node, name);
}
}
}
function Watcher (vm, node, name) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function () {
this.get();
this.node.nodeValue = this.value;
},
// 获取data中的属性值
get: function () {
this.value = this.vm[this.name]; // 触发相应属性的get
}
}
function Dep () {
this.subs = []
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
function Vue (options) {
this.data = options.data;
var data = this.data;
observe(data, this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this);
// 编译完成后,将dom返回到app中
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
</script>
</body>
</html>
五、其他绑定方式
1.Angularjs内部会维护一个序列,将所有需要监控的属性放在这个序列中,当发生某些特定事件时(注意,这里并不是定时的而是由某些特殊事件触发的),Angularjs会调用
$digest方法,这个方法内部做的逻辑就是遍历所有的watcher,对被监控的属性做对比,对比其在方法调用前后属性值有没有发生变化,如果发生变化,则调用对应的handler。http://www.html-js.com/article/2145
这种方式的缺点很明显,遍历轮训watcher是非常消耗性能的,特别是当单页的监控数量达到一个数量级的时候。
2.使用ECMAScript7中的
Object.observe方法对对象(或者其属性)进行监控观察,一旦其发生变化时,将会执行相应的handler。(目前已被发起人撤回)
3.封装属性访问器
在php中的
__get()和
__set()方法。在javascript中也有类似的概念,不过不叫魔术方法,而是叫做访问器
var data = { name: "erik", getName: function() { return this.name; }, setName: function(name) { this.name = name; } };
从上面的代码中我们可以管中窥豹,比如
data中的
getName()和
setName()方法,我们可以简单的将其看成
data.name的访问器(或者叫做 存取器 )。
其实,针对上述的代码,更加严格一点的话,不允许直接访问
data.name属性,所有对
data.name的读写都必须通过
data.getName()和
data.setName()方法。所以,想象一下,一旦某个属性不允许对其进行直接读写,而必须是通过访问器进行读写时,那么我当然通过重写属性的访问器方法来做一些额外的情,比如属性值变更监控。使用属性访问器来做数据双向绑定的原理就是在此。
这种方法当然也有弊端,最突出的就是每添加一个属性监控,都必须为这个属性添加对应访问器方法,否则这个属性的变更就无法捕获。
Object.defineProperty
方法
vue.js和avalon.js实现数据双向绑定的原理就是属性访问器。不过它当然不会像上述示例代码一样原始。它使用了ECMAScript5.1(ECMA-262)中定义的标准属性 Object.defineProperty方法。针对国内行情,部分还不支持
Object.defineProperty低级浏览器采用VBScript作了完美兼容,不像其他的mvvm框架已经逐渐放弃对低端浏览器的支持。
参考:
http://www.cnblogs.com/kidney/p/6052935.html?utm_source=gold_browser_extension
相关文章推荐
- 剖析Vue原理&实现双向绑定MVVM
- Vue实现双向绑定的原理以及响应式数据的方法
- vue双向数据绑定原理
- vue双向数据绑定原理
- 剖析Vue原理&实现双向绑定MVVM
- vue的双向绑定原理及实现
- vue双向数据绑定原理
- vue双向数据绑定原理探究(附demo)
- Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定)
- Vue2.0源码阅读笔记--双向绑定实现原理
- 初探Vue原理之view-model的数据动态双向绑定
- 详解Vue双向数据绑定原理解析
- Vue.js双向绑定的实现原理
- vue双向绑定的原理及实现双向绑定MVVM源码分析
- Vue双向绑定的实现原理
- 浅谈vue,angular,react数据双向绑定原理分析
- vue数据双向绑定原理解析(get & set)
- 剖析Vue原理&实现双向绑定MVVM
- 剖析Vue原理&实现双向绑定MVVM
- Vue 双向数据绑定原理分析