平时开发中,Vue的响应式系统让我们不再去操作DOM,只需关心数据逻辑的处理,极大地降低了代码的复杂度。而响应式系统也是Vue的核心,作为开发者有必要了解其实现原理!
简易版
以watch为切入点
watch是平时开发中使用率非常高的功能,其目的是观测一个数据,当数据变化时执行我们预先定义的回调。使用方式如下:
{ watch: { obj(val, oldVal) { console.log(val, oldVal); } }}
上面观测了Vue实例的obj属性,当其值发生变化时,打印出新值与旧值。
因此,我们定义一个watch函数:
function watch (data, key, cb) { // do something}
Object.defineProperty
既然要在数据变化后再执行回调,所以需要知道数据是什么时候被修改的,这就是Object.defineProperty的作用,其为数据定义了访问器属性。在数据被读取时会触发get,在数据被修改时会触发set。
我们定义一个defineReactive函数,其用来将一个数据变成响应式的:
function defineReactive(data, key) { let val = data[key]; Object.defineProperty(data, key, { configurable: true, enumerable: true, get: function() { return val; }, set: function(newVal) { if (newVal === val) { return; } val = newVal; } });}
defineReactive函数为data对象的key属性定义了get、set,get返回属性key的值val,set中修改key的值为新值newVal。到目前为止,key属性还是没有什么特殊之处。
数据被修改会触发set,那cb一定是在set中被执行。但set与cb之间好像并没有什么联系,所以我们来搭建一座桥梁,来构建两者的联系:
let target = null;
我们在全局定义了一个target变量,它用来保存cb的值,然后在set中调用。所以,cb什么时候被保存在target中?回到出发点,我们要调用watch函数来观测data的key属性,当值被修改时执行我们定义的回调cb,这就是cb被保存在target中的时机了:
function watch(data, key, cb) { target = cb;}
watch函数中target被修改了,但我要是再想调用watch函数一次,也就是说我想在data[key]被修改时,执行两个不同的回调,又或者说,我想再观测data的其它属性,那该怎么办?必须得在target被再次修改前,将其值保存到别处。因为,target是同个属性的不同回调或不同属性的回调所共有的。
我们有必要为key属性建立一个私有的仓库,来保存回调。其实defineReactive函数有一点特殊地方:函数内部定义了一个val变量,然后在get和set函数都使用了val变量,这形成一个闭包,defineReactive函数的作用域是key属性私有的,这就是天然的私有仓库了:
新闻热点
疑难解答
图片精选