//第一步 把页面中的 vue 语法 转成正常 的数据
// 先把模板获取到
function nodeToFragment($el, vm) {
let fargment = document.createDocumentFragment();//创建一个文档碎片 为了存储页面上的节点
let child;
while (child = $el.firstChild) {
// console.log(child);
compile(child, vm);
fargment.appendChild(child);//把 child 从
}
//通过while 循环 我们把要编译的模板 转移到了文档碎片中 页面上成为空白
// 下一步我们要编译模板 然后把编译好的模板放到页面上;
// compile(fargment,vm);//编译模板函数
$el.appendChild(fargment)
}
function compile(node, vm) {
//这里需要我们判断node 节点是文本节点 3 还是元素节点 1
if (node.nodeType === 1) {
//说明是元素节点
let attrs = node.attributes;//获取node的所以行内属性
// console.log(attrs);
[...attrs].forEach(item => {
if (/v\-/.test(item.nodeName)) {
let valN = item.nodeValue;//指令对应的vue 变量名 name age
// console.log(valN,vm.$data);
let val = vm.$data[valN];//vue 变量对应的值
//要把val 设置成对应的元素值
//node 就是我们对应的元素
node.addEventListener('input', (e) => {
vm.$data[valN] = e.target.value;
}, false);
new Watcher(node, vm, valN);
node.value = val;//把元素的val设置成 对应的val
}
});
//对该节点继续进行编译 编译该节点的子节点
// console.log(node);
[...node.childNodes].forEach(item => {
compile(item, vm)
})
//若没有v-m 指令则 对 该节点 继续进行编译
} else {
//文本节点
//我们要去查找小胡子语法 把小胡子语法对应的变量换成对应的值
// console.log(node.textContent);//可以获取到对应的文本字符串
let str = node.textContent;
node.str = str;//str 是带着小胡子的编译之前的那个值
if (/\{\{(\w+)\}\}/.test(str)) {
str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
// console.log($1);
//只要使用了对应的数据我们就增加一个监听者
new Watcher(node, vm, $1);
return vm.$data[$1];
})
node.textContent = str
}
}
}
function observe(obj) {
//执行数据劫持
let keys = Object.keys(obj)//获取obj中所有的属性名
keys.forEach(key => {
//执行 真正的数据劫持
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, key, value) {
let dep = new Dep();//针对每一个属性各自创造了一个事件池 name 对应一个事件池
//name事件池中 都是用到name 节点的那些更新操作
Object.defineProperty(obj, key, {
get() {
console.log(`${key}被使用了`);
if (Dep.target) {
//存储的是对应更新DOM 的操作
dep.addSub(Dep.target);
}
return value
},
set(newValue) {
if (newValue == value) return;//若 两次数据没改变 就不需要更新DOM
console.log(`${key}被设置了`);
value = newValue
dep.notifty();
}
})
}
class Dep { //订阅器
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notifty() {
this.subs.forEach(item => {
item.update();
})
}
}
class Watcher {
//订阅者
constructor(node, vm, key) {
Dep.target = this;
this.node = node;
this.vm = vm;
this.key = key;
this.update();
Dep.target = null;
}
update() {
this.get(); //this.value 存储的就是更改后的数据的新值
let str = this.node.str;
if (str) {
str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
if ($1 == this.key) {
return this.value;
}
return this.vm.$data[$1];
});
this.node.textContent = str;//把更新完成的str 重新放回页面
} else {
//证明是input
this.node.value = this.value;
}
}
get() {
this.value = this.vm.$data[this.key];
console.log(this.value);
}
}
function Vue(options) {
// $el 就是我们要操作的元素
this.$el = document.querySelector(options.el);
// $data 中存储的是 对应的vue变量
this.$data = options.data || {};
observe(this.$data);//对数据进行劫持
nodeToFragment(this.$el, this);//为了把页面中的节点 转移到文档碎片上
}
let vm = new Vue({
el: '#app',
data: {
name: '珠峰',
age: 10
}
})