2019/4/13 10:26:19
1. 说明
- 仿vue实现的mvvm库地址
2. 准备知识
2.1 将伪数组转为真数组
/*
将伪数组转换为真数组
*/
const lis = document.getElementsByTagName('li')
// lis是伪数组,不能使用真数组的各种方法,如foreach等
console.log('lis', lis, lis[1].innerHTML, lis instanceof Array)
// 数组的slice方法会对已有的数组进行截取,并返回截取后的新数组
// call指定this对象是谁
const lis2 = Array.prototype.slice.call(lis)
// 将伪数组lis转换为真数组,目的是可以使用数组各种方法了
console.log('lis2', lis2, lis2 instanceof Array)
2.2 常用的node节点类型
- document文档
- element元素
- attr属性
- text文本
代码测试
<div id="title">我的测试</div>
/*
得到节点类型
*/
const elementNode = document.getElementById('title')
const attrNode = elementNode.getAttributeNode('id')
const textNode = elementNode.firstChild
console.log('节点', elementNode, attrNode, textNode)
console.log('节点类型', elementNode.nodeType, attrNode.nodeType, textNode.nodeType)
console.log('节点名称', elementNode.nodeName, attrNode.nodeName, textNode.nodeName)
/*
节点 <div id="title">我的测试</div> id="title" "我的测试"
节点类型 1 2 3
节点名称 DIV id #text
*/
2.3 给对象添加属性
- 平时常用的给对象添加属性
obj.fullName = 'zneg-jie'
- 利用
Object.defineProperty
给对象添加属性- 这个API IE8浏览器是不能执行的,但是这个API是vue源码的核心,所以vue不支持IE8以下
- 语法
Object.defineProperty(obj, prop, descriptor)
- 查看地址
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
/*
给对象添加属性(指定描述符)
*/
const obj1 = {firstName: 'zeng',lastName: 'jie'}
const obj2 = {firstName: 'zeng',lastName: 'jie'}
const obj3 = {firstName: 'zeng',lastName: 'jie'}
// 这个API IE8浏览器是不能执行的,但是这个API是vue源码的核心,所以vue不支持IE8以下
Object.defineProperty(obj1,'fullName', {})
Object.defineProperty(obj2,'fullName', {
// 回调函数,根据对象其他属性变化动态计算这个属性
get(){
return this.firstName + '-' + this.lastName
},
// 回调函数,监视这个属性变化,更改其他属性的值
set(value){
this.firstName = value
this.lastName = value
}
})
obj2.firstName = 'jay'
Object.defineProperty(obj3,'fullName', {
// 是否可以重新配置属性, 默认为false
configurable:false,
// 是否可以枚举,默认为false
enumerable:true,
// 是否可以修改, 默认为false
writable:false,
value:'peng-yy'
})
console.log(obj1.fullName, obj2.fullName, obj3.fullName)
// undefined jay-jie peng-yy
console.log('枚举', Object.keys(obj1), Object.keys(obj2), Object.keys(obj3))
// 枚举 (2) ["firstName", "lastName"] (2) ["firstName", "lastName"] (3) ["firstName", "lastName", "fullName"]
console.log('是否含有该属性', obj1.hasOwnProperty('fullName')) // true
2.4 DocumentFragment
- DocumentFragments 是DOM节点。它们不是主DOM树的一部分。
- 文档片段存在于内存中,并不在DOM树中
代码测试
html代码
<ul id="list">
<li>test1</li>
<li>test2</li>
<li></li>
<li></li>
</ul>
JS代码
<script>
/*
DocumentFragment
*/
const list = document.getElementById('list')
// 1. 创建fragment
const fragment2 = document.createDocumentFragment()
// 2. 取出list子节点依次放入fragment中(最后list子节点为空)
let child
while (child=list.firstChild) {
fragment2.appendChild(child) // 一个节点只能有一个父亲,先将listd的child移除,再放到fragment2的子节点中
}
console.log(fragment2)
console.log(fragment2.childNodes)
// 3. 更新 fragment 中所有子节点的文本
Array.prototype.slice.call(fragment2.childNodes).forEach(node => {
if (node.nodeType === 1) { // 判断为元素节点
node.textContent = 'faefas'
}
})
list.appendChild(fragment2)
</script>
3. 数据代理
- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
- Vue数据代理:通过 vm 对象来代理 data 对象中所有属性的操作
- 好处:更方便的操作data中的数据
- 基本实现流程
- 通过
Object.defineProperty()
给 vm 添加与 data 对象的属性对应的属性描述符 - 所有添加的属性都包含 getter/setter
- getter/setter 内部去操作 data 中对应的属性数据
- 通过
// 数据代理实现的核心
Object.defineProperty(me,key, {
configurable:false,
enumerable:true,
// 回调函数,当通过vm.key读取属性值时,返回_data对象中的对应属性值
get(){
return me._data[key]
},
// 回调函数,当通过vm.key改变属性值时,实时更改_data对象中对应的属性值
set(value){
me._data[key] = value
}
})
4. 模板解析
4.1 关键三步骤
- 取出 el 元素中所有子节点保存到一个 fragment 对象中
this.$fragment = this.node2Fragment(this.$el);
- 编译 fragment 中所有层次子节点(递归)
this.init();
- 对大括号表达式文本节点进行解析
- 对元素节点的指令属性进行解析
- 事件指令解析
- 一般指令解析
- 将编译好的 fragment 添加到页面的 el 元素中
this.$el.appendChild(this.$fragment);
node2Fragment函数的定义
node2Fragment(el) {
var fragment = document.createDocumentFragment(),
child;
// 将原生节点拷贝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
},
4.2 模板解析之大括号表达式解析
- 根据正则对象得到匹配出的表达式字符串
var reg = /\{\{(.*)\}\}/
- 从 data 中取出表达式对应的属性值
- 将属性值设置为文本节点的 textContent
4.3 模板解析之事件指令解析
- 遍历元素节点中的属性节点,如果有
v-xxx
的属性名,并且是v-on
- 从指令中取出事件名, 如为’click’、‘blur’
- 根据指令的值从 methods 中得到对应的事件处理函数对象
- 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
- 指令解析完后,移除此指令属性
- (因此,在浏览器中看一个页面的源代码,标签中看不到
v-on
之类的属性)
- (因此,在浏览器中看一个页面的源代码,标签中看不到
4.4 模板解析之一般指令解析
- 遍历元素节点中的属性节点,如果有
v-xxx
的属性名,如v-text
、v-html
… - 得到指令名和指令值,如
v-text:'msg'
- 从 data 中指令值得到对应的值
- 根据指令名确定需要操作元素节点的对应属性
v-text
对应操作节点的textContent
属性v-html
对应操作节点的innerHTML
属性- …
- 将得到的 data 值设置到对应属性上
- 移除元素的指令属性