# 零碎知识
# 为什么说 DOM 操作耗时
线程切换:每次 DOM 操作就会引发线程的上下文切换——从 JavaScript 引擎切换到渲染引擎执行对应操作,然后再切换回 JavaScript 引擎继续执行。
重新渲染:重排和重绘。渲染耗时(粗略地认为渲染耗时为紫色 Rendering 事件和绿色 Painting 事件耗时之和)
# 得到 undefined 的情况
- 引用已声明但未初始化的变量;
- 引用未定义的对象属性;
- 执行无返回值函数;
- 执行 void 表达式;
- 全局常量 window.undefined 或 undefined
# new 实现
new 在执行操作时的步骤
- 开辟一块新的堆空间,用来保存创建出来的对象
- 将这个对象中的地址设置给 this
- 执行构造函数(给对象添加属性和方法)
- 将对象的地址返回给实例
function _new(obj, ...rest) {
// 基于obj的prototype构建对象的原型
const ctx = Object.create(obj.prototype)
// 将ctx作为obj的this,继承其属性,并返回结果为result
const result = obj.apply(ctx, rest)
// 根据result对象类型决定返回结果
const isObject = typeof result === 'object' && result !== null
const isFunction = typeof result === 'function'
return isObject || isFunction ? result : ctx
}
function Test(name) {
this.name = name
this.say = function () {
console.log('name = ' + this.name)
}
}
const test = _new(Test, 'aaa')
test.say() //'name = aaa'
console.log(test instanceof Test) // true
# async/await 实现
async/await 语法糖原理:就是使用 Generator 函数+自动执行器来运作的。
// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num + 1)
}, 1000)
})
}
// 自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func) {
let gen = func()
function next(data) {
let result = gen.next(data)
if (result.done) {
return result.value
}
result.value.then(function (data) {
next(data)
})
}
next()
}
// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
let func = function* () {
let f1 = yield getNum(1)
let f2 = yield getNum(f1)
console.log(f2)
}
asyncFun(func)
# this 机制
this 到底是什么?this 是在运行时进行绑定的,并不是在编写时绑定的,它的上下文取决于函数调用的各种条件。
判断 this 的绑定对象
- 由 call 或 apply(或 bind)调用?绑定到指定对象
- 由上下文对象调用?绑定到那个上下文对象
- 由 new 调用?绑定到新创建的对象
- 默认:在严格模式下绑定到 undefined,否则绑定到全局对象
# 原型和 call 的组合继承
function Parent(name, color) {
this.name = name
this.color = color
}
Parent.prototype.showName = function () {
return `parent: ${this.name}`
}
function Child(age, name, color) {
this.age = age
Parent.call(this, name, color)
}
// Child.prototype = new Parent('parent',['yellow'])
Child.prototype = Object.create(Parent.prototype)
// 1. 这一步不用Child.prototype = Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
// 2. 不用Child.prototype = new Parent() 的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
// 3. Object.create是创建了父类原型的副本,与父类原型完全隔离
Child.prototype.getAge = function () {
return `child:my age is ${this.age}`
}
# 自定义事件
<button class="btn">触发事件</button>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
- 创建一个 look 自定义事件
- 给 li 元素绑定 look 事件
- 通过点击 button 按钮,触发 li 调用自定义事件
实现如下:
// 创建一个支持冒泡且不能被取消的look事件
const LOOKEV = new Event('look', {
bubbles: true,
cancelable: false
})
// 点击元素触发事件
document.querySelector('.btn').addEventListener('click', function () {
dispatchEvent()
})
// document.dispatchEvent(LOOKEV)
const doms = Array.from(document.getElementsByTagName('li'))
// 事件可以在任何元素触发,不仅仅是document
function dispatchEvent() {
doms.forEach(function (dom) {
// 调用自定义事件
dom.dispatchEvent(LOOKEV)
})
}
// 给元素绑定look事件
doms.forEach((dom, i) => {
dom.addEventListener('look', function (e) {
e.target.innerText = `${i + 1}-${Date.now()}`
})
})