包含:选择器属性、闭包、遍历对象、虚拟DOM树、观察者模式、Object.defineProperties()方法改造data中的变量、递归、匿名函数等
代码(具体看注释):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>welecome</h1>
<h2>积分:<span>{{score}}</span></h2>
<h2>用户名:<span>{{uname}}</span></h2>
<h2>积分:<span>{{score}}</span></h2>
<script>
var data = {
uname: 'dingding',
score: 3000
}
//VUE第一步:改造data
//ES5新函数:Object.keys(obj) 获取obj对象中的所有属性名的列表
//获得data对象中所有变量名(属性名)
var keys = Object.keys(data);
console.log(keys);
//遍历data中每个属性
for(var key of keys) {
// 每遍历一个属性,就要改造这个属性为访问器属性
// key变量是循环变量,当循环结束时,key四种指向最后一个属性名
// 而访问器属性中的get()和set()函数,其中用到了key变量,但是不是立刻调用,而是在将来,有人试图修改属性值时,才发出get()和set(),到那个时候,key变量早就不是当前属性名了
// 所以,在for of循环内使用匿名函数自调用,并传参的方式,将当前正在遍历的key 值,传入匿名函数中,交给匿名函数的形参变量key(局部变量)来保存,以此形成闭包结构。
// 结果,每个get()和set()函数,即使将来要访问key,也是访问的闭包中专属的key
(function(key) {
// key是局部变量
//每遍历data中一个变量,就改造这个变量
Object.defineProperties(data,{
//先定义一个以_开头,后跟当前变量名的隐藏变量,实际保存当前属性值。
//_uname
//`_${key}` //错误:在定义对象时,属性名禁止使用反引号
//"_"+key //错误:在定义对象时,属性名禁止拼接
[`_`+key]: {//_name
value: data[key],//data["uname"] = "dingding"
writable: true,
enumerable: false,
},
[key]: {//uname
get() {
return this[`${key}`];//return this["_uname"]
},
set(value) {
this[`_${key}`] = value; //this["_uname"] = value
console.log(`${key}属性变了`);
//知道变量变化,就自动调用change()方法,扫描数你DOM树,找到受影响的元素,更新真事DOM树上对应节点的内容
change(key);
},
enumerable:true
}
})(key);
})
}
Object.seal(data);//将每个属性的configurable: false
console.log(data);
//Vue第二部:构建虚拟DOM树,并完成首次绑定元素内容
//准备数组保存所有可能受影响的元素
var arr = [];
//递归遍历指定父元素下所有内容为{{***}}的元素
function getChildren(parent) {
//获取当前父元素下直接子元素
var children = parent.children;
//遍历每个直接子元素
for(var c of children) {
//如果子元素还有子元素
if(c.children.length > 0) {
//就继续递归遍历子元素的下一级
arguments.callee(c);
} else {//否则,子元素没有根子一级元素,说明当前元素的内容了
//遍历data中每个变量名
for(var key of keys) {
//每遍历一个变量名,就用当前元素的内容和变量名作比较
//只有内容{{}}中的变量名和data中某个属性名一致,才有必要将元素添加到虚拟DOM树数组中
if(c.innerHTML == `{{${key}}}`) {
arr.push({
elem: c,//虚拟DOM树中每个元素,不但要记录DOM元素对象地址
innerHTML: c.innerHTML//而且还要记录这个DOM元素哪个属性发生变化,打算变成哪个变量的值
});
//在首次扫描是,还需要首次填充页面内容
c.innerHTML = data[key];
}
}
}
}
}
getChildren(document.body);
console.log(arr);
//封装虚拟DOM树并修改内容的方法
//根据奔驰发生变化的属性,查找虚拟DOM树中受影响的节点,跟真事DOM树中节点的内容
// 比如 score
function change(key) {
//遍历虚拟DOM树
for(var obj of arr) {
//只有当前节点的内容收到当前属性key变化影响时
if(obj.innerHTML == `{{$key}}`) {
//才更新真事DOM元素的内容为data中当前key属性的瞬时值
obj.elem.innerHTML = data[key]
}
}
}
//测试响应系统
setInterval(function() {
data.score+=100;
},2000);
</script>
</body>
</html>