先看一下vue的用法,兩個參數(el的id,data)
創建一個類,
數據劫持:defineProperty或者proxy',給data里的數據添加get、set才能及時獲取數據改變
使用Proxy:
只要data數據具備數據劫持屬性,就不再操縱dom也能改變視圖
更深一點
//ES6實現 class Vue { constructor(options) { //緩存配置項 this.$el = options.el || document.body; this.$options = options; const data = this.$data = options.data; //劫持數據 this.hijackData(data);
//編譯模版
this.compile(this.$el); }
數據劫持Observer
hijackData(data) { if (!data || typeof data !== 'object') { //不存在或者不為對象,返回(遞歸退出條件) return; } //拿到data中所有可枚舉的屬性 Object.keys(data).forEach(key => { //給每個屬性創建觀察者 new Observer(data, key); //遞歸遍歷所有層次 this.hijackData(data[key]); }); } } //觀察者實現 class Observer { constructor(data, key) { //對數據進行觀察 this.observe(data, key, data[key]); } observe(data, key, val) { //data中每一個數據對應一個Dep容器,存放所有依賴於該數據的依賴項 const dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, //可枚舉 configurable: false,//不能再配置 get() { if (Dep.target) {//Dep.target存放具體的依賴,在編譯階段檢測到依賴后被賦值 dep.addDep(Dep.target); //依賴收集 } return val; }, set(newVal) { if (newVal === val) { return; } val = newVal; dep.notify(); //當數據發生變化時,通知所有的依賴進行更新顯示 } }); } }
Dep依賴收集
//Dep容器,data中的每個數據會對應一個,用來收集並存儲依賴 class Dep { constructor() { this.deps = []; //所有的依賴將存放在該數組中 } //收集依賴 addDep(dep) { this.deps.push(dep); } //通知更新 notify() { this.deps.forEach(dep => { dep.updata(); }); } }
模板編譯 compile
compile(el) { //找到vue管理的區域 this.$el = this.isElementNode(el) ? el : document.querySelector(el); if (this.$el) { //將this.$el中的子節點轉移到內存中,document --> 內存 this.$fragment = this.node2Fragment(this.$el); //編譯 this.compileElements(this.$fragment); //內存 --> document this.$el.appendChild(this.$fragment); } } //將文檔節點轉移到fragment(內存)中 得到虛擬dom node2Fragment(node) { let child = null;
//createdocumentfragment()方法創建了一虛擬的節點對象,節點對象包含所有屬性和方法。 let fragment = document.createDocumentFragment(); while (child = node.firstChild) { //節點有且只有一個父節點, //所以是轉移,不是復制,不會出現兩份, //相當於是一個水缸的水舀到另一個水缸里,fragemnt滿了,this.$el空了 fragment.appendChild(child); } return fragment; } compileElements(vNode) { let text = ''; //正則表達式,用於匹配大括號表達式 {{}} const reg = /\{\{(.*)\}\}/; //轉為真數組並遍歷所有節點 Array.from(vNode.childNodes).forEach(node => { text = node.textContent; if (this.isElementNode(node) { //元素節點,解析所有的指令屬性 let exp = ''; //表達式 let dir = ''; //指令 let attrName = ''; let attrs = node.attributes; //取出所有屬性,轉為數組並遍歷 Array.from(attrs).forEach(attr => { exp = attr.value; attrName = attr.name; // 普通指令v-text,v-html,v-model等 if (this.isDirective(attrName) { dir = attrName.substring(2); this.update(node, exp, dir); node.removeAttribute(attrName); //事件指令@click等 } else if (this.isEvent(attrName) { dir = attrName.substring(1); this.eventHandler(node, exp, dir); node.removeAttribute(attrName); } }); } else if (this.isTextNode(node) && reg.test(text)) { //文本節點,大括號表達式 this.update(node, RegExp.$1.trim(), 'text'); } //遞歸遍歷所有層次的節點 if (node.hasChildNodes()) { this.compileElements(node); } }); } //事件處理器 eventHandler(node, exp, eType) { const cb = this.$options.methods && this.$options.methods[exp]; cb && node.addEventListener(eType, cb.bind(this)); } //更新視圖,依賴的統一入口,每個引用過data中數據的依賴都會進來這里 update(node, exp, dir) { //拿到相對應的更新函數 const fn = this[dir+'Updater']; //初始化更新顯示,這里需要指定調用的實例this,即vue實例 fn && fn.call(this, node, exp); //變化更新顯示,下一節內容 //。。。。 } textUpdater(vm, node, exp) { node.textContent = vm[exp]; } htmlUpdater(vm, node, exp) { node.innerHTML = vm[exp]; } modelUpdater(vm, node, exp) { node.value = vm[exp]; //添加input事件監聽 //v-model雙向數據綁定的其中一個方向,即:視圖 --> 內存 node.addEventListener('input', e => { vm[exp] = e.target.value; }); } //判斷是否為元素節點 isElementNode(node) { return (node.nodeType === 1); } //判斷是否為文本節點 isTextNode(node) { return (node.nodeType === 3); }
更新顯示
//更新顯示實現 class Watcher { constructor(vm, exp, cb) { this.cb = cb; Dep.target = this; //把Watcher賦值給Dep.target vm[exp]; //調用get Dep.target = null; } update() { //dep.notify()通知更新最終入口 //內存 --> 視圖,也是雙向數據綁定中的另一方向 this.cb(); //更新顯示 } }
//更新視圖,依賴的統一入口,每個引用過data中數據的依賴都會進來這里 update(node, exp, dir) { //拿到相對應的更新函數 const fn = this[dir+'Updater']; //初始化更新顯示,這里需要指定調用的實例this,即vue實例 fn && fn.call(this, node, exp); //變化更新顯示,下一節內容 new Watcher(this, exp, () => { fn && fn.call(this, node, exp); }); }
原文鏈接:https://blog.csdn.net/huolinianyu/article/details/100190032