在文章 源码学习VUE之响应式原理咱们大概描述了响应式的实现流程,主要写了observe,dep和wather的简易实现,以及推导思路。但相应代码逻辑并不完善,今天咱们再来填以前的一些坑。数组
以前实现的observe函数只能处理一个对象的单个属性,但咱们更多的数据是保存在对象中,为了抽象话,咱们也封装一个对象Observe,只要传进一个参数,就能够把这个对象进行监听。app
var obj = { a: 1, b: 2 }
好比一个对象有两个属性 a,b。咱们能够尝试写出下面的实现类函数
class Observe{ constructor(value){ this.value = value //要监听的值。 this.walk(); } walk(){ //经过walk函数,依次处理 const keys = Object.keys(obj); let self = this; for (let i = 0; i < keys.length; i++) { self.defineReactive(obj, keys[i]) } } defineReactive (data, key, val) { var dep = new Dep(); Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ if(Dep.target){ dep.addSub(Dep.target); // Dep.target是Watcher的实例 } }, set: function(newVal){ if(val === newVal) return val = newVal; dep.notify(); } }) } }
固然,为了防止重复监听,咱们能够给原object设置一个标识符以做辨别。学习
class Obsever(){ construct(){ this.value = value //要监听的值。 Object.defineProperty(value, "__ob__", { value: this, enumerable: false, writable: true, configurable: true }) this.walk(); } }
虽然数组也是一个对象,可是咱们队数组的操做却不会触发set,get方法。所以必须对数组特殊处理。
首先须要对操做数组的方法进行改写,如push
,pop
,shift
等优化
//首先拿到Array的原生原型链 const arrayProto = Arrary.prototype; //为了保证修改不会影响原生方法,咱们建立一个新对象 const arrayMethods = Object.create(arrayProto); //要改写的方法 const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'] methodsToPatch.forEach(function (method) { const original = arrayProto[method] // 先拿到原生方法 def(arrayMethods, method, function mutator (...args) { // 改写后的方法,都是先拿到原生方法的计算结果 const result = original.apply(this, args) const ob = this.__ob__ // 拿到插入的值。 let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } //Observe插入的值 if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
其实逻辑很简单。对于能够改变array的方法咱们都改写一下。只要调用了这些方法,除了返回正确的值,咱们都通知观察对象,数据改变了,触发观察者update操做。同时,数组里面多是个对象,咱们不改变数组自己,可是改变数组里面的某个值,这也算是一种改变,所以,除了监听数组自己的改变,也要对数组每一个值进行observe。
这涉及到两点,一是observe Array的时候,就要对每一个值进行Observe。另外,插入数组的每一个值也要observe.第二点就是上面代码中特别关注push
,unshift
,splice
这三个能够插值方法的缘由。this
class Obsever(){ construct(){ this.value = value //要监听的值。 Object.defineProperty(value, "__ob__", { value: this, enumerable: false, writable: true, configurable: true }) if(Array.isArray(value)){ this.observeArray(); }else{ this.walk(); } } observeArray(items){ for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }, function observe (value) { let ob if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 若是已经observe的对象就再也不进行重复的observe操做 ob = value.__ob__ } else { ob = new Observer(value) } return ob }
实际开发中咱们常常会遇到一个很大的数据。如渲染tables时,table的数据极可能很大(一个多多维数组)。若是都进行observe无心会是很大的开销。关键是咱们只是须要拿这些数据来渲染,并不关心数据内部的变化。所以可能就存在这种需求,能够不对array或object深层遍历observe。咱们可使用Object.freeze()将这个数据冻结起来。
所以对于冻结的数据咱们就再也不进行observe。上面的代码能够这么优化prototype
function observe (value) { let ob if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 若是已经observe的对象就再也不进行重复的observe操做 ob = value.__ob__ } else if(Object.isExtensible(value)){// 若是数据被冻结,或者不可扩展,则不进行observe操做 ob = new Observer(value) } return ob } defineReactive (data, key, val) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key) // 若是数据被冻结,或者不可扩展,则改写set,get方法 if (property && property.configurable === false) { return } //传进来的对象可能以前已经被定义了set,get方法,所以咱们不能直接拿value var getter = property && property.get var setter = property && property.set Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ var value = getter ? getter.call(obj) : val; if(Dep.target){ dep.addSub(Dep.target); // Dep.target是Watcher的实例 } return value }, set: function(newVal){ if(val === newVal) return val = newVal; dep.notify(); } }) }