Vue工作機制
- vue工作機制
- Vue響應式的原理
- 依賴收集與追蹤
- 編譯compile
html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> </head> <body> <div id="app"> <!-- 插值綁定 --> <p>{{name}}</p> <!-- 指令系統 --> <p k-text="name"></p> <p>{{age}}</p> <p>{{doubleAge}}</p> <!-- 雙向綁定 --> <input type="text" k-model="name"> <!-- 事件處理 --> <button @click="changeName">呵呵</button> <!-- html內容解析 --> <div k-html="html"></div> </div> <script src='./compile.js'></script> <script src='./kvue.js'></script> <script> new KVue({ el: '#app', data: { name: "I am test.", age: 17, html: '<button>這是一個按鈕</button>' }, created() { console.log('開始啦'); setTimeout(() => { this.name = '我是異步事件' }, 1500) }, methods: { changeName() { this.name = '哈嘍,哈哈哈哈'; this.age = 20; this.id = 'xx' ; console.log(1, this) ; } } }) </script> </body> </html>
kvue.js
/* * @Author: liguowei01 * @Date: 2019-12-31 11:17:12 * @Last Modified by: liguowei01 * @Last Modified time: 2020-01-02 17:55:53 */ // 用法: new KVue({data:{...}}) class KVue { constructor(options) { this.$options = options; //數據的響應化 this.$data = options.data; this.observe(this.$data); //觀察數據 //模擬一下watcher創建 // new Watcher(); //實例一 // this.$data.test; // new Watcher(); //實例二 實例二不等於實例一 // this.$data.foo.bar; new Compile(options.el, this); //生命周期函數 //created if (options.created) { //options.created(); //本來是這樣執行,下面的調用call()方法,為函數指定執行作用域 options.created.call(this); //這樣就可以在created函數中用this了。 } } observe(obj) { //檢驗數據類型必須是對象 if (!obj || typeof obj !== 'object') { return; } //遍歷該對象 Object.keys(obj).forEach(key => { this.defineReactive(obj, key, obj[key]); //代理配置項 data 中的屬性到vue實例上 this.proxyData(key); }) } //數據響應化(數據劫持) defineReactive(obj, key, val) { this.observe(val); //遞歸解決數據的嵌套 const dep = new Dep(); Object.defineProperty(obj, key, { get() { Dep.target && dep.addDep(Dep.target); return val }, set(newVal) { if (newVal === val) { return; } val = newVal; // console.log(`${key}屬性更新了:${newVal}`) dep.notify(); } }) } //代理函數() proxyData(key) { Object.defineProperty(this, key, { get() { return this.$data[key]; }, set(newVal) { this.$data[key] = newVal; } }) } } //vue 數據綁定的原理是什么? //首先,把vue選項里的data中的每個屬性都利用了Object.defineProperty()定義了一個屬性, //都定義了get和set這樣的話讓我們的機會監聽數據和變化, //當這些屬性發生變化時,我們可以通知那些需要更新的地方去更新 //依賴搜集 //Dep: 用來管理 Watcher class Dep { constructor() { //這里存在若干依賴(watcher,一個watcher對應一個屬性) this.deps = []; } //添加依賴的方法,搜集依賴時,往這里面放東西 addDep(dep) { this.deps.push(dep) } //通知方法,用來通知所有的watcher 去更新 notify() { this.deps.forEach(dep => dep.updata()) } } //Watcher 用來做具體更新的對象 class Watcher { constructor(vm, key, cb) { this.vm = vm; this.key = key; this.cb = cb; //將當前watcher實例指定到Dep靜態屬性target Dep.target = this; this.vm[this.key]; //觸發getter,添加依賴 Dep.target = null; } updata() { // console.log('屬性更新了'); this.cb.call(this.vm, this.vm[this.key]) } }
Compile.js
/* * @Author: liguowei01 * @Date: 2020-01-02 10:34:50 * @Last Modified by: liguowei01 * @Last Modified time: 2020-01-03 09:18:12 */ //用法 new Compile(el,vm) class Compile { constructor(el, vm) { //要遍歷的宿主節點 this.$el = document.querySelector(el); this.$vm = vm; //在其他方法中方便使用 //編譯 if (this.$el) { //轉換內部內容為片段Fragment this.$fragment = this.node2Fragment(this.$el); //執行編譯 this.compile(this.$fragment); //將編譯完的html追加到$el this.$el.appendChild(this.$fragment); } } //將宿主元素中的代碼片段拿出來遍歷,這樣做比較高效 node2Fragment(el) { //創建一個代碼塊 const frag = document.createDocumentFragment(); //將el中所有子元素“搬家”(移動)到frag中 let child; while (child = el.firstChild) { frag.appendChild(child); } return frag; } compile(el) { const childNodes = el.childNodes; Array.from(childNodes).forEach(node => { //判斷類型 if (this.isElement(node)) { //元素 console.log('編譯元素', node.nodeName); //查找k-, @, : const nodeAttrs = node.attributes; Array.from(nodeAttrs).forEach(attr => { const attrName = attr.name; const exp = attr.value; if (this.isDirective(attrName)) { //k-text const dir = attrName.substring(2); //執行指令 this[dir] && this[dir](node, this.$vm, exp); } if (this.isEvent(attrName)) { //@click let dir = attrName.substring(1); // text this.eventHandler(node, this.$vm, exp, dir); } }) } else if (this.isInterpolation(node)) { //插值文本{{}} console.log('編譯文本', node.nodeName); this.compileText(node); } //遞歸子節點 if (node.childNodes && node.childNodes.length > 0) { this.compile(node) } }) } isDirective(attr) { return attr.indexOf('k-') == 0; } isEvent(attr) { return attr.indexOf('@') == 0; } isElement(node) { return node.nodeType === 1; } //插值文本 isInterpolation(node) { return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent); } //編譯文本 compileText(node) { //console.log(RegExp.$1); //正則對象RegExp的靜態屬性$1就是第一個匹配的值 就是上面'name' //node.textContent = this.$vm.$data[RegExp.$1]; this.updata(node, this.$vm, RegExp.$1, 'text'); } /* * @作用: 更新函數 根據指令決定是哪個更新器 它將來需要知道(參數) * @params: node 更新的節點 * @params: vm kvue的實例 * @params: exp 正則表達式 匹配的結果 如:name * @params: dir 指令(文本、事件、其他) 如:text,html,model * 這個方法是個通用方法,將來要被調用很多次 */ updata(node, vm, exp, dir) { const updaterFn = this[dir + 'Updater']; //在當前的類里面組合一個函數名 /*這種寫法和 this.a 一樣,this是代表當前對象,也是一個對象, 對象名.方法名 或 對象名.屬性名 調用對象中的屬性和方法 還有一種調用方式:對象名['方法名'] 或 對象名['屬性名'] 也可以使用 對象名['方法名']() 執行此方法 */ //先判斷updaterFn是否存在,如果存在則執行 updaterFn && updaterFn(node, vm[exp]); //初始化(第一次) //依賴收集 new Watcher(vm, exp, function(value) { //觀察vm 里的exp(屬性),並在屬性變化時,如何更新 updaterFn && updaterFn(node, value); }) } //更新的具體操作 textUpdater(node, value) { node.textContent = value; } text(node, vm, exp) { this.updata(node, vm, exp, 'text'); } // 事件處理 eventHandler(node, vm, exp, dir) { let fn = vm.$options.methods && vm.$options.methods[exp]; if (dir && fn) { node.addEventListener(dir, fn.bind(vm), false); } } model(node, vm, exp) { this.updata(node, vm, exp, 'model'); let val = vm.exp; node.addEventListener('input', (e) => { let newValue = e.target.value; vm[exp] = newValue; val = newValue; }) } modelUpdater(node, value) { node.value = value; } html(node, vm, exp) { this.updata(node, vm, exp, 'html'); } htmlUpdater(node, value) { node.innerHTML = value; } }
/*
問題1:vue編譯過程是怎樣的?
遵循3W1H原則,什么是編譯,為什么要編譯。
首先寫的這些模板的語句,html根本就不能識別,
我們通過編譯的過程,可以進行依賴的收集,
進行依賴收集以后,我們就把data中的數據模型和視圖之間產生了綁定關系
產生了依賴關系,那么以后模型發生變化的時候,
我們就會通知這些依賴的地方讓他們進行更新,
這就是我們執行編譯的目的,這樣就做到了模型驅動視圖的變化。
問題2:雙向綁定的原理是什么?
做雙向綁定時,通常在表單元素上綁定一個v-model,
我們在編譯的時候,可以解析到v-model
操作時做了兩件事:
1.在表單元素上做了事件監聽(監聽input、change事件)
2.如果值發生變化時,在事件回調函數把最新的值設置到vue的實例上
3.因為vue的實例已經實現了數據的響應化,
它的響應化的set函數會觸發,通知界面中所有模型的依賴的更新。
所以界面中的,跟這個數據相關的部分就更新了
*/