最近很慌, 想看源码, 先把简单的相关概念理一理
Object.defineProperty
首先, 概念 :
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
没啥好说的, 直接上例子
// 定义一个对象
const obj = {
firstName: 'D',
lastName: 'Z'
}
// 数据描述符-----------------------------------------------
// 新增一个属性
Object.defineProperty(obj, 'age', {
configurable: false, // 不可重新定义
enumerable: false, // 不可枚举
value: 18, // 值
writable: false // 不可写
})
// 不可重新配置
Object.defineProperty(obj, 'age', {
configurable: false,
enumerable: true
}) // 报错
// 不可删除
delete obj.age // false
console.log(obj.age) // 18
// 不可枚举
Object.keys(obj) // ["firstName", "lastName"]
// 不可写
obj.age = 17
console.log(obj.age) // 18
// 存取描述符-----------------------------------------------
Object.defineProperty(obj, 'fullName', {
get() {
return this.firstName + '-' + this.lastName
},
set(value) {
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
})
obj.fullName // D-Z
obj.fullName = 'A-B'
obj.lastName // B
obj.firstName // A
configurable: false
不可重新定义, 也就是说再执行 Object.defineProperty(obj, 'age', {...})
将会报错, 并且, 属性不可删除
enumerable: false
不可枚举, 也就是再执行Object.keys()
或for in
等将取不到该属性
value:18
值
writable: false
不可写, 也就是对age
赋值操作将不会生效, 但不会报错
get
取值操作 , function
, 默认undefined
set
属性值修改时,触发执行该方法 , function
, 默认undefined
以上这一些属性, 称为描述符, 需要注意的是, 当使用了getter或setter方法,不允许使用writable和value这两个属性
get set 看起来就很像vue的计算属性
伪数组
伪数组 (ArrayLike) ,又称类数组。是一个类似数组的对象,但是有如下几个特征。
按索引方式储存数据
0: xxx, 1: xxx, 2: xxx...
具有length属性
但是length属性不是动态的,不会随着成员的变化而改变
不具有数组的push(), forEach()等方法
arrayLike.__proto__ === Object.prototype; //true
arrayLike instanceof Object; //true
arrayLike instanceof Array; //false
以下是常见伪数组:
- arguments
- NodeList => document.querySelectorAll(‘div’).constructor.name // NodeList
- HTMLCollection => document.getElementsByClassName(‘head_wrapper’).constructor.name // HTMLCollection
- jQuery => $()
转换为真数组方法
- 遍历伪数组存入真数组
- Array.prototype.splice.call(arrayLike)
- Array.from(arrayLike)
- […arrayLike]
- 原型继承,arrayLike.proto=Array.prototype
- 其他工具库中的方法,如jQuery中的makeArray() toArray()等
NodeType
元素,属性,文本等, 都实现了 Node 接口, 都会有NodeType属性, 它标识了节点属于的 类型
常见的有:
1 -> 元素 节点
3 -> 文字节点
8 -> 注释节点
11 -> DocumentFragment 节点
<div class="my">
123
<span>456</span>
<span>789</span>
</div>
上面的html片段, 分别用childNodes和children取值, 结果分别是什么呢
document.querySelector('.my').childNodes // NodeList(5) [text, span, text, span, text]
document.querySelector('.my').children // HTMLCollection(2) [span, span]
从图中可以看到, my div下, 包含了5个Node, 其中123, 以及两个小红框位置的换行符, 属于 TextNode , span标签属于 ElementNode
childNodes和children结果分别是节点list 和 子元素 list
DocumentFragment
文档片段接口,表示一个没有父级文件的最小文档对象.
通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。
因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
let content = document.createDocumentFragment()
let el = document.querySelector('.nums')
content.appendChild(el)
// 这个时候 .nums 元素会从页面消失, 存入content这个Fragment片段
Proxy
Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。
var obj = new Proxy({}, {
get(target, key, receiver) {
console.log(`getting ${key}!`)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log(`setting ${key}!`)
return Reflect.set(target, key, value, receiver)
}
})
上面代码对一个空对象架设了一层拦截,重新定义了属性的读取(get)和设置(set)行为。对设置了拦截行为的对象obj,去读写它的属性,用自己的定义覆盖了语言的原始定义,运行得到下面的结果
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2