欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue reactive函數(shù)實現(xiàn)流程詳解

 更新時間:2023年01月04日 10:39:38   作者:volit_  
一個基本類型的數(shù)據(jù),想要變成響應(yīng)式數(shù)據(jù),那么需要通過ref函數(shù)包裹,而如果是一個對象的話,那么需要使用reactive函數(shù),這篇文章主要介紹了Vue reactive函數(shù)

1.Reflect

  Proxy有著可以攔截對對象各種操作的能力,比如最基本的get和set操作,而Reflect也有與這些操作同名的方法,像Reflect.set()、Reflect.get(),這些方法和它們所對應(yīng)的對象基本操作完全一致。

const data = {
    value: '1',
    get fn() {
        console.log(this.value);
        return this.value;
    }
};
data.value; // 1
Reflect.get(data,'value'); // 1

  除此之外,Reflect除了和基本對象操作等價外,它還具有第三個參數(shù)receiver,即指定該基礎(chǔ)操作的this對象。

Reflect.get(data,'value',{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->value: '2'}); // 會輸出2

  對于Proxy,它只能夠攔截對象的基本操作,而對于data.fn(),這是一個復(fù)合操作,它由一個get操作和一個apply操作組成,即先通過get獲取fn的值,然后調(diào)用即apply對應(yīng)的函數(shù)。而現(xiàn)在,用我們之前創(chuàng)建的響應(yīng)式系統(tǒng)來執(zhí)行一次這個復(fù)合操作,我們期望的結(jié)果是,在對fn屬性綁定的同時,對value的值也進行綁定,因為在fn函數(shù)的執(zhí)行過程中,操作了value值??蓪嶋H情況是,value的值并沒有進行綁定。

effect(() => {
	obj.fn(); // 假設(shè)obj是一個已經(jīng)做了響應(yīng)式代理的Proxy對象    
})
obj.value = '2'; // 改變obj.value的值,預(yù)想中的響應(yīng)式操作沒有執(zhí)行

  這里就涉及到fn()函數(shù)中,this指向的問題了。實際上,在fn函數(shù)中,this指向的是原來的data對象,即this.value實際上是data.value,因為操作的是原對象,因此并不會觸依賴收集。了解到問題的原因之后,我們就可以用上之前所說的Reflect的特性了,將get操作實際的this對象指定為obj,這樣就可以順利的實現(xiàn)我們我期望的功能了。

const obj = new Proxy(data, {
  get(target, key, receiver) { // get 接收第三個參數(shù),即操作的調(diào)用者,對應(yīng)obj.fn()就是obj了
    track(target, key);
    return Reflect.get(target, key, receiver); // 將原來直接返回target[key]的操作改為Reflect.get
  }
}

2.Proxy的工作原理

  在js中一個對象必須部署包括[[GET]]、[[SET]]在內(nèi)的11個內(nèi)部方法,除此之外,函數(shù)擁有額外的[[Call]]和[[Construct]]兩個方法。而在創(chuàng)建Proxy對象時,指定的攔截函數(shù),實際上就是用來自定義代理對象本身的內(nèi)部方法和行為,而不是指定。

3.代理Object

(1)代理讀取操作

對一個普通對象的所有可能的讀取操作:

  • 訪問屬性:obj.foo
  • 判斷對象或原型上是否存在給定的key;key in obkj
  • 使用for … in循環(huán)遍歷對象

  首先對于基本的訪問屬性,我們可以使用get方法攔截。

const obj = new Proxy(data, {
  get(target, key, receiver) { // get 接收第三個參數(shù),即操作的調(diào)用者,對應(yīng)obj.fn()就是obj了
    track(target, key);
    return Reflect.get(target, key, receiver); // 將原來直接返回target[key]的操作改為Reflect.get
  }
}

  然后,對于in操作符,我們使用has方法進行攔截。

has(target, key) {
    track(target, key);
    return Reflect.has(target,key);
}

  最后,對于for … in操作,我們使用ownKeys方法進行攔截。這里使用和唯一標識ITERATE_KEY和副作用函數(shù)綁定,因為對于ownKeys操作來說,無論如何它都是對一個對象上所存在的所有屬性進行遍歷,并不會產(chǎn)生實際的屬性讀取操作,因此我們需要用一個唯一的標識來標記ownKeys操作。

ownKeys(target, key) {
  // 這里將副作用函數(shù)和唯一標識ITERATE_KEY綁定了
  track(target, ITERATE_KEY);
  return Reflect.ownKeys(target);
},

  相應(yīng)的,在進行賦值操作的時候,也需要相應(yīng)的對ITERATE_KEY這個標識進行處理

function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  const iterateEffects = depsMap.get(ITERATE_KEY); // 讀取ITERATE_KEY
  const effectToRun = new Set();
  effects &&
    effects.forEach((fn) => {
      if (fn !== activeEffect) {
        effectToRun.add(fn);
      }
    });
  // 將與 ITERATE_KEY 相關(guān)聯(lián)的副作用函數(shù)也添加到 effectsToRun
  iterateEffects &&
    iterateEffects.forEach((fn) => {
      if (fn !== activeEffect) {
        effectToRun.add(fn);
      }
    });
  effectToRun.forEach((fn) => {
    if (fn.options.scheduler) {
      fn.options.scheduler(fn);
    } else {
      fn();
    }
  });
}

  雖然以上的代碼解決了添加屬性的問題,但是隨之而來的是修改屬性的問題。對于for … in循環(huán)來說,無論原對象的屬性如何修改,對它來說只需要進行一次遍歷就好了,因此我們需要區(qū)分添加和修改的操作。這里使用Object.prototype.hasOwnProperty檢查當前操作的屬性是否已經(jīng)存在于目標對象上,如果是,則說明當前的操作類型是’SET‘,否則說明是’ADD‘。然后將type作為第三個參數(shù),傳入trigger函數(shù)中。

set(target, key, newVal, receiver) {
    const type = Object.prototype.hasOwnProperty.call(target, key)
      ? "SET"
      : "ADD";
    Reflect.set(target, key, newVal, receiver);
    trigger(target, key, type);
},

(2)代理delete操作符

  代理delete操作符使用的是deleteProperty方法,因為delete操作符刪除屬性會導(dǎo)致屬性的數(shù)量變少,因此當操作類型為DELETE時也要觸發(fā)一下for … in循環(huán)的操作。

deleteProperty(target, key) {
    // 檢查刪除的key是否為自身屬性
    const hadKey = Object.prototype.hasOwnProperty.call(target, key);
    const res = Reflect.deleteProperty(target, key);
    if (res && hadKey) {
      trigger(target, key, "DELETE");
    }
    return res;
},
// 當type為ADD或DELETE的時候,才執(zhí)行ITERATE_KEY相關(guān)的操作
if (type === "ADD" || type === "DELETE") {
    iterateEffects &&
      iterateEffects.forEach((fn) => {
        if (fn !== activeEffect) {
          effectToRun.add(fn);
        }
      });
}

4.合理的觸發(fā)響應(yīng)

(1)完善響應(yīng)操作

  觸發(fā)修改操作時,若新值和舊值相等,則不需要觸發(fā)修改響應(yīng)操作。

set(target, key, newVal, receiver) {
    const oldVal = target[key];  // 獲取舊值
    const type = Object.prototype.hasOwnProperty.call(target, key)
      ? "SET"
      : "ADD";
    const res = Reflect.set(target, key, newVal, receiver);
    if (oldVal !== newVal) { // 比較新值和舊值
      trigger(target, key, type);
    }
    return res;
},

  但是全等有一個特殊情況,就是NaN === NaN的值為false,因此我們需要對NaN進行一個特殊判斷。

(2)封裝一個reactive函數(shù)

  其實就是對new Proxy進行了一個簡單的封裝。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, newVal, receiver) {
      const oldVal = target[key]; // 獲取舊值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
        // 比較新值和舊值
        trigger(target, key, type);
      }
      return res;
    },
    has(target, key) {
      track(target, key);
      return Reflect.has(target, key);
    },
    ownKeys(target, key) {
      // 這里將副作用函數(shù)和唯一標識ITERATE_KEY綁定了
      track(target, ITERATE_KEY);
      return Reflect.ownKeys(target);
    },
    deleteProperty(target, key) {
      // 檢查刪除的key是否為自身屬性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (res && hadKey) {
        trigger(target, key, "DELETE");
      }
      return res;
    },
  });
}

  現(xiàn)在,我們使用reactive創(chuàng)建兩個響應(yīng)式對象,child和parent,然后將child原型設(shè)置為parent。然后為child.bar函數(shù)綁定副作用函數(shù)。當修改child.bar的值的時候,可以看到,副作用函數(shù)實際執(zhí)行了兩次。這是因為,child的原型是parent,child本身并沒有bar這個屬性,所以根據(jù)原型鏈的規(guī)則,最終會在parent身上拿到bar這個屬性。因為在進行原型鏈查找的過程中,訪問到了parent上的屬性,因襲進行了一次額外的綁定操作,所以最終副作用函數(shù)執(zhí)行了兩次。

const obj = {};
const proto = {
  bar: 1,
};
const child = reactive(obj);
const parent = reactive(proto);
Object.setPrototypeOf(child, parent);
effect(() => {
  console.log(child.bar);
});
child.bar = 2; // 輸出 1 2 2

  這里我們比較一下child和parent的攔截函數(shù),可以發(fā)現(xiàn)receiver的值都是相同的,發(fā)生變化的是target的值,因此我們可以通過比較taregt的值來取消parent觸發(fā)的那一次響應(yīng)操作。

// child 的攔截函數(shù)
get(target, key, receiver) {
// target是原始對象obj
// receiver 是child
}
// parent 的攔截函數(shù)
get(target, key, receiver) {
// target是proto對象
// receiver 是child
}

  這里我們通過添加一個raw操作來實現(xiàn),當訪問raw屬性的時候,會返回該對象的target值。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 添加一個新值 raw
        return target;
      }
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, newVal, receiver) {
      const oldVal = target[key]; // 獲取舊值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        // 比較target值,如果receiver的target和當前target相同,說明就不是原型鏈操作。
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 比較新值和舊值
          trigger(target, key, type);
        }
      }
      return res;
    }
  }
}

5.深響應(yīng)和淺響應(yīng)

  實際上,前面我們實現(xiàn)的reactive還只是淺層響應(yīng),也就是說只有對象的第一層具有響應(yīng)式反應(yīng)。比如對于一個obj:{bar{val:1}}對象,當對obj.bar.val進行操作的時候,我們首先從obj中拿到bar,但是這時候的bar只是一個普通對象bar:{val:1},因此無法進行響應(yīng)式操作。這里我們對Reflect.get獲取的值進行一個判斷,如果拿到的值是一個對象,遞歸調(diào)用reactive函數(shù),最后拿到一個深層響應(yīng)的對象。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        return target;
      }
      track(target, key);
      const res =  Reflect.get(target, key, receiver);
      if(typeof res === 'object') {
          return reactive(res);
      }
      return res;
    }
  }
}

  但是我們并非所有時候都期望深層響應(yīng),因此我們調(diào)整一下reactive函數(shù)。

function createReactive(obj, isShallow = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        return target;
      }
      track(target, key);
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果淺層響應(yīng),直接返回
      if (typeof res === "object") {
        return reactive(res);
      }
      return res;
    },
    set(target, key, newVal, receiver) {
      const oldVal = target[key]; // 獲取舊值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 比較新值和舊值
          trigger(target, key, type);
        }
      }
      return res;
    },
    has(target, key) {
      track(target, key);
      return Reflect.has(target, key);
    },
    ownKeys(target, key) {
      // 這里將副作用函數(shù)和唯一標識ITERATE_KEY綁定了
      track(target, ITERATE_KEY);
      return Reflect.ownKeys(target);
    },
    deleteProperty(target, key) {
      // 檢查刪除的key是否為自身屬性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (res && hadKey) {
        trigger(target, key, "DELETE");
      }
      return res;
    },
  });
}
function reactive(obj) {
  return createReactive(obj, true);
}
function shallowReactive(obj) {
  return createReactive(obj, false);
}

6.只讀和淺只讀

  實現(xiàn)只讀其實只需要在createReactiv函數(shù)中添上第三個參數(shù)isReadOnly。

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    set(target, key, newVal, receiver) {
      if (isReadOnly) {
        console.warn(`屬性${key}是只讀的`);
        return true;
      }
      const oldVal = target[key]; // 獲取舊值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 比較新值和舊值
          trigger(target, key, type);
        }
      }
      return res;
    },
    deleteProperty(target, key) {
      if (isReadOnly) {
        console.warn(`屬性${key}是只讀的`);
        return true;
      }
      // 檢查刪除的key是否為自身屬性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (res && hadKey) {
        trigger(target, key, "DELETE");
      }
      return res;
    },
  }
}

  當然,對于設(shè)置了只讀屬性的對象的屬性,很明顯就沒必要添加依賴了,所以對于get也要進行相應(yīng)的修改.

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通過獲取raw屬性,拿到初始對象
        return target;
      }
      if (!isReadOnly) {
        // 只讀情況下不需要建立聯(lián)系
        track(target, key);
      }
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果淺層響應(yīng),直接返回
      if (typeof res === "object") {
        // 如果獲取的值是對象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對象
        return reactive(res);
      }
      return res;
    },
  }
}

  但是,上述操作只能做到淺只讀,深只讀實現(xiàn)起來也很簡單,判斷只讀標記然后遞歸添加只讀屬性就行了.

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通過獲取raw屬性,拿到初始對象
        return target;
      }
      if (!isReadOnly) {
        // 只讀情況下不需要建立聯(lián)系
        track(target, key);
      }
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果淺層響應(yīng),直接返回
      if (typeof res === "object" && res !== null) {
        // 如果獲取的值是對象,且只讀標記的值為true,遞歸調(diào)用readonly函數(shù),得到深層只讀響應(yīng)對象.否則,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對象
        return isReadOnly ? readonly(res) : reactive(res);
      }
      return res;
    },

  然后和reactive函數(shù)一樣,封裝一下只讀readonly函數(shù).

function readonly(obj) {
  return createReactive(obj, true, true);
}
function shallowReadonly(obj) {
  return createReactive(obj, false, true);
}

7.代理數(shù)組

(1)讀取和修改操作

數(shù)組的讀取操作:

  • 通過索引訪問元素,arr[0]
  • 訪問數(shù)組長度,arr.length
  • for in循環(huán)訪問arr對象
  • for of循環(huán)訪問arr對象
  • 數(shù)組的原型方法,find,concat等

數(shù)組的修改操作:

  • 通過索引修改數(shù)組,arr[0] = 1
  • 修改數(shù)組長度,arr.length = 1
  • 數(shù)組的棧、隊列方法,arr.push
  • 修改數(shù)組的原型方法,arr.slice,arr.sort等

  對于通過索引訪問這一操作,它實際上和普通對象是一樣的,都可以通過get直接攔截。但是對于通過索引修改這一操作,就稍有不同了,因為如果當前設(shè)置的索引>數(shù)組長度的話,相應(yīng)的也會對數(shù)組的長度進行修改,而且在修改數(shù)組長度的過程中,還需要對數(shù)組長度的修改做出響應(yīng)。同時,直接修改數(shù)組的length屬性也會造成影響,如果小于當前數(shù)組長度,那么會對差值內(nèi)元素進行清楚操作,否則則對之前的元素沒有影響。

  首先我們對應(yīng)修改數(shù)組索引設(shè)置這一操作:

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    set(target, key, newVal, receiver) {
      if (isReadOnly) {
        // 如果對象只讀,提示報錯信息
        console.warn(`屬性${key}是只讀的`);
        return true;
      }
      const oldVal = target[key]; // 獲取舊值
      // 判斷操作類型,如果是數(shù)組類型,則根據(jù)索引大小來判斷
      const type = Array.isArray(target)
        ? Number(key) < target.length
          ? "SET"
          : "ADD"
        : Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD"; // 獲取操作類型
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          trigger(target, key, type, newVal); // 添加第四個參數(shù)
        }
      }
      return res;
    }
  }
}

  然后修改trigger函數(shù),判斷是否為數(shù)組和ADD操作,然后添加length屬性的相關(guān)操作

// trigger函數(shù)添加第四個參數(shù)newVal,即觸發(fā)響應(yīng)的值
function trigger(target, key, type) {
  const depsMap = bucket.get(target); // 首先從對象桶中取出當前對象的依賴表
  if (!depsMap) return;
  const effects = depsMap.get(key); // 從依賴表中拿到當前鍵值的依賴集合
  const iterateEffects = depsMap.get(ITERATE_KEY); // 嘗試獲取for in循環(huán)操作的依賴集合
  const effectToRun = new Set(); // 創(chuàng)建依賴執(zhí)行隊列
  if (type === "ADD" && Array.isArray(target)) {
    // 如果操作類型是ADD且對象類型是數(shù)組,將length相關(guān)依賴添加到待執(zhí)行隊列中
    const lengthEffects = depsMap.get("length");
    lengthEffects &&
      lengthEffects.forEach((fn) => {
        if (fn !== activeEffect) {
          effectToRun.add(effectFn);
        }
      });
  }
  if (Array.isArray(target) && key === "length") {
    // 對于索引大于等于新length值的元素,需要將所有相關(guān)聯(lián)的函數(shù)取出添加到effectToRun中待執(zhí)行
    if (key >= newVal) {
      effects.forEach((fn) => {
        if (fn !== activeEffect) {
          effectToRun.add(fn);
        }
      });
    }
  }

(2)數(shù)組的遍歷

  首先是for in循環(huán),會影響for in循環(huán)的操作主要是根據(jù)索引設(shè)置數(shù)組值和修改數(shù)組的length屬性,而這兩種操作,實際上都是對數(shù)組length值的操作,因此我們只需要在onwKeys方法里判斷,當前操作的是否是數(shù)組,如果是數(shù)組的話,就使用length屬性作為key并建立聯(lián)系。

ownKeys(target, key) {
      // 這里將副作用函數(shù)和唯一標識ITERATE_KEY綁定了
      track(target, Array.isArray(target) ? "length" : ITERATE_KEY); // 進行依賴收集
      return Reflect.ownKeys(target);
},

  然后是for of循環(huán),它主要是通過和索引和length進行操作,所以不需要進行額外的操作,就可以實現(xiàn)依賴。但是在使用for of循環(huán)的時候,會對數(shù)組的Symbol.iterator屬性進行讀取,該屬性是一個symbol值,為了避免發(fā)生意外錯誤,以及性能上的考慮,需要對類型為了symbol的值進行隔離。

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通過獲取raw屬性,拿到初始對象
        return target;
      }
      if (!isReadOnly && typeof key !== "symbol") {
        // 只讀情況和key值為symbol的情況下不需要建立聯(lián)系
        track(target, key);
      }
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果淺層響應(yīng),直接返回
      if (typeof res === "object" && res !== null) {
        // 如果獲取的值是對象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對象
        return isReadOnly ? readonly(res) : reactive(res);
      }
      return res;
    },
  }
}                 

(3)數(shù)組的查找方法

  arr.includes方法在正常情況下是可以正常觸發(fā)綁定的,因為arr.include方法會在查找過程中訪問數(shù)組對象的length屬性和索引。但是在一些特殊的情況下,比如說數(shù)組元素是對象的情況下,在我們目前的響應(yīng)式系統(tǒng)下,就會出現(xiàn)一些特殊的情況。

const obj = {};
const arr = reactive([arr]);
console.log(arr.includes(arr)); // false

  運行上述代碼,得到的結(jié)果為false,這是因為在我們之前代碼設(shè)計中,如果讀取操作取到的值是一個可代理對象,那么我們會繼續(xù)對這個對象進行代理。而進行繼續(xù)代理后,得到的對象就是一個全新的對象了。

if (typeof res === "object" && res !== null) {
  // 如果獲取的值是對象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對象
  return isReadOnly ? readonly(res) : reactive(res);
}

  對此,我們創(chuàng)建一個緩存Map,避免重復(fù)創(chuàng)建的問題。

const reactiveMap = new Map();
function reactive(obj) {
  // 獲取當前對象的緩存值
  const existionProxy = reactiveMap.get(obj);
  // 如果當前對象存在緩存值,直接返回
  if (existionProxy) return existionProxy;
  // 否則創(chuàng)建新的響應(yīng)對象
  const proxy = createReactive(obj, true);
  // 緩存新對象
  reactiveMap.set(obj, proxy);
  return proxy;
}

  但是這個時候我們又會碰到一個新問題,就是如果傳入原始對象,也就是obj的話,也會返回false,這是因為我們會從arr中拿到的是響應(yīng)式對象,所以我們需要修改arr.includes的默認行為。

const originMethod = Array.prototype.includes;
const arrayInstrumentations = {
  includes: function (...args) {
    // this是代理對象,先在代理對象中進行查找
    let res = originMethod.apply(this, args);
    if (res === false) {
      // 如果在代理對象上無法找到,再到原始對象上找
      res = originMethod.apply(this.raw, args);
    }
    return res;
  },
};
function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通過獲取raw屬性,拿到初始對象
        return target;
      }
      // 如果操作目標是數(shù)組,而且key處于arrayInstrumentations之上,那么返回自定義的行為
      if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
        return Reflect.get(arrayInstrumentations, key, receiver);
      }
    }
  }
}

  除了includes外,需要做類似處理的還有indexof和lastIndexOf

const arrayInstrumentations = {};
["includes", "indexof", "lastIndexof"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    // this 是代理對象,先在代理對象中查找,將結(jié)果存儲到 res 中
    let res = originMethod.apply(this, args);
    // res 為 false 說明沒找到,通過 this.raw 拿到原始數(shù)組,再去其中查找,并更新 res 值
    if (res === false || res === -1) {
      res = originMethod.apply(this.raw, args);
    }
    return res;
  };
});

(4)隱式修改數(shù)組的方法

  主要有push、pop、shift、unshift和splice,以push為例,push在添加元素的同時,也會讀取length屬性,而這回導(dǎo)致兩個獨立的副作用函數(shù)相互影響。因此我們也需要重寫push操作,來避免這種情況的產(chǎn)生。這里我們添加一個是否進行追蹤的標記,在push方法執(zhí)行之前,將標記置為false

let shouldTrack = true; // 是否進行追蹤標記
["push"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    // 在調(diào)用原始方法之前,禁止追蹤
    shouldTrack = false;
    // 默認行為
    let res = originMethod.apply(this, args);
    // 在調(diào)用原始方法之后,恢復(fù)原來的行為,即允許追蹤
    shouldTrack = true;
    return res;
  };
});
function track(target, key) {
  if (!activeEffect || !shouldTrack) {
    // 如果沒有當前執(zhí)行的副作用函數(shù),不進行處理
    return;
  }
}

? 最后,修改所以該類行為。

["push", "pop", "shift", "unshift", "splice"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    // 在調(diào)用原始方法之前,禁止追蹤
    shouldTrack = false;
    // 默認行為
    let res = originMethod.apply(this, args);
    // 在調(diào)用原始方法之后,恢復(fù)原來的行為,即允許追蹤
    shouldTrack = true;
    return res;
  };
});

到此這篇關(guān)于Vue reactive函數(shù)實現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)Vue reactive函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue + Elementui實現(xiàn)多標簽頁共存的方法

    Vue + Elementui實現(xiàn)多標簽頁共存的方法

    這篇文章主要介紹了Vue + Elementui實現(xiàn)多標簽頁共存的方法,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • vue多級多選菜單組件開發(fā)

    vue多級多選菜單組件開發(fā)

    這篇文章主要為大家分享了vue多級多選菜單組件開發(fā)案例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Vue注冊組件命名時不能用大寫的原因淺析

    Vue注冊組件命名時不能用大寫的原因淺析

    這段時間一直在弄vue,當然也遇到很多問題,這里就來跟大家分享一些注冊自定義模板組件的心得 ,需要的朋友可以參考下
    2019-04-04
  • 純前端使用Vue3上傳文件到minio文件服務(wù)器(粘貼可直接用)

    純前端使用Vue3上傳文件到minio文件服務(wù)器(粘貼可直接用)

    vue是目前最流行的前端框架,下面這篇文章主要給大家介紹了關(guān)于純前端使用Vue3上傳文件到minio文件服務(wù)器的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-04-04
  • 詳解mpvue小程序中怎么引入iconfont字體圖標

    詳解mpvue小程序中怎么引入iconfont字體圖標

    這篇文章主要介紹了詳解mpvue小程序中怎么引入iconfont字體圖標,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • Vue中Vue router和axios的封裝使用教程

    Vue中Vue router和axios的封裝使用教程

    當用戶登錄后,后臺會返回一個token給前端,前端下次進入首頁后,會先判斷token是否過期,如果過期自動進入登錄頁面,本文給大家介紹Vue中Vue router和axios的封裝使用教程,感興趣的朋友一起看看吧
    2023-11-11
  • Vue指令之 v-cloak、v-text、v-html實例詳解

    Vue指令之 v-cloak、v-text、v-html實例詳解

    當用戶頻繁刷新頁面或網(wǎng)速慢時,頁面未完成 Vue.js 的加載時,導(dǎo)致 Vue 來不及渲染,這就會導(dǎo)致在瀏覽器中直接暴露插值(表達式),這篇文章主要介紹了Vue指令 v-cloak、v-text、v-html,需要的朋友可以參考下
    2019-08-08
  • Vue實現(xiàn)移除數(shù)組中特定元素的方法小結(jié)

    Vue實現(xiàn)移除數(shù)組中特定元素的方法小結(jié)

    這篇文章主要介紹了Vue如何優(yōu)雅地移除數(shù)組中的特定元素,文中介紹了單個去除和批量去除的操作方法,并通過代碼示例講解的非常詳細,具有一定的參考價值,需要的朋友可以參考下
    2024-03-03
  • 使用vue腳手架(vue-cli)搭建一個項目詳解

    使用vue腳手架(vue-cli)搭建一個項目詳解

    這篇文章主要介紹了vue腳手架(vue-cli)搭建項目,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • vue返回首頁后如何清空路由問題

    vue返回首頁后如何清空路由問題

    這篇文章主要介紹了vue返回首頁后如何清空路由問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06

最新評論