Vue源码分析
准备知识
1. 将类数组对象转化为数组
<ul>
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
const lis = document.getElementsByTagName('li')
console.log(lis instanceof Array, lis.forEach)
const lis2 = Array.from(lis) // es6
console.log(lis2 instanceof Array)
const lis3 = Array.prototype.slice.call(lis) // es5
console.log(lis3 instanceof Array)
控制台输出:
false undefined
prepare.html:23 true ƒ forEach() { [native code] }
prepare.html:25 true ƒ forEach() { [native code] }
2. 获取节点类型
DOM节点
根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:
- 整个文档是一个文档节点(Document)
- 每个 HTML 元素是元素节点(Element)
- HTML 元素内的文本是文本节点(Text)
- 每个 HTML 属性是属性节点(Attr)
- 注释是注释节点
HTML DOM 节点树
HTML DOM 将 HTML 文档视作树结构。这种结构被称为节点树:
HTML DOM Tree 实例
通过 HTML DOM,树中的所有节点均可通过 JavaScript 进行访问。所有 HTML 元素(节点)均可被修改,也可以创建或删除节点。
<div id="test">IT</div>
const elementNode = document.getElementById('test')
const attrNode = elementNode.getAttributeNode('id')
const textNode = elementNode.firstChild
console.log(elementNode.nodeType, attrNode.nodeType, textNode.nodeType)
输出结果:
1 2 3
3. Object.defineProperty()方法
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
**备注:**应当直接在
Object
构造器对象上调用此方法,而不是在任意一个Object
类型的实例上调用。
语法
Object.defineProperty(obj, prop, descriptor)
参数
-
obj
要定义属性的对象。
-
prop
要定义或修改的属性的名称或
Symbol
。 -
descriptor
要定义或修改的属性描述符。
数据描述符(4个):
-
configurable
: 是否可以重新定义,默认为false
。 -
enumerable
: 是否可以枚举,默认为false
。 -
value
: 初始值,默认为undefined
。 -
writable
: 是否可以修改属性值,默认为false
。
访问描述符(2个):
-
get
: 回调函数,根据其他相关的属性动态计算得到当前属性值,默认为undefined
。 -
set
: 回调函数,监视当前属性值的变化,更新其他相关的属性值,默认为undefined
。
-
返回值
被传递给函数的对象。
在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而
Object.defineProperty
是定义key为Symbol的属性的方法之一。
const obj = {
firstName: 'A',
lastName: 'B'
}
// 给obj添加fullName属性,并且能随firstName和lastName变化
Object.defineProperty(obj, 'fullName', {
get() {
return this.firstName + '-' + this.lastName
},
set(value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
})
console.log(obj.fullName)
obj.firstName = 'C'
obj.lastName = 'D'
console.log(obj.fullName)
obj.fullName = 'E-F'
console.log(obj.firstName, obj.lastName)
输出结果:
A-B
C-D
E F
Object.defineProperty(obj, 'fullName2', {
configurable: false,
enumerable: true,
value: 'G-H',
writable: false
})
console.log(obj.fullName2)
obj.fullName2 = 'J-K'
console.log(obj.fullName2)
Object.defineProperty(obj, 'fullName2', {
configurable: false,
enumerable: false,
value: 'G-H',
writable: true
})
输出结果:
G-H
G-H
Object.defineProperty(obj, 'fullName2', {
^
TypeError: Cannot redefine property: fullName2
该语法仅支持IE9及以上,因此导致vue不支持IE8及以下。
4. Object.keys()方法
得到对象自身所有可枚举属性组成的数组。
const names = Object.keys(obj)
console.log(names)
输出结果:
[ 'firstName', 'lastName', 'fullName2' ]
5. Object.prototype.hasOwnProperty()方法
hasOwnProperty()
方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
console.log(obj.hasOwnProperty('fullName'), obj.hasOwnProperty('toString'))
输出结果:
true false
6. DocumentFragment文档片段
Document:对应显示的页面,包含n个Element,一旦更新Document内部的某个元素,界面也会更新。
DocumentFragment:内存中保存n个Element的容器对象(不与界面关联),如果更新Fragment中的某个元素,界面不会改变。
实现:将li标签中的文本替换为atguigu
<ul id="fragment_test">
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
const ul = document.getElementById('fragment_test')
// 1. 创建fragment
const fragment = document.createDocumentFragment()
// 2. 取出ul中所有的子节点保存到fragment中
// 换行是一个文本节点
let child
while (child = ul.firstChild) {
// 因为每个节点只能有一个父节点,所以不会死循环
// appendChild方法先将child从ul中移除,然后将child添加为fragment的子节点
fragment.appendChild(child)
}
// 3. 更新fragment中所有li的文本
// childNodes获取所有的子节点,children获取所有的子标签
// 先将伪数组转换成真数组
Array.from(fragment.childNodes).forEach(node => {
if (node.nodeType === 1) {
// 说明当前节点为元素节点
node.textContent = 'atguigu'
}
})
// 4. 将fragment插入到ul中
ul.appendChild(fragment)