您的位置:首页 > 产品设计 > UI/UE

Vue双向绑定原理(二)访问器属性defineProperty()和发布/订阅模式

2017-03-05 18:54 531 查看

访问器属性的介绍

参考资料:《JavaScript高级程序设计》(第三版)第六章

js的对象有两种属性: 数据属性和访问器属性。

1.数据属性

数据属性包含一个数据值的位置。这个位置可以读取和写入值。数据属性也就是我们最常见的对象属性。数据属性有4个描述他行为的特性:

Configurable: 能否用delete删除属性从而重新定义属性。默认为true

Enumerable: 能否通过for-in遍历,即是否可枚举。默认为true

Writable: 是否能修改属性的值。默认为true

Value: 包含这个属性的数据值,读写属性的时候其实就在这里读写。默认为undefined

要修改属性的上述4个默认特性,就必须使用ECMAScript的Object.defineProperty()方法,该方法包含3个参数:属性缩在的对象,属性名,描述符对象。描述符对象的属性必须在上述4个属性中。例如:

var person = {};
Object.defineProperty(person,"name",{
writable: false,
value: "Nicholas"
});

alert(person.name);   // "Nicholas"
person.name = "Tom";
alert(person.name);  // "Nicholas"


上例创建了一个不可写的name属性并赋值。所以无法修改。

注意,一旦把Configurable属性设置为false,就无法再将其变回true了,此时再想修改特性,就都会报错了。

2.访问器属性

访问器属性不包含数据值,他们包含一对getter和setter函数(非必须)。在读写访问器属性的值的时候,会调用相应的getter和setter函数,而我们的vue就是在getter和setter函数中增加了我们需要的操作。

访问器属性有以下4个特性:

Configurable: 能否用delete删除属性从而重新定义属性。默认true

Enumerable: 能否通过for-in遍历,即是否可枚举。默认true

get: 读取属性时调用的函数,默认undefined

set: 写入属性时调用的函数,默认undefined

var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book,"year",{
get: function(){
return this._year;
},
set: function(newValue){
if(newValue>2004){
this.year= newValue;
this.edition += new Value-2004;
}
}
});

book.year = 2005;
alert(book.edition); //2


上例创建了一个book对象,并定义了两个默认属性 _year 和 edition 。_year前边的下划线表示只能通过对象方法访问的属性。而访问器属性year则包含getter和setter函数。修改year属性,会导致_year和edition改变。这就是访问器属性的常用方式,即设置一个属性值会导致其他属性发生变化。

当然,不一定非要同时制定getter和setter,只有getter则不能写,只有setter则不能读。

发布/订阅模式的介绍

发布/订阅模式是设计模式的一种,很多地方把他和观察者模式混为一谈,其实他们是有一些区别的。对于设计模式,其实目前我的了解其实也非常有限,但如果只是想搞清楚这一种模式的原理,其实还是很容易的。

简单来说,发布/订阅模式就是他的字面意思:他定义了一种一对多的关系,让多个订阅者同时关注发布者,当发布者状态改变时,会通知所有订阅他的对象。就好比在B站追番,点了订阅之后,就成为了该番的订阅者,当动画更新的时候,就会发布消息,通知所有的订阅者,然后你就可以去新的一集了。

在Vue中的作用

Vue会遍历实例的data属性,把每一个data都设置为访问器,然后在该属性的getter函数中将其设为watcher,在setter中向其他watcher发布改变的消息。这样,配合发布/订阅模式,改变其中的一个值,会发布消息,所有的watcher会更新自己,这些watcher也就是绑定在dom中的显示信息,比如 v-text=”year” 和 {{ year }} 这些节点。从而达到改变浏dom,在浏览器中实时变化的效果,代码如下:

//遍历传入实例的data对象的属性,将其设置为Vue对象的访问器属性
function observe(obj,vm){
Object.keys(obj).forEach(function(key){
defineReactive(vm,key,obj[key]);
});
}
//设置为访问器属性,并在其getter和setter函数中,使用订阅发布模式。互相监听。
function defineReactive(obj,key,val){
//这里用到了观察者(订阅/发布)模式,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
//实例化一个主题对象,对象中有空的观察者列表
var dep = new Dep();
//将data的每一个属性都设置为Vue对象的访问器属性,属性名和data中相同
//所以每次修改Vue.data的时候,都会调用下边的get和set方法。然后会监听v-model的input事件,当改变了input的值,就相应的改变Vue.data的数据,然后触发这里的set方法
Object.defineProperty(obj,key,{
get: function(){
//Dep.target指针指向watcher,增加订阅者watcher到主体对象Dep
if(Dep.target){
dep.addSub(Dep.target);
}
return val;
},
set: function(newVal){
if(newVal === val){
return
}
val = newVal;
//console.log(val);
//给订阅者列表中的watchers发出通知
dep.notify();
}
});
}

//主题对象Dep构造函数
function Dep(){
this.subs = [];
}
//Dep有两个方法,增加观察者  和  发布消息
Dep.prototype = {
addSub: function(sub){
this.subs.push(sub);
},
notify: function(){
this.subs.forEach(function(sub){
sub.update();
});
}
}


Object.defineProperty 是仅 ES5 支持,且无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。

结合documentFragment,defineProperty和发布/订阅模式,就可以简单的实现一个双向绑定了,当然这只是最基本的思路,实际上vue还要处理要复杂的多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript 对象 数据