vue2.0和vue3.0的響應式原理


前言

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

  • 基於ProxyReflect,可以原生監聽數組,可以監聽對象屬性的添加和刪除。
  • 不需要一次性遍歷data的屬性,可以顯著提高性能。
  • Proxy是ES6新增的屬性。

Vue2.x實現響應式

下面是基於Object.defineProperty ,一步步實現簡單版Vue2.0。

  1. 由於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);
             })
         )
  2. 將傳入的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])
         }
     }
  3. 核心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();
                }
            }
        })
    }
     
  4. 數據更新會觸發視圖更新,這是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更加的清晰明了。

  1. 核心代碼

    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
       }
     }) 
  2. 使用

     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]
     };

     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM