vue.js 中双向绑定的实现---初级

时间:2023-03-09 02:06:37
vue.js  中双向绑定的实现---初级

1. 1 我们看到的变量,其实都不是独立的,它们都是windows对象上的属性

 <!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>
</head>
<body>
<script>
var a = 117;
console.log(a);
console.log(window.a);
</script>
</body>
</html>

运行结果:直接打印a 和 打印window下的a 结果一样

vue.js  中双向绑定的实现---初级

1.2  defineProperty():  每个对象在对本身属性的设置或者调用时,都会调用set()或则get()函数;这个和php类的调用差不多,在php中,叫做魔术方法,在调用类时,会调用__construct()方法,在结束调用时,会调用__destruct() 方法;

  <script>
var obj = {};
Object.defineProperty(obj, 'name1', {
set: function() {
console.log("set()方法被调用了");
},
get: function() {
console.log("get()方法被调用了");
}
});
var a = obj.name1; // 调用get()方法
obj.name1 = "huanying2015"; // 调用set()方法
</script>

运行结果:

vue.js  中双向绑定的实现---初级

2.  对obj对象的属性进行监控,当属性改变时,调用set方法:其实这里监控的也不是obj的属性,而是监控输入框value值的变换,通过keyup 事件来监控

 <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>
window.onload = function() {
var OInput = document.querySelector("#aa");
var Odiv = document.querySelector("#bb");
var obj = {};
Object.defineProperty(obj, 'content', {
set: function(val) {
OInput.value = val;
Odiv.innerHTML = val;
},
});
// e.target.value获取的就是你选择接受事件的元素输入的或者选择的值。
// 参数e接收事件对象。
// 而事件对象也有很多属性和方法, 其中target属性是获取触发事件对象的目标, 也就是绑定事件的元素,
// e.target表示该DOM元素, 然后在获取其相应的属性值。
OInput.addEventListener('keyup', function(e) {
obj.content = e.target.value;
});
}
</script>
</head>
<body>
<input type="text" id="aa">
<div id="bb"></div>
</body>

运行结果:

vue.js  中双向绑定的实现---初级

3. 双向绑定预热之------碎片节点说明:创建一个碎片节点---->然后在id 为box的范围内查找节点,把所有的节点都插入到碎片节点中,然后在把碎片节点插入到id为box 的范围内,即先把 id = box 中的东西拿出来,然后玩一玩,检查一下,再把东西放回去

注意:DocumentFragment节点不属于文档树,继承的parentNode属性总是null。它有一个很实用的特点,当请求把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment自身,而是它的所有子孙节点。

 <body>
<div id="box">
<input type="text" id="txt">
<span id="box2"></span>
</div>
<script>
function nodeToFragment(node) {
var frag = document.createDocumentFragment();
var childNode;
// 这里childNode 和node.firstChild 都同时指向node 的第一个节点,放在while 中,是作为一个判断语句使用,
// 如果node.firstChild 为true ,即有第一个节点,那么执行do的内容(while(true){do;}),如果没有第一个节点,那么终止循环
while (childNode = node.firstChild) {
// 1.把node的第一个子节点插入碎片节点中
// 2.注意注意:
// 3.有一个很重要的特性是,如果使用appendChid方法将原dom树中的节点添加到DocumentFragment中时,会删除原来的节点。
// 4.以上的第3点是while循环的基础:所以每次循环,node.firstChild 都指向不同的节点,直到循环完毕
frag.appendChild(childNode);
}
return frag;
}
var oBox = document.querySelector("#box");
var nodeList = nodeToFragment(oBox);
// 把原先被删除的节点重新一次性插入原处,当插入的节点比较多时,这可以调高效率,减少页面对DOM的渲染次数
oBox.appendChild(nodeList);
</script>
</body>

运行结果:

vue.js  中双向绑定的实现---初级

4. 绑定编译原理:

 <!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>
window.onload = function() {
// 替换函数:对html 中的content 进行替换
function compile(node, ghost) {
// 当需要替换的节点为元素节点时(即节点类型 nodeType ==1),遍历元素中的属性节点,查找元素中有没有'v-model'属性,如果有,则进行替换
if (node.nodeType == 1) {
// 获取元素的所有属性,使用attributes属性获取
var attrs = node.attributes;
for (var i = 0, len = attrs.length; i < len; i++) {
// 当元素的属性名字为‘v-model’时
if (attrs[i].nodeName == "v-model") {
// 获取'v-model'属性的属性值,存在key中
var key = attrs[i].nodeValue;
// 设置元素节点的value 属性为 ghost.data.key // 所有的.都可以使用[]来替代//
node.value = ghost.data[key];
// 清除元素中的 ‘v-mdel’属性
node.removeAttribute('v-model');
}
}
};
// 设置正则匹配
var re = /\{\{(.*)?\}\}/;
// 如果为文本节点,即属性值为3时,
if (node.nodeType == 3) {
// 查找文本节点的值,如果匹配正则
if (re.test(node.nodeValue)) {
// 获取正则中的第一个匹配对象,即{{}}中的内容
var key = RegExp.$1;
// 清除两侧的空格
key = key.trim();
// 将文本内容替换为 ghost.data[key];
node.nodeValue = ghost.data[key];
}
}
};
// 替换函数
function nodeToFragment(node, ghost) {
var flag = document.createDocumentFragment();
var childNode;
while (childNode = node.firstChild) {
compile(childNode, ghost);
flag.appendChild(childNode);
}
return flag;
};
// 传值函数
function Ghost(opt) {
this.data = opt.data;
var id = opt.el;
var Obox = document.querySelector(id);
var node = nodeToFragment(Obox, this);
Obox.appendChild(node);
}
// 实例化函数,这里相当于在vue.js 中的 new Vue({});
new Ghost({
el: "#box",
data: {
content: 'hello,huanying2015,how are you?'
}
});
}
</script>
</head>
<body>
<div id="box">
<input type="text" id="txt" v-model="content">
<br> {{content}}
</div>
</body>
</html>

运行结果:

vue.js  中双向绑定的实现---初级

5.相应式绑定数据:

 <!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>
window.onload = function() {
function responsive(obj, key, val) {
Object.defineProperty(obj, key, {
get: function() {
return val;
},
set: function(newVal) {
if (val == newVal) {
return;
};
val = newVal;
console.log('值改变了,新值为:' + val);
}
});
}; function observe(obj, ghost) {
Object.keys(obj).forEach(function(key) {
responsive(ghost, key, obj[key]);
});
};
// 替换函数:对html 中的content 进行替换
function compile(node, ghost) {
// 当需要替换的节点为元素节点时(即节点类型 nodeType ==1),遍历元素中的属性节点,查找元素中有没有'v-model'属性,如果有,则进行替换
if (node.nodeType == 1) {
// 获取元素的所有属性,使用attributes属性获取
var attrs = node.attributes;
for (var i = 0, len = attrs.length; i < len; i++) {
// 当元素的属性名字为‘v-model’时
if (attrs[i].nodeName == "v-model") {
var key = attrs[i].nodeValue;
// 这里 ghost[key] 只是一个中间变量
// 监控键盘松开事件,当键盘松开时,在ghost上增加一个属性key,然后获取监听目标的值,把它赋值给ghost[key]
node.addEventListener('keyup', function(ev) {
ghost[key] = ev.target.value;
});
// 然后把这个ghost[key]赋值给节点的value 属性
node.value = ghost[key];
// 删除属性节点‘v-model’
node.removeAttribute('v-model');
}
}
};
// 设置正则匹配
var re = /\{\{(.*)?\}\}/;
// 如果为文本节点,即属性值为3时,
if (node.nodeType == 3) {
// 查找文本节点的值,如果匹配正则
if (re.test(node.nodeValue)) {
// 获取正则中的第一个匹配对象,即{{}}中的内容
var key = RegExp.$1;
// 清除两侧的空格
key = key.trim();
// 将文本内容替换为 ghost.data[key];
node.nodeValue = ghost.data[key];
}
}
};
// 替换函数
function nodeToFragment(node, ghost) {
var flag = document.createDocumentFragment();
var childNode;
while (childNode = node.firstChild) {
compile(childNode, ghost);
flag.appendChild(childNode);
}
return flag;
};
// 传值函数
function Ghost(opt) {
this.data = opt.data;
var data = this.data;
var id = opt.el;
// 对当前对象及当前对象的data 数据对象进行监控
observe(data, this);
var Obox = document.querySelector(id);
var node = nodeToFragment(Obox, this);
Obox.appendChild(node);
}
// 实例化函数,这里相当于在vue.js 中的 new Vue({});
new Ghost({
el: "#box",
data: {
content: 'hello,huanying2015,how are you?'
}
});
}
</script>
</head>
<body>
<div id="box">
<input type="text" id="txt" v-model="content">
<br> {{content}}
</div>
</body>
</html>

运行结果:

vue.js  中双向绑定的实现---初级

6. 发布者,订阅者模式解析:

 <!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>
// 发布者1 发布信息
sub1 = {
update: function() {
console.log("这是发布者1发出的信息");
}
}
// 发布者2 发布信息
sub2 = {
update: function() {
console.log("这是发布者2发出的信息");
}
}
// 发布者3 发布信息
sub3 = {
update: function() {
console.log("这是发布者3发出的信息");
}
}
//发布清单构造函数
function Dep() {
this.subs = [sub1, sub2, sub3];
}
// 发布清单原型对象上加nodify()方法
Dep.prototype.nodify = function() {
this.subs.forEach(function(sub) {
// 执行发布框架操作
sub.update();
});
}
// 实例化发布清单,即填写发布清单
var dep = new Dep();
// 一个实体指定出一套自己的发布方法
var pub = {
publish: function() {
// 发布方法中,执行发布清单的操作
dep.nodify();
}
}
// 发布执行
pub.publish();
</script>
</head>
<body>
</body>
</html>

运行结果:

vue.js  中双向绑定的实现---初级

7. 响应式数据绑定:即输入的同时,显示输出

 <!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>
window.onload = function() {
var Watcher = function(ghost, node, name) {
Dep.target = this;
this.name = name;
this.node = node;
this.ghost = ghost;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function() {
this.get();
this.node.nodeValue = this.value;
},
get: function() {
this.value = this.ghost[this.name];
}
} function Dep() {
this.subs = [];
}
Dep.prototype = {
addsub: function(sub) {
this.subs.push(sub);
},
nodify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
} function responsive(obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function() {
if (Dep.target) {
dep.addsub(Dep.target);
}
console.log("access:" + val);
return val;
},
set: function(newVal) {
if (val == newVal) {
return;
}
val = newVal;
console.log("值改变了:" + val);
dep.nodify();
}
});
} function observe(obj, ghost) {
Object.keys(obj).forEach(function(key) {
responsive(ghost, key, obj[key]);
});
} function compile(node, ghost) {
var re = /\{\{(.*)\}\}/;
if (node.nodeType == 1) {
var attrs = node.attributes;
for (var i = 0, len = attrs.length; i < len; i++) {
if (attrs[i].nodeName == "v-model") {
var key = attrs[i].nodeValue;
node.addEventListener("keyup", function(ev) {
ghost[key] = ev.target.value;
});
node.value = ghost[key];
node.removeAttribute("v-model");
}
}
}
if (node.nodeType == 3) {
if (re.test(node.nodeValue)) {
var key = RegExp.$1;
key = key.trim();
new Watcher(ghost, node, key);
}
}
} function nodeToFragment(node, ghost) {
var flag = document.createDocumentFragment();
var childNode;
while (childNode = node.firstChild) {
compile(childNode, ghost);
flag.appendChild(childNode);
}
return flag;
} function Ghost(opt) {
this.data = opt.data;
var data = this.data;
observe(data, this);
var id = opt.el;
var obox = document.querySelector(id);
var node = nodeToFragment(obox, this);
obox.appendChild(node);
}
var ogt = new Ghost({
el: "#box",
data: {
content: "huanying2015",
msg: "ahifahha",
}
});
}
</script>
</head>
<body>
<div class="box" id="box">
<input type="text" id="tet" v-model="content"><br> {{content}}
</div>
</body>
</html>

运行结果:

vue.js  中双向绑定的实现---初级