利用Object.defineProperty簡單實現vue的數據響應式原理

前言:最近學了vue的響應式原理,可是學了以後,感受模模糊糊的,不知道本身是否真的理解其中的精髓,因此就本身動手簡單的實現一下vue的響應式原理。畢竟概念終究仍是概念,實踐纔是檢驗本身會不會的硬道理!javascript

在開始以前,咱們須要瞭解一下基礎的知識:
  • Object.defineProperty():它的做用是直接在一個對象上定義一個屬性,或者去修改一個已經存在的屬性。obj:表示須要定義屬性的當前對象。prop:當前須要定義的屬性名。 desc:屬性描述符,就是更精確的控制對象屬性。
Object.defineProperty(obj,prop,desc)
  • vue的數據響應式原理:就是vm中的data數據在頁面上有渲染,當data數據改變的時候,頁面上渲染的數據也跟着改變。
    例如頁面上我用了data中的msg渲染了,如圖:
    在這裏插入圖片描述
    當我data.msg的值改變了的時候:
    在這裏插入圖片描述

進入正題:

目標:成品就跟上面截圖的那樣,當你的數據發生變化,頁面的也跟着變化。
須要知識:
  • Object.defineProperty()
  • es6部分語法
實踐:

html頁面:html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>

<body>
    <div id="app">
        msg
    </div>
</body>
</html>

開始編寫js代碼:vue

第一步:定義咱們要渲染的數據
let data = { 
 
   
        msg: '你好'
    }

解釋:定義一個咱們要渲染到頁面的數據。java

第二步:獲取頁面的變量並渲染
class Render { 
 
   
        constructor() { 
 
   
            this.app = document.querySelector('#app')
            this.arg = this.app.innerText
            this.render()
        }
        render() { 
 
   
            this.app.innerText = data[this.arg]
        }
    }

解釋:爲了方便獲取參數,咱們就使用一個參數簡單模擬,不像vue那樣用{ {}}來包括變量,若是用{ {}}包裹變量,就須要利用正則表達式獲取裏面內容。node

第三步:監聽數據的改變。
class Observe { 
 
   
        constructor() { 
 
   
            this.init(data.msg)
        }
        init(value) { 
 
   
            Object.defineProperty(data, 'msg', { 
 
   
                set(newVal) { 
 
   
                    if (newVal !== value) { 
 
   
                        value = newVal
                        dep.notify()
                    }
                },
                get() { 
 
   
                    return value
                }
            })
        }
    }

解釋:咱們想要知道一個變量的值有沒有改變呢,就須要監聽數據的變化。vue中就是使用Object.defineProperty(obj,prop,desc)這個方法中的set()get()來監聽的。set()就是當值要改變的時候,就觸發。而get()就是當你獲取這個變量的時候觸發。咱們利用這個方法就能夠監聽獲得數據的變化了。es6

第三步:建立收集者
class Dep { 
 
   
        constructor() { 
 
   
            this.subs = []
        }
        addSub(watcher) { 
 
   
            this.subs.push(watcher)
        }
        notify() { 
 
   
            this.subs.forEach(w => w.update())
        }
    }

解釋:收集者的做用就存儲觀察者和通知觀察者去更新頁面的。(觀察者在下面。)web

第三步:建立觀察者

class Watcher { 
 
   
        constructor(node, arg, callback) { 
 
   
            this.node = node  // 變量所在的節點
            this.arg = arg // 變量名
            this.oldVal = this.getOldVal() // 沒更新前的值
            this.callback = callback // 值更新後執行的操做
        }
        getOldVal() { 
 
   
            Dep.target = this
            let oldVal = data[this.arg]
            Dep.target = null
            return oldVal
        }
        update() { 
 
   
            this.callback(data.msg)
        }
    }

解釋:咱們想要知道一個值有沒有改變,改變以後從新渲染的操做是怎麼樣的。這個時候就須要一個觀察者了。在數據渲染到頁面的時候,爲這個數據添加一個觀察者,當這個數據改變的時候,就執行回調函數,去更新頁面。正則表達式

第五步:調用收集者和觀察者
let data = { 
 
   
        msg: '你好'
    }
    class Dep { 
 
   
        constructor() { 
 
   
            this.subs = []
        }
        addSub(watcher) { 
 
   
            this.subs.push(watcher)
        }
        notify() { 
 
   
            this.subs.forEach(w => w.update())
        }
    }
    class Watcher { 
 
   
        constructor(node, arg, callback) { 
 
   
            this.node = node
            this.arg = arg
            this.oldVal = this.getOldVal()
            this.callback = callback
        }
        getOldVal() { 
 
   
            Dep.target = this
            let oldVal = data[this.arg]
            Dep.target = null
            return oldVal
        }
        update() { 
 
   
            this.callback(data.msg)
        }
    }
    class Render { 
 
   
        constructor() { 
 
   
            this.app = document.querySelector('#app')
            this.arg = this.app.innerText
            this.render()
        }
        render() { 
 
   
        	// 安排觀察者監視數據
            new Watcher(this.app, this.arg, (newVal) => { 
 
   
                this.app.innerText = newVal
            })
            this.app.innerText = data[this.arg]
        }
    }
    class Observe { 
 
   
        constructor() { 
 
   
            this.init(data.msg)
        }
        init(value) { 
 
   
            let dep = new Dep() // 建立收集者
            Object.defineProperty(data, 'msg', { 
 
   
                set(newVal) { 
 
   
                    if (newVal !== value) { 
 
   
                        value = newVal
                        dep.notify() // 通知觀察者
                    }
                },
                get() { 
 
   
                    Dep.target && dep.addSub(Dep.target)  // 添加觀察者
                    return value
                }
            })
        }
    }
    new Observe()
    new Render()

解釋:在有註釋的方法,就是使用收集者和觀察者的地方。app

代碼的基本運行邏輯:

  1. 首先咱們先使用Object.defineProperty去監聽全部數據,而後獲取頁面中的內容,看看頁面有沒有使用data中的屬性,若是有,就將對應的變量渲染成對應的值。
  2. 渲染的時候,咱們要幫他建立一個觀察者(watcher),傳入當前的dom節點、屬性名、還有一個回調函數,觀察者內部就會獲取參數的值,保存在oldval中。回調函數,就是當數據更新以後才觸發的。
  3. 咱們在獲取oldVal,先爲Dep.target設置爲this,而後再獲取oldVal,獲取的時候就會觸發get方法,Dep.target有值就會添加進收集者(Dep)中,只會把Dep.target該成null,由於get方法會在不少時候觸發,添加進收集者中,我就們就不要要添加了。
  4. 在值改變的時候,就會觸發set()方法,dep.notify()就會通知它裏面的觀察者就進更新頁面的操做,watcher就會調用他們的callback函數更新頁面。

總的代碼:dom

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>

<body>
    <div id="app">
        msg
    </div>
</body>
<script>
    let data = { 
 
   
        msg: '你好'
    }
    class Dep { 
 
   
        constructor() { 
 
   
            this.subs = []
        }
        addSub(watcher) { 
 
   
            this.subs.push(watcher)
        }
        notify() { 
 
   
            this.subs.forEach(w => w.update())
        }
    }
    class Watcher { 
 
   
        constructor(node, arg, callback) { 
 
   
            this.node = node
            this.arg = arg
            this.oldVal = this.getOldVal()
            this.callback = callback
        }
        getOldVal() { 
 
   
            Dep.target = this
            let oldVal = data[this.arg]
            Dep.target = null
            return oldVal
        }
        update() { 
 
   
            this.callback(data.msg)
        }
    }
    class Render { 
 
   
        constructor() { 
 
   
            this.app = document.querySelector('#app')
            this.arg = this.app.innerText
            this.render()
        }
        render() { 
 
   
            new Watcher(this.app, this.arg, (newVal) => { 
 
   
                this.app.innerText = newVal
            })
            this.app.innerText = data[this.arg]
        }
    }
    class Observe { 
 
   
        constructor() { 
 
   
            this.init(data.msg)
        }
        init(value) { 
 
   
            let dep = new Dep()
            Object.defineProperty(data, 'msg', { 
 
   
                set(newVal) { 
 
   
                    if (newVal !== value) { 
 
   
                        value = newVal
                        dep.notify()
                    }
                },
                get() { 
 
   
                    Dep.target && dep.addSub(Dep.target)
                    return value
                }
            })
        }
    }
    new Observe()
    new Render()
</script>

</html>

本文分享 CSDN - 冬天愛吃冰淇淋。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索