首頁 > 軟體

Vue2響應式系統之set和delete

2022-04-13 04:00:08

1、陣列集

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
    list: [1, 2],
};
observe(data);
const updateComponent = () => {
    console.log(data.list);
};

new Watcher(updateComponent);

list[0] = 3;

list[0]會觸發 的重新執行嗎?updateComponent

可以先思考一下。

答案是否定的,陣列我們只能通過重寫的 、 等方法去觸發更新,詳見pushspliceVue2響應式系統之陣列 

如果我們想要替換陣列某個元素的話可以轉一下彎,通過 去實現。splice

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
    list: [1, 2],
};
observe(data);
const updateComponent = () => {
    console.log(data.list);
};

new Watcher(updateComponent);

// list[0] = 3;
data.list.splice(0, 1, 3);

每次這樣寫太麻煩了,我們可以提供一個 方法供使用者使用。set

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(target, key, val) {
    if (Array.isArray(target)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val;
    }

    // targe 是物件的情況
    // ...
}

然後我們直接使用 方法就可以了。set

import { observe, set } from "./reactive";
import Watcher from "./watcher";
const data = {
    list: [1, 2],
};
observe(data);
const updateComponent = () => {
    console.log(data.list);
};

new Watcher(updateComponent);

// list[0] = 3;
// data.list.splice(0, 1, 3);
set(data.list, 0, 3);

2、陣列 del

同陣列 ,我們順便提供一個 的方法,支援陣列響應式的刪除某個元素。setdel

/**
 * Delete a property and trigger change if necessary.
 */
export function del(target, key) {
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1);
        return;
    }
    // targe 是物件的情況
    // ...
}

3、物件 set

import { observe, set, del } from "./reactive";
import Watcher from "./watcher";
const data = {
    obj: {
        a: 1,
        b: 2,
    },
};
observe(data);
const updateComponent = () => {
    const c = data.obj.c ? data.obj.c : 0;
    console.log(data.obj.a + data.obj.b + c);
};

new Watcher(updateComponent);

data.obj.c = 3;

updateComponent 方法中雖然使用了 的 屬性,但是在呼叫 之前, 中並沒有 屬性,所以 屬性不是響應式的。objcobservedata.objcc

當我們修改 的值的時候,並不會觸發 的執行。data.obj.cupdateComponent

如果想要變成響應式的話,一種方法就是在最開始就定義 屬性。c

const data = {
    obj: {
        a: 1,
        b: 2,
        c: null,
    },
};
observe(data);
const updateComponent = () => {
    const c = data.obj.c ? data.obj.c : 0;
    console.log(data.obj.a + data.obj.b + c);
};

new Watcher(updateComponent);

data.obj.c = 3;

另一種方法就是通過 去設定新的屬性了,在 中我們可以將新新增的屬性設定為響應式的。setset

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(target, key, val) {
    if (Array.isArray(target)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val;
    }

    // targe 是物件的情況
    // key 在 target 中已經存在
    if (key in target && !(key in Object.prototype)) {
        target[key] = val;
        return val;
    }

    const ob = target.__ob__;
    // target 不是響應式資料
    if (!ob) {
        target[key] = val;
        return val;
    }
  	// 將當前 key 變為響應式的
    defineReactive(target, key, val);
    return val;
}

回到我們之前的程式:

import { observe, set, del } from "./reactive";
import Watcher from "./watcher";
const data = {
    obj: {
        a: 1,
        b: 2,
    },
};
observe(data);
const updateComponent = () => {
    const c = data.obj.c ? data.obj.c : 0;
    console.log(data.obj.a + data.obj.b + c);
};

const ob = new Watcher(updateComponent);

set(data.obj, "c", 3);

雖然通過 增加了屬性,但是此時 並不會重新觸發,原因的話我們看下依賴圖。setWatcher

雖然屬性 擁有了 物件,但由於沒有呼叫過依賴屬性 的 ,所以它並沒有收集到依賴。cDepcWatcher

當然我們可以 完手動呼叫一次相應的 。setWatcher

const data = {
    obj: {
        a: 1,
        b: 2,
    },
};
observe(data);
const updateComponent = () => {
    const c = data.obj.c ? data.obj.c : 0;
    console.log(data.obj.a + data.obj.b + c);
};

const ob = new Watcher(updateComponent);

set(data.obj, "c", 3);
ob.update(); // 手動呼叫 Watcher

data.obj.c = 4;

這樣的話,當執行 的時候就會觸發 的執行了。data.obj.c = 4Watcher

那麼我們能將觸發相應的 的邏輯放到 函數中嗎?Watcherset

可以看到 裡也有個 ,這個其實當時是為陣列準備的,參考 objDepVue2響應式系統之陣列,但 的 什麼都沒收集。objdep

我們修改一下程式碼讓它也收集一下:

export function defineReactive(obj, key, val, shallow) {
    const property = Object.getOwnPropertyDescriptor(obj, key);
    // 讀取使用者可能自己定義了的 get、set
    const getter = property && property.get;
    const setter = property && property.set;
    // val 沒有傳進來話進行手動賦值
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }
    const dep = new Dep(); // 持有一個 Dep 物件,用來儲存所有依賴於該變數的 Watcher
    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val;
            if (Dep.target) {
                dep.depend();
                if (childOb) {
                  	/******新位置 *************************/
                		childOb.dep.depend();
                  	/**********************************/
                    if (Array.isArray(value)) {
                       // childOb.dep.depend(); //原來的位置
                        dependArray(value);
                    }
                }
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            const value = getter ? getter.call(obj) : val;

            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            childOb = !shallow && observe(newVal);
            dep.notify();
        },
    });
}

function dependArray(value) {
    for (let e, i = 0, l = value.length; i < l; i++) {
        e = value[i];
      	/******新位置 *************************/
        e && e.__ob__ && e.__ob__.dep.depend();
      	/**********************************/
        if (Array.isArray(e)) {
           //  e && e.__ob__ && e.__ob__.dep.depend(); // 原位置
            dependArray(e);
        }
    }
}

因為讀取 屬性,一定先會讀取 屬性,即 。 也同理。aobjdata.obj.ab

所以通過上邊的修改, 的 會收集到它的所有屬性的依賴,也就是這裡的 、 的依賴,但因為 和 的依賴是相同的,所以收集到一個依賴。objdepabab

但其實我們並不知道 被哪些 依賴,我們只知道和 同屬於一個物件的 和 被哪些 依賴,但大概率 也會被其中的 依賴。cWatchercabWatchercWatcher

所以我們可以在 中手動執行一下 的 ,依賴 的 大概率會被執行,相應的 也會成功收集到依賴。setobjDepcWatcherc

export function set(target, key, val) {
    if (Array.isArray(target)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val;
    }

    // targe 是物件的情況
    // key 在 target 中已經存在
    if (key in target && !(key in Object.prototype)) {
        target[key] = val;
        return val;
    }

    const ob = target.__ob__;
    // target 不是響應式資料
    if (!ob) {
        target[key] = val;
        return val;
    }
    defineReactive(target, key, val);
   /******新增 *************************/
    ob.dep.notify() 
    /************************************/
    return val;
}

回到最開始的程式碼:

const data = {
    obj: {
        a: 1,
        b: 2,
    },
};
observe(data);
const updateComponent = () => {
    const c = data.obj.c ? data.obj.c : 0;
    console.log(data.obj.a + data.obj.b + c);
};

const ob = new Watcher(updateComponent);

set(data.obj, "c", 3);

執行完後 除了變為響應式的,也成功觸發了 執行,並且收集到了 。cWatcherWatcher

此時如果修改 的值,也會成功觸發 的執行了。cWatcher

4、物件 del

有了上邊的瞭解,刪除就很好解決了。

如果要刪除 屬性,刪除後執行下它相應的 就可以。但 的 是存在閉包中的,我們並不能拿到。aDepaDep

退而求其次,我們可以去執行 屬性所在的物件 的 就可以了。aobjDep

/**
 * Delete a property and trigger change if necessary.
 */
export function del(target, key) {
    if (Array.isArray(target)) {
        target.splice(key, 1);
        return;
    }
    // targe 是物件的情況
    const ob = target.__ob__;
    if (!hasOwn(target, key)) {
        return;
    }
    delete target[key];
    if (!ob) {
        return;
    }
    ob.dep.notify();
}

5、總結

通過為物件收集依賴,將物件、陣列的修改、刪除也變成響應式的了,同時為使用者提供了 和 方法。

到此這篇關於Vue2響應式系統之set和delete的文章就介紹到這了,更多相關Vue2 set和delete內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com