先看一下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
