<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么采用Proxy重構(gòu)響應(yīng)系統(tǒng) | Vue3源碼系列

          共 9011字,需瀏覽 19分鐘

           ·

          2020-09-16 22:06


          前言

          我們先看一下官方對(duì)其的定義

          用于定義基本操作的自定義行為

          修改的是程序默認(rèn)形為,形同于在編程語(yǔ)言層面上做修改,屬于元編程(meta programming)

          • 元編程(英文:Metaprogramming,又譯超編程,是指某類(lèi)計(jì)算機(jī)程序的編寫(xiě),這類(lèi)計(jì)算機(jī)程序編寫(xiě)或者操縱其它程序(或者自身)作為它們的數(shù)據(jù),或者在運(yùn)行時(shí)完成部分本應(yīng)在編譯時(shí)完成的工作

          一段代碼來(lái)理解元編程

          #!/bin/bash
          #?metaprogram
          echo?'#!/bin/bash'?>program
          for?((I=1;?I<=1024;?I++))?do
          ????echo?"echo?$I"?>>program
          done
          chmod?+x?program

          這段程序每執(zhí)行一次能幫我們生成一個(gè)名為program的文件,文件內(nèi)容為1024行echo,如果我們手動(dòng)來(lái)寫(xiě)1024行代碼,效率顯然低效

          元編程優(yōu)點(diǎn):與手工編寫(xiě)全部代碼相比,程序員可以獲得更高的工作效率,或者給與程序更大的靈活度去處理新的情形而無(wú)需重新編譯

          proxy 譯為代理,可以理解為在操作目標(biāo)對(duì)象前架設(shè)一層代理,將所有本該我們手動(dòng)編寫(xiě)的程序交由代理來(lái)處理

          生活中也有許許多多的proxy, 如代購(gòu),中介,因?yàn)樗麄兯械男袨槎疾粫?huì)直接觸達(dá)到目標(biāo)對(duì)象

          正文

          本篇文章作為 Vue3 源碼系列前置篇章之一,Proxy 的科普文,跟Vue3并沒(méi)有絕對(duì)關(guān)系,但是當(dāng)你靜下心讀完了前置篇章,再去讀后續(xù)的源碼系列,感受定會(huì)截然不同

          前置篇章包含

          下來(lái)將介紹 Proxy 的基本使用

          語(yǔ)法

          • target 要使用 Proxy 包裝的目標(biāo)對(duì)象(可以是任何類(lèi)型的對(duì)象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理
          • handler 一個(gè)通常以函數(shù)作為屬性的對(duì)象,用來(lái)定制攔截行為

          const?proxy?=?new?Proxy(target,?handle)

          舉個(gè)例子

          const?origin?=?{}
          const?obj?=?new?Proxy(origin,?{
          ??get:?function?(target,?propKey,?receiver)?{
          ??return?'10'
          ??}
          });

          obj.a?//?10
          obj.b?//?10
          origin.a?//?undefined
          origin.b?//?undefined

          上方代碼我們給一個(gè)空對(duì)象的get架設(shè)了一層代理,所有get操作都會(huì)直接返回我們定制的數(shù)字10,需要注意的是,代理只會(huì)對(duì)proxy對(duì)象生效,如上方的origin就沒(méi)有任何效果

          Handler 對(duì)象常用的方法

          方法描述
          handler.has()in 操作符的捕捉器。
          handler.get()屬性讀取操作的捕捉器。
          handler.set()屬性設(shè)置操作的捕捉器。
          handler.deleteProperty()delete 操作符的捕捉器。
          handler.ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
          handler.apply()函數(shù)調(diào)用操作的捕捉器。
          handler.construct()new 操作符的捕捉器

          下面挑handler.get重點(diǎn)講一下,其它方法的使用也都大同小異,不同的是參數(shù)的區(qū)別

          handler.get

          get我們?cè)谏厦胬右呀?jīng)體驗(yàn)過(guò)了,現(xiàn)在詳細(xì)介紹一下,用于代理目標(biāo)對(duì)象的屬性讀取操作

          授收三個(gè)參數(shù) get(target, propKey, ?receiver)

          • target 目標(biāo)對(duì)象
          • propkey 屬性名
          • receiver Proxy 實(shí)例本身

          舉個(gè)例子

          const?person?=?{
          ??like:?"vuejs"
          }

          const?obj?=?new?Proxy(person,?{
          ??get:?function(target,?propKey)?{
          ????if?(propKey?in?target)?{
          ??????return?target[propKey];
          ????}?else?{
          ??????throw?new?ReferenceError("Prop?name?\""?+?propKey?+?"\"?does?not?exist.");
          ????}
          ??}
          })

          obj.like?//?vuejs
          obj.test?//?Uncaught?ReferenceError:?Prop?name?"test"?does?not?exist.

          上面的代碼表示在讀取代理目標(biāo)的值時(shí),如果有值則直接返回,沒(méi)有值就拋出一個(gè)自定義的錯(cuò)誤

          注意:

          • 如果要訪問(wèn)的目標(biāo)屬性是不可寫(xiě)以及不可配置的,則返回的值必須與該目標(biāo)屬性的值相同
          • 如果要訪問(wèn)的目標(biāo)屬性沒(méi)有配置訪問(wèn)方法,即get方法是undefined的,則返回值必須為undefined

          如下面的例子

          const?obj?=?{};
          Object.defineProperty(obj,?"a",?{?
          ??configurable:?false,?
          ??enumerable:?false,?
          ??value:?10,?
          ??writable:?false?
          })

          const?p?=?new?Proxy(obj,?{
          ??get:?function(target,?prop)?{
          ????return?20;
          ??}
          })

          p.a?//?Uncaught?TypeError:?'get'?on?proxy:?property?'a'?is?a?read-only?and?non-configurable..

          可撤消的Proxy

          proxy有一個(gè)唯一的靜態(tài)方法,Proxy.revocable(target, handler)

          Proxy.revocable()方法可以用來(lái)創(chuàng)建一個(gè)可撤銷(xiāo)的代理對(duì)象

          該方法的返回值是一個(gè)對(duì)象,其結(jié)構(gòu)為:{"proxy": proxy, "revoke": revoke}

          • proxy 表示新生成的代理對(duì)象本身,和用一般方式 new Proxy(target, handler) 創(chuàng)建的代理對(duì)象沒(méi)什么不同,只是它可以被撤銷(xiāo)掉。
          • revoke 撤銷(xiāo)方法,調(diào)用的時(shí)候不需要加任何參數(shù),就可以撤銷(xiāo)掉和它一起生成的那個(gè)代理對(duì)象。

          該方法常用于完全封閉對(duì)目標(biāo)對(duì)象的訪問(wèn), 如下示例

          const?target?=?{?name:?'vuejs'}
          const?{proxy,?revoke}?=?Proxy.revocable(target,?handler)
          proxy.name?//?正常取值輸出?vuejs
          revoke()?//?取值完成對(duì)proxy進(jìn)行封閉,撤消代理
          proxy.name?//?TypeError:?Revoked

          Proxy的應(yīng)用場(chǎng)景

          Proxy的應(yīng)用范圍很廣,下方列舉幾個(gè)典型的應(yīng)用場(chǎng)景

          校驗(yàn)器

          想要一個(gè)number,拿回來(lái)的卻是string,驚不驚喜?意不意外?

          下面我們使用Proxy實(shí)現(xiàn)一個(gè)邏輯分離的數(shù)據(jù)格式驗(yàn)證器

          嗯,真香!

          const?target?=?{
          ??_id:?'1024',
          ??name:??'vuejs'
          }

          const?validators?=?{??
          ????name(val)?{
          ????????return?typeof?val?===?'string';
          ????},
          ????_id(val)?{
          ????????return?typeof?val?===?'number'?&&?val?>?1024;
          ????}
          }

          const?createValidator?=?(target,?validator)?=>?{
          ??return?new?Proxy(target,?{
          ????_validator:?validator,
          ????set(target,?propkey,?value,?proxy){
          ??????let?validator?=?this._validator[propkey](value)
          ??????if(validator){
          ????????return?Reflect.set(target,?propkey,?value,?proxy)
          ??????}else?{
          ????????throw?Error(`Cannot?set?${propkey}?to?${value}.?Invalid?type.`)
          ??????}
          ????}
          ??})
          }

          const?proxy?=?createValidator(target,?validators)

          proxy.name?=?'vue-js.com'?//?vue-js.com
          proxy.name?=?10086?//?Uncaught?Error:?Cannot?set?name?to?10086.?Invalid?type.
          proxy._id?=?1025?//?1025
          proxy._id?=?22??//?Uncaught?Error:?Cannot?set?_id?to?22.?Invalid?type?

          私有屬性

          在日常編寫(xiě)代碼的過(guò)程中,我們想定義一些私有屬性,通常是在團(tuán)隊(duì)中進(jìn)行約定,大家按照約定在變量名之前添加下劃線(xiàn) _ 或者其它格式來(lái)表明這是一個(gè)私有屬性,但我們不能保證他能真正‘私有化’,

          下面使用Proxy輕松實(shí)現(xiàn)私有屬性攔截

          const?target?=?{
          ??_id:?'1024',
          ??name:??'vuejs'
          }

          const?proxy?=?new?Proxy(target,?{
          ??get(target,?propkey,?proxy){
          ????if(propkey[0]?===?'_'){
          ??????throw?Error(`${propkey}?is?restricted`)
          ????}
          ????return?Reflect.get(target,?propkey,?proxy)
          ??},
          ??set(target,?propkey,?value,?proxy){
          ????if(propkey[0]?===?'_'){
          ??????throw?Error(`${propkey}?is?restricted`)
          ????}
          ????return?Reflect.get(target,?propkey,?value,?proxy)
          ??}
          })

          proxy.name?//?vuejs
          proxy._id?//?Uncaught?Error:?_id?is?restricted
          proxy._id?=?'1025'?//?Uncaught?Error:?_id?is?restricted

          Proxy 使用場(chǎng)景還有很多很多,不再一一列舉,如果你需要在某一個(gè)動(dòng)作的生命周期內(nèi)做一些特定的處理,那么Proxy 都是適合的

          為什么要用Proxy重構(gòu)

          Proxy 之前,JavaScript 中就提供過(guò) Object.defineProperty,允許對(duì)對(duì)象的 getter/setter 進(jìn)行攔截

          Vue3.0之前的雙向綁定是由 defineProperty 實(shí)現(xiàn), 在3.0重構(gòu)為 Proxy,那么兩者的區(qū)別究竟在哪里呢?

          首先我們?cè)賮?lái)回顧一下它的定義

          Object.defineProperty() 方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回此對(duì)象

          上面給兩個(gè)詞劃了重點(diǎn),對(duì)象上,屬性,我們可以理解為是針對(duì)對(duì)象上的某一個(gè)屬性做處理的

          語(yǔ)法

          • obj 要定義屬性的對(duì)象
          • prop 要定義或修改的屬性的名稱(chēng)或 Symbol
          • descriptor 要定義或修改的屬性描述符
          Object.defineProperty(obj,?prop,?descriptor)

          舉個(gè)例子

          const?obj?=?{}
          Object.defineProperty(obj,?"a",?{
          ??value?:?1,
          ??writable?:?false,?//?是否可寫(xiě)?
          ??configurable?:?false,?//?是否可配置
          ??enumerable?:?false?//?是否可枚舉
          })

          //?上面給了三個(gè)false,?下面的相關(guān)操作就很容易理解了
          obj.a?=?2?//?無(wú)效
          delete?obj.a?//?無(wú)效
          for(key?in?obj){
          ??console.log(key)?//?無(wú)效?
          }

          Vue中的defineProperty

          Vue3之前的雙向綁定都是通過(guò) definePropertygetter,setter 來(lái)實(shí)現(xiàn)的,我們先來(lái)體驗(yàn)一下 getter,setter

          const?obj?=?{};
          Object.defineProperty(obj,?'a',?{
          ??set(val)?{
          ????console.log(`開(kāi)始設(shè)置新值:?${val}`)
          ??},
          ??get()?{?
          ????console.log(`開(kāi)始讀取屬性`)
          ????return?1;?
          ??},
          ??writable?:?true
          })

          obj.a?=?2?//?開(kāi)始設(shè)置新值:?2
          obj.a?//?開(kāi)始獲取屬性?

          看到這里,我相信有些同學(xué)已經(jīng)想到了實(shí)現(xiàn)雙向綁定背后的流程了,其實(shí)很簡(jiǎn)單嘛,只要我們觀察到對(duì)象屬性的變更,再去通知更新視圖就好了

          我們摘抄一段 Vue 源碼中的核心實(shí)現(xiàn)驗(yàn)證一下,這一部分一筆代過(guò),不是本文重點(diǎn)

          ??//?源碼位置:https://github.com/vuejs/vue/blob/ef56410a2c/src/core/observer/index.js#L135
          ??//?...
          ??Object.defineProperty(obj,?key,?{
          ????enumerable:?true,
          ????configurable:?true,
          ????get:?function?reactiveGetter?()?{
          ??????//?...
          ??????if?(Dep.target)?{
          ????????//?收集依賴(lài)
          ????????dep.depend()
          ??????}
          ??????return?value
          ????},
          ????set:?function?reactiveSetter?(newVal)?{
          ??????//?...
          ??????//?通知視圖更新
          ??????dep.notify()
          ????}
          ??})

          對(duì)象新增屬性為什么不更新

          這個(gè)問(wèn)題用過(guò)Vue的同學(xué)應(yīng)該有超過(guò)95%比例遇到過(guò)

          data??()?{
          ??return??{
          ????obj:?{
          ??????a:?1
          ????}
          ??}
          }

          methods:?{
          ??update?()?{
          ????this.obj.b?=?2
          ??}
          }

          上面的偽代碼,當(dāng)我們執(zhí)行 update 更新 obj 時(shí),我們預(yù)期視圖是要隨之更新的,實(shí)際是并不會(huì)

          這個(gè)其實(shí)很好理解,我們先要明白 vuedata init 的時(shí)機(jī),data init 是在生命周期 created 之前的操作,會(huì)對(duì) data ?綁定一個(gè)觀察者 Observer,之后 data 中的字段更新都會(huì)通知依賴(lài)收集器Dep觸發(fā)視圖更新

          然后我們回到 defineProperty 本身,是對(duì)對(duì)象上的屬性做操作,而非對(duì)象本身

          一句話(huà)來(lái)說(shuō)就是,在 Observer data 時(shí),新增屬性并不存在,自然就不會(huì)有 getter, setter,也就解釋了為什么新增視圖不更新,解決有很多種,Vue 提供的全局$set 本質(zhì)也是給新增的屬性手動(dòng) observer

          //?源碼位置?https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L201
          function?set?(target:?Array?|?Object,?key:?any,?val:?any):?any?{
          ??//?....
          ??if?(!ob)?{
          ????target[key]?=?val
          ????return?val
          ??}
          ??defineReactive(ob.value,?key,?val)
          ??ob.dep.notify()
          ??return?val
          }

          數(shù)組變異

          由于 JavaScript 的限制,Vue 不能檢測(cè)以下數(shù)組的變動(dòng):當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:vm.items[indexOfItem] = newValue

          先來(lái)看一段代碼

          var?vm?=?new?Vue({
          ??data:?{
          ????items:?['1',?'2',?'3']
          ??}
          })
          vm.items[1]?=?'4'?//?視圖并未更新

          文檔已經(jīng)做出了解釋?zhuān)⒉皇?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">defineProperty的鍋,而是尤大在設(shè)計(jì)上對(duì)性能的權(quán)衡,下面這段代碼可以驗(yàn)證

          function?defineReactive(data,?key,?val)?{
          ??Object.defineProperty(data,?key,?{
          ????enumerable:?true,
          ????configurable:?true,
          ??????get:?function?defineGet()?{
          ????????console.log(`get?key:?${key}?val:?${val}`);
          ????????return?val;
          ??????},
          ??????set:?function?defineSet(newVal)?{
          ????????console.log(`set?key:?${key}?val:?${newVal}`);
          ????????val?=?newVal;
          ??????}
          ??})
          }

          function?observe(data)?{
          ??Object.keys(data).forEach(function(key)?{
          ????defineReactive(data,?key,?data[key]);
          ??})
          }

          let?test?=?[1,?2,?3];

          observe(test);

          test[0]?=?4?//?set?key:?0?val:?4

          雖然說(shuō)索引變更不是 defineProperty 的鍋,但新增索引的確是 defineProperty 做不到的,所以就有了數(shù)組的變異方法

          能看到這里,大概也能猜到內(nèi)部實(shí)現(xiàn)了,還是跟$set一樣,手動(dòng) observer,下面我們驗(yàn)證一下

          const?methodsToPatch?=?[
          ??'push',
          ??'pop',
          ??'shift',
          ??'unshift',
          ??'splice',
          ??'sort',
          ??'reverse'
          ]

          methodsToPatch.forEach(function?(method)?{
          ??//?緩存原生數(shù)組
          ??const?original?=?arrayProto[method]
          ??//?def使用Object.defineProperty重新定義屬性
          ??def(arrayMethods,?method,?function?mutator?(...args)?{
          ????const?result?=?original.apply(this,?args)?//?調(diào)用原生數(shù)組的方法

          ????const?ob?=?this.__ob__??//?ob就是observe實(shí)例observe才能響應(yīng)式
          ????let?inserted
          ????switch?(method)?{
          ??????// push和unshift方法會(huì)增加數(shù)組的索引,但是新增的索引位需要手動(dòng)observe的
          ??????case?'push':
          ??????case?'unshift':
          ????????inserted?=?args
          ????????break
          ??????//?同理,splice的第三個(gè)參數(shù),為新增的值,也需要手動(dòng)observe
          ??????case?'splice':
          ????????inserted?=?args.slice(2)
          ????????break
          ????}
          ????//?其余的方法都是在原有的索引上更新,初始化的時(shí)候已經(jīng)observe過(guò)了
          ????if?(inserted)?ob.observeArray(inserted)
          ????//?dep通知所有的訂閱者觸發(fā)回調(diào)
          ????ob.dep.notify()
          ????return?result
          ??})
          })

          對(duì)比

          一個(gè)優(yōu)秀的開(kāi)源框架本身就是一個(gè)不斷打碎重朔的過(guò)程,上面做了些許鋪墊,現(xiàn)在我們簡(jiǎn)要總結(jié)一下

          • Proxy 作為新標(biāo)準(zhǔn)將受到瀏覽器廠商重點(diǎn)持續(xù)的性能優(yōu)化

          • Proxy 能觀察的類(lèi)型比 defineProperty 更豐富

          • Proxy 不兼容IE,也沒(méi)有 polyfill, defineProperty 能支持到IE9

          • Object.definedProperty 是劫持對(duì)象的屬性,新增元素需要再次 definedProperty。而 Proxy 劫持的是整個(gè)對(duì)象,不需要做特殊處理

          • 使用 defineProperty 時(shí),我們修改原來(lái)的 obj 對(duì)象就可以觸發(fā)攔截,而使用 proxy,就必須修改代理對(duì)象,即 Proxy 的實(shí)例才可以觸發(fā)攔截

          參考文獻(xiàn)

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久久久黄色片 | 亚洲乱码国产 | 操一操撸一撸 | 国产一区二区免费播放 | 色五月丁香在线 |