Vue雙向綁定原理(我儘可能寫的。簡潔)

先問本身兩個問題: 
    1.app.message修改數據的時候,Vue內部是如何監聽message數據發生改變的
         使用Object.defineProperty ->監聽對象屬性的改變
    2.當數據發生改變後,Vue是如何知道 通知哪些 '人',界面發生刷新呢 (張三,李四,王五
         發佈訂閱者模式
        
<div id='app'> {{ message }} //張三 {{ message }} //李四 {{ message }} //王五 這裏假設他們分別對應這三個message           {{ name }} </div> <script src='./node_modules/vue/dist/vue.js'></script> <script> const app = new Vue({ el:'#app', data:{//注意!這個是對象 message:'哈哈哈', name:'kobe ' } }) </script>

  第一步確定是建立Vue實例。  注意這裏的data是 !對象javascript

能夠理解爲咱們把  這個obj對象傳入Vue,Vue內部拿到的就是一個html

const obj = {
   message:'哈哈哈',
   name:'kobe '
}

  拿到這個對象後,Vue先用Object.keys(obj)拿到一個包含obj對象的全部屬性(message,name)的數組,而後進行forEach遍歷,拿到每個屬性對應的value值vue

Object.keys(obj).forEach(key => {
        let value = obj[key]

再進行‘數據劫持’       Object.defineProperty字面意思就是   給  obj對象從新 定義 屬性。由於obj對象內的屬性不容易監聽java

 1 Object.defineProperty(obj, key, {
 2             set(newValue){
 3                 console.log('監聽' + key + '改變' ) !!!注意這裏的監聽  4                      value = newValue
 7                 },
 8             get(){
 9                     console.log('獲取'+ key +'對應的值')
14                     return value
15                 }
16             })
17     })           
    此時咱們在控制檯給message從新賦值
    app.message = '老詹'
    就會觸發set方法,打印出:
     '監聽message改變'
      '老詹'
    直接app.message則觸發get

當咱們設置或者訪問對象的屬性的時候,都會觸發相應的函數,而後在這個函數裏進行打印/返回/或者設置屬性的值node

既然如此,咱們固然能夠在觸發函數的時候動一些手腳作點咱們本身想作的事情,這也就是「劫持」操做。數組

------------------------------------------------------------------------------------------------------------app

既然set內監聽到數據發生了變化ide

set(newValue){
               console.log('監聽' + key + '改變' ) !!!注意這裏的監聽
                    value = newValue
               },

那麼監聽到值改變後,告訴誰?誰在用呢?(記不記得一開始的 張三,李四,王五。讓咱們進行‘擬人’,更好理解)函數

誰在用實際上是解析HTML代碼,獲取到哪些人有用到咱們的屬性性能

{{ message }} //張三 {{ message }} //李四 {{ message }} //王五 

哎!!獲取!獲取!那麼它確定會調用一次message的get,那我就知道是張三,李四,王五大家在用這個message屬性
那麼到時候!一旦message屬性的值發生變化set,那我再去通知大家三個。
---即發佈者訂閱者模式

class Dep{  //Dep   即 Depdency 依賴  存儲全部對我這個屬性有依賴的
    constructor(){
        this.subs = [] //用來記錄如今是誰要訂閱咱們的屬性的   subs 即subscribe訂閱            
    }      
}

const dep = new Dep() //這個Dep對象就能夠用subs這個數組去記錄全部的訂閱者 (就是剛剛的張三,李四,王五啊)
 

那我怎麼知道全部的訂閱者在哪裏呢  ,定義一個addSub方法,以後往裏面傳入sub形參,表明即將要傳入進來的訂閱者

class Dep {
        constructor(){
            // 這個數組是用來記錄如今是誰要訂閱咱們的屬性的 
            this.subs = []
        }

        addSub(){  //定義一個addSub方法,以後往裏面傳入sub形參,表明即將要傳入進來的訂閱者  爲了拿到訂閱者,咱們得再建立一個類  class Watcher
    
        }
}
爲了拿到訂閱者,咱們得再建立一個類  class Watcher
class Dep {
        constructor(){
            // 這個數組是用來記錄如今是誰要訂閱咱們的屬性的 
            this.subs = []
        }

        addSub(){
         
        }
    }

    // 監聽觀察
    class Watcher{//訂閱者
        constructor(name){
            this.name = name;
        }

        update(){
            console.log(this.name + '發送update') //update  你細想,通知到張三,李四的時候,是否是須要他們本身更新一下,把界面更新一下
        }
    }
到時候咱們建立一個watcher 實例,就能夠實例化出來張三 w1對象 李四w2 對象, !!就能夠把這些w1,w2實例對象放進dep實例的addSub內

   addSub(watcher){               !形參
    this.subs.push(watcher)
   }

以後,誰用message屬性了,咱們就趕忙建立一個w1shiliduix

const w1  = new Watcher('張三') //意味着張三使用了一次

就把w1傳進  dep.addSub(w1)

const obj = {
        message:'哈哈哈',
        name:'kobe '
    }

    Object.keys(obj).forEach(key => {
        let value = obj[key]

        Object.defineProperty(obj, key, {
            set(newValue){
                console.log('監聽' + key + '改變' )
                    // 監聽到值改變後告訴誰?誰在用呢?
                    // 解析HTML代碼,獲取到哪些人有用咱們的屬性  (獲取一次--誰用-誰就調用一次get)
                    value = newValue

                    // dep.notify()//通知  !!!若是有一天,值發生改變了,咱們拿到這個dep實例對象,調用notify
                },
                get(){
                    console.log('獲取'+ key +'對應的值')
                    // 張三 get ->通知到就須要本身 update一下
                    // 李四 get -> update
                    // 王五 get -> update

                    return value
                }
            })
    })

    // 發佈訂閱者模式   Dependency  subscribe訂閱
    class Dep {//發佈者
        // 存儲全部對我這個屬性有依賴的
        constructor(){
            // 這個數組是用來記錄如今是誰要訂閱咱們的屬性的 
            this.subs = []
        }

        addSub(watcher){
            this.subs.push(watche r)
        }

    再定義一個notify方法 notify(){
this.subs.forEach( item => { item.update() //拿到咱們的subs,遍歷找到裏面全部的訂閱者,讓他去調用本身的update }) } } // 監聽觀察 class Watcher{//訂閱者 constructor(name){ this.name = name; } update(){ console.log(this.name + '發送update') } } const dep = new Dep() const w1 = new Watcher('張三') dep.addSub(w1) const w2 = new Watcher('李四') dep.addSub(w2) const w3 = new Watcher('王五') dep.addSub(w3)

 

-------

最終代碼一覽

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <!--
    1.app.message修改數據,vue內部是如何監聽message數據發生改變
    Object.defineProperty ->監聽對象屬性的改變

    2.當數據發生改變,Vue是如何知道通知哪些人,界面發生刷新呢 
    發佈訂閱者模式
-->

<div id='app'>
    {{ mess    age }} //張三
    {{ message }} //李四
    {{ message }}    //王五

    {{ name }}
</div>

<script>
    const obj = {
        message:'哈哈哈',
        name:'kobe '
    }

    Object.keys(obj).forEach(key => {
        let value = obj[key]

        Object.defineProperty(obj, key, {
            set(newValue){
                console.log('監聽' + key + '改變' )
                    // 監聽到值改變後告訴誰?誰在用呢?
                    // 解析HTML代碼,獲取到哪些人有用咱們的屬性  (獲取一次--誰用-誰就調用一次get)
                    value = newValue

                    // dep.notify()//通知
                },
                get(){
                    console.log('獲取'+ key +'對應的值')
                    // 張三 get ->通知到就須要本身 update一下
                    // 李四 get -> update
                    // 王五 get -> update

                    return value
                }
            })
    })

    // 發佈訂閱者模式   Dependency  subscribe訂閱
    class Dep {//發佈者
        // 存儲全部對我這個屬性有依賴的
        constructor(){
            // 這個數組是用來記錄如今是誰要訂閱咱們的屬性的 
            this.subs = []
        }

        addSub(watcher){
            this.subs.push(watche r)
        }

        notify(){
            this.subs.forEach( item => {
                item.update()
            })
        } 
    }

    // 監聽觀察
    class Watcher{//訂閱者
        constructor(name){
            this.name = name;
        }

        update(){
            console.log(this.name + '發送update')
        }
    }

    const dep = new Dep()

    const w1 = new Watcher('張三')
    dep.addSub(w1)

    const w2 = new Watcher('李四')
    dep.addSub(w2)

    const w3 = new Watcher('王五')
    dep.addSub(w3)

    dep.notify()

</script>

<script src='./node_modules/vue/dist/vue.js'></script>
<script>
    const app = new Vue({
        el:'#app',
        data:{
            message:'哈哈哈',
            name:'kobe '
        }
    })
</script>
</body>
</html>
View Code

如下是copy來的,更爲幹練

 

數據雙向綁定做爲 Vue 核心功能之一,Vue 則採用的是數據劫持與發佈訂閱相結合的方式實現雙向綁定。

其中數據劫持是利用了 Object.defineProperty() 方法從新定義了對象獲取屬性值get和設置屬性值set的操做來實現的;

preview

劫持了數據以後,咱們就須要一個監聽器 Observer 來監聽屬性的變化。得知屬性發生變化以後咱們須要一個 Watcher 訂閱者來更新視圖,咱們還須要一個 compile 指令解析器,用於解析咱們的節點元素的指令與初始化視圖。

  • Observer 監聽器:用來監聽屬性的變化通知訂閱者
  • Watcher 訂閱者:收到屬性的變化,而後更新視圖(這個過程當中咱們可能會有不少個訂閱者 Watcher 因此咱們要建立一個容器 Dep 去作一個統一的管理)
  • Compile 解析器:解析指令,初始化模版,綁定訂閱者

​ 當咱們訪問或設置對象的屬性的時候,都會觸發相對應的函數,而後在這個函數裏返回或設置屬性的值。既然如此,咱們固然能夠在觸發函數的時候動一些手腳作點咱們本身想作的事情,這也就是「劫持」操做。

​ 在Vue中其實就是經過Object.defineProperty劫持對象屬性的settergetter操做,並「種下」一個監聽器,當數據發生變化的時候發出通知。

注意: 該方法每次只能設置一個屬性,那麼就須要遍歷對象來完成其屬性的配置:

Object.keys(student).forEach(key => defineReactive(student, key)) 

​ 另外還必須是一個具體的屬性,這也很是的致命。假如後續須要擴展該對象,那麼就必須手動爲新屬性設置 setter 和 getter 方法,這就是爲何不在 data 中聲明的屬性沒法自動擁有雙向綁定效果的緣由 。這時須要調用 Vue.set() 手動設置。

 

 

​ 針對 Array 類型的劫持

​ 數組是一種特殊的對象,其下標實際上就是對象的屬性,因此理論上是能夠採用 Object.defineProperty() 方法處理數組對象。 

​ 可是 Vue 並無採用上述方法劫持數組對象,緣由分析:一、特殊的 length 屬性,相比較對象的屬性,數組下標變化地相對頻繁,而且改變數組長度的方法也比較靈活,一旦數組的長度發生變化,那麼在沒法自動感知的狀況下,開發者只能手動更新新增的數組下標,這但是一個很繁瑣的工做。二、數組主要的操做場景仍是遍歷,而對於每個元素都掛載一個 get 和 set 方法,恐怕也是不小的性能負擔。

​ 數組方法的劫持:最終 Vue 選擇劫持一些經常使用的數組操做方法,從而知曉數組的變化狀況:push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'。數組方法的劫持涉及到原型相關的知識,首先數組實例大部分方法都是來源於 Array.prototype 對象。順便提一下,採用 Vue.set() 方法設置數組元素時,Vue 內部其實是調用劫持後的 splice() 方法來觸發更新。

總結:由上述內容可知,Vue 中的數據劫持分爲兩大部分:

針對 Object 類型,採用 Object.defineProperty() 方法劫持屬性的讀取和設置方法;

針對 Array 類型,採用原型相關的知識劫持經常使用的函數,從而知曉當前數組發生變化。

而且 Object.defineProperty() 方法存在如下缺陷:每次只能設置一個具體的屬性,致使須要遍歷對象來設置屬性,同時也致使了沒法探測新增屬性;屬性描述符 configurable 對其的影響是致命的。

 



 

*Object.keys(obj)

參數:要返回其枚舉自身屬性的對象

返回值:一個表示給定對象的全部可枚舉屬性的字符串數組

相關文章
相關標籤/搜索