- 模板編譯(Compile)
- 數據劫持(Observer) Object.defineProperty
- 發佈的訂閱(Dep)
- 觀察者(Watcher)
- 數據就是簡單的javascript對象,需要將數據綁定到模板上
- 監聽視圖的變化,視圖變化後通知數據更新,數據更新會再次導致視圖的變化!
<body> <div id="app"> <!-- 測試data數據:實現雙向綁定 --> <input type="text" id="input" /> <div> {{message}} <div> {{message}} </div> </div> {{info}} </div> <!-- 簡單實現 Vue MVVM模式 --> <script src="ziChin_mock_vue.js"></script> <script> let message = '子卿的初始message' // 實例MVVM: var vm = new Vue({ el: '#app', data: { message, info:'初始info' } }) // 利用oninput輸入框測試雙向綁定: let input = document.querySelector('#input') input.value = message input.oninput = function (e) { vm.$data.message = e.target.value } </script> </body>
// 構建一個MVVM實例(ES6實現) class Vue { constructor(options) { // 初始化變量 this.$options = options this.$el = options.el this.$data = options.data // 1.監聽數據 this.observer(this.$data) // 2.編譯模版 this.compile(this.$el) } compile(el) { // ... } observer(data) { // ... } } // 觀察者模式 class Dep { // ... } class Watcher { // 訂閱信息 // ... }
observer(data) { Object.keys(data).forEach(key => { let dep = new Dep() let value = data[key] // 數據劫持的核心方法: Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.target) { dep.addSub(Dep.target) // 把訂閱信息緩存起來 } return value }, set(newValue) { dep.notify(newValue, value) value = newValue } }) }) }
compile(el) { let element = document.querySelector(el) let childNodes = element.childNodes const compileNodes = childNodes => { // 遞歸 Array.from(childNodes).forEach(node => { if (node.nodeType === 3) { // 文本節點 let reg = /\{\{\s*(\S*)\s*\}\}/ let dataKey = null node.textContent = node.textContent.replace(reg, ($0, $1) => { dataKey = $1 return this.$data[dataKey] }) if (dataKey !== null) { // 監聽(視圖與數據一一對應) new Watcher(this, dataKey, (newValue, value) => { node.textContent = node.textContent.replace(value, newValue) }) } } else if (node.nodeType === 1) { // 標籤節點 compileNodes(node.childNodes) } }) } compileNodes(childNodes) }
// 觀察者模式 class Dep { constructor() { this.subs = [] } addSub(sub) { // 緩存訂閱內容 this.subs.push(sub) } notify(newValue, value) { // 發佈信息 this.subs.forEach(item => item.update(newValue, value)) } } class Watcher { constructor(vm, dataKey, cb) { Dep.target = this vm.$data[dataKey] // 觸發Object中get函數的 --> addSub,緩存訂閱內容 Dep.target = null this.cb = cb } update(newValue, value) { this.cb(newValue, value) // 由notify觸發 } }
所有代碼:
html:
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>ziChin_mock_vue</title> </head> <body> <div id="app"> <!-- 測試data數據:實現雙向綁定 --> <input type="text" id="input" /> <div> {{message}} <div> {{message}} </div> </div> {{info}} </div> <!-- 簡單實現 Vue MVVM模式 --> <script src="ziChin_mock_vue.js"></script> <script> let message = '子卿的初始message' // 實例MVVM: var vm = new Vue({ el: '#app', data: { message, info:'初始info' } }) // 利用oninput輸入框測試雙向綁定: let input = document.querySelector('#input') input.value = message input.oninput = function (e) { vm.$data.message = e.target.value } </script> </body> </html>
js:
// 構建一個MVVM實例(ES6實現) class Vue { constructor(options) { // 初始化變量 this.$options = options this.$el = options.el this.$data = options.data // 1.監聽數據以便更新視圖(數據劫持) this.observer(this.$data) // 2.編譯模版(這裏我沒有用虛擬Node) this.compile(this.$el) } compile(el) { let element = document.querySelector(el) let childNodes = element.childNodes const compileNodes = childNodes => { Array.from(childNodes).forEach(node => { if (node.nodeType === 3) { // 文本節點 let reg = /\{\{\s*(\S*)\s*\}\}/ let dataKey = null node.textContent = node.textContent.replace(reg, ($0, $1) => { dataKey = $1 return this.$data[dataKey] }) if (dataKey !== null) { // 監聽(視圖與數據一一對應) new Watcher(this, dataKey, (newValue, value) => { node.textContent = node.textContent.replace(value, newValue) }) } } else if (node.nodeType === 1) { // 標籤節點 compileNodes(node.childNodes) } }) } compileNodes(childNodes) } observer(data) { Object.keys(data).forEach(key => { let dep = new Dep() let value = data[key] Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.target) { dep.addSub(Dep.target) } return value }, set(newValue) { dep.notify(newValue, value) value = newValue } }) }) } } // 觀察者模式 class Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify(newValue, value) { this.subs.forEach(item => item.update(newValue, value)) } } class Watcher { constructor(vm, dataKey, cb) { Dep.target = this vm.$data[dataKey] Dep.target = null this.cb = cb } update(newValue, value) { this.cb(newValue, value) } }