茶餘飯後聊聊 Vue3.0 響應式數據那些事兒

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Vue3.0 爲了達到更快、更小、更易於維護、更貼近原生、對開發者更友好的目的,在很多方面進行了重構:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"使用 Typescript"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"放棄 class 採用 function-based API"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"重構 complier"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"重構 virtual DOM"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"新的響應式機制"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"今天咱就聊聊重構後的響應式數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"嚐鮮"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重構後的 Vue3.0 和之前在寫法上有很大的差別,早前在網絡上對於 Vue3.0 這種激進式的重構方式發起了一場討論,見仁見智。不多說先看看 Vue3.0 在寫法上激進到什麼程度。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\n\n\n \n Document\n <script src="%5C%22..%5C/packages%5C/vue%5C/dist%5C/vue.global.js%5C%22"><\/script>\n<\/head>\n<body>\n <div id=\"app\"><\/div>\n <script>\n const { reactive, computed, effect, createApp } = Vue\n const App = {\n template: `\n <div id=\"box\">\n <button @click=\"add\">{{ state.count }}<\/button>\n <\/div>\n `,\n setup() {\n const state = reactive({\n count: 0\n })\n function add() {\n state.count++\n }\n effect(() => {\n console.log('count改變', state.count);\n })\n return {\n state,\n add\n }\n }\n }\n createApp().mount(App, '#app')\n <\/script>\n<\/body>\n<\/html>\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"確實寫法上和 Vue2.x 差別有點大,還整出了個 setup。不過我的第一感覺倒不是寫法上的差異,畢竟寫過 React,這種寫法也沒啥特別的。關鍵是這種響應式數據的寫法好像在哪裏見過有沒有?寫過 React 項目的人可能一眼就能看出來,沒錯就是它 mobx,一種 React 的響應式狀態管理插件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import {observable,computed,autorun} from\"mobx\"\nvar numbers = observable([1,2,3]);\nvar sum = computed(() => numbers.reduce((a, b) => a + b, 0));\n\nvar disposer = autorun(() =>console.log(sum.get()));\n\/\/ 輸出 '6'\nnumbers.push(4);\n\/\/ 輸出 '10'\nnumbers.push(5);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再看看 Vue3.0 暴露的這幾個和響應式數據相關的方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"reactive(value)"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建可觀察的變量,參數可以是 JS 原始類型、引用、純對象、類實例、數組、集合(Map|Set)。"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":2},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"effect(fn)"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"effect 意思是副作用,此方法默認會先執行一次。如果 fn 中有依賴的可觀察屬性變化時,會再次觸發此回調函數"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":3},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"computed(()=>expression)"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建一個計算值,"},{"type":"codeinline","content":[{"type":"text","text":"computed"}]},{"type":"text","text":" 實現也是基於 "},{"type":"codeinline","content":[{"type":"text","text":"effect"}]},{"type":"text","text":" 來實現的,特點是 "},{"type":"codeinline","content":[{"type":"text","text":"computed"}]},{"type":"text","text":" 中的函數不會立即執行,多次取值是有緩存機制的,"},{"type":"codeinline","content":[{"type":"text","text":"expression"}]},{"type":"text","text":" 不應該有任何副作用,而僅僅是返回一個值。當這個 "},{"type":"codeinline","content":[{"type":"text","text":"expression"}]},{"type":"text","text":" 依賴的可觀察屬性變化時,這個表達式會重新計算。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和 mobx 有異曲同工之妙。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Vue3.0 把創建響應式對象從組件實例初始化中抽離了出來,通過暴露 API 的方式將響應式對象創建的權利交給開發者,開發者可以自由的決定何時何地創建響應式對象,就衝這點 Vue3.0 我先粉了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"重構後的響應式機制帶來了哪些改變?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每一個大版本的發佈都意味着新功能、新特性的出現,那麼重構後的響應式數據部分相比 3.0 之前的版本有了哪些方面的改變呢?下面聽我娓娓道來:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"對數組的全面監聽"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Vue2.x 中被大家吐槽的最多的一點就是針對數組只實現了 "},{"type":"codeinline","content":[{"type":"text","text":"push,pop,shift,unshift,splice,sort,reverse'"}]},{"type":"text","text":" 這七個方法的監聽,以前通過數組下標改變值的時候,是不能觸發視圖更新的。這裏插一個題外話,很多人認爲 Vue2.x 中數組不能實現全方位監聽是 Object.defineProperty 不能監聽數組下標的改變,這可就冤枉人家了,人家也能偵聽數組下標變化的好嗎,不信你看"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const arr = [\"2019\",\"雲\",\"棲\",\"音\",\"樂\",\"節\"];\narr.forEach((val,index)=>{\n Object.defineProperty(arr,index,{\n set(newVal){\n console.log(\"賦值\");\n },\n get(){\n console.log(\"取值\");\n return val;\n }\n })\n})\nlet index = arr[1];\n\/\/取值\narr[0] = \"2050\";\n\/\/賦值\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"沒毛病,一切變化都在人家的掌握中。上面這段代碼,有沒有人沒看懂,我假裝你們都不懂,貼張圖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/49\/497760f46cfd83694917c122d94b8f19.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從數組的數據結構來看,數組也是一個 Key-Value 的鍵值對集合,只是 Key 是數字罷了,自然也可以通過 Object.defineProperty 來實現數組的下標訪問和賦值攔截了。其實 Vue2.x 沒有實現數組的全方位監聽主要有兩方面原因:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"數組和普通對象相比,JS 數組太\"多變\"了。比如:"},{"type":"codeinline","content":[{"type":"text","text":"arr.length=0"}]},{"type":"text","text":",可以瞬間清空一個數組;"},{"type":"codeinline","content":[{"type":"text","text":"arr[100]=1"}]},{"type":"text","text":"又可以瞬間將一個數組的長度變爲 100(其他位置用空元素填充),等等騷操作。對於一個普通對象,我們一般只會改變 Key 對應的 Value 值,而不會連 key 都改變了,而數組就不一樣了 Key 和 Value 都經常增加或減少,因此每次變化後我們都需要重新將整個數組的所有key遞歸的使用 Object.defineProperty 加上 setter 和 getter,同時我們還要窮舉每一種數組變化的可能,這樣勢必就會帶來性能開銷問題,有的人會覺得這點性能開銷算個 x 呀,但是性能問題都是由小變大的,如果數組中存的數據量大而且操作頻繁時,這就會是一個大問題。React16.x 在就因爲在優化 textNode 的時候,移除了無意義的 span 標籤,性能據說都提升了多少個百分點,所以性能問題不可小看。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"數組在應用中經常會被操作,但是通常 "},{"type":"codeinline","content":[{"type":"text","text":"push,pop,shift,unshift,splice,sort,reverse"}]},{"type":"text","text":" 這 7 種操作就能達到目的。因此,出於性能方面的考慮 Vue2.x 做出了一定的取捨。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼 Vue3.0 怎麼又走回頭路去實現了數組的全面監聽了呢?答案就是 Proxy 和 Reflet 這一對原生 CP 的出現,Vue3.0 使用 Proxy 作爲響應式數據實現的核心,用 Proxy 返回一個代理對象,通過代理對象來收集依賴和觸發更新。大概的原理像這段代碼一樣:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const arr = [\"2019\",\"雲\",\"棲\",\"音\",\"樂\",\"節\"];\nlet ProxyArray = newProxy(arr,{\n get:function(target, name, value, receiver) {\n console.log(\"取值\")\n returnReflect.get(target,name);\n },\n set: function(target, name, value, receiver) {\n console.log(\"賦值\")\n Reflect.set(target,name, value, receiver);;\n }\n })\n const index = ProxyArray[0];\n \/\/取值\n ProxyArray[0]=\"2050\"\n \/\/賦值\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"效果和 Object.defineProperty 一樣一樣的,又顯得清新脫俗有沒有?而且 Proxy 只要是對象都能代理,後面還會提到。當然 Vue3.0 是雖然有了新歡,但也沒忘記舊愛,對於在之前版本中數組的幾種方法的監聽還是照樣支持的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"惰性監聽"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"什麼是\"惰性監聽\"?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單講就是\"偷懶\",開發者可以選擇性的生成可觀察對象。在平時的開發中常有這樣的場景,一些頁面上的數據在頁面的整個生命週期中是不會變化的,這時這部分數據不需要具備響應式能力,這在 Vue3.0 以前是沒有選擇餘地的,所有在模板中使用到的數據都需要在 data 中定義,組件實例在初始化的時候會將 data 整個對象變爲可觀察對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"惰性監聽有什麼好處?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"提高了組件實例初始化速度"}]},{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Vue3.0 以前組件實例在初始化的時候會將 data 整個對象變爲可觀察對象,通過遞歸的方式給每個 Key 使用 Object.defineProperty 加上 getter 和 settter,如果是數組就重寫代理數組對象的七個方法。而在 Vue3.0 中,將可響應式對象創建的權利交給了開發者,開發者可以通過暴露的 reactive, compted, effect 方法自定義自己需要響應式能力的數據,實例在初始化時不需要再去遞歸 data 對象了,從而降低了組件實例化的時間。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"降低了運行內存的使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"Vue3.0 以前生成響應式對象會對對象進行深度遍歷,同時爲每個 Key 生成一個 def 對象用來保存 Key 的所有依賴項,當 Key 對應的 Value 變化的時候通知依賴項進行 update。但如果這些依賴項在頁面整個生命週期內不需要更新的時候,這時 def 對象收集的依賴項不僅沒用而且還會佔用內存,如果可以在初始化 data 的時候忽略掉這些不會變化的值就好了。Vue3.0 通過暴露的 reactive 方法,開發者可以選擇性的創建可觀察對象,達到減少依賴項的保存,降低了運行內存的使用。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Map、Set、WeakSet、WeakMap的監聽"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面提到 Proxy 可以代理所有的對象,立馬聯想到了 ES6 裏面新增的集合 Map、Set, 聚合類型的支持得益於 Proxy 和 Reflect。講真的這之前還真不知道 Proxy 這麼剛啥都能代理,二話不說直接動手用 Proxy 代理了一個 map 試試水"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let map = newMap([[\"name\",\"zhengcaiyun\"]])\nlet mapProxy = newProxy(map, {\n get(target, key, receiver) {\n console.log(\"取值:\",key)\n returnReflect.get(target, key, receiver)\n }\n})\nmapProxy.get(\"name\")\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Uncaught TypeError: Method Map.prototype.get called on incompatible receiver [object Object]"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一盆涼水潑來,報錯了。原來 "},{"type":"codeinline","content":[{"type":"text","text":"Map、Set"}]},{"type":"text","text":" 對象賦值、取值和他們內部的 this 指向有關係,但這裏的 this 指向的是其實是 Proxy 對象,所以得這樣幹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let map = newMap([['name','wangyangyang']])\nlet mapProxy = newProxy(map, {\n get(target, key, receiver) {\n var value = Reflect.get(...arguments)\n console.log(\"取值:\",...arguments)\n returntypeof value == 'function' ? value.bind(target) : value\n }\n})\nmapProxy.get(\"name\")\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當獲取的是一個函數的時候,通過作用域綁定的方式將原對象綁定到 "},{"type":"codeinline","content":[{"type":"text","text":"Map、Set"}]},{"type":"text","text":" 對象上就好了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Vue3.0 是如何實現集合類型數據監聽的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"眼尖的同學看完上面這段代碼會發現一個問題,集合是沒有 set 方法,集合賦值用的是 add 操作,那咋辦呢?來看看那麼 Vue3.0 是怎麼處理的,上一段簡化後的源碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function reactive(target: object) {\n return createReactiveObject(\n target,\n rawToReactive,\n reactiveToRaw,\n mutableHandlers,\n mutableCollectionHandlers\n )\n}\n\nfunction createReactiveObject(\n target: any,\n toProxy: WeakMap<any, any>,\n toRaw: WeakMap<any, any>,\n baseHandlers: ProxyHandler<any>,\n collectionHandlers: ProxyHandler<any>\n) {\n \/\/collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])\n const handlers = collectionTypes.has(target.constructor)\n ? collectionHandlers\n : baseHandlers\n \/\/生成代理對象\n observed = newProxy(target, handlers)\n toProxy.set(target, observed)\n toRaw.set(observed, target)\n if (!targetMap.has(target)) {\n targetMap.set(target, newMap())\n }\n return observed\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據 target 類型適配不同的 handler,如果是集合 ("},{"type":"codeinline","content":[{"type":"text","text":"Map、Set"}]},{"type":"text","text":")就使用 collectionHandlers,是其他類型就使用 baseHandlers。接下來看看 collectionHandlers"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"export const mutableCollectionHandlers: ProxyHandler<any> = {\n get: createInstrumentationGetter(mutableInstrumentations)\n}\nexport const readonlyCollectionHandlers: ProxyHandler<any> = {\n get: createInstrumentationGetter(readonlyInstrumentations)\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"沒有意外只有 get,騷就騷在這兒:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/\/ 可變數據插樁對象,以及一系列相應的插樁方法\nconst mutableInstrumentations: any = {\n get(key: any) {\n returnget(this, key, toReactive)\n },\n get size() {\n return size(this)\n },\n has,\n add,\n set,\n delete: deleteEntry,\n clear,\n forEach: createForEach(false)\n}\n\/\/ 迭代器相關的方法\nconst iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]\niteratorMethods.forEach(method => {\n mutableInstrumentations[method] = createIterableMethod(method, false)\n readonlyInstrumentations[method] = createIterableMethod(method, true)\n})\n\/\/ 創建getter的函數\nfunction createInstrumentationGetter(instrumentations: any) {\n returnfunction getInstrumented(\n target: any,\n key: string | symbol,\n receiver: any\n ) {\n target =\n hasOwn(instrumentations, key) && key in target ? instrumentations : target\n returnReflect.get(target, key, receiver)\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於 Proxy 的 traps 跟 "},{"type":"codeinline","content":[{"type":"text","text":"Map|Set"}]},{"type":"text","text":" 集合的原生方法不一致,因此無法通過 Proxy 劫持 set,所以作者在在這裏進行了\"偷樑換柱\",這裏新創建了一個和集合對象具有相同屬性和方法的普通對象,在集合對象 get 操作時將 target 對象換成新創建的普通對象。這樣,當調用 get 操作時 Reflect 反射到這個新對象上,當調用 set 方法時就直接調用新對象上可以觸發響應的方法,是不是很巧妙?所以多看源碼好處多多,可以多學學人家的騷操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"IE 怎麼辦?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是個實在不想提但又繞不開的話題,IE 在前端開發者眼裏和魔鬼沒什麼區別。在 Vue3.0 之前,響應式數據的實現是依賴 ES5 的 Object.defineProperty,因此只要支持 ES5 的瀏覽器都支持 Vue,也就是說 Vue2.x 能支持到 IE9。Vue3.0 依賴的是 Proxy 和 Reflect 這一對出生新時代的 CP,且無法被轉譯成 ES5,或者通過 Polyfill 提供兼容,這就尷尬了。開發者技術前線獲悉的信息,官方在發佈最終版本之前會做到兼容 IE11,至於更低版本的 IE 那就只有送上一曲涼涼了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實也不用太糾結 IE 的問題,因爲連微軟自己都已經放棄治療 IE 擁抱 Chromium 了,我們又何必糾結呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結語"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用開源框架時不要忘了,我們之所以能免費試用他,靠的維護者投入的大量精力。希望我們多去發現它帶來的優點和作者想通過它傳遞的編程思想。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頭圖:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:赤墨"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/vxUHy3Az58T_4MrSz0PR1Q"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:茶餘飯後聊聊 Vue3.0 響應式數據那些事兒"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:政採雲前端團隊 - 微信公衆號 [ID:Zoo-Team]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章