proxy 實現(xiàn)數(shù)據(jù)劫持方案的比較

前言

介紹雙向綁定,數(shù)據(jù)劫持的文章有很多,作為前端圈的熱點話題,我就不跟風寫同質(zhì)化的文章了,今天只是把發(fā)現(xiàn)的一些不容易被注意到的現(xiàn)象寫出來。

方案一

核心:在 get 方法中進行遞歸調(diào)用

const target = {
  name: 'lee',
  info: {
    age: 24
  }
};

const isObj = (data) => {
  return Object.prototype.toString.call(data) === '[object Object]'
}

const handler = {
  get(trapTarget,key,receiver) {
    if (!(key in receiver)) {
      throw new Error(`Property ${key} does not exist.`);
    }
    console.log(`監(jiān)聽到了${key}`)
    
    // 遞歸調(diào)用
    if (isObj(trapTarget[key])) {
      return new Proxy(trapTarget[key], handler);
    }
    return Reflect.get(trapTarget,key,receiver);
  },
  set(trapTarget,key,value,receiver) {
    console.log(`修改了${key}`)
    return Reflect.set(trapTarget,key,value,receiver);
  }
};

const observe = (data) => {
  if (!data || !isObj(data)) {
    return false;
  }
  
  return new Proxy(data, handler);
}

測試:

let proxyData = observe(target);
console.log(proxyData.name);
proxyData.info.age = 30;
console.log(proxyData.info.age);
圖一:方案一的運行結(jié)果

這種方法確實實現(xiàn)了對深層次數(shù)據(jù)的監(jiān)聽,但是仔細觀察會發(fā)現(xiàn)如下現(xiàn)象:

  • info 這個對象并不是 proxy 實例
  • 當 get 被觸發(fā)時,get 方法中的遞歸運算總是被執(zhí)行

我們本來的期望是,在經(jīng)過第一次的轉(zhuǎn)化為 proxy 后,以后不再執(zhí)行,但事實顯然不是這樣的。
這里發(fā)現(xiàn)第一個小坑:get 方法中的返回值并不會改變 proxy 實例。

如果你更細心的思考,會發(fā)現(xiàn)我這句結(jié)論來的有些突然,因為你會質(zhì)疑我是因為 if 判斷語句總是為 true 才導致上面的現(xiàn)象,那請你和我繼續(xù)探究。

if (isObj(trapTarget[key])) {
  return new Proxy(trapTarget[key], handler);
}

開始我也覺得是因為判斷條件的問題,所以可以加上對 proxy 實例的判斷,如果已經(jīng)是 proxy ,那么不再執(zhí)行,修改后是下面這樣:

if (isObj(trapTarget[key]) && !isProxy(key)) {
    console.log(`執(zhí)行了幾次${isProxy(key)}`)
    const res = new Proxy(trapTarget[key], handler);
    proxies[key] = 1;
    return res;
 }

說說如何實現(xiàn) isProxy ?

原理就是將轉(zhuǎn)換過的 key收集起來,之后判斷是否存在。

let proxies = {};
const isProxy = (data) => {
  return proxies[data];
}

推薦閱讀 es6-proxies.html, 其中的實現(xiàn)方法是下面這樣子的:

let proxies = new WeakSet();

export function createProxy(obj) {
    let handler = {};
    let proxy = new Proxy(obj, handler);
    proxies.add(proxy);
    return proxy;
}

export function isProxy(obj) {
    return proxies.has(obj);
}

運行結(jié)果:

圖二:增加對 proxy 實例的判斷

這里發(fā)現(xiàn)確實按照我們的期望,僅執(zhí)行了一次,但是請你仔細對比圖一和圖二,你會發(fā)現(xiàn)只能監(jiān)聽到 info ,而無法監(jiān)聽到 age。

通過以上現(xiàn)象,可以發(fā)現(xiàn),info 仍然是普通對象,get 中只是臨時獲取了 info 的 proxy 實例

總結(jié):

  • 不推薦使用這種方法:
    因為這種方式的監(jiān)聽是在操作數(shù)據(jù)時,才會對數(shù)據(jù)臨時進行 proxy 轉(zhuǎn)換,然后才能夠監(jiān)聽到,而且總要執(zhí)行 proxy 轉(zhuǎn)化操作,這太消耗性能了。

方案二

核心:初始就遞歸所有屬性,將深層次的是對象類型(數(shù)組類型可自行拓展)轉(zhuǎn)換成 proxy 實例,這樣只需執(zhí)行一次。

const target = {
  name: 'lee',
  info: {
    age: 24
  }
};

const isObj = (data) => {
  return Object.prototype.toString.call(data) === '[object Object]'
}

const handler = {
  get(trapTarget,key,receiver) {
    if (!(key in receiver)) {
      throw new Error(`Property ${key} does not exist.`);
    }
    console.log(`監(jiān)聽到了${key}`)
    return Reflect.get(trapTarget,key,receiver);
  },
  set(trapTarget,key,value,receiver) {
    console.log(`修改了${key}`)
    return Reflect.set(trapTarget,key,value,receiver);
  }
};

let proxyData = {}

const observe = (data, key) => {
  if (!data || !isObj(data)) {
    return false;
  }

  if (key) {
    proxyData[key] = new Proxy(data, handler);
  } else {
    // 初始化
    proxyData = new Proxy(data, handler);
  }
  
  
  Object.keys(data).forEach(child => {
    if (isObj(data[child])) {
      observe(data[child], child)
    }
  })
}

observe(target);


console.log(proxyData.name);
proxyData.info.age = 30;
console.log(proxyData.info.age);

運行結(jié)果:

圖三:初始就轉(zhuǎn)換好

觀察運行結(jié)果,可以發(fā)現(xiàn)基本上滿足了我們的期望,只是有個小小的缺陷,請看綠色方框標注的地方,此時屬于初始化過程中,起始我們并不關心此時的數(shù)據(jù)變化,所以還要增加個狀態(tài)判斷。

遞歸執(zhí)行完 observe 后,將狀態(tài)變成 false 即可。

if (!isInit) {
   console.log(`修改了${key}`)
}

方案三

基本和方案二是相似的,唯一的區(qū)別是,直接更改了原始數(shù)據(jù)。這里不建議使用此種方法,除非應用 revoked 取消代理。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,621評論 1 32
  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,306評論 6 13
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應的列上鍵入重復值時,會觸發(fā)此異常。 O...
    我想起個好名字閱讀 5,920評論 0 9
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,295評論 0 17
  • 有段時間,我的好朋友春陽不停給我講一句話:“一個人收入的下限取決于他的智商和努力程度,一個人的收入上限取決于他的人...
    向明月1413閱讀 759評論 9 4

友情鏈接更多精彩內(nèi)容