Array变化侦测
1.為什么Array的偵測要和Object的偵測區分實現呢?
- Object是通過getter、setter實現的偵測,但數組中使用push等方法改變數組時,不會觸發getter、setter
- 在數組中的元素如果是個對象,也要對他們的子屬性進行監聽。
- 在數組新增一個對象元素時,也要對新增的元素進行監聽。
雖然Array的原型最終還是Object,但是正是由于Array的這些特性,導致了對Object的那一套偵測方式無法直接使用在數組Array中。
2.Array對象中包含了什么?
想要對Array進行監聽,就需要了解Array里面到底有什么。
我們直接打印數組對象的原型
console.log(Array.prototype)可以看到Array中的一些屬性
可以看到其中包含了push concat等方法。
3.監聽Array對象的大致思路
從上可以得出,可以通過覆蓋Array對象中的原生方法push等,實現一些我們想要的操作。
或者說是直接定義一個攔截器,覆蓋數組原型 Array.prototype
4.攔截器的實現思路
我們需要實現的攔截器,就相當于是把數組的原型對象Array.prototype復制出來, 然后將副本原型進行修改,并讓數組的實例對象的原型指向該副本原型。
整理后發現,Array中有7個可以改變自身的方法:push、pop、shift、unshift、splice、sort、reverse。
所以只需要先重寫這7個方法即可。
我們可以定義如下的攔截方式:
// 得到原生數組原型 const arrayProto = Array.prototype // 相當于復制數組原型對象。 const arrayMethods = Object.create(arrayProto);['push','pop','shift','unshift','splice','sort','reverse'].forEach(function (method) {// 取出原生數組原型中的方法const original = arrayProto[method]// 監聽副本中對應的方法Object.defineProperty(arrayMethods, method, {value: function mutator (...args) {// 攔截!處理完自己的邏輯后,調用原生的方法return original.apply(this, args)},enumerable: false,writable: true,configurable: true}) })5.攔截器怎么覆蓋Array的原型
我們要的效果:
- 攔截器不能直接覆蓋Array原型,會污染全局的Array
- 只針對那些被監聽的數組進行攔截。
大致思路:
在Object 的變化偵測 中,我們實現了一個屬性遍歷偵測器Observer。是用來遞歸監聽某個對象中所有屬性的的一個類。我們就可在它遞歸監聽時,判斷該屬性是否是數組,如果是數組,則在該子對象的原型中添加我們的副本原型(也就是我們的監聽器)。
class Observer {constructor(value) {this.value = valueif (Array.isArray(value)) {// 修改數組原型value.__proto__ = arrayMethods} else {this.walk(value);}}walk(obj) {for (let key in obj) {defineReactive(obj, key, obj[key]);console.log('value', obj[key]);console.log('key', key);}} }**注意:**如果瀏覽器不支持_proto_,就手動遍歷副本原型的屬性
// 判斷瀏覽器是否能使用__proto__ const hasProto = '__proto__' in {} const arrayKeys = Object.getOwnPropertyNames(arrayMethods)class Observer {constructor(value) {this.value = valueif (Array.isArray(value)) {// 根據是否支持proto來決定使用哪種方式覆蓋原型對象。const augment = hasProto ? protoAugment : copyAugment} else {this.walk(value);}}walk(obj) {for (let key in obj) {defineReactive(obj, key, obj[key]);console.log('value', obj[key]);console.log('key', key);}}// 三個參數:target:要修改的對象;src:用于替換的對象,keys:替換對象的字段名function protoAument (target, src, keys) {target.__proto__ = src}function copyAugment(target, src, keys) {for (let i = 0; i < keys.length; i++) {const key = keys[i]// 修改原型對象def(target, key, src[key])}} }// 修改原型對象的工具方法 def(obj, key, val, enumerable) {Object.defineProperty(obj, key, {value: val,enumerable: !!enumerable,writable: true,configurable: true}) }注意:這里遍歷對象屬性為什么用 Object.getOwnPropertyNames 而不是Object.keys或者for in呢:
因為Array.prototype 中的屬性都是不可枚舉的屬性。使用 Object.keys 和 for in 無法遍歷到它們
6.收集依賴
在Observer類中存儲依賴。
收集依賴時。每個值上新增一個_ob_字段,用于存儲該值的Observer實例。判斷某個元素已經創建Observer監聽實例,則不用再次創建。
7.Observer的簡單邏輯流程
總結
- 上一篇: 【DevOps实战|基于Jenkins与
- 下一篇: gets和fgets函数及其区别,C语言