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

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

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

1.Reflect

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

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

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

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

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

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

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

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

2.Proxy的工作原理

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

3.代理Object

(1)代理讀取操作

對(duì)一個(gè)普通對(duì)象的所有可能的讀取操作:

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

  首先對(duì)于基本的訪問(wèn)屬性,我們可以使用get方法攔截。

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

  然后,對(duì)于in操作符,我們使用has方法進(jìn)行攔截。

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

  最后,對(duì)于for … in操作,我們使用ownKeys方法進(jìn)行攔截。這里使用和唯一標(biāo)識(shí)ITERATE_KEY和副作用函數(shù)綁定,因?yàn)閷?duì)于ownKeys操作來(lái)說(shuō),無(wú)論如何它都是對(duì)一個(gè)對(duì)象上所存在的所有屬性進(jìn)行遍歷,并不會(huì)產(chǎn)生實(shí)際的屬性讀取操作,因此我們需要用一個(gè)唯一的標(biāo)識(shí)來(lái)標(biāo)記ownKeys操作。

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

  相應(yīng)的,在進(jìn)行賦值操作的時(shí)候,也需要相應(yīng)的對(duì)ITERATE_KEY這個(gè)標(biāo)識(shí)進(jìn)行處理

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();
    }
  });
}

  雖然以上的代碼解決了添加屬性的問(wèn)題,但是隨之而來(lái)的是修改屬性的問(wèn)題。對(duì)于for … in循環(huán)來(lái)說(shuō),無(wú)論原對(duì)象的屬性如何修改,對(duì)它來(lái)說(shuō)只需要進(jìn)行一次遍歷就好了,因此我們需要區(qū)分添加和修改的操作。這里使用Object.prototype.hasOwnProperty檢查當(dāng)前操作的屬性是否已經(jīng)存在于目標(biāo)對(duì)象上,如果是,則說(shuō)明當(dāng)前的操作類(lèi)型是’SET‘,否則說(shuō)明是’ADD‘。然后將type作為第三個(gè)參數(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方法,因?yàn)閐elete操作符刪除屬性會(huì)導(dǎo)致屬性的數(shù)量變少,因此當(dāng)操作類(lèi)型為DELETE時(shí)也要觸發(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;
},
// 當(dāng)type為ADD或DELETE的時(shí)候,才執(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ā)修改操作時(shí),若新值和舊值相等,則不需要觸發(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;
},

  但是全等有一個(gè)特殊情況,就是NaN === NaN的值為false,因此我們需要對(duì)NaN進(jìn)行一個(gè)特殊判斷。

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

  其實(shí)就是對(duì)new Proxy進(jìn)行了一個(gè)簡(jiǎn)單的封裝。

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ù)和唯一標(biāo)識(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)建兩個(gè)響應(yīng)式對(duì)象,child和parent,然后將child原型設(shè)置為parent。然后為child.bar函數(shù)綁定副作用函數(shù)。當(dāng)修改child.bar的值的時(shí)候,可以看到,副作用函數(shù)實(shí)際執(zhí)行了兩次。這是因?yàn)椋琧hild的原型是parent,child本身并沒(méi)有bar這個(gè)屬性,所以根據(jù)原型鏈的規(guī)則,最終會(huì)在parent身上拿到bar這個(gè)屬性。因?yàn)樵谶M(jìn)行原型鏈查找的過(guò)程中,訪問(wèn)到了parent上的屬性,因襲進(jìn)行了一次額外的綁定操作,所以最終副作用函數(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的值,因此我們可以通過(guò)比較taregt的值來(lái)取消parent觸發(fā)的那一次響應(yīng)操作。

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

  這里我們通過(guò)添加一個(gè)raw操作來(lái)實(shí)現(xiàn),當(dāng)訪問(wèn)raw屬性的時(shí)候,會(huì)返回該對(duì)象的target值。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 添加一個(gè)新值 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和當(dāng)前target相同,說(shuō)明就不是原型鏈操作。
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 比較新值和舊值
          trigger(target, key, type);
        }
      }
      return res;
    }
  }
}

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

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

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;
    }
  }
}

  但是我們并非所有時(shí)候都期望深層響應(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ù)和唯一標(biāo)識(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.只讀和淺只讀

  實(shí)現(xiàn)只讀其實(shí)只需要在createReactiv函數(shù)中添上第三個(gè)參數(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;
    },
  }
}

  當(dāng)然,對(duì)于設(shè)置了只讀屬性的對(duì)象的屬性,很明顯就沒(méi)必要添加依賴(lài)了,所以對(duì)于get也要進(jìn)行相應(yīng)的修改.

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通過(guò)獲取raw屬性,拿到初始對(duì)象
        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") {
        // 如果獲取的值是對(duì)象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對(duì)象
        return reactive(res);
      }
      return res;
    },
  }
}

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

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通過(guò)獲取raw屬性,拿到初始對(duì)象
        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) {
        // 如果獲取的值是對(duì)象,且只讀標(biāo)記的值為true,遞歸調(diào)用readonly函數(shù),得到深層只讀響應(yīng)對(duì)象.否則,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對(duì)象
        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ù)組的讀取操作:

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

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

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

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

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

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

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

// trigger函數(shù)添加第四個(gè)參數(shù)newVal,即觸發(fā)響應(yīng)的值
function trigger(target, key, type) {
  const depsMap = bucket.get(target); // 首先從對(duì)象桶中取出當(dāng)前對(duì)象的依賴(lài)表
  if (!depsMap) return;
  const effects = depsMap.get(key); // 從依賴(lài)表中拿到當(dāng)前鍵值的依賴(lài)集合
  const iterateEffects = depsMap.get(ITERATE_KEY); // 嘗試獲取for in循環(huán)操作的依賴(lài)集合
  const effectToRun = new Set(); // 創(chuàng)建依賴(lài)執(zhí)行隊(duì)列
  if (type === "ADD" && Array.isArray(target)) {
    // 如果操作類(lèi)型是ADD且對(duì)象類(lèi)型是數(shù)組,將length相關(guān)依賴(lài)添加到待執(zhí)行隊(duì)列中
    const lengthEffects = depsMap.get("length");
    lengthEffects &&
      lengthEffects.forEach((fn) => {
        if (fn !== activeEffect) {
          effectToRun.add(effectFn);
        }
      });
  }
  if (Array.isArray(target) && key === "length") {
    // 對(duì)于索引大于等于新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),會(huì)影響for in循環(huán)的操作主要是根據(jù)索引設(shè)置數(shù)組值和修改數(shù)組的length屬性,而這兩種操作,實(shí)際上都是對(duì)數(shù)組length值的操作,因此我們只需要在onwKeys方法里判斷,當(dāng)前操作的是否是數(shù)組,如果是數(shù)組的話,就使用length屬性作為key并建立聯(lián)系。

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

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

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通過(guò)獲取raw屬性,拿到初始對(duì)象
        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) {
        // 如果獲取的值是對(duì)象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對(duì)象
        return isReadOnly ? readonly(res) : reactive(res);
      }
      return res;
    },
  }
}                 

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

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

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

  運(yùn)行上述代碼,得到的結(jié)果為false,這是因?yàn)樵谖覀冎按a設(shè)計(jì)中,如果讀取操作取到的值是一個(gè)可代理對(duì)象,那么我們會(huì)繼續(xù)對(duì)這個(gè)對(duì)象進(jìn)行代理。而進(jìn)行繼續(xù)代理后,得到的對(duì)象就是一個(gè)全新的對(duì)象了。

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

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

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

  但是這個(gè)時(shí)候我們又會(huì)碰到一個(gè)新問(wèn)題,就是如果傳入原始對(duì)象,也就是obj的話,也會(huì)返回false,這是因?yàn)槲覀儠?huì)從arr中拿到的是響應(yīng)式對(duì)象,所以我們需要修改arr.includes的默認(rèn)行為。

const originMethod = Array.prototype.includes;
const arrayInstrumentations = {
  includes: function (...args) {
    // this是代理對(duì)象,先在代理對(duì)象中進(jìn)行查找
    let res = originMethod.apply(this, args);
    if (res === false) {
      // 如果在代理對(duì)象上無(wú)法找到,再到原始對(duì)象上找
      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") {
        // 通過(guò)獲取raw屬性,拿到初始對(duì)象
        return target;
      }
      // 如果操作目標(biāo)是數(shù)組,而且key處于arrayInstrumentations之上,那么返回自定義的行為
      if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
        return Reflect.get(arrayInstrumentations, key, receiver);
      }
    }
  }
}

  除了includes外,需要做類(lèi)似處理的還有indexof和lastIndexOf

const arrayInstrumentations = {};
["includes", "indexof", "lastIndexof"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    // this 是代理對(duì)象,先在代理對(duì)象中查找,將結(jié)果存儲(chǔ)到 res 中
    let res = originMethod.apply(this, args);
    // res 為 false 說(shuō)明沒(méi)找到,通過(guò) 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在添加元素的同時(shí),也會(huì)讀取length屬性,而這回導(dǎo)致兩個(gè)獨(dú)立的副作用函數(shù)相互影響。因此我們也需要重寫(xiě)push操作,來(lái)避免這種情況的產(chǎn)生。這里我們添加一個(gè)是否進(jìn)行追蹤的標(biāo)記,在push方法執(zhí)行之前,將標(biāo)記置為false

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

? 最后,修改所以該類(lèi)行為。

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

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

相關(guān)文章

  • Vue + Elementui實(shí)現(xiàn)多標(biāo)簽頁(yè)共存的方法

    Vue + Elementui實(shí)現(xiàn)多標(biāo)簽頁(yè)共存的方法

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

    vue多級(jí)多選菜單組件開(kāi)發(fā)

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

    Vue注冊(cè)組件命名時(shí)不能用大寫(xiě)的原因淺析

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

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

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

    詳解mpvue小程序中怎么引入iconfont字體圖標(biāo)

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

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

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

    Vue指令之 v-cloak、v-text、v-html實(shí)例詳解

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

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

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

    使用vue腳手架(vue-cli)搭建一個(gè)項(xiàng)目詳解

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

    vue返回首頁(yè)后如何清空路由問(wèn)題

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

最新評(píng)論