复制可直接运行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="app">
<p>a.a: {{ a.a }}</p>
<p>b: {{ b }}</p>
<p>{{ add }}</p>
</div>
<script>
// 发布订阅模式 订阅在有发布[fn1,fn2,fn3]
// 绑定的方法都有update属性
function Dep() {
this.subs = [];
}
// 把订阅收集起来为后面依次执行准备
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
};
// 通知每个订阅执行update方法
Dep.prototype.notify = function() {
this.subs.forEach(sub => sub.update());
};
// 订阅器
function Watcher(vm, exp, fn) {
// exp=>vm下的表达式
// console.log('Watcher', vm, exp, fn);
this.fn = fn;
// 把vm和exp绑到this上,在方法上可以共享并拿到
this.vm = vm;
this.exp = exp;
Dep.target = this; //为了读数据的时候收集Watcher
// debugger;
let val = vm;
let arr = exp.split('.');
arr.forEach(k => {
val = val[k];
});
Dep.target = null;
}
// 执行传进来的函数
Watcher.prototype.update = function() {
let val = this.vm;
let arr = this.exp.split('.');
arr.forEach(k => {
val = val[k];
});
this.fn(val);
// this.fn();
};
function Vue(options) {
this.$options = options;
var data = (this._data = this.$options.data);
observe(data);
//data劫持完然后代理给实例vm
for (let key in data) {
Object.defineProperty(this, key, {
numerable: true,
get() {
return this._data[key];
},
set(newVal) {
this._data[key] = newVal;
}
});
}
initCumputed.call(this);
// 处理完数据开始编译模板 传入要挂载的节点和上下文上下文里有data还有其他东西可以处理
// 挂载放在最后面
new Compile(options.el, this);
}
function initCumputed() {
let vm = this;
// 拿到computed属性
let computed = this.$options.computed;
Object.keys(computed).forEach(k => {
// debugger;
Object.defineProperty(vm, k, {
get:
typeof computed[k] === 'function' ? computed[k] : computed[k].get, //判断是函数还是对象
set() {}
});
});
}
function Compile(el, vm) {
// 挂到vm的$el属性
vm.$el = document.querySelector(el);
let fragment = document.createDocumentFragment();
// 把el内的内容都转移到内存中
while ((child = vm.$el.firstChild)) {
fragment.append(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)) {
// 取第一个匹配的值
// console.log(RegExp.$1); //a.a.a a.b
// 因为slint所以左右多出了空格
let arr = RegExp.$1.trim().split('.'); //[a,a]
let val = vm;
arr.forEach(function(key) {
val = val[key]; //第一次val[k] 第二次val[k][k] 一个个的往下取...
});
// 当视图改变要重新刷新视图,数据一遍可以再执行内容的替换
// 替换之前先订阅下数据,在Watcher构造函数里读取了一遍data会触发get方法,所以在get里把前面缓存的vm=>this给存进收集器
// 想要知道是哪一个newVal就要取到当前实例vm和当前vm下对应的属性名,然后从属性上取值
new Watcher(vm, RegExp.$1.trim(), function(newVal) {
//数据改变了 函数要接收一个新的值
node.textContent = text.replace(reg, newVal);
});
// 替换文本中要替换的内容
node.textContent = text.replace(reg, val);
}
// 如果有子节点就继续替换
if (node.childNodes) {
replace(node);
}
});
}
vm.$el.appendChild(fragment);
}
//观察对象给对象增加Object.defineProperty
function Observe(data) {
// 实例化一个收集器,
let dep = new Dep();
for (let key in data) {
// debugger;
let val = data[key];
// 这边对值进行再劫持
observe(val);
//data:{a:1} => 换Object.defineProperty形式写入data
Object.defineProperty(data, key, {
enumerable: true,
get() {
// debugger; //因为没有把加在原型上的方法丢到最上面,所以调不到addSub方法,可以实例化是因为变量声明提升
Dep.target && dep.addSub(Dep.target); //收集[Watcher]
// console.log(dep);
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
val = newVal;
// 新赋值的对象也要加上数据劫持
observe(newVal);
// 把新值拿去更新视图
// console.log(dep.subs);
dep.notify(); //让所有的update方法执行
}
});
}
}
function observe(data) {
if (typeof data !== 'object') return;
return new Observe(data);
}
// let watcher = new Watcher('', '', function() {
// console.log(1);
// });
// let dep = new Dep();
// dep.addSub(watcher); //将watcher放进收集依赖
// dep.addSub(watcher);
// dep.addSub(watcher);
// console.log(dep.subs);
// dep.notify();
// vm._data.a= 2;
var vm = new Vue({
el: '#app',
data: {
a: { a: '1' },
b: '2'
},
computed: {
add() {
return this.a.a + this.b;
}
}
});
console.log(vm._data.a);
</script>
</body>
</html>