数据劫持,订阅者模式,双向绑定
//index.html文件
<!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>
<script src="./cvue.js"></script>
</head>
<body>
<div id="app">
{{message}}
<!-- <p>{{message}}</p> -->
<input type="text" c-model="name" />{{name}}
</div>
</body>
</html>
<script>
let vm = new Cvue({
el : '#app',
data : {
message : '测试数据',
name : 'cher'
}
})
vm._data.message = '11';
vm._data.name = 'chun'
</script>
//cvue.js文件
class Cvue{
constructor(options){
this.$options = options; //为什么加 $;因为防止冲突,和vm里面的数据做区分
this._data = options.data;
this.observer(this._data); //数据劫持
this.compile(options.el);
}
observer(data){
Object.keys(data).forEach(key => { //为每个data属性进行数据劫持
let value = data[key];
let dep = new Dep();
Object.defineProperty(data,key,{
configurable : true,
enumerable : true,
get(){
if(Dep.target){ //添加一个订阅者,必须要触发get (2)
dep.addSub(Dep.target)
}
return value;
},
set(newValue){
value = newValue;
dep.notify(newValue); //提醒订阅者 (3)
}
})
})
}
compile(el){
let element = document.querySelector(el); //获取 #app节点
this.compileNode(element);
}
compileNode(element){
let childNodes = element.childNodes; //获取 #app下的所有子节点
Array.from(childNodes).forEach((node) => {
if(node.nodeType == 3){ //文本节点
let nodeContent = node.textContent; //文本内容
let reg = /\{\{\s*(\S*)\s*\}\}/; //匹配文本节点
if(reg.test(nodeContent)){
node.textContent = this._data[RegExp.$1]; //替换文本
new Watcher(this,RegExp.$1,newValue => {
node.textContent = newValue; //更新视图
}); //初次渲染订阅者,this指Cvue (1)
}
}else if(node.nodeType == 1){ //标签节点
//双向绑定 c-model
let attrs = node.attributes;
Array.from(attrs).forEach(attr => {
let attrName = attr.name;
let attrValue = attr.value;
if(attrName.indexOf('c-') == 0){
attrName = attrName.substr(2);
if(attrName == 'model'){
node.value = this._data[attrValue]; //把data里的值放到c-model的值里
}
node.addEventListener('input',e => { //c-model值改变的时候data里的值也随着改变
this._data[attrValue] = e.target.value
})
new Watcher(this,attrValue,newValue => { //双向绑定
node.value = newValue;
});
}
})
}
if(node.childNodes.length > 0){ //递归
this.compileNode(node);
}
})
}
}
class Dep{ //发布者
constructor(){
this.subs = []; //要订阅的数据
}
addSub(sub){
this.subs.push(sub); //添加那些订阅者
}
notify(newValue){ //提醒所有订阅者更新
this.subs.forEach(v => {
v.update(newValue);
})
}
}
class Watcher{ //订阅者
constructor(vm,exp,cb){
Dep.target = this; //为了判断是否有watcher;这个this就是订阅者实例化的类
this.cb = cb;
vm._data[exp]; //触发get
Dep.target = null; //不在重复添加
}
update(newValue){
this.cb(newValue);
console.log('更新了');
}
}