『壹』 Vue源碼閱讀【番外篇】:為什麼Proxy需要搭配Reflect來實現響應式
前言我們都知道vue3.x版本是通過Proxy來實現的,用以解決2.X的一些缺陷。
但是我最近通過看vue源代碼發現它內部很多地方是Proxy、Reflect搭配一起使用的,例:
我們通過上面幾張圖可以看到get、set、deleteProperty、has、ownKeys都用了Reflect。
基於此,這就是我寫這篇文章的緣由,那我們探索一下為什麼要搭配這兩使用。
Proxy和Reflect概念Proxy:代理,可以通過代理對象完成對目標對象的攔截,並在攔截後進行過濾和改寫等操作,支持的攔截操作共13種
Reflect:反射,它提供攔截JavaScript操作的方法。這些方法與?Proxy的方法一一對應,也是13種。
Proxy簡單使用示例:const?person?=?{????name:?'張三',????get?nickName()?{????????return?`${this.name}是壞蛋`????}}const?personProxy?=?new?Proxy(person,?{????get(target,?key,?receiver)?{????????console.log('進來了吧')?????????return?target[key]????}})console.log(personProxy.nickName)//?先列印了:進來了吧//?然後列印了:張三是壞蛋通過上面這個示例,我們發現他成功代理了,雖然我們沒做什麼處理。
不過get裡面的receiver這個參數我們還沒用到,receiver在這個時候表示代理對象(也就是personProxy)。
我們在get裡面的returntarget[key],不能returnreceiver[key],否則會死循環。
我們再來個Proxy示例2:const?person?=?{????name:?'張三',????get?nickName()?{????????console.log(this)????????return?`${this.name}是壞蛋`????}}const?personProxy?=?new?Proxy(person,?{????get(target,?key,?receiver)?{????????return?target[key]????}})const?person2?=?{????name:?'李四'}Object.setPrototypeOf(person2,?personProxy)console.log(person2.nickName)上面這個示例種的console.log(this)和console.log(person2.nickName)會列印上面呢?
我們希望的應該是person2這個對象和李四是壞蛋是吧,但實際上沒有如期列印。
首先person2上面沒有nickName這個屬性,所以他會去personProxy上面找,然後返回了person上面的nickName屬性,nickName中的this指向了person他自己。
所以console.log(this)列印出來的是person對象,console.log(person2.nickName)列印出來的是person中的name,也就是張三是壞蛋。
那我們怎麼才能讓他按照我們設想的那樣列印李四是壞蛋?
我們來到第三個示例:const?person?=?{????name:?'張三',????get?nickName()?{????????console.log(this)????????return?`${this.name}是壞蛋`????}}const?personProxy?=?new?Proxy(person,?{????get(target,?key,?receiver)?{????????return?Reflect.get(target,?key,?receiver)????}})const?person2?=?{????name:?'李四'}Object.setPrototypeOf(person2,?personProxy)console.log(person.nickName)????????//?張三是壞蛋console.log(personProxy.nickName)???//?張三是壞蛋console.log(person2.nickName)???????//?李四是壞蛋/**???三次this列印的順序如下:*???{?name:?'張三',?nickName:?[Getter]?}*???{?name:?'張三',?nickName:?[Getter]?}*???{?name:?'李四'?}*?*/我們通過在get裡面returnReflect.get(target,key,receiver)來實現按我們想要的那樣列印出來。
我們給Reflect的get傳遞第三個參數(Proxy中的receiver),然後他就會修改調用時的this指向(也就是把指向person修改為:指向person2)。
我們引用阮老師中的Reflect代碼片段來說明一下receiver,例:var?myObject?=?{??foo:?1,??bar:?2,??get?baz()?{????return?this.foo?+?this.bar;??},};var?myReceiverObject?=?{??foo:?4,??bar:?4,};Reflect.get(myObject,?'baz',?myReceiverObject)?//?8如果name屬性部署了讀取函數(getter),則讀取函數的this綁定receiver。
最後最後我們引用vue官網的描述來理解一下:
使用Proxy的一個難點是?this?綁定。我們希望任何方法都綁定到這個Proxy,而不是目標對象,這樣我們也可以攔截它們。值得慶幸的是,ES6引入了另一個名為?Reflect?的新特性,它允許我們以最小的代價消除了這個問題傳送門
『貳』 webpack作者評價vite
評價:Vite 是 vue 的作者尤雨溪在開發 vue3.0 的時候開發的一個 基於原生ES-Mole的前端構建工具。其本人在後來對 vue3 的宣傳中對自己的新作品 Vite 贊不絕口,並表示自己 」再也回不去 webpack 了「 。
webpack缺點是緩慢的伺服器啟動
當冷啟動開發伺服器時,基於打包器的方式是在提供服務前去急切地抓取和構建你的整個應用。
vite改進
Vite 通過在一開始將應用中的模塊區分為依賴和源碼兩類,改進了開發伺服器啟動時間。
依賴大多為純JavaScript並在開發時不會變動。一些較大的依賴(例如有上百個模塊的組件庫)處理的代價也很高。依賴也通常會以某些方式(例如 ESM 或者 CommonJS)被拆分到大量小模塊中。
Vite 將會使用 esbuild 預構建依賴。Esbuild 使用 Go 編寫,並且比以 JavaScript 編寫的打包器預構建依賴快10-100倍。
源碼通常包含一些並非直接是 JavaScript 的文件,需要轉換(例如 JSX,CSS 或者 Vue/Svelte 組件),時常會被編輯。同時,並不是所有的源碼都需要同時被載入。(例如基於路由拆分的代碼模塊)。
Vite以原生ESM方式服務源碼。這實際上是讓瀏覽器接管了打包程序的部分工作:Vite 只需要在瀏覽器請求源碼時進行轉換並按需提供源碼。根據情景動態導入的代碼,即只在當前屏幕上實際使用時才會被處理。
webpack: 分析依賴=> 編譯打包=> 交給本地伺服器進行渲染。首先分析各個模塊之間的依賴,然後進行打包,在啟動webpack-dev-server,請求伺服器時,直接顯示打包結果。
webpack打包之後存在的問題:隨著模塊的增多,會造成打出的 bundle 體積過大,進而會造成熱更新速度明顯拖慢。
vite: 啟動伺服器=> 請求模塊時按需動態編譯顯示。是先啟動開發伺服器,請求某個模塊時再對該模塊進行實時編譯,因為現代游覽器本身支持ES-Mole,所以會自動向依賴的Mole發出請求。
所以vite就將開發環境下的模塊文件作為瀏覽器的執行文件,而不是像webpack進行打包後交給本地伺服器。
分析了webpack和vite的打包方式後,也就明白了為什麼vite比webpack打包快,因為它在啟動的時候不需要打包,所以不用分析模塊與模塊之間的依賴關系,不用進行編譯。這種方式就類似於我們在使用某個UI框架的時候,可以對其進行按需載入。
熱更新方面,效率更高。當改動了某個模塊的時候,也只用讓瀏覽器重新請求該模塊,不需要像webpack那樣將模塊以及模塊依賴的模塊全部編譯一次。