1. 實(shí)現(xiàn)雙向綁定Object.defineProperty與proxy的VS

        共 5558字,需瀏覽 12分鐘

         ·

        2022-01-11 00:43

        關(guān)注 入坑互聯(lián)網(wǎng) ,回復(fù)“加群

        加入我們一起學(xué)習(xí),天天進(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)一叫『劫持』。)

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

        指的是在訪問(wèn)或者修改對(duì)象的某個(gè)屬性時(shí),通過(guò)一段代碼攔截這個(gè)行為,進(jìn)行額外的操作或者修改返回結(jié)果。

        比較典型的是 Object.defineProperty() 和 ES2015 中新增的 Proxy 對(duì)象。數(shù)據(jù)劫持最著名的應(yīng)用當(dāng)屬雙向綁定,這也是一個(gè)已經(jīng)被討論爛了的面試必考題。例如 Vue 2.x 使用的是 Object.defineProperty()(Vue 在 3.x 版本之后改用 Proxy 進(jìn)行實(shí)現(xiàn))。

        Object.defineProperty

        ES5 提供了 Object.defineProperty 方法,該方法可以在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回這個(gè)對(duì)象。

        語(yǔ)法:

        Object.defineProperty(obj, prop, descriptor)

        參數(shù):

        obj: 要在其上定義屬性的對(duì)象。

        prop:  要定義或修改的屬性的名稱。

        descriptor: 將被定義或修改的屬性的描述符。

        來(lái)個(gè)例子:

        // 這是將要被劫持的對(duì)象const data = {  name: '',};
        function say(name) { if (name === '孫紅雷') { console.log('給大家推薦一款超好玩的游戲'); } else if (name === '綠茶婊') { console.log('綠茶婊,撩妹屬性爆表'); } else { console.log('來(lái)做我的兄弟'); }}
        // 遍歷對(duì)象,對(duì)其屬性值進(jìn)行劫持Object.keys(data).forEach(function(key) { Object.defineProperty(data, key, { //當(dāng)且僅當(dāng)該屬性的 enumerable 為 true 時(shí),該屬性才能夠出現(xiàn)在對(duì)象的枚舉屬性中。默認(rèn)為 false。 enumerable: true, //當(dāng)且僅當(dāng)該屬性的 configurable 為 true 時(shí),該屬性描述符才能夠被改變,也能夠被刪除。默認(rèn)為 false。 configurable: true, get: function() { console.log('get'); }, set: function(newVal) { // 當(dāng)屬性值發(fā)生變化時(shí)我們可以進(jìn)行額外操作 console.log(`大家好,我系${newVal}`); say(newVal); }, });});
        data.name = '綠茶婊';//大家好,我系綠茶婊//綠茶婊,撩妹屬性爆表

        Proxy

        Proxy 用于修改某些操作的默認(rèn)行為,等同于在語(yǔ)言層面做出修改,所以屬于一種“元編程”(meta programming),即對(duì)編程語(yǔ)言進(jìn)行編程。

        Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫(xiě)。Proxy 這個(gè)詞的原意是代理,用在這里表示由它來(lái)“代理”某些操作,可以譯為“代理器”。

        我們來(lái)看看它的語(yǔ)法:

        var proxy = new Proxy(target, handler);

        proxy 對(duì)象的所有用法,都是上面這種形式,不同的只是handler參數(shù)的寫(xiě)法。其中,new Proxy()表示生成一個(gè)Proxy實(shí)例,target參數(shù)表示所要攔截的目標(biāo)對(duì)象,handler參數(shù)也是一個(gè)對(duì)象,用來(lái)定制攔截行為。

        var proxy = new Proxy({}, {    get: function(obj, prop) {        console.log('設(shè)置 get 操作')        return obj[prop];    },    set: function(obj, prop, value) {        console.log('設(shè)置 set 操作')        obj[prop] = value;    }});
        proxy.time = 35; // 設(shè)置 set 操作
        console.log(proxy.time); // 設(shè)置 get 操作 // 35

        除了 get 和 set 之外,proxy 可以攔截多達(dá) 13 種操作,比如 has(target, propKey),可以攔截 propKey in proxy 的操作,返回一個(gè)布爾值。

        // 使用 has 方法隱藏某些屬性,不被 in 運(yùn)算符發(fā)現(xiàn)var handler = {  has (target, key) {    if (key[0] === '_') {      return false;    }    return key in target;  }};var target = { _prop: 'foo', prop: 'foo' };var proxy = new Proxy(target, handler);console.log('_prop' in proxy); // false

        又比如說(shuō) apply 方法攔截函數(shù)的調(diào)用、call 和 apply 操作。

        apply 方法可以接受三個(gè)參數(shù),分別是目標(biāo)對(duì)象、目標(biāo)對(duì)象的上下文對(duì)象(this)和目標(biāo)對(duì)象的參數(shù)數(shù)組,不過(guò)這里我們簡(jiǎn)單演示一下:

        var target = function () { return 'I am the target'; };var handler = {  apply: function () {    return 'I am the proxy';  }};
        var p = new Proxy(target, handler);
        p();// "I am the proxy"

        又比如說(shuō) ownKeys 方法可以攔截對(duì)象自身屬性的讀取操作。具體來(lái)說(shuō),攔截以下操作:

        Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()

        下面的例子是攔截第一個(gè)字符為下劃線的屬性名,不讓它被 for of 遍歷到。

        let target = {  _bar: 'foo',  _prop: 'bar',  prop: 'baz'};
        let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] !== '_'); }};
        let proxy = new Proxy(target, handler);for (let key of Object.keys(proxy)) { console.log(target[key]);}// "baz"

        我們使用 proxy 再來(lái)寫(xiě)一下 watch 函數(shù)。使用效果如下:

        (function() {    var root = this;
        function watch(target, func) {
        var proxy = new Proxy(target, { get: function(target, prop) { return target[prop]; }, set: function(target, prop, value) { target[prop] = value; func(prop, value); } });
        if(target[name]) proxy[name] = value; return proxy; }
        this.watch = watch;})()
        var obj = { value: 1}
        var newObj = watch(obj, function(key, newvalue) { if (key == 'value') document.getElementById('container').innerHTML = newvalue;})
        document.getElementById('button').addEventListener("click", function() { newObj.value += 1});

        我們也可以發(fā)現(xiàn),使用 defineProperty 和 proxy 的區(qū)別,當(dāng)使用 defineProperty,我們修改原來(lái)的 obj 對(duì)象就可以觸發(fā)攔截,而使用 proxy,就必須修改代理對(duì)象,即 Proxy 的實(shí)例才可以觸發(fā)攔截。

        vue3為什么用proxy?舍棄Object.defineProperty

        Object.defineProperty缺點(diǎn):

        ①不能監(jiān)聽(tīng)數(shù)組的變化

        數(shù)組的以下幾個(gè)方法不會(huì)觸發(fā) set:

        push、pop、shift、unshift、splice、sort、reverse;

        Vue 把這些方法定義為變異方法 (mutation method),指的是會(huì)修改原來(lái)數(shù)組的方法。與之對(duì)應(yīng)則是非變異方法 (non-mutating method),例如 filter, concat, slice 等,它們都不會(huì)修改原始數(shù)組,而會(huì)返回一個(gè)新的數(shù)組。

        let arr = [1,2,3]let obj = {}Object.defineProperty(obj, 'arr', {  get () {    console.log('get arr')    return arr  },  set (newVal) {    console.log('set', newVal)    arr = newVal  }})obj.arr.push(4) // 只會(huì)打印 get arr, 不會(huì)打印 setobj.arr = [1,2,3,4] // 這個(gè)能正常 set

        ②必須遍歷對(duì)象的每個(gè)屬性

        使用 Object.defineProperty() 多數(shù)要配合 Object.keys() 和遍歷,于是多了一層嵌套。

        Object.keys(obj).forEach(key => {  Object.defineProperty(obj, key, {    // ...  })})

        ③必須深層遍歷嵌套的對(duì)象

        如果是這一類嵌套對(duì)象,那就必須逐層遍歷,直到把每個(gè)對(duì)象的每個(gè)屬性都調(diào)用 Object.defineProperty() 為止。Vue 的源碼中就能找到這樣的邏輯 (叫做 walk 方法)。

        let obj = {  info: {    name: 'eason'  }}

        針對(duì)Object.defineProperty有這些缺點(diǎn),為什么用proxy?

        1.proxy可以直接監(jiān)聽(tīng)數(shù)組的變化;

        2.proxy可以監(jiān)聽(tīng)對(duì)象而非屬性.它在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫(xiě)。

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

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

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

        ?? 看完三件事

        如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

        1. 點(diǎn)贊,讓更多的人也能看到這篇內(nèi)容(收藏不點(diǎn)贊,都是耍流氓)。
        2. 關(guān)注公眾號(hào)「入坑互聯(lián)網(wǎng)」,不定期分享原創(chuàng)知識(shí)。
        3. 也看看其它文章

        如何做到優(yōu)秀,好難!

        面試題聯(lián)盟之CSS篇

        年底前端面試及答案-html/css

        年底面試之es6總結(jié)

        年底面試之JavaScript總結(jié)(用心收集)

        - END -


        結(jié)伴同行前端路



        瀏覽 43
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 久久久无码亚洲精品日韩京东 | 美女航空一级毛片在线播放 | 国产精品一区成人精品果冻传媒 | 日韩 欧美p片内射久久 | 免费人爱视频 |