vue 響應式原理

一:vue2的響應式原理 (數據雙向綁定原理)

Object.defineProperty()

Object.defineProperty() 其實核心的並不是爲對象做數據雙向綁定,而是去給對象做屬性標籤,只不過屬性裏的 get 和 set 實現了響應式。

1. Object.defineproperty()

Object.defineProperty()
屬性名 默認值
value undefined
set undefined
get undefined
writable(可寫性) true
enumerable (可枚舉性)for/in 是否可循環 true
configurable (可配置性) true

例子:

var o = {}
    Object.defineProperty(o, 'name', {
      value: 'zhoufangbing',
      configurable: false,
      enumerable: false,
      writable: false
    })
    console.log(o) // { name: "zhoufangbing" }

    console.log(o.name) // zhoufangbing
    o.name = 'xx'
    console.log(o.name) // zhoufangbing 
    // 因爲 writable (可寫性)爲false,所以值不可更改,仍爲zhoufangbing
    // 若值爲 true , 則會輸出 xx
    
    // Object.keys() 方法會返回一個由一個給定對象的自身可枚舉屬性組成的數組,數組中屬性名的排列順序和使用 for...in 循環遍歷該對象時返回的順序一致 。
    console.log(Object.keys(o)) // []
    // 因爲 enumerable (可枚舉性)false 所以返回 [], 如果enumerable 爲true
    console.log(Object.keys(o)) // ['name']

    // 訪問器屬性 get set
    var person = {
      _age: 22, // 屬性前面加_,代表屬性只能通過對象方法訪問 不然瀏覽器報錯
      isGrowUp: false
    }
    Object.defineProperty(person, 'age', {
      get: function() {
        return this._age // this 指向 person
      },
      set: function(value) {
        this._age = value // set 重新賦值
        if (value >= 18) {
          this.isGrowUp = true
        } else {
          this.isGrowUp = false
        }
      }
    })

    console.log(person.age) // 22
    person.age = 19
    console.log(person.age) // 19
    console.log(person.isGrowUp) // true
    person.age = 12
    console.log(person.age) // 12
    console.log(person.isGrowUp) // false

2. Object.defineProperties()

Object.defineProperty(object, propertyName, descriptor) 定義新屬性時,descriptor 中不能同時有 訪問器(getter/setter) 與 value/writable 屬性

var person = {}
    Object.defineProperties(person, {
      age: {
        value: 18,
        writable: true,
        enumerable: true
      },
      isGroup: {
        get: function() {
          if (this.age >= 18) {
            return true
          } else {
            return false
          }
        }
      }
    })

筆記:

1. vue2 中的數據雙向綁定原理:使用Object.defineProperty() 中的 get 和 set

2. 當從對象中取值時,會觸發 get 函數,當重新賦值時,會觸發 set 函數~

如果定義了get 函數,get函數必須要有 return ,否則 【對象.屬性】 取值 會是undefined~ return 的值 就是 【對象.屬性】

二: vue 從改變一個數據到發生改變的全過程

1. 數據更改觸發 Object.defineProperty() 中的set函數

2. Set 函數觸發更新

3. 更改對應的虛擬dom

4. 更新render

三:數據雙向綁定簡易版代碼

<body>
  <div id="app"></div>
  <script>
    function Vue(dataObj) {
      this.$data = dataObj
      this.virtualDom = ''
      this.el = document.getElementById('app')
      this.observer(dataObj)
      this.render()
    }
    // 註冊 set 和 get
    Vue.prototype.observer = function(obj) {
      var _this = this
      // 遞歸一下 防止對象裏面套對象
      for(var key in obj) {
        var value = obj[key]
        if (typeof value === 'object') {
          this.observer(value)
        } else {
          Object.defineProperty(obj, key, { // TODO
            get: function() {
              // 進行依賴收集
              // 1. data 中的數據並不是所有地方都能用到的
              // 2. 如果直接更新整個視圖,有點虧
              // 3. 先收集來依賴 改變變量 的視圖(組件
              // 4. 把依賴的組件進行視圖更新,其他不變視圖
              return value
            },
            set: function(newValue) {
              value = newValue
              _this.render()
            }
          })
        }
      }
    }
    // 渲染頁面
    Vue.prototype.render = function() {
      this.virtualDom = 'i am ' + this.$data.a
      this.el.innerHTML = this.virtualDom
    }

   // 創建實例
    var vm = new Vue({
      a: 1
    })
    setTimeout(() => {
      vm.$data.a = 2 // 值改變,執行set函數
    }, 1000)
  </script>
</body>

四: Object.defineProperty() 定義的get 和 set 是對象的屬性,那麼數組怎麼辦?

在 Vue 中,通過數組下標更改數組內容,是不會觸發視圖更新的

var arr = [1,2,3,4]
arr[3] = 5

只有使用數組的方法,視圖才能更新

arr.push()
arr.unshift()
arr.shift()
arr.pop()
arr.reserve()

先學透知識、會用,再去看原理、源碼~

五:Vue3 的響應式原理

Proxy
// Proxy 對象用於定義基本操作的自定義行爲
// 和 Object.defineProperty() 功能基本一樣,但是用法不同

Proxy: 代理

作用:擴展(增強)對象的一些功能,比如預警、上報、擴展功能、統計等

語法: new Proxy(被代理的對象,對代理的對象做什麼操作)

new Proxy : 優點不會污染原對象,重新代理 objProxy

比如: 

// 原始對象
    let obj = { name: 'zhouzhou' }
    // 代理對象
    let objProxy = new Proxy(obj, {
      // get 方法兩個參數,第一個是對象,第二個是你訪問的是誰就是誰 
      // objProxy.name 則 property 就是 name
      get: function(target, property) {
        console.log(`您訪問了${property}屬性`)
        return target[property]
      }
    })
    console.log(objProxy.name) // get 函數的return值

例子1:實現一個訪問一個對象的屬性,正常情況下是返回 undefined,希望如果不存在返回 warning 警告信息

  <script>
    let obj = {
      name: 'zhouzhou',
      age: 24,
      sex: '女'
    }
    let objProxy = new Proxy(obj, {
      get: function(target, property) {
        if (obj[property]) {
          return target[property]
        } else {
          throw new Error(`您訪問的屬性${property}不存在`)
        }
      }
    })

    console.log(objProxy.sex)
    console.log(objProxy.money)
  </script>

例子2:定義一個Proxy,DOM.div() 則生成一個div,DOM.a() 生成一個 a 標籤,DOM.span 生成一個 span

<div id="app"></div>
  <script>
    let DOM = new Proxy({}, {
      get: function(target, peoperty) {
        return function(attr = {}, ...argument) {
          // 創建對象
          const el = document.createElement(peoperty)
          // 設定屬性
          for(keys of Object.keys(attr)) {
            el.setAttribute(keys, attr[keys])
          }
          // 設定內容
          for(let elem of argument) {
            if (typeof elem === 'string') {
              child = document.createTextNode(elem)
            } else {
              child = elem
            }
            el.appendChild(child)
          }
          return el
        }
      }
    })

    const node = DOM.div(
      { class: 'wrapper'},
      'hhhhh',
      DOM.p({}, 'aaa'),
      DOM.a({href: 'https:www.baidu.com'}, '點擊跳轉')
    )
    console.log(node)
    const app = document.getElementById('app')
    app.appendChild(node)
  </script>

Proxy 的其他屬性~~

1. set : 設置值 攔截

var obj = new Proxy({}, {
      // set 三個參數:目標對象、屬性、屬性值
      set: function(target, property, value) {
        // 可以進行值攔截校驗
        if (property === 'age') {
          if (!Number.isInteger(value)) {
            throw TypeError('年齡必須爲整數')
            return
          }
          if (value < 18 || value > 80) {
            throw RangeError('年齡不在合法範圍內')
            return
          }
        }
        // 設置屬性值
        target[property] = value
      }
    })

    obj.name = 'zhouzhou'
    obj.age = 19
    console.log(obj) //  {name: "zhouzhou", age: 19}

2. deleteProperty() : 刪除屬性

let json = {
      a: 1,
      b: 2
    }
    var obj = new Proxy(json, {
      deleteProperty: function(target, property) {
        console.log(`you will delete ${property}`) // 刪除之前提示
        delete target[property]
      }
    })
    delete obj.a
    console.log(obj)

3. has():判斷屬性是否在 搭配關鍵字 in

let json = {
      a: 1,
      b: 2
    }
    var obj = new Proxy(json, {
      has: function(target, property) {
        console.log(`判斷是否存在${property}屬性`)
        return property in target
      }
    })
    
    console.log('a' in obj) // true
    console.log('c' in obj) // false

4. apply() 攔截方法

function sum (a, b) {
    return a + b
   }

  let newSum = new Proxy(sum, {
    // 目標對象、this指向、函數參數(數組形式)
    apply: function(target, context, args) {
      
      return Reflect.apply(...arguments) // 執行目標函數 sum 返回 5
    }
  })

  const result = newSum(2,3)
  console.log(result)

所以 Vue3 的響應式原理:Proxy 的 set 和 get

重寫:

function Vue(dataObj) {
      this.$data = dataObj
      this.virtualDom = ''
      this.el = document.getElementById('app')
      this.observer(dataObj)
      this.render()
    }
    // 註冊 set 和 get
    Vue.prototype.observer = function(obj) {
      var _this = this
      // 遞歸一下 防止對象裏面套對象
      this.$data = new Proxy(this.$data, {
        set: function(target, property, value) {
          target[property] = value
          _this.render()
        },
        get: function(target, property) {
          return target[property]
        }
      })
    }
    // 渲染頁面
    Vue.prototype.render = function() {
      this.virtualDom = 'i am ' + this.$data.a
      this.el.innerHTML = this.virtualDom
    }

   // 創建實例
    var vm = new Vue({
      a: 1
    })
    setTimeout(() => {
      vm.$data.a = 2 // 值改變,執行set函數
    }, 1000)

 

發佈了172 篇原創文章 · 獲贊 12 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章