vue的資料劫持以及運算元組的坑

顏醬發表於2019-04-16

TL;DR

  • 給data新增新屬性的時候vm.$set(vm.info,'newKey','newValue')
  • data上面屬性值是陣列的時候,需要用陣列的方法運算元組,而不能通過index或者length屬性去運算元組,因為監聽不到屬性操作的動作。

安裝和初使用vue

vue是構建使用者介面的漸進式框架。所謂的漸進式:vue + components + vue-router + vuex + vue-cli可以根據需要選擇相應的功能。

來串命令mkdir vue-apply;cd vue-apply;npm init -y;npm i vue

來一個html檔案如下,瀏覽器瞄下~,瀏覽器控制檯vm.msg=0再看下

    <div id="app">{{msg}}</div>
    <script src="node_modules/vue/dist/vue.js"></script>
    <script>
    let vm = new Vue({
        el:'#app',
        // template加上之後會替換掉#app這個標籤
        // template:'<h1>en</h1>',
        data:{msg:'msg'}
    })
    vm.msg = 'msg'
    </script>
複製程式碼

說說mvvm mvc

mvc其實是model view Model傳統所有邏輯在controller,難以維護。使用者輸入 => 控制器 => 資料改變,如果資料變了需要獲取dom,操作屬性,再渲染到檢視上。
mvvm其實是model view viewModel資料變化驅動檢視。資料變了,不需要你獲取dom,然後改變dom的內容。這邊資料變了,vm負責監聽,檢視那邊自動發生變化。最明顯的是不需要document.querySelector之類的了。

vm的實質

上面說了vm負責讓資料變了,檢視能自動發生變化。這麼神奇魔術背後的原理是Object.defineProperty。其實就是屬性的讀取和設定操作都進行了監聽,當有這樣的操作的時候,進行某種動作。來一個demo玩下。

// 對obj上面的屬性進行讀取和設定監聽
let obj = {
        name:'huahua',
        age:18
    }
    function observer(obj){
        if(typeof obj === 'object'){
            for (const key in obj) {
                defineReactive(obj,key,obj[key])
            }
        }
    }
    // get的return的值才是最終你讀取到的值。所以設的值是為讀取準備的。
    // set傳的引數是設定的值,注意這裡不要有obj.name = newVal 這樣又觸發set監聽,會死迴圈的。
    function defineReactive(obj,key,value){
        Object.defineProperty(obj,key,{
            get:function(){
                console.log('你在讀取')
                // happy的話這邊可以value++,這樣你發現讀取的值始終比設定的大一個,因為return就是讀取到的值
                return value
            },
            set:function(newVal){
                console.log('資料更新了')
                value = newVal
            }

        })
    }
    observer(obj)
    obj.age = 2
    console.log(obj.age)

複製程式碼

在瀏覽器執行的時候,控制檯隨手也可以obj.name="hua1"類似的操作,發現都監聽到了。但是如果更深一步,obj.name={firstname:'hua',lastname:'piaoliang'};obj.name.lastname='o'就不能監聽到屬性修改了。因為並沒有將新的賦值物件監聽其屬性。所以函式需要改進。 需要在defineReactive的第一行加上observer(value)。設定值的時候如果是物件的話,也需要將這個物件資料劫持。同理,set那邊也需要加這行。

   function defineReactive(obj,key,value){
       // 注意這裡!!!!!!!
       observer(value)
        Object.defineProperty(obj,key,{
            get:function(){
                return value
            },
            set:function(newVal){
                // 注意這裡!!!!!!!
                observer(newVal)
                console.log('資料更新了')
                value = newVal
            }

        })
    }

複製程式碼

繼續,如果obj.name=[1,2,3];obj.name.push(4)發現又沒有通知了,這是因為Object.defineProperty不支援監聽陣列變化。所以需要重寫陣列上面的方法。話說,最近看了個文章,理論上也可以監聽陣列,但是效能消耗和收益不成正比,所以,vue就沒去實現了。

   // 把陣列上大部分方法重寫了,這裡不一一列舉。但是如果你 [1,2].length--,這是捕捉不到的
    let arr = ['push','slice','split']
    arr.forEach(method=>{
        let oldPush = Array.property[method]
        Array.property[method] = function(value){
            console.log('資料更新')
            oldPush.call(this,value)
        }
    })
複製程式碼

vue使用的時候注意的坑

正如上面的解釋,vue2.0的底層約莫是這個邏輯,所以使用需要注意的點:

  • 因為是一開始就資料劫持了。所以後來如果新繫結屬性,是沒有資料劫持的。如果需要呼叫 vm.$set(vm.info,'newKey','newValue'),vm是vue的例項。

  • 當屬性值是陣列,陣列變化的時候,跟蹤不到變化。因為陣列雖然是物件,但是Object.defineProperty不支援陣列,所以vue改寫了陣列的所有方法,當呼叫陣列方法的時候,就調動變動事件。但是不能通過屬性或者索引控制陣列,比如length,index。

  • 總結:data上,繫結所有屬性避免後期加新屬性。如果是陣列,只能通過陣列方法修改陣列。如下例子,控制檯vm.arr--發現檢視並不會變化,vm.arr.push(4)就能變化

    <div id="app">{{msg}}{{arr}}</div>
    <script src="node_modules/vue/dist/vue.js"></script>
    <script>
    let vm = new Vue({
        el:'#app',
        // template加上之後會替換掉#app這個標籤
        // template:'<h1>en</h1>',
        data:{msg:'msg',arr:[1,2,3]}
    })
    vm.msg = 'msg'
    </script>
複製程式碼

相關文章