<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 測試實例 - 菜鳥教程(runoob.com)</title> <script src="https://unpkg.com/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input type="text" v-model="message"> <p>{{ message }}</p> </div> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue.js!' } }) </script> </body> </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>Document</title> </head> <body> </body> </html> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 測試實例 - 菜鳥教程(runoob.com)</title> <script src="./js/mvvm.js"></script> <script src="./js/compile.js"></script> </head> <body> <div id="app"> <input type="text" v-model="message"> <div>{{message}}</div> <ul> <li></li> </ul> {{message}} </div> <script> let vm = new MVVM({ el: '#app', data: { message: 'Hello Vue.js!' } }) </script> </body> </html>
class MVVM { constructor(options) { this.$el = options.el; this.$data = options.data; if (this.$el) { new Compile(this.$el); } } }
0:35html
class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; if (this.el) { let fragment = this.node2frament(this.el); this.compile(fragment); } } //輔助方法 isElementNode(node) { return node.nodeType === 1; } //核心方法 compile(fragment) { let childNodes = fragment.childNodes; console.log(childNodes) } node2frament(el) { let fragment = document.createDocumentFragment(); let firstChild; while (firstChild = el.firstChild) { fragment.appendChild(firstChild); } return fragment } }
node2frament中的el.firstChild
只能獲取到#app的第一層子節點,<ul><li></li></ul>中的<li>獲取不到vue
node2fragment(el) { //文檔碎片,內存中的dom節點 let fragment = document.createDocumentFragment(); let firstChild; // 臨界條件 firstChild = null while (firstChild = el.firstChild) { console.log(el.firstChild) fragment.appendChild(firstChild) } return fragment; }
node2fragment(el) { console.log(el) //文檔碎片,內存中的dom節點 let fragment = document.createDocumentFragment(); let firstChild; // 臨界條件 firstChild = null while (firstChild = el.firstChild) { fragment.appendChild(firstChild) } console.log(fragment) return fragment; }
console.log(fragment) this.compile(fragment);
0:52node
class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; if (this.el) { let fragment = this.node2frament(this.el); this.compile(fragment); } } //輔助方法 isElementNode(node) { return node.nodeType === 1; } isDirective(name) { return name.includes('v-') } //核心方法 compileElement(node) { let attrs = node.attributes; Array.from(attrs).forEach(arrt => { let attrName = attr.name; if (this.isDirective(attrName)) { let expr = attr.value; } }) } compileText(node) { let text = node.textContent; let reg = /\{\{([^}]+)\}\}/g; if (reg.test(text)) { } } compile(fragment) { let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (this.isElementNode(node)) { this.compile(node) } else { console.log('text', node) } }) } node2frament(el) { let fragment = document.createDocumentFragment(); let firstChild; while (firstChild = el.firstChild) { fragment.appendChild(firstChild); } return fragment } }
compile(fragment) { let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (this.isElementNode(node)) { console.log(node) this.compileElement(node) this.compile(node) } else { this.compileText(node) } }) }
compile(fragment) { let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (this.isElementNode(node)) { this.compileElement(node) this.compile(node) } else { console.log(node) this.compileText(node) } }) }
compileText(node) { let expr = node.textContent; let reg = /\{\{([^}]+)\}\}/g; if (reg.test(expr)) { console.log(expr) } }
compileElement(node) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { // console.log(attr) let attrName = attr.name; if (this.isDirective(attrName)) { let expr = attr.value; let [, type] = attrName.split('-'); console.log(node) console.log(this.vm) console.log(expr) CompileUtil[type](node, this.vm, expr); } }) }
4.3.1 完整代碼 1:16app
class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; if (this.el) { let fragment = this.node2frament(this.el); this.compile(fragment); this.el.appendChild(fragment) } } //輔助方法 isElementNode(node) { return node.nodeType === 1; } isDirective(name) { return name.includes('v-') } //核心方法 compileElement(node) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { let attrName = attr.name; if (this.isDirective(attrName)) { let expr = attr.value; let [, type] = attrName.split('-'); CompileUtil[type](node, this.vm, expr) } }) } compileText(node) { console.log(node) let expr = node.textContent; let reg = /\{\{([^}]+)\}\}/g; if (reg.test(expr)) { CompileUtil['text'](node, this.vm, expr) } } compile(fragment) { let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (this.isElementNode(node)) { this.compileElement(node) this.compile(node) } else { this.compileText(node) } }) } node2frament(el) { let fragment = document.createDocumentFragment(); let firstChild; while (firstChild = el.firstChild) { fragment.appendChild(firstChild); } return fragment } } CompileUtil = { getVal(vm, expr) { // 獲取實例上對應的數據 expr = expr.split('.'); // [message,a] return expr.reduce((prev, next) => { // vm.$data.a return prev[next]; }, vm.$data); }, getTextVal(vm, expr) { // 獲取編譯文本後的結果 return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => { return this.getVal(vm, arguments[1]); }) }, text(node, vm, expr) { //文本處理 let updateFn = this.updater['textUpdater']; let value = this.getTextVal(vm, expr); updateFn && updateFn(node, value) }, model(node, vm, expr) { let updateFn = this.updater['modelUpdater']; updateFn && updateFn(node, this.getVal(vm, expr)); }, updater: { textUpdater(node, value) { node.textContent = value; }, modelUpdater(node, value) { node.value = value; } } }
根據<input type="text" v-model="message.a">
中的message.a
,找到data.message.a
中真實的數據,例如如今是hello world
原始dom
getVal(vm, expr) { // 獲取實例上對應的數據 console.log(vm) console.log(expr) expr = expr.split('.'); // [message,a] return expr.reduce((prev, next) => { // vm.$data.a return prev[next]; }, vm.$data); },
方便看數據,改造mvvm
getVal(vm, expr) { // 獲取實例上對應的數據 console.log(vm) console.log(expr) expr = expr.split('.'); // [message,a] var result = expr.reduce((prev, next) => { // vm.$data.a return prev[next]; }, vm.$data); console.log(result) return result; },
index.html測試
<body> <div id="app"> <input type="text" v-model="message.a"> <input type="text" v-model="b"> <div>{{message.a}}</div> <ul> <li></li> </ul> {{message.a}} </div> <script> let vm = new MVVM({ el: '#app', data: { message: { a: "hello world" }, b: 'bb' } }) </script> </body>
model(node, vm, expr) { console.log(this.getVal(vm, expr)) let updateFn = this.updater['modelUpdater']; updateFn && updateFn(node, this.getVal(vm, expr)); },
把data.message.a
的中數據(hello world),替換在對應的節點v-model="message.a"
中的valueui
updater: { // 輸入框更新 modelUpdater(node, value) { console.log(node) console.log(value) node.value = value; console.log(node.value) }, }
class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; if (this.el) { // 1.先把這些真實的dom移入到內存中fragment let fragment = this.node2fragment(this.el); // 2.編譯 => 提取想要的元素節點 v-model 和文本節點 {{}} this.compile(fragment); // 3.把編譯好的fragment,再賽回到頁面裏去 this.el.appendChild(fragment); } } /* 輔助方法 */ //是不是元素節點 isElementNode(node) { return node.nodeType === 1; } isDirective(name) { return name.includes('v-') } /* 核心方法 */ node2fragment(el) { //文檔碎片,內存中的dom節點 let fragment = document.createDocumentFragment(); let firstChild; // 臨界條件 firstChild = null while (firstChild = el.firstChild) { fragment.appendChild(firstChild) } // console.log(fragment) return fragment; } compile(fragment) { let childNodes = fragment.childNodes; // console.log(childNodes) Array.from(childNodes).forEach(node => { if (this.isElementNode(node)) { // console.log(node) this.compileElement(node) this.compile(node) } else { // console.log(node) this.compileText(node) } }) } compileElement(node) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { // console.log(attr) let attrName = attr.name; if (this.isDirective(attrName)) { let expr = attr.value; let [, type] = attrName.split('-'); // console.log(node) // console.log(this.vm) // console.log(expr) CompileUtil[type](node, this.vm, expr); } }) } compileText(node) { let expr = node.textContent; let reg = /\{\{([^}]+)\}\}/g; if (reg.test(expr)) { // console.log(expr) // CompileUtil['text'](node, this.vm, expr) } } } CompileUtil = { getVal(vm, expr) { // 獲取實例上對應的數據 console.log(vm) console.log(expr) expr = expr.split('.'); // [message,a] // var result = expr.reduce((prev, next) => { // vm.$data.a // return prev[next]; // }, vm.$data); // console.log(result) // return result; return expr.reduce((prev, next) => { // vm.$data.a return prev[next]; }, vm.$data); }, getTextVal(vm, expr) { return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => { return this.getVal(vm, arguments[1]) }) }, text(node, vm, expr) { let updateFn = this.updater['textUpdater']; let value = this.getTextVal(vm, expr) updateFn && updateFn(node, value); }, model(node, vm, expr) { // console.log(this.getVal(vm, expr)) let updateFn = this.updater['modelUpdater']; updateFn && updateFn(node, this.getVal(vm, expr)); }, updater: { textUpdater(node, value) { node.textContent = value; }, // 輸入框更新 modelUpdater(node, value) { // console.log(node) // console.log(value) node.value = value; // console.log(node.value) }, } }
class Observer { constructor(data) { this.observe(data) } observe(data) { if (!data || typeof data !== 'object') { return; } Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]); this.observe(data[key]) }) } defineReactive(obj, key, value) { let that = this; Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { return value; }, set(newValue) { if (newValue != value) { that.observe(newValue) value = newValue } } }) } }
class Watcher { constructor(vm, expr, cb) { this.vm = vm; this.expr = expr; this.cb = cb; this.value = this.get(); } getVal(vm, expr) { expr = expr.split('.'); return expr.reduce((prev, next) => { return prev[next] }, vm.$data) } get() { let value = this.getVal(this.vm, this.expr); return value; } update() { let newValue = this.getVal(this.vm, this.expr); let oldValue = this.value; if (newValue != oldValue) { this.cb(newValue); } } }
增長watcherthis
text(node, vm, expr) { //文本處理 let updateFn = this.updater['textUpdater']; let value = this.getTextVal(vm, expr); expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => { return this.getVal(vm, arguments[1], (newValue) => { updateFn && updateFn(node, this.getTextVal(vm, expr)); }); }) updateFn && updateFn(node, value) }, model(node, vm, expr) { let updateFn = this.updater['modelUpdater']; new Watcher(vm, expr, (newValue) => { updateFn && updateFn(node, this.getVal(vm, expr)); }) updateFn && updateFn(node, this.getVal(vm, expr)); },
完成的compile.js代碼spa
class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; if (this.el) { let fragment = this.node2frament(this.el); this.compile(fragment); this.el.appendChild(fragment) } } //輔助方法 isElementNode(node) { return node.nodeType === 1; } isDirective(name) { return name.includes('v-') } //核心方法 compileElement(node) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { let attrName = attr.name; if (this.isDirective(attrName)) { let expr = attr.value; let [, type] = attrName.split('-'); CompileUtil[type](node, this.vm, expr) } }) } compileText(node) { // console.log(node) let expr = node.textContent; let reg = /\{\{([^}]+)\}\}/g; if (reg.test(expr)) { CompileUtil['text'](node, this.vm, expr) } } compile(fragment) { let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (this.isElementNode(node)) { // console.log(node) this.compileElement(node) this.compile(node) } else { // console.log(node) this.compileText(node) } }) } node2frament(el) { let fragment = document.createDocumentFragment(); let firstChild; while (firstChild = el.firstChild) { fragment.appendChild(firstChild); } return fragment } } CompileUtil = { getVal(vm, expr) { // 獲取實例上對應的數據 expr = expr.split('.'); // [message,a] return expr.reduce((prev, next) => { // vm.$data.a return prev[next]; }, vm.$data); }, getTextVal(vm, expr) { // 獲取編譯文本後的結果 return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => { return this.getVal(vm, arguments[1]); }) }, text(node, vm, expr) { //文本處理 let updateFn = this.updater['textUpdater']; let value = this.getTextVal(vm, expr); expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => { return this.getVal(vm, arguments[1], (newValue) => { updateFn && updateFn(node, this.getTextVal(vm, expr)); }); }) updateFn && updateFn(node, value) }, model(node, vm, expr) { let updateFn = this.updater['modelUpdater']; new Watcher(vm, expr, (newValue) => { updateFn && updateFn(node, this.getVal(vm, expr)); }) updateFn && updateFn(node, this.getVal(vm, expr)); }, updater: { textUpdater(node, value) { node.textContent = value; }, modelUpdater(node, value) { node.value = value; } } }
修改observer.js
class Observer { constructor(data) { this.observe(data) } observe(data) { if (!data || typeof data !== 'object') { return; } Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]); this.observe(data[key]) }) } defineReactive(obj, key, value) { let that = this; let dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { Dep.target && dep.addSub(Dep.target) return value; }, set(newValue) { if (newValue != value) { that.observe(newValue) value = newValue dep.notify(); } } }) } } class Dep { constructor() { this.subs = []; } addSub(watcher) { this.subs.push(watcher); } notify() { this.subs.forEach(watcher => watcher.update()) } }
監聽input,增長setval,到2:10
class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; if (this.el) { let fragment = this.node2frament(this.el); this.compile(fragment); this.el.appendChild(fragment) } } //輔助方法 isElementNode(node) { return node.nodeType === 1; } isDirective(name) { return name.includes('v-') } //核心方法 compileElement(node) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { let attrName = attr.name; if (this.isDirective(attrName)) { let expr = attr.value; let [, type] = attrName.split('-'); CompileUtil[type](node, this.vm, expr) } }) } compileText(node) { // console.log(node) let expr = node.textContent; let reg = /\{\{([^}]+)\}\}/g; if (reg.test(expr)) { CompileUtil['text'](node, this.vm, expr) } } compile(fragment) { let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (this.isElementNode(node)) { // console.log(node) this.compileElement(node) this.compile(node) } else { // console.log(node) this.compileText(node) } }) } node2frament(el) { let fragment = document.createDocumentFragment(); let firstChild; while (firstChild = el.firstChild) { fragment.appendChild(firstChild); } return fragment } } CompileUtil = { getVal(vm, expr) { // 獲取實例上對應的數據 expr = expr.split('.'); // [message,a] return expr.reduce((prev, next) => { // vm.$data.a return prev[next]; }, vm.$data); }, getTextVal(vm, expr) { // 獲取編譯文本後的結果 return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => { return this.getVal(vm, arguments[1]); }) }, setVal(vm, expr, value) { // [message,a] expr = expr.split('.'); // 收斂 return expr.reduce((prev, next, currentIndex) => { if (currentIndex === expr.length - 1) { return prev[next] = value; } return prev[next]; }, vm.$data); }, text(node, vm, expr) { //文本處理 let updateFn = this.updater['textUpdater']; let value = this.getTextVal(vm, expr); expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => { new Watcher(vm, arguments[1], (newValue) => { updateFn && updateFn(node, this.getTextVal(vm, expr)); }); }) updateFn && updateFn(node, value) }, setVal(vm, expr, value) { expr = expr.split('.'); return expr.reduce((prev, next, currentIndex) => { if (currentIndex === expr.length - 1) { return prev[next] = value; } return prev[next]; }, vm.$data) }, model(node, vm, expr) { let updateFn = this.updater['modelUpdater']; new Watcher(vm, expr, (newValue) => { updateFn && updateFn(node, this.getVal(vm, expr)); }) node.addEventListener('input', (e) => { let newValue = e.target.value; this.setVal(vm, expr, newValue) }) updateFn && updateFn(node, this.getVal(vm, expr)); }, updater: { textUpdater(node, value) { node.textContent = value; }, modelUpdater(node, value) { node.value = value; } } }
vm.$data.message => vm.message
mvvm.js增長proxyData
class MVVM { constructor(options) { this.$el = options.el; this.$data = options.data; if (this.$el) { new Compile(this.$el, this); this.proxydata(this.$data); new Observer(this.$data); } } proxyData(data) { Object.keys(data).forEach(key => { Object.defineProperty(this, key, { get() { return data[key] }, set(newValue) { data[key] = newValue; } }) }) } }