html <div id="app"> <div>{{ someStr }}</div> </div> js let myMvvm = new Mvvm({ el: document.getElementById('app'), data: { someStr: '你好' } })
上面是最多見的vue的用法, 如今我就只實現一件事html
myMvvm.someStr = '改變' // 執行這一句時, 頁面會及時更新
一、 第一步, 先聲明一個Mvvm類vue
class Mvvm { constructor (option) { this.$option = option || {} } }
咱們一開始定義的 someStr屬性是定義在option.data中的, 咱們想要 myMvvm.someStr這樣賦值的時候和option的data相關聯, 須要中間作一個代理,修改代碼node
class Mvvm { constructor (option) { this.$option = option || {} this._proxyData(option.data, this) // 執行函數實現代理 } _proxyData (obj, context) { Object.keys(obj).forEach(key => { Object.defineProperty(context, key, { configurable: false, enumerable: true, get () { return obj[key] }, set (val) { obj[key] = val } }) }) } }
二、觀察option的data屬性
要想實現 myMvvm.someStr = 1 這樣賦值的時候,頁面能及時更新,那麼咱們就要對someStr的賦值過程作一個監聽才行, 開心的是 , Object.defineProperty能夠輕易作到這點
寫一個observe類app
class Observe { constructor (obj) { Object.keys(obj).forEach(key => { this.defineReactive(obj, key, obj[key]) }) } defineReactive (obj, key, val) { let initVal = val Object.defineProperty(obj, key, { enumerable: true, configurable: false, get () { return initVal }, set (val) { // 每一次的複製咱們均可以在這裏獲知,天然能夠隨心所欲了 initVal = val return initVal } }) } } 而後修改一下Mvvm這個類的constructor constructor (option) { this.$option = option || {} this._proxyData(option.data, this) new Observe(option.data) }
三、實現元素的實時更新
如今爲止, 還只是顯示 一個 {{someStr}} 而已, 咱們如今須要作的是讓能變成 你好 這個值
寫一個Compile類函數
{{someStr}}是一個文本節點,先聲明一個能夠渲染文本節點的函數 let compileText = function (node, vm, str) { let val = vm[str] if (val) { node.nodeValue = val } } class Compile { constructor(el, vm) { // el 是 #app這個元素 vm是Mvvm這個實例 let frag = this.node2Fragment(el) this.vm = vm this.compileElement(frag) // 讀取子節點進行渲染 el.appendChild(frag) } node2Fragment(el) { // 建立一個文檔片斷把#app元素的子節點拷貝 let frag = document.createDocumentFragment() let child while (child = el.firstChild) { frag.appendChild(child) } return frag } compileElement(el) { // 渲染節點 let childNodes = el.childNodes; [].forEach.call(childNodes, (node) => { // 遍歷全部的子節點 if (this.isElementNode(node)) { // 若是是元素節點, 重複便利 this.compileElement(node) } else if (this.isTextNode(node)) { // 若是是文本節點 let matchStr = this.isMustache(node.nodeValue) // 判斷這個文本值是否是 {{}} 這種類型 if (matchStr) { // 若是有匹配到 compileText(node, this.vm, matchStr) } } }) } isElementNode(node) { // 元素節點 return node.nodeType === 1 } isTextNode(node) { // 文本節點 return node.nodeType === 3 } isMustache(str) { if (!str) { return null } let reg = /\{\{(.*)\}\}/ let arr = str.match(reg) return arr ? arr[1].replace(/\s/g, '') : null } } 如今修改一下 Mvvm這個類的constructor函數 constructor(option) { this.$option = option || {} this._proxyData(option.data, this) new Observe(option.data) new Compile(option.el, this) }
如今你好這個值終因而被渲染出來, 咱們踏出了第一步, 如今開始實現 myMvvm.someStr = 1 也能及時更新this
在實現complie的時候, 咱們知道渲染的時候調用了compileText函數,那麼咱們如今更改someStr時及時渲染,就只要再執行這個函數就能夠了, 咱們能夠把這個更新函數放到一個隊列裏, 每次更新someStr的時候, 把這個隊列裏的更新函數執行一遍就能夠了
咱們實現一個Dep類代理
// 這裏聲明兩個變量待會使用 let updateFn let canMount class Dep { constructor () { this.queue = [] } mount () { this.queue.push(updateFn) } notify () { this.queue.forEach(fn => fn()) } } 而後修改一下Observe類的defineReactive函數 defineReactive(obj, key, val) { let initVal = val let dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: false, get() { if (canMount) { // 防止每次get都會執行這裏 dep.mount() } return initVal }, set(val) { // 每一次的複製咱們均可以在這裏獲知,天然能夠隨心所欲了 if (val !== initVal) { initVal = val dep.notify() } return initVal } }) } 實現一個生成更新函數 的方法 let bindTextUpdater = function (node, vm, matchStr) { canMount = true updateFn = compileText.bind(null, node, vm, matchStr) updateFn() canMount = false } 而後最後一步 修改一下Complie類的compileElement方法 let childNodes = el.childNodes; [].forEach.call(childNodes, (node) => { // 遍歷全部的子節點 if (this.isElementNode(node)) { // 若是是元素節點, 重複便利 this.compileElement(node) } else if (this.isTextNode(node)) { // 若是是文本節點 let matchStr = this.isMustache(node.nodeValue) // 判斷這個文本值是否是 {{}} 這種類型 if (matchStr) { // 若是有匹配到 bindUpdater(node, this.vm, matchStr) // 綁定更新函數 } } })
如今執行 myMvvm.someStr = 155 會發現簡單的例子實現了code