源码解析之数据代理
学习准备
了解以下几点
[].slice.call()
:将arguments
转为数组node.nodeType
:判断节点类型- 了解
Object.defineProperty
是什么?- 理解:
Object.hasOwnProperty
DocumentFragment
文档碎片
<ul id="liBox">
<li>[].slice.call(childNodes)</li>
<li>Object.defineProperty</li>
<li>Object.keys</li>
<li>Object.hasOwnProperty</li>
<li>node.nodeType</li>
<li>createDocumentFragment</li>
</ul>
1、[].slice.call()
:
// 1. [].slice.call(childNodes) 伪数组转换为数组
// 伪数组 ==> 不能直接使用数组方法 但是内部有下标 长度
//伪数组__proto__ 不指向Array 指向的是 对象object
let lis = document.getElementsByTagName("li");
console.log(lis);
let arr = [].slice.call(lis);
console.log(arr);
2、node.nodeType
<div id="app" class="app">
node.nodeType
</div>
let elementNode = document.getElementById("app");
//let attrNode = elementNode.getAttribute("id");//获取属性值
let attrNode = elementNode.getAttributeNode("id"); //获取属性节点
let textNode = elementNode.firstChild; //获取文本节点
console.log(elementNode, attrNode, textNode);
console.log(elementNode.nodeType, attrNode.nodeType, textNode.nodeType)
//textNode.getAttribute("v-model") -->name
console.dir(textNode);
//所有的节点都继承自Node
// Node上拥有一个nodeType
3、Object.defineProperty()
//Object上常用的API defineProperty keys hasOwnProperty assign
// 2.Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
/**
* 定义属性
* obj: 要增加或者修改属性的对象
* prop: 属性名
* descriptor: 属性描述
* return obj
*/
//Object.defineProperty(obj, prop, descriptor)
//属性描述符
// 数据描述符
//configurable: 布尔 --> 是否可配置
//enumerable: 布尔 --> 是否可枚举
//value: 默认值
//writable: 布尔 --> 是否可重写
// 访问(存取)描述符
//get //回调函数 根据其他属性,动态计算当前属性的值
//set //回调函数 监听当前属性值是否发生改变 然后更新其他相关属性
let obj = {
firstName: "A",
lastName: "B"
};
Object.defineProperty(obj, "fullName", {
enumerable: true,
get() { //回调函数 根据其他属性,动态计算当前属性的值
console.log("读取fullName");
return this.firstName + "-" + this.lastName
},
set(newValue) { //回调函数 监听当前属性值是否发生改变 然后更新其他相关属性
console.log("set方法--->", newValue); //"C-D"
let fullName = newValue.split("-"); //["C","D"]
this.firstName = fullName[0];
this.lastName = fullName[1];
}
});
Object.defineProperty(obj, "fullName1", {
configurable: true,
enumerable: false,
});
Object.defineProperty(obj, "fullName1", {
enumerable: true, // Cannot redefine property: fullName1
writable: false, // --> 是否可写
value: "1234" //默认值
});
console.log(obj.fullName); //A-B
obj.firstName = "C";
console.log(obj.fullName); //C-B
obj.fullName = "E-F";
console.log(obj.firstName, obj.lastName); //E ,F
4、Object.hasOwnProperty()
-
**hasOwnProperty()**
方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。 - 所有继承了
Object
的对象都会继承到hasOwnProperty
方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和in
运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
console.log("hasOwnProperty-->", obj.hasOwnProperty("fullName")); //true
5、DocumentFragment
它被作为一个轻量版的 Document
使用,就像标准的document
一样,存储由节点(nodes
)组成的文档结构。
与document
相比,最大的区别是DocumentFragment
不是真实 DOM
树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。
<body>
<ul id="liBox">
<li>这是一个文档碎片</li>
<li>这是一个文档碎片</li>
<li>这是一个文档碎片</li>
<li>这是一个文档碎片</li>
<li>这是一个文档碎片</li>
</ul>
</body>
</html>
<script>
/*
利用类似于虚拟DOM思想
1、首先在内存中创建一个空的文档
2、获取出要更改的节点
3、查看节点状态,判断这个节点是否可更改
4、对符合条件的节点进行修改,
5、成功修改后将其一次行的更新到页面视图上
*/
// 1、创建一个文档
var frag = document.createDocumentFragment()
console.dir(frag);
// 2、获取ul标签
var li = document.getElementById('liBox')
console.log(li);
while (li.firstChild) {
frag.appendChild(li.firstChild)
}
let child = frag
// console.log(child);
// // 3、修改
Array.from(child).forEach(item => {
/*
#text==>nodeType:3
li===>nodeType:1
*/
// console.log(child);
if (item.nodeType === 1) {
item.innerHTML = '这是修改后的文档碎片'
}
})
// console.log(frag);
// // 4、将frag进行添加
li.appendChild(frag)
</script>
数据代理
什么是数据代理?
- 通过一个对象代理另一个对象中属性的操作
- 通过
vm
对象代理vm.data
对象中所有的属性的操作 - 方便操作
data
中的所有的操作。
基本流程:
- 通过
Object.defineProperty
给vm
添加与data
对象的属性对应的属性描述符。 - 给所有的属性添加
getter/setter
。 - 通过
getter/setter
操作data
中对应的属性数据
function MVVM(options) {
//给实例新增一个$options属性,.并且把传递过来的配置进行暂存
this.$options = options;
//在实例上新增一个_data 保存传递过来的data数据
var data = this._data = this.$options.data;
//保存this 为了之后使用this的时候保证this指向的正确性
var me = this;
//通过Object.keys取出data中每一项数据的属性名,然后遍历调用_proxy方法
Object.keys(data).forEach(function(key) {
// 数据代理
me._proxy(key);
});
//为data所有数据进行劫持 结合订阅发布模式
observe(data, this);
//增加模版解析
this.$compile = new Compile(options.el || document.body, this)
}
MVVM.prototype = {
$watch: function(key, cb, options) {
new Watcher(this, key, cb);
},
_proxy: function(key) {//实现数据代理
var me = this;//暂存this 保证this的指向正确 这里的this还是实例vm
//通过defineProperty方法在实例(vm)上新增所有与data中属性所对应属性,并且为该属性添加get和set方法
Object.defineProperty(me, key, {//vm.name
configurable: false,
enumerable: true,
get: function proxyGetter() {
//实现了vm代理data中数据的读操作
return me._data[key];
},
set: function proxySetter(newVal) {//vm.name = "bb"
//实现了vm代理data中数据的写操作
me._data[key] = newVal;
}
});
}
};