Vue2中响应式系统的底层细节。
vue2是利用Object.defineProperty方法劫持对象或对象的属性的访问器,在属性值发生变化时获取属性值变化, 从而进行后续操作。这个方法有三个参数,第一个参数obj表示要定义的对象,第二个参数prop表示对象的某个属性值,用字符串表示,第三个参数descriptor是用来控制prop的,重点介绍一下第三个参数,它传入一个对象,对象包括以下的属性值:
value:给prop属性赋值
enumerable:控制属性是否可被遍历,默认是false
writeable:控制属性是否可以被修改,默认是false
configurable:控制属性是否可以被删除,默认是false
get():当程序读取prop属性的时候,get函数(getter)就会被调用
set():当程序修改prop属性的时候,set函数(setter)就会被调用
数据代理
数据代理就是通过一个对象去代理另外一个对象中属性的操作(读/写),下面通过defineProperty方法实现一下数据代理:
function MVVM(options) {
// 将选项对象保存到vm
this.$options = options;
// 将data对象保存到vm和data变量中
var data = this._data = this.$options.data;
//将vm保存在me变量中
var me = this;
// 遍历data中所有属性
Object.keys(data).forEach(function (key) { // 属性名: name
// 对指定属性实现代理
me._proxy(key);
});
// 对data进行监视
observe(data, this);
// 创建一个用来编译模板的compile对象
this.$compile = new Compile(options.el || document.body, this)
}
MVVM.prototype = {
$watch: function (key, cb, options) {
new Watcher(this, key, cb);
},
// 对指定属性实现代理
_proxy: function (key) {
// 保存vm
var me = this;
// 给vm添加指定属性名的属性(使用属性描述)
Object.defineProperty(me, key, {
configurable: false, // 不能再重新定义
enumerable: true, // 可以枚举
// 当通过vm.name读取属性值时自动调用
get: function proxyGetter() {
// 读取data中对应属性值返回(实现代理读操作)
return me._data[key];
},
// 当通过vm.name = 'xxx'时自动调用
set: function proxySetter(newVal) {
// 将最新的值保存到data中对应的属性上(实现代理写操作)
me._data[key] = newVal;
}
});
}
};
基本实现流程
(1)通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符
(2)所有添加的属性都包含 getter/setter
(3) getter/setter 内部去操作 data 中对应的属性数据
3.Vue中的数据代理
是通过vm实例对象来代理data对象中属性的读取和修改,这样的好处就是更加方便的操作data中的数据,他的基本原理是通过Object.defindProprerty
方法把data所有属性都添加到vm实例对象上,为添加到vm实例对象的每一个属性都指定一个getter/setter,在getter和setter内部去读写data中对应的属性。
每当要修改的Vue中data对应的属性值,就会触发vm中对应属性的set方法,同时去更新View层视图的值,这就是Vue中的响应式原理,但是defindProperty()方法中没有定义属性值添加/删除的方法,当要往data中添加或者删除的时候,defindProerty()无法完成响应式,这个问题在Vue3中得到了改善。Vue2也提供了完整的解决方案,可通过Vue.set()/this.$set()添加属性,Vue.delete()/this.$delete()删除属性
总结一下,今天讲了vue2中数据的响应式是通过数据代理完成的,而数据代理的主要核心就是Object.defindProperty()这个方法