正文开始 <p>其原理重新梳理了一下,很多函数个人喜好重写了一下,仅方便本人阅读,虽然还是感觉逻辑有点绕,但好像确实没办法再去直观调整了。</p><p></p> <br /> //dom首次获取值时 会声明一个 内容更新器,利用全局变量:临时内容更新器 把它传给 数据监听器<br /> // 数据监听器在遍历每个数据时,会一一分配一个空的订阅器<br /> // 数据监听器在被取出数据时,根据是否有 临时内容更新器 来判断是否要写入订阅通知器 <br /> // (在dom初始化时 会为每个dom实例化一个内容更新器,并告诉全局变量“临时内容更新器 ” 指向这个更新器, 在dom赋值即是触发VM数据get动作,get的方法内会判断是否有“临时内容更新器 ”来给当前数据补充所有的订阅者。然后在这个内容更新器结尾立刻注销“临时内容更新器”的这个指向。)<br /><p> // 数据监听器会在数据被设置内容时 触发当前数据绑定的订阅通知器 让它告诉对应的订阅者(dom) 执行更新操作。</p><p>此文的意义:在几个月后再回头看一遍,以验证自己的表达是否不够清晰。<br /></p><p>测试链接:<a href="http://li6.cc/tool/vue_binding.html">http://li6.cc/tool/vue_binding.html</a><br /></p><p></p><pre><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Two-way-data-binding</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
<input type="text" v-model="vvv">
{{ vvv }}
<p id="view"> {{ text }} </p>
<p> {{ vvv }} </p>
</div>
<script>
//临时内容更新器
var tmpUpdater = null;
//订阅通知器 用于通知订阅者
var notifyer = function() {
var self = this;
this.receivrs= [];
this.addReceivrs= function(newReceiver) {
self.receivrs.push(newReceiver);
};
this.notify= function() {
self.receivrs.forEach(function(tmpReceiver) {
tmpReceiver.update();
});
}
};
//数据监听器
function objAddListener(vm) {
var datas = vm.data;
Object.keys(datas).forEach(function (key) {
listenObj(vm, key, datas[key]);
});
//监听对象的改变
function listenObj (obj, key, val) {
var dep = new notifyer();
Object.defineProperty(obj, key, {
get: function () {
//input等首次取值时 会将input对应的监听器告诉给全局变量tmpUpdater。此时要立刻记录,过时不候。
if(tmpUpdater) dep.addReceivrs(tmpUpdater);
return val;
},
set: function (newVal) {
if (newVal === val) return;
val = newVal;
// 作为发布者发出通知
dep.notify();
}
});
}
}
//内容自动更新器
function elementUpdater(vm, node, dataName, nodeType) {
tmpUpdater = this;// 利用全局变量 记录当前实例化的通知器 this. 只有初始化当前对象时才会激活 tmpUpdater
this.dataName = dataName;
this.node = node;
this.vm = vm;
this.nodeType = nodeType;
this.update = function () {
this.value = this.vm[this.dataName]; // 触发vm对应属性的 get, 同时通过全局标记tmpUpdater 传递当前通知实例给listener,末尾再去掉全局的标记.
if (this.nodeType == 'text') {
this.node.nodeValue = this.value; //无标签包裹的文本
this.node.innerHTML = this.value; //标签包裹的文本
}
if (this.nodeType == 'input') {
this.node.value = this.value;
}
};
this.update();
tmpUpdater = null;
}
//打包元素
function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child;
// 循环时,node.firstChild 一直是被剪切后的下一个
while (child = node.firstChild) {
compile(child, vm);
flag.appendChild(child); // 将子节点劫持到文档片段中
}
function compile (node, vm) {
var reg = /{{(.*)}}/;
// 节点类型为元素 <p> <input>
if (node.nodeType === 1) {
var attr = node.attributes;
// 解析属性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var dataName = attr[i].nodeValue; // 获取 v-model 绑定的属性名
node.removeAttribute('v-model');
node.addEventListener('input', function (e) { //input绑定事件 同步修改vm的数据
vm[dataName] = e.target.value;
});
}
}
new elementUpdater(vm, node, dataName, 'input');// 数据首次同步给input
//解析内容
if (reg.test(node.innerHTML)) {
var dataName = RegExp.$1; // 获取匹配到的字符串
dataName = dataName.trim();
new elementUpdater(vm, node, dataName, 'text');
}
}
// 节点类型为 text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var dataName = RegExp.$1; // 获取匹配到的字符串
dataName = dataName.trim();
new elementUpdater(vm, node, dataName, 'text');
}
}
}
return flag
}
function Vue (options) {
this.data = options.data;
objAddListener(this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this);
// 编译完成后,将 dom 返回到 app 中
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el: 'app',
data: {
text: 'hello worldss',
vvv: '是不是什么内容都可以显示?'
}
})
</script>
</body>
</html></pre> <br /><p></p>
正文结束 |
js 正则替换返回值做回调函数 | js监听对象的属性发生改变的两种方式 |