背景
以vue为例 使用数据劫持defineProperty(老版本),新版本使用proxy
实现
- Object.defineProperty
- 编译模板compile
- 发布订阅模式
- 视图与数据链接
代码部分
使用function实现
- 主体骨架
function Vue(options = {}) {
this.$options = options;
let data = (this._data = this.$options.data);
observe(data);
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key];
},
set(newValue) {
this._data[key] = newValue;
},
});
}
new Compile(options.el, this);
}
- 实现 Observe
function Observe(data) {
let dep = new Dep();
for (let key in data) {
let value = data[key];
observe(value);
Object.defineProperty(data, key, {
enumerable: true,
get() {
Dep.target && dep.addSub(Dep.target);
return value;
},
set(newValue) {
if (newValue === value) {
return;
}
value = newValue;
observe(newValue);
dep.notify();
},
});
}
}
- 实现发布订阅
function Dep() {
this.subs = [];
}
Dep.prototype.addSub = function (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function (sub) {
this.subs.pop(sub);
};
Dep.prototype.notify = function (sub) {
this.subs.forEach(function (sub) {
sub.update();
});
};
// 通过类创建的实例都有原型上的方法
function Watcher(vm, exp, fn) {
this.fn = fn;
this.vm = vm;
this.exp = exp;
Dep.target = this;
let val = vm,
arr = exp.split('.');
arr.forEach((k) => {
val = val[k];
});
Dep.target = null;
}
Watcher.prototype.update = function () {
let val = this.vm,
arr = this.exp.split('.');
arr.forEach(function (k) {
val = val[k];
});
this.fn(val);
};
- 实现compile
function Compile(el, vm) {
vm.$el = document.getElementById(el);
let fragment = document.createDocumentFragment();
while ((child = vm.$el.firstChild)) {
fragment.appendChild(child);
}
replace(fragment);
function replace(fragment) {
Array.from(fragment.childNodes).forEach(function (node) {
let text = node.textContent;
let reg = /\{\{(.*)\}\}/;
if (node.nodeType === 3 && reg.test(text)) {
let arr = RegExp.$1.split('.');
let val = vm;
arr.forEach((k) => {
val = val[k];
});
new Watcher(vm, RegExp.$1, function (newValue) {
node.textContent = text.replace(reg, newValue);
});
node.textContent = text.replace(reg, val);
}
if (node.childNodes) {
replace(node);
}
});
}
vm.$el.appendChild(fragment);
}
- 最后
let vue = new Vue({ el: 'app', data: { a: { a: 12 } } });
<div id="app">
<p>a:{{a.a}}</p>
</div>
总结
可以使用class实现更方便,发布订阅模式也可以直接继承Extends,当中代码需要优化得比较多,主题思路大概就是这样子。