前言
vue 2.0跟3.0的區別原理,
- 結構: 2.0用Flow ,3.0用 TypeScript。
- 性能: 3.0優化了Virtual Dom的算法。
- 響應式原理:2.0用 Object.defineProperty,3.0用Proxy
Vue2.0和Vue3.0實現原理
-
Vue 2.0
Vue2.0實現MVVM(雙向數據綁定)的原理是通過 Object.defineProperty 來劫持各個屬性的setter、getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。
-
Vue 3.0 實現響應式基於ES6: Proxy
Vue2.0和Vue3.0的差異如下:
Vue2.0
- 基於Object.defineProperty,不具備監聽數組的能力,需要重新定義數組的原型來達到響應式。
- Object.defineProperty 無法檢測到對象屬性的添加和刪除 。
- 由於Vue會在初始化實例時對屬性執行getter/setter轉化,所有屬性必須在data對象上存在才能讓Vue將它轉換為響應式。
- 深度監聽需要一次性遞歸,對性能影響比較大。
Vue3.0
- 基於Proxy和Reflect,可以原生監聽數組,可以監聽對象屬性的添加和刪除。
- 不需要一次性遍歷data的屬性,可以顯著提高性能。
- Proxy是ES6新增的屬性。
Vue2.x實現響應式
下面是基於Object.defineProperty ,一步步實現簡單版Vue2.0。
-
由於Object.defineProperty 無法監聽數組,所以數組類型實現響應式,需要處理。 判斷如果是數組類型,就重寫數組的原型方法('push','pop','shift',unshift)
// 重新定義數組原型,Object.defineProperty不具備監聽數組的方法 const oldArrayProperty = Array.prototype; const arrProto = Object.create(oldArrayProperty); ["push","pop","shift","unshift","splice"].forEach( methodName => (arrProto[methodName] = function() { updateView(); oldArrayProperty[methodName].call(this, ...arguments); }) )
-
將傳入的data屬性進行深度監聽,判斷是對象還是數組。
function observer(target){ if(typeof target !== 'object' || target === null){ return target } // 如果是數組類型,重寫數組原型的方法("push","pop","shift","unshift","splice") if(Array.isArray(target)){ target.__proto__ == arrProto; } // 如果是對象,遍歷對象所有的屬性,並使用Object.defineProperty把這些屬性全部轉為getter/setter for(let key in target){ defineReactive(target,key,target[key]) } }
-
核心API Object.defineProperty,將傳入屬性轉為 getter/setter
function defineReactive(target, key, value){ // 如果對象有更多的層級,再次調用observer監聽方法,實現深層次的監聽。 observer(value); Object.defineProperty(target, key, { get(){ return value; }, set(newValue){ // 設置值的時候也需要深度監聽 observer(value); if(newValue !== value){ value = newValue; // 數據驅動視圖,如果數據改變,就調用視圖更新的方法。對應到Vue中是執行VDOM updateView(); } } }) }
-
數據更新會觸發視圖更新,這是MVVM的綁定原理,這就會涉及到Vue的 template 編譯為 render 函數,在執行 Virtual Dom, Diff算法, Vnode等 這些東西了。
function updateView(){ console.log('視圖更新') }
5.使用
const data = { name: "zhangsan", age: 20, info: { address: "杭州" // 需要深度監聽 }, nums: [10, 20, 30] }; observer(data);
Vue3.0實現響應式
Vue3.0基於Proxy來做數據大劫持代理,可以原生支持到數組的響應式,不需要重寫數組的原型,還可以直接支持新增和刪除屬性, 比Vue2.x的Object.defineProperty更加的清晰明了。
-
核心代碼
const proxyData = new Proxy(data, { get(target,key,receive){ // 只處理本身(非原型)的屬性 const ownKeys = Reflect.ownKeys(target) if(ownKeys.includes(key)){ console.log('get',key) // 監聽 } const result = Reflect.get(target,key,receive) return result }, set(target, key, val, reveive){ // 重復的數據,不處理 const oldVal = target[key] if(val == oldVal){ return true } const result = Reflect.set(target, key, val,reveive) return result }, // 刪除屬性 deleteProperty(target, key){ const result = Reflect.deleteProperty(target,key) return result } })
-
使用
const data = { name: "zhangsan", age: 20, info: { address: "杭州" // 需要深度監聽 }, nums: [10, 20, 30] };
直接這樣就可以了,也不需要聲明,Proxy直接會代理監聽data的內容,非常的簡單方便,唯一的不足就是部分瀏覽器無法兼容Proxy,也不能hack
全部源碼
-
Vue2.0
function defineReactive(target, key, value) { //深度監聽 observer(value); Object.defineProperty(target, key, { get() { return value; }, set(newValue) { //深度監聽 observer(value); if (newValue !== value) { value = newValue; updateView(); } } }); } function observer(target) { if (typeof target !== "object" || target === null) { return target; } if (Array.isArray(target)) { target.__proto__ = arrProto; } for (let key in target) { defineReactive(target, key, target[key]); } } // 重新定義數組原型 const oldAddrayProperty = Array.prototype; const arrProto = Object.create(oldAddrayProperty); ["push", "pop", "shift", "unshift", "spluce"].forEach( methodName => (arrProto[methodName] = function() { updateView(); oldAddrayProperty[methodName].call(this, ...arguments); }) ); // 視圖更新 function updateView() { console.log("視圖更新"); } // 聲明要響應式的對象 const data = { name: "zhangsan", age: 20, info: { address: "杭州" // 需要深度監聽 }, nums: [10, 20, 30] }; // 執行響應式 observer(data);
-
Vue3.0
const proxyData = new Proxy(data, { get(target,key,receive){ // 只處理本身(非原型)的屬性 const ownKeys = Reflect.ownKeys(target) if(ownKeys.includes(key)){ console.log('get',key) // 監聽 } const result = Reflect.get(target,key,receive) return result }, set(target, key, val, reveive){ // 重復的數據,不處理 const oldVal = target[key] if(val == oldVal){ return true } const result = Reflect.set(target, key, val,reveive) console.log('set', key, val) return result }, deleteProperty(target, key){ const result = Reflect.deleteProperty(target,key) console.log('delete property', key) console.log('result',result) return result } }) // 聲明要響應式的對象,Proxy會自動代理 const data = { name: "zhangsan", age: 20, info: { address: "杭州" // 需要深度監聽 }, nums: [10, 20, 30] };