<!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>简单vue的实现</title>
</head>
<body>
<div id="app">
{{name}}
{{text.name}}
<p v-if="isShow">
<span>{{name}}</span>
</p>
<input type="text" id="a" v-model="name">
</div>
<script>
function compile(node, vm) {
var reg = /\{\{(.*)\}\}/; //正则验证双大括号
if (node.nodeType === 1) { //元素节点
var attr = Array.from(node.attributes);
//解析属性
for (var i = 0; i < attr.length; i++) { //遍历元素上的属性,查找特殊的属性,比如v-model和v-if
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue;
node.addEventListener('input', function (e) {
vm[name] = e.target.value;
//eval(`vm.data.${name}=e.target.value`)
console.log(vm)
})
node.value = eval(`vm.${name}`);
node.removeAttribute('v-model');
}
if (attr[i].nodeName == 'v-if') {
var name = attr[i].nodeValue;
var isInsert = eval(`vm.${name}`);
if (!isInsert) {
node = '';
return node;
} else {
node.removeAttribute('v-if');
}
}
}
}
if (node.nodeType === 3) { //文本节点
if (reg.test(node.nodeValue)) { //正则验证节点内容是否符合双大括号包裹
var name = RegExp.$1; //name是双大括号包裹内容
//RegExp.$n是非标准的,尽量不要在生产环境中使用。
//RegExp.$n(n为1-9之间的数值)指的是与正则表达式匹配的第n个 子匹配(以括号为标志)字符串。
//RegExp.$_与正则表达式匹配的完整字符串。
name = name.trim();
//node.nodeValue = eval(`vm.data.${name}`);
new Watcher(vm, node, name)//这里给每个属性文本节点生成一个Watcher对象,嗯,大致跟vue的原理相似
}
}
return node;
}
function nodeToFragment(node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
child = compile(child, vm)
if (child !== "") {
if (child.childNodes.length != 0) {
child.append(nodeToFragment(child, vm));
}
} else {
node.removeChild(node.firstChild)
}
flag.append(child);
}
return flag;
}
function defineReactive(obj, key, val) {
var dep = new Dep();//这里给每个属性生成一个数据订阅中心,它可以存储订阅它的所有watcher,
Object.defineProperty(obj, key, { //实现双向数据绑定
get: function () { //get获取数据
if (Dep.target) dep.addSub(Dep.target);//这里的Dep.target是对应的Watcher对象,这里是dep对象调用addSub
return val;
},
set: function (newVal) { //set设置数据
if (newVal === val) return;
console.log('修改了', key)
val = newVal;
dep.notify();//数据更新了,就通知所有的观察者实例
}
})
}
function observer(obj, vm) { //遍历obj对象的属性
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
})
}
function Watcher(vm, node, name) {
Dep.target = this;//在实例化新的watcher对象时把Dep.target赋值为this,也就是每个指令对应的那个watcher对象,这样在下面调用this.update,从而调用this.get时触发数据的get方法,从而触发dep.addSub(Dep.target),这样这个watcher就被添加进去
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null; //为了保证全局只有一个,在最后需要清空,为下一个指令做准备
}
Watcher.prototype = {
update: function () {
this.get();//更新时调用get()
this.node.nodeValue = this.value;
},
get: function () {
this.value = this.vm[this.name]; //会触发vm.data中属性的get方法,进而可以添加watcher到Dep中
}
}
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub);
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
})
}
}
function Vue(options) {
this.data = options.data;
var id = options.el;
var data = this.data;
observer(data, this) //遍历data对象的属性
var dom = nodeToFragment(document.getElementById(id), this);
document.getElementById(id).appendChild(dom);
}
//简单操作
var vm = new Vue({
el: 'app',
data: {
text: {
name: 'byk'
},
'name': 'vue',
isShow: true
}
})
</script>
</body>
</html>