为了能够快速读懂代码,首先要先弄明白以下三个概念:
1、观察者(observer):也就是数据监听器,负责数据对象的所有属性进行监听劫持,并将消息发送给订阅者进行数据更新
2、订阅者(watcher):负责接收数据的变化,更新视图(view),数据与订阅者是一对多的关系。
3、解析器(compile):负责对你的每个节点元素指令进行扫描和解析,负责相关指令的数据初始化及创造订阅者
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="myApp">
<input type="button" value="加个!" z-on:click="fn">
<input type="text" style="width:400px" z-model="site">
<div z-text="site"></div>
<div z-html="site"></div>
</div>
</body>
<script>
function Zhou (options) {
this.$el = document.querySelector(options.el) // 指定挂载的元素
this.$data = options.data // 存放的数据
this.$methods = options.methods // 存放你的方法
this.binding = {} // 存放所有数据的订阅者, 最终结果为{数据属性:[订阅者对象, 订阅者对象]}
this.observe() // 调用观察者,对数据进行劫持
this.compile(this.$el) // 对元素指令进行解析,订阅者也是在这里创建的
}
Zhou.prototype.observe = function () {
var value="";// 定义用于存放数据属性值的变量value
for(var key in this.$data){ // 遍历数据对象
value=this.$data[key];// 对象属性值
this.binding[key]=[];// 数据订阅者初始化,是一个数组,
let that = this
Object.defineProperty(this.$data,key,{// 开始设置劫持
get(){
return value;// 读取值为value
},
set(v){// v为设置的值
if(v!==value){// 当设置的值与当前值不相等时
value=v;// 将读取值更新为v
that.binding[key].forEach(watcher=>{
watcher.update();// 通知与本数据相关的订阅者们进行视图更新
})
}
}
})
}
}
// 定义解析器,解析指令,创建订阅者
Zhou.prototype.compile = function (el) {
var nodes=el.children;// 获得所有子节点
for(var i=0;i<nodes.length;i++){// 对子节点进行遍历
var node=nodes[i];// 具体节点
if(node.children.length>0)// 判断是否具有子节点
this.compile(node);// 如果有子点进行递归操作
if(node.hasAttribute("z-on:click")){// 该节点是否拥有z-on指令
var attrVal=node.getAttribute("z-on:click");// 得到指令对应的方法名
// 为元素绑定click事件,事件方法为$methods下的方法,并将其this指向this.$data
node.addEventListener("click",this.$methods[attrVal].bind(this.$data))
}
if(node.hasAttribute("z-model")){// 该节点是否拥有z-model指令
var attrVal=node.getAttribute("z-model");// 获得指令对应的数据属性
node.addEventListener("input",((i)=>{// 为指令添加input事件
this.binding[attrVal].push(new Watcher(node,"value",this,attrVal));// 为该数据添加订阅者
return ()=>{
this.$data[attrVal]=nodes[i].value;// 更新$data的属性值,会在观察者中进行劫持
}
})(i))
}
if(node.hasAttribute("z-html")){// 该节点是否拥有z-html指令
var attrVal=node.getAttribute("z-html");// 获得指令对应的数据属性
this.binding[attrVal].push(new Watcher(node,"innerHTML",this,attrVal));
}
if(node.hasAttribute("z-text")){// 该节点是否拥有z-text指令
var attrVal=node.getAttribute("z-text");// 获得指令对应的数据属性
this.binding[attrVal].push(new Watcher(node,"innerText",this,attrVal));
}
}
}
function Watcher (el, attr, vm, val) {
this.el = el // 指令所在元素
this.attr = attr // 绑定的属性名
this.vm = vm // 指令所在实例
this.val = val // 指令的值
this.update()
}
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.$data[this.val]
}
var vm = new Zhou({
el: "#myApp",
data: {
site: "<a href='http://www.baidu.com'>zhouyajing</a>"
},
methods: {
fn() {
this.site += "!";
}
}
})
</script>
</html>