實(shí)現(xiàn)雙向綁定Proxy比defineproperty優(yōu)劣如何

面試官: 實(shí)現(xiàn)雙向綁定Proxy比defineproperty優(yōu)劣如何?

面試官系列(4): 實(shí)現(xiàn)雙向綁定Proxy比defineproperty優(yōu)劣如何?


往期


前言

雙向綁定其實(shí)已經(jīng)是一個(gè)老掉牙的問(wèn)題了,只要涉及到MVVM框架就不得不談的知識(shí)點(diǎn),但它畢竟是Vue的三要素之一.

Vue三要素

  • 響應(yīng)式: 例如如何監(jiān)聽(tīng)數(shù)據(jù)變化,其中的實(shí)現(xiàn)方法就是我們提到的雙向綁定
  • 模板引擎: 如何解析模板
  • 渲染: Vue如何將監(jiān)聽(tīng)到的數(shù)據(jù)變化和解析后的HTML進(jìn)行渲染

可以實(shí)現(xiàn)雙向綁定的方法有很多,KnockoutJS基于觀察者模式的雙向綁定,Ember基于數(shù)據(jù)模型的雙向綁定,Angular基于臟檢查的雙向綁定,本篇文章我們重點(diǎn)講面試中常見(jiàn)的基于數(shù)據(jù)劫持的雙向綁定榛鼎。

常見(jiàn)的基于數(shù)據(jù)劫持的雙向綁定有兩種實(shí)現(xiàn),一個(gè)是目前Vue在用的Object.defineProperty,另一個(gè)是ES2015中新增的Proxy,而Vue的作者宣稱將在Vue3.0版本后加入Proxy從而代替Object.defineProperty,通過(guò)本文你也可以知道為什么Vue未來(lái)會(huì)選擇Proxy

嚴(yán)格來(lái)講Proxy應(yīng)該被稱為『代理』而非『劫持』,不過(guò)由于作用有很多相似之處,我們?cè)谙挛闹芯筒辉僮鰠^(qū)分,統(tǒng)一叫『劫持』。

我們可以通過(guò)下圖清楚看到以上兩種方法在雙向綁定體系中的關(guān)系.

<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳中...(image-6f9b58-1526012269856-2)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

基于數(shù)據(jù)劫持的當(dāng)然還有已經(jīng)涼透的Object.observe方法,已被廢棄。

提前聲明: 我們沒(méi)有對(duì)傳入的參數(shù)進(jìn)行及時(shí)判斷而規(guī)避錯(cuò)誤,僅僅對(duì)核心方法進(jìn)行了實(shí)現(xiàn).


文章目錄

  1. 基于數(shù)據(jù)劫持實(shí)現(xiàn)的雙向綁定的特點(diǎn)
  2. 基于Object.defineProperty雙向綁定的特點(diǎn)
  3. 基于Proxy雙向綁定的特點(diǎn)

1.基于數(shù)據(jù)劫持實(shí)現(xiàn)的雙向綁定的特點(diǎn)

1.1 什么是數(shù)據(jù)劫持

數(shù)據(jù)劫持比較好理解,通常我們利用Object.defineProperty劫持對(duì)象的訪問(wèn)器,在屬性值發(fā)生變化時(shí)我們可以獲取變化,從而進(jìn)行進(jìn)一步操作凶杖。

// 這是將要被劫持的對(duì)象
const data = {
  name: '',
};

function say(name) {
  if (name === '古天樂(lè)') {
    console.log('給大家推薦一款超好玩的游戲');
  } else if (name === '渣渣輝') {
    console.log('戲我演過(guò)很多,可游戲我只玩貪玩懶月');
  } else {
    console.log('來(lái)做我的兄弟');
  }
}

// 遍歷對(duì)象,對(duì)其屬性值進(jìn)行劫持
Object.keys(data).forEach(function(key) {
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      console.log('get');
    },
    set: function(newVal) {
      // 當(dāng)屬性值發(fā)生變化時(shí)我們可以進(jìn)行額外操作
      console.log(`大家好,我系${newVal}`);
      say(newVal);
    },
  });
});

data.name = '渣渣輝';
//大家好,我系渣渣輝
//戲我演過(guò)很多,可游戲我只玩貪玩懶月

1.2 數(shù)據(jù)劫持的優(yōu)勢(shì)

目前業(yè)界分為兩個(gè)大的流派,一個(gè)是以React為首的單向數(shù)據(jù)綁定,另一個(gè)是以Angular玄捕、Vue為主的雙向數(shù)據(jù)綁定凰盔。

其實(shí)三大框架都是既可以雙向綁定也可以單向綁定,比如React可以手動(dòng)綁定onChange和value實(shí)現(xiàn)雙向綁定,也可以調(diào)用一些雙向綁定庫(kù),Vue也加入了props這種單向流的api,不過(guò)都并非主流賣(mài)點(diǎn)搬俊。

單向或者雙向的優(yōu)劣不在我們的討論范圍,我們需要討論一下對(duì)比其他雙向綁定的實(shí)現(xiàn)方法,數(shù)據(jù)劫持的優(yōu)勢(shì)所在拧晕。

  1. 無(wú)需顯示調(diào)用: 例如Vue運(yùn)用數(shù)據(jù)劫持+發(fā)布訂閱,直接可以通知變化并驅(qū)動(dòng)視圖,上面的例子也是比較簡(jiǎn)單的實(shí)現(xiàn)data.name = '渣渣輝'后直接觸發(fā)變更,而比如Angular的臟檢測(cè)則需要顯示調(diào)用markForCheck(可以用zone.js避免顯示調(diào)用,不展開(kāi)),react需要顯示調(diào)用setState隙姿。
  2. 可精確得知變化數(shù)據(jù):還是上面的小例子,我們劫持了屬性的setter,當(dāng)屬性值改變,我們可以精確獲知變化的內(nèi)容newVal,因此在這部分不需要額外的diff操作,否則我們只知道數(shù)據(jù)發(fā)生了變化而不知道具體哪些數(shù)據(jù)變化了,這個(gè)時(shí)候需要大量diff來(lái)找出變化值,這是額外性能損耗厂捞。

1.3 基于數(shù)據(jù)劫持雙向綁定的實(shí)現(xiàn)思路

數(shù)據(jù)劫持是雙向綁定各種方案中比較流行的一種,最著名的實(shí)現(xiàn)就是Vue输玷。

基于數(shù)據(jù)劫持的雙向綁定離不開(kāi)ProxyObject.defineProperty等方法對(duì)對(duì)象/對(duì)象屬性的"劫持",我們要實(shí)現(xiàn)一個(gè)完整的雙向綁定需要以下幾個(gè)要點(diǎn)。

  1. 利用ProxyObject.defineProperty生成的Observer針對(duì)對(duì)象/對(duì)象的屬性進(jìn)行"劫持",在屬性發(fā)生變化后通知訂閱者
  2. 解析器Compile解析模板中的Directive(指令)靡馁,收集指令所依賴的方法和數(shù)據(jù),等待數(shù)據(jù)變化然后進(jìn)行渲染
  3. Watcher屬于Observer和Compile橋梁,它將接收到的Observer產(chǎn)生的數(shù)據(jù)變化,并根據(jù)Compile提供的指令進(jìn)行視圖渲染,使得數(shù)據(jù)變化促使視圖變化

<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳中...(image-1f5ab-1526012269856-1)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

我們看到欲鹏,雖然Vue運(yùn)用了數(shù)據(jù)劫持,但是依然離不開(kāi)發(fā)布訂閱的模式臭墨,之所以在系列2做了Event Bus的實(shí)現(xiàn),就是因?yàn)槲覀儾还茉趯W(xué)習(xí)一些框架的原理還是一些流行庫(kù)(例如Redux赔嚎、Vuex),基本上都離不開(kāi)發(fā)布訂閱模式,而Event模塊則是此模式的經(jīng)典實(shí)現(xiàn),所以如果不熟悉發(fā)布訂閱模式,建議讀一下系列2的文章。


2.基于Object.defineProperty雙向綁定的特點(diǎn)

關(guān)于Object.defineProperty的文章在網(wǎng)絡(luò)上已經(jīng)汗牛充棟,我們不想花過(guò)多時(shí)間在Object.defineProperty上面,本節(jié)我們主要講解Object.defineProperty的特點(diǎn),方便接下來(lái)與Proxy進(jìn)行對(duì)比胧弛。

對(duì)Object.defineProperty還不了解的請(qǐng)閱讀文檔

兩年前就有人寫(xiě)過(guò)基于Object.defineProperty實(shí)現(xiàn)的文章,想深入理解Object.defineProperty實(shí)現(xiàn)的推薦閱讀,本文也做了相關(guān)參考尤误。

上面我們推薦的文章為比較完整的實(shí)現(xiàn)(400行代碼),我們?cè)诒竟?jié)只提供一個(gè)極簡(jiǎn)版(20行)和一個(gè)簡(jiǎn)化版(150行)的實(shí)現(xiàn),讀者可以循序漸進(jìn)地閱讀。

2.1 極簡(jiǎn)版的雙向綁定

我們都知道,Object.defineProperty的作用就是劫持一個(gè)對(duì)象的屬性,通常我們對(duì)屬性的gettersetter方法進(jìn)行劫持,在對(duì)象的屬性發(fā)生變化時(shí)進(jìn)行特定的操作结缚。

我們就對(duì)對(duì)象objtext屬性進(jìn)行劫持,在獲取此屬性的值時(shí)打印'get val',在更改屬性值的時(shí)候?qū)OM進(jìn)行操作,這就是一個(gè)極簡(jiǎn)的雙向綁定损晤。

const obj = {};
Object.defineProperty(obj, 'text', {
  get: function() {
    console.log('get val');&emsp;
  },
  set: function(newVal) {
    console.log('set val:' + newVal);
    document.getElementById('input').value = newVal;
    document.getElementById('span').innerHTML = newVal;
  }
});

const input = document.getElementById('input');
input.addEventListener('keyup', function(e){
  obj.text = e.target.value;
})

在線示例 極簡(jiǎn)版雙向綁定 by Iwobi (@xiaomuzhu) on CodePen.

2.2 升級(jí)改造

我們很快會(huì)發(fā)現(xiàn),這個(gè)所謂的雙向綁定貌似并沒(méi)有什么亂用掺冠。沉馆。。

原因如下:

  1. 我們只監(jiān)聽(tīng)了一個(gè)屬性,一個(gè)對(duì)象不可能只有一個(gè)屬性,我們需要對(duì)對(duì)象每個(gè)屬性進(jìn)行監(jiān)聽(tīng)德崭。
  2. 違反開(kāi)放封閉原則,我們?nèi)绻私?a target="_blank" rel="nofollow">開(kāi)放封閉原則的話,上述代碼是明顯違反此原則,我們每次修改都需要進(jìn)入方法內(nèi)部,這是需要堅(jiān)決杜絕的斥黑。
  3. 代碼耦合嚴(yán)重,我們的數(shù)據(jù)、方法和DOM都是耦合在一起的眉厨,就是傳說(shuō)中的面條代碼锌奴。

那么如何解決上述問(wèn)題?

Vue的操作就是加入了發(fā)布訂閱模式憾股,結(jié)合Object.defineProperty的劫持能力鹿蜀,實(shí)現(xiàn)了可用性很高的雙向綁定。

首先服球,我們以發(fā)布訂閱的角度看我們第一部分寫(xiě)的那一坨代碼,會(huì)發(fā)現(xiàn)它的監(jiān)聽(tīng)茴恰、發(fā)布訂閱都是寫(xiě)在一起的,我們首先要做的就是解耦。

我們先實(shí)現(xiàn)一個(gè)訂閱發(fā)布中心斩熊,即消息管理員(Dep),它負(fù)責(zé)儲(chǔ)存訂閱者和消息的分發(fā),不管是訂閱者還是發(fā)布者都需要依賴于它往枣。

  let uid = 0;
  // 用于儲(chǔ)存訂閱者并發(fā)布消息
  class Dep {
    constructor() {
      // 設(shè)置id,用于區(qū)分新Watcher和只改變屬性值后新產(chǎn)生的Watcher
      this.id = uid++;
      // 儲(chǔ)存訂閱者的數(shù)組
      this.subs = [];
    }
    // 觸發(fā)target上的Watcher中的addDep方法,參數(shù)為dep的實(shí)例本身
    depend() {
      Dep.target.addDep(this);
    }
    // 添加訂閱者
    addSub(sub) {
      this.subs.push(sub);
    }
    notify() {
      // 通知所有的訂閱者(Watcher),觸發(fā)訂閱者的相應(yīng)邏輯處理
      this.subs.forEach(sub => sub.update());
    }
  }
  // 為Dep類設(shè)置一個(gè)靜態(tài)屬性,默認(rèn)為null,工作時(shí)指向當(dāng)前的Watcher
  Dep.target = null;

現(xiàn)在我們需要實(shí)現(xiàn)監(jiān)聽(tīng)者(Observer),用于監(jiān)聽(tīng)屬性值的變化。

// 監(jiān)聽(tīng)者,監(jiān)聽(tīng)對(duì)象屬性值的變化
  class Observer {
    constructor(value) {
      this.value = value;
      this.walk(value);
    }
    // 遍歷屬性值并監(jiān)聽(tīng)
    walk(value) {
      Object.keys(value).forEach(key => this.convert(key, value[key]));
    }
    // 執(zhí)行監(jiān)聽(tīng)的具體方法
    convert(key, val) {
      defineReactive(this.value, key, val);
    }
  }

  function defineReactive(obj, key, val) {
    const dep = new Dep();
    // 給當(dāng)前屬性的值添加監(jiān)聽(tīng)
    let chlidOb = observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: () => {
        // 如果Dep類存在target屬性分冈,將其添加到dep實(shí)例的subs數(shù)組中
        // target指向一個(gè)Watcher實(shí)例圾另,每個(gè)Watcher都是一個(gè)訂閱者
        // Watcher實(shí)例在實(shí)例化過(guò)程中,會(huì)讀取data中的某個(gè)屬性雕沉,從而觸發(fā)當(dāng)前get方法
        if (Dep.target) {
          dep.depend();
        }
        return val;
      },
      set: newVal => {
        if (val === newVal) return;
        val = newVal;
        // 對(duì)新值進(jìn)行監(jiān)聽(tīng)
        chlidOb = observe(newVal);
        // 通知所有訂閱者集乔,數(shù)值被改變了
        dep.notify();
      },
    });
  }

  function observe(value) {
    // 當(dāng)值不存在,或者不是復(fù)雜數(shù)據(jù)類型時(shí)坡椒,不再需要繼續(xù)深入監(jiān)聽(tīng)
    if (!value || typeof value !== 'object') {
      return;
    }
    return new Observer(value);
  }

那么接下來(lái)就簡(jiǎn)單了,我們需要實(shí)現(xiàn)一個(gè)訂閱者(Watcher)扰路。

  class Watcher {
    constructor(vm, expOrFn, cb) {
      this.depIds = {}; // hash儲(chǔ)存訂閱者的id,避免重復(fù)的訂閱者
      this.vm = vm; // 被訂閱的數(shù)據(jù)一定來(lái)自于當(dāng)前Vue實(shí)例
      this.cb = cb; // 當(dāng)數(shù)據(jù)更新時(shí)想要做的事情
      this.expOrFn = expOrFn; // 被訂閱的數(shù)據(jù)
      this.val = this.get(); // 維護(hù)更新之前的數(shù)據(jù)
    }
    // 對(duì)外暴露的接口,用于在訂閱的數(shù)據(jù)被更新時(shí)肠牲,由訂閱者管理員(Dep)調(diào)用
    update() {
      this.run();
    }
    addDep(dep) {
      // 如果在depIds的hash中沒(méi)有當(dāng)前的id,可以判斷是新Watcher,因此可以添加到dep的數(shù)組中儲(chǔ)存
      // 此判斷是避免同id的Watcher被多次儲(chǔ)存
      if (!this.depIds.hasOwnProperty(dep.id)) {
        dep.addSub(this);
        this.depIds[dep.id] = dep;
      }
    }
    run() {
      const val = this.get();
      console.log(val);
      if (val !== this.val) {
        this.val = val;
        this.cb.call(this.vm, val);
      }
    }
    get() {
      // 當(dāng)前訂閱者(Watcher)讀取被訂閱數(shù)據(jù)的最新更新后的值時(shí)幼衰,通知訂閱者管理員收集當(dāng)前訂閱者
      Dep.target = this;
      const val = this.vm._data[this.expOrFn];
      // 置空靴跛,用于下一個(gè)Watcher使用
      Dep.target = null;
      return val;
    }
  }

那么我們最后完成Vue,將上述方法掛載在Vue上缀雳。

  class Vue {
    constructor(options = {}) {
      // 簡(jiǎn)化了$options的處理
      this.$options = options;
      // 簡(jiǎn)化了對(duì)data的處理
      let data = (this._data = this.$options.data);
      // 將所有data最外層屬性代理到Vue實(shí)例上
      Object.keys(data).forEach(key => this._proxy(key));
      // 監(jiān)聽(tīng)數(shù)據(jù)
      observe(data);
    }
    // 對(duì)外暴露調(diào)用訂閱者的接口,內(nèi)部主要在指令中使用訂閱者
    $watch(expOrFn, cb) {
      new Watcher(this, expOrFn, cb);
    }
    _proxy(key) {
      Object.defineProperty(this, key, {
        configurable: true,
        enumerable: true,
        get: () => this._data[key],
        set: val => {
          this._data[key] = val;
        },
      });
    }
  }

看下效果:

<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳中...(image-1da193-1526012269854-0)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

在線示例 雙向綁定實(shí)現(xiàn)---無(wú)漏洞版 by Iwobi (@xiaomuzhu) on CodePen.

至此,一個(gè)簡(jiǎn)單的雙向綁定算是被我們實(shí)現(xiàn)了梢睛。

2.3 Object.defineProperty的缺陷

其實(shí)我們升級(jí)版的雙向綁定依然存在漏洞,比如我們將屬性值改為數(shù)組肥印。

let demo = new Vue({
  data: {
    list: [1],
  },
});

const list = document.getElementById('list');
const btn = document.getElementById('btn');

btn.addEventListener('click', function() {
  demo.list.push(1);
});

const render = arr => {
  const fragment = document.createDocumentFragment();
  for (let i = 0; i < arr.length; i++) {
    const li = document.createElement('li');
    li.textContent = arr[i];
    fragment.appendChild(li);
  }
  list.appendChild(fragment);
};

// 監(jiān)聽(tīng)數(shù)組,每次數(shù)組變化則觸發(fā)渲染函數(shù),然而...無(wú)法監(jiān)聽(tīng)
demo.$watch('list', list => render(list));

setTimeout(
  function() {
    alert(demo.list);
  },
  5000,
);

在線示例 雙向綁定-數(shù)組漏洞 by Iwobi (@xiaomuzhu) on CodePen.

是的,Object.defineProperty的第一個(gè)缺陷,無(wú)法監(jiān)聽(tīng)數(shù)組變化。 然而Vue的文檔提到了Vue是可以檢測(cè)到數(shù)組變化的绝葡,但是只有以下八種方法,vm.items[indexOfItem] = newValue這種是無(wú)法檢測(cè)的深碱。

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

其實(shí)作者在這里用了一些奇技淫巧,把無(wú)法監(jiān)聽(tīng)數(shù)組的情況hack掉了,以下是方法示例。

const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];

aryMethods.forEach((method)=> {

    // 這里是原生Array的原型方法
    let original = Array.prototype[method];

   // 將push, pop等封裝好的方法定義在對(duì)象arrayAugmentations的屬性上
   // 注意:是屬性而非原型屬性
    arrayAugmentations[method] = function () {
        console.log('我被改變啦!');

        // 調(diào)用對(duì)應(yīng)的原生方法并返回結(jié)果
        return original.apply(this, arguments);
    };

});

let list = ['a', 'b', 'c'];
// 將我們要監(jiān)聽(tīng)的數(shù)組的原型指針指向上面定義的空數(shù)組對(duì)象
// 別忘了這個(gè)空數(shù)組的屬性上定義了我們封裝好的push等方法
list.__proto__ = arrayAugmentations;
list.push('d');  // 我被改變啦藏畅! 4

// 這里的list2沒(méi)有被重新定義原型指針敷硅,所以就正常輸出
let list2 = ['a', 'b', 'c'];
list2.push('d');  // 4

由于只針對(duì)了八種方法進(jìn)行了hack,所以其他數(shù)組的屬性也是檢測(cè)不到的,其中的坑很多,可以閱讀上面提到的文檔。

我們應(yīng)該注意到在上文中的實(shí)現(xiàn)里,我們多次用遍歷方法遍歷對(duì)象的屬性愉阎,這就引出了Object.defineProperty的第二個(gè)缺陷,只能劫持對(duì)象的屬性,因此我們需要對(duì)每個(gè)對(duì)象的每個(gè)屬性進(jìn)行遍歷绞蹦,如果屬性值也是對(duì)象那么需要深度遍歷,顯然能劫持一個(gè)完整的對(duì)象是更好的選擇。

Object.keys(value).forEach(key => this.convert(key, value[key]));


3.Proxy實(shí)現(xiàn)的雙向綁定的特點(diǎn)

Proxy在ES2015規(guī)范中被正式發(fā)布,它在目標(biāo)對(duì)象之前架設(shè)一層“攔截”榜旦,外界對(duì)該對(duì)象的訪問(wèn)幽七,都必須先通過(guò)這層攔截,因此提供了一種機(jī)制溅呢,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫(xiě),我們可以這樣認(rèn)為,Proxy是Object.defineProperty的全方位加強(qiáng)版,具體的文檔可以查看此處;

3.1 Proxy可以直接監(jiān)聽(tīng)對(duì)象而非屬性

我們還是以上文中用Object.defineProperty實(shí)現(xiàn)的極簡(jiǎn)版雙向綁定為例,用Proxy進(jìn)行改寫(xiě)澡屡。

const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
  get: function(target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver) {
    console.log(target, key, value, receiver);
    if (key === 'text') {
      input.value = value;
      p.innerHTML = value;
    }
    return Reflect.set(target, key, value, receiver);
  },
});

input.addEventListener('keyup', function(e) {
  newObj.text = e.target.value;
});

在線示例 Proxy版 by Iwobi (@xiaomuzhu) on CodePen.

我們可以看到,Proxy直接可以劫持整個(gè)對(duì)象,并返回一個(gè)新對(duì)象,不管是操作便利程度還是底層功能上都遠(yuǎn)強(qiáng)于Object.defineProperty

3.2 Proxy可以直接監(jiān)聽(tīng)數(shù)組的變化

當(dāng)我們對(duì)數(shù)組進(jìn)行操作(push咐旧、shift驶鹉、splice等)時(shí),會(huì)觸發(fā)對(duì)應(yīng)的方法名稱和length的變化铣墨,我們可以借此進(jìn)行操作,以上文中Object.defineProperty無(wú)法生效的列表渲染為例室埋。

const list = document.getElementById('list');
const btn = document.getElementById('btn');

// 渲染列表
const Render = {
  // 初始化
  init: function(arr) {
    const fragment = document.createDocumentFragment();
    for (let i = 0; i < arr.length; i++) {
      const li = document.createElement('li');
      li.textContent = arr[i];
      fragment.appendChild(li);
    }
    list.appendChild(fragment);
  },
  // 我們只考慮了增加的情況,僅作為示例
  change: function(val) {
    const li = document.createElement('li');
    li.textContent = val;
    list.appendChild(li);
  },
};

// 初始數(shù)組
const arr = [1, 2, 3, 4];

// 監(jiān)聽(tīng)數(shù)組
const newArr = new Proxy(arr, {
  get: function(target, key, receiver) {
    console.log(key);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver) {
    console.log(target, key, value, receiver);
    if (key !== 'length') {
      Render.change(value);
    }
    return Reflect.set(target, key, value, receiver);
  },
});

// 初始化
window.onload = function() {
    Render.init(arr);
}

// push數(shù)字
btn.addEventListener('click', function() {
  newArr.push(6);
});

在線示例 Proxy列表渲染 by Iwobi (@xiaomuzhu) on CodePen.

很顯然,Proxy不需要那么多hack(即使hack也無(wú)法完美實(shí)現(xiàn)監(jiān)聽(tīng))就可以無(wú)壓力監(jiān)聽(tīng)數(shù)組的變化,我們都知道,標(biāo)準(zhǔn)永遠(yuǎn)優(yōu)先于hack。

3.3 Proxy的其他優(yōu)勢(shì)

Proxy有多達(dá)13種攔截方法,不限于apply、ownKeys词顾、deleteProperty八秃、has等等是Object.defineProperty不具備的。

Proxy返回的是一個(gè)新對(duì)象,我們可以只操作新的對(duì)象達(dá)到目的,而Object.defineProperty只能遍歷對(duì)象屬性直接修改肉盹。

Proxy作為新標(biāo)準(zhǔn)將受到瀏覽器廠商重點(diǎn)持續(xù)的性能優(yōu)化昔驱,也就是傳說(shuō)中的新標(biāo)準(zhǔn)的性能紅利。

當(dāng)然,Proxy的劣勢(shì)就是兼容性問(wèn)題,而且無(wú)法用polyfill磨平,因此Vue的作者才聲明需要等到下個(gè)大版本(3.0)才能用Proxy重寫(xiě)上忍。

下期預(yù)告

下期準(zhǔn)備一篇我們主要講為什么我們需要前端框架骤肛,或者換幾種問(wèn)法,對(duì)于此項(xiàng)目你為什么選擇Angular窍蓝、Vue腋颠、React等框架,而不是直接JQuery或者js?不使用框架可能遇到什么問(wèn)題吓笙?使用框架的優(yōu)勢(shì)在哪里?框架解決了JQuery解決不了的什么問(wèn)題?

這個(gè)問(wèn)題是電面神器,問(wèn)題開(kāi)放性很好,也不需要面對(duì)面摳一些細(xì)節(jié),同時(shí)有功底有思考的同學(xué)與跟風(fēng)學(xué)框架的同學(xué)差距很容易暴露出來(lái)淑玫。

我們會(huì)邊解答這個(gè)問(wèn)題邊用Proxy構(gòu)建一個(gè)Mini版Vue,構(gòu)建Vue的過(guò)程就是我們不斷解決不使用框架的情況下遇到的各種問(wèn)題的過(guò)程。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末面睛,一起剝皮案震驚了整個(gè)濱河市絮蒿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叁鉴,老刑警劉巖土涝,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異幌墓,居然都是意外死亡但壮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)常侣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜡饵,“玉大人,你說(shuō)我怎么就攤上這事袭祟⊙椴校” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵巾乳,是天一觀的道長(zhǎng)您没。 經(jīng)常有香客問(wèn)我,道長(zhǎng)胆绊,這世上最難降的妖魔是什么氨鹏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮压状,結(jié)果婚禮上仆抵,老公的妹妹穿的比我還像新娘跟继。我一直安慰自己,他們只是感情好镣丑,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布舔糖。 她就那樣靜靜地躺著,像睡著了一般莺匠。 火紅的嫁衣襯著肌膚如雪金吗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天趣竣,我揣著相機(jī)與錄音摇庙,去河邊找鬼。 笑死遥缕,一個(gè)胖子當(dāng)著我的面吹牛卫袒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播单匣,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼夕凝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了封孙?” 一聲冷哼從身側(cè)響起迹冤,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤讽营,失蹤者是張志新(化名)和其女友劉穎虎忌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體橱鹏,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膜蠢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了莉兰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挑围。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖糖荒,靈堂內(nèi)的尸體忽然破棺而出杉辙,到底是詐尸還是另有隱情,我是刑警寧澤捶朵,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布蜘矢,位于F島的核電站,受9級(jí)特大地震影響综看,放射性物質(zhì)發(fā)生泄漏品腹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一红碑、第九天 我趴在偏房一處隱蔽的房頂上張望舞吭。 院中可真熱鬧,春花似錦、人聲如沸羡鸥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惧浴。三九已至澎剥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赶舆,已是汗流浹背哑姚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芜茵,地道東北人叙量。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像九串,于是被迫代替她去往敵國(guó)和親绞佩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容