html:
<div id="app">
<p>{{opt}}</p>
<p>{{arr}}</p>
<p v-text="a"></p>
<p v-html="des"></p>
<p><input type="text" v-model="a"></p>
</div>
vue构造函数
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data;
// 数据拦截
new Observer(this.$data)
// 代理实例
proxy(this, this.$data)
// 模板编译
new Complier(this, this.$options.el);
}
}
// 将data数据,代理到Vue实例上
function proxy(vm, data) {
Object.keys(data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return data[key]
},
set(newVal) {
data[key] = newVal;
}
});
});
}
observe.js数据拦截
class Observer {
constructor(data) {
this.init(data)
}
init(data) {
if (typeof data !== "object" || data == null) {
return false
}
if (Array.isArray(data)) { // 数组
this.walkArr(data);
Object.defineProperty(data, '__dep__', {
enumerable: false,
value: new Dep()
});
} else { // 对象, 更新触发set
this.walk(data)
}
}
walk(data) {
Object.keys(data).forEach(key => {
this.walkDefinedReactive(data, key, data[key])
});
}
walkDefinedReactive(data, key, val) {
new Observer(val);
let _this = this;
// 一个key对应一个dep收集器
let dep = new Dep();
Object.defineProperty(data, key, {
get() {
Dep.target && dep.add(Dep.target);
if (val.__dep__) { // 说明该值是数组, 则把使用该数组的watcher数据到该数组__dep__上
Dep.target && val.__dep__.add(Dep.target);
}
return val
},
set(newVal) {
if (newVal != val) {
val = newVal;
_this.init(val); //如果新值时对象
dep.notify(); //所有对象的值发生改变(包含数组=> 整个值变化,不是值内部变化, 所以当数组内值变化push..等发生时,采用拦截数据方法内触发)
}
}
})
}
walkArr(data) {
const arrayProto = Object.create(Array.prototype); //创建新对象
// 装饰一下方法
const methodList = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
methodList.forEach(methods => {
arrayProto[methods] = function (...arg) {
Array.prototype[methods].apply(this, arg);
// 向数组插入值
let inserted = null;
switch (methods) {
case 'push':
case 'unshift':
inserted = arg;
break;
case 'splice':
inserted = arg.splice(2);
break;
};
// Todo 。。。。
data.__dep__.notify();
}
});
// 设置数据的原型
Object.setPrototypeOf(data, arrayProto);
// 数据每项数据响应
console.log(data);
data.forEach(val => new Observer(val));
}
};
complier.js编译模板
class Complier {
constructor(vm, el) {
this.$vm = vm;
this.$el = document.querySelector(el);
this.$el && this.complier(this.$el);
}
complier(node) {
const childNodes = Array.from(node.childNodes);
childNodes.forEach(node => {
if (node.childNodes.length > 0) {
this.complier(node)
}
// 文本
if (this.isTextNode(node)) {
let key = RegExp.$1;
this.complierText(node, key)
}
// 元素
if (this.isElementNode(node)) {
const attrs = Array.from(node.attributes);
attrs.forEach(att => {
const { name, value: key } = att;
if (name.startsWith('v-')) {
const dir = name.slice(2);
this.updater(node, key, dir);
if (name == 'v-model') {
node.addEventListener('input', () => {
this.$vm[key] = node.value;
})
}
}
});
}
});
}
// 所有使用到数据的地方,统一使用updater更新,主要这样方便在这里统一收集所有watcher
// 每一个使用到key值的节点node,都对应一个watcher,该watcher内记录了使用的节点,和被使用使用的key(值更新时,this.$vm[key]可以获取到最新的值)
updater(node, key, dir) {
let fn = this[dir + 'Updater'];
Dep.target = new Watcher(() => {
fn && fn(node, this.$vm[key]);
});
fn && fn(node, this.$vm[key]);
Dep.target = null;
}
textUpdater(node, val) {
node.textContent = JSON.stringify(val);
}
htmlUpdater(node, val) {
node.innerHTML = JSON.stringify(val);
}
modelUpdater(node, val) {
node.value = val;
}
complierText(node, key) {
// node.nodeValue = this.$vm[key];
this.updater(node, key, 'text')
}
isElementNode(node) {
return node.nodeType == 1
}
isTextNode(node) {
return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
};
Dep.js 观察者模式
class Dep {
constructor() {
this.collectArr = [];
}
add(arg) {
this.collectArr.push(arg)
}
notify() {
this.collectArr.forEach(w => w.updater())
}
}
watcher.js 观察者
class Watcher {
constructor(callBack) {
this.callBack = callBack;
}
updater() {
this.callBack();
}
}
let app = new Vue({
el: '#app',
data: {
opt: {
age: 10
},
arr: [1, 2, 3, 4],
a: 150,
des: '<em>em</em>'
}
});
setTimeout(() => {
app.arr.push(100);
}, 1000)
setTimeout(() => {
app.arr.pop();
}, 2000)