Vue:Array变化侦测
Vue:Array變化偵測
1. 數(shù)組追蹤變化
? 與Object不同,數(shù)組無法通過getter和setter方式來追蹤變化,因此,我們需要自定義一個攔截器來追蹤變化。
2. 攔截器的準備
? 攔截器其實就是一個與Array.prototype一樣的Object,里面所包含的屬性一樣,但是改變數(shù)組的方法是我們修改過的。
? 首先,我們先對Array.prototype來個定制。
const arrayProto = Array.prototype;const arrayMethods = Object.create(arrayProto); let methods = ['push','pop','shift','unshift','splice','sort','reverse'];methods.forEach(function(method){// 獲取原始方法const original = arrayMethods[method];Object.defineProperty(arrayMethods,method,{value: function mutator(...args){return original.apply(this,args);},enumerable: true,writable: true,configurable: true}) })- 上面代碼中,我們對Array.prototype進行了拷貝,創(chuàng)建出新的對象arrayMethods,其中的mutator就是我們追蹤變化的關鍵,未來將對其進行擴展。
3. 使用攔截器覆蓋Array原型
? 要將一個數(shù)據(jù)轉(zhuǎn)化成響應式的數(shù)據(jù),需要通過Observer,在Observer里使用攔截器覆蓋那些即將被轉(zhuǎn)換成響應式的Array類型數(shù)據(jù):
const hasProto = '__proto__' in {}; // 檢測是否可用proto const arrayKeys = Object.getOwnPropertyNames(arrayMethods); class Observer{constructor(value){this.value = value;if(Array.isArray(value){// 根據(jù)是否支持ES6來對原型對象進行覆蓋const arguments = hasProto? 'protoArguments' : 'copyArguments';arguments(value,arrayMethods,arrayKeys);}else{this.walk(value); })}...... }function protoArguments(target,src,keys){target.__proto__ = src; } // 遞歸復制屬性 function copyArguments(target,src,keys){for(let i=0;i<keys.length;i++){const key = keys[i];def(target,key,src[key]); // 將arrayMethods的方法添加到target中} }4. 收集依賴
? 我們先來回顧一下收集依賴的類Dep:
class Dep{constructor(){this.subs = [];}addSub(sub){this.subs.push(sub);}removeSub(sub){let index = this.subs.indexOf(sub);if(index > -1){this.subs = this.subs.splice(index,1);}}depend(){if(window.target){this.addSub(window.target);}}notify(){let subs = this.subs.splice();for(let i=0;i<subs.length;i++){subs[i].update();}} }? 與Object不一樣的是,Array的依賴保存在Observer中,原因是這樣做既能在getter中訪問到Observer實例,又能在攔截器中訪問到Observer實例。
class Observer{constructor(value){this.value = value;this.dep = new Dep();if(Array.isArray(value)){// 這里不再贅述,直接用ES6語法value.__proto__ = arrayMethods;}else{this.walk(value);}}...... }? Array的收集依賴也是在defineReactive中收集的,當我們把依賴保存到Observer后,我們可以在getter中對其進行收集:
function defineReactive(data,key,val){let childOb = observe(val); //為val創(chuàng)建Observer實例let dep = new Dep();Object.defineProperty(data,key,{enumerable: true,configurable: true,get:function(){dep.depend();// 收集依賴if(childOb){childOb.dep.depend();}return val;},set:function(newVal){if(val === newVal){return ;}dep.notify();val = newVal;}}) }/*為val創(chuàng)建一個Observer實例,創(chuàng)建成功返回新的Observer實例,若val已存在一個,則返回val */ function observe(val,asRootData){if(typeof val !== 'object'){return ;}let ob = null;if(hasOwn(val,'__ob__') && val.__ob__.instanceof Observer){ob = val.__ob__;}else{ob = new Observer(val);}return ob; }5. 在攔截器中獲取Observer實例
? 由于Array攔截器是對原型的封裝,因此可以在攔截器中訪問到當前正在操作數(shù)組的this:
function def(obj,key,val,enumerable){Object.defineProperty(obj,key,{value: val,enumerable: !!enumerable,writable: true,configurable: true}) }class Observer{constructor(value){this.value = value;this.dep = new Dep();def(value,'__ob__',this); // 在value上新增一個不可枚舉的屬性__ob__,即當前Observer實例value.__proto__ = arrayMethods;}....... }? 通過上面的方式,我們就可以通過__ob__來拿到Observer實例了,進而拿到dep實例。
? __ob__的作用還可以用來記錄當前value是否被Observer轉(zhuǎn)換成響應式數(shù)據(jù)。如果擁有ob屬性,則說明他們是響應式的;如果沒有,則通過new Observer將其轉(zhuǎn)換成響應式的。
? 由于攔截器是原型方法,所以可以通過this.__ob__來訪問Observer實例。
let methods = ['push','pop','shift','unshift','splice','sort','reverse'];methods.forEach(function(method){// 獲取原始方法const original = arrayMethods[method];Object.defineProperty(arrayMethods,method,{value: function mutator(...args){const ob = this.__ob__;return original.apply(this,args);},enumerable: true,writable: true,configurable: true}) })6. 向數(shù)組依賴發(fā)送通知
? 想要發(fā)送通知,必須得先獲取依賴,然后直接調(diào)用依賴的發(fā)送通知方法:
let methods = ['push','pop','shift','unshift','splice','sort','reverse'];methods.forEach(function(method){// 獲取原始方法const original = arrayMethods[method];def(arrayMethods,method,function mutator(...args){const result = original.apply(this,args);const ob = this.__ob__;ob.dep.notify(); // 發(fā)送通知return result;}) })7. 偵測數(shù)組中元素的變化
? 在這之前我們偵測的變化,指的是數(shù)組自身的變化,包括新增或刪除一個元素;那么,要如何偵測數(shù)組中每一項元素的改變呢?
class Observer{constructor(value){this.value = value;def(value,'__ob__',this);if(Array.isArray(value)){this.observeArray(value);}else{this.walk(value);}}// 偵測數(shù)組內(nèi)的每一項數(shù)據(jù)observeArray(items){for(let i=0;i<items.length;i++){observe(items[i]);}}...... }? 上面的代碼中,我們對數(shù)組中每一項都執(zhí)行了一遍new Observer,即全部元素都轉(zhuǎn)化成響應式的。
8. 偵測新增元素
? 要偵測新增元素,首先得獲取新增元素,我們可以使用Observer來實現(xiàn):
let methods = ['push','pop','shift','unshift','splice','sort','reverse'];methods.forEach(function(method){// 獲取原始方法const original = arrayMethods[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;}// 將新增元素轉(zhuǎn)化成響應式if(inserted) ob.observeArray(inserted);ob.dep.notify(); // 發(fā)送通知return result;}) })9. 總結
-
Array與Object的追蹤方式不同。Array是通過方法來改變內(nèi)容的,因此我們需要創(chuàng)建攔截器去覆蓋數(shù)組原型的方式來追蹤變化;
-
為了不污染全局Array.prototype,我們在Observer只針對需要偵測變化的數(shù)組,使用ES6的__proto__屬性來覆蓋其原型方法;對于不支持ES6語法的瀏覽器,我們直接循環(huán)攔截器,將所有方法直接設置到數(shù)組身上來攔截數(shù)組原型的方法;
-
Array的依賴保存在Observer實例上;
-
在Observer中,我們對數(shù)組每一項元素都做了偵測并印上了標記__ob__,并把this保存起來,目的是:一是標記數(shù)據(jù)已經(jīng)被偵測化;二是可以很方便拿到__ob__,從而拿到保存在Observer的依賴。方便后續(xù)通知。
-
對于新增數(shù)據(jù),我們對當前操作數(shù)組的方法進行判斷,如果為’push’,‘unshift’,‘splice’,我們就從參數(shù)中將數(shù)據(jù)提取出來,然后使用observeArray方法對其進行數(shù)據(jù)偵測。
總結
以上是生活随笔為你收集整理的Vue:Array变化侦测的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今天写了个自定义函数验证身份证号是否符合
- 下一篇: 安卓Android公交查询系统app资源