# 零碎知识

# 为什么说 DOM 操作耗时

  1. 线程切换:每次 DOM 操作就会引发线程的上下文切换——从 JavaScript 引擎切换到渲染引擎执行对应操作,然后再切换回 JavaScript 引擎继续执行。

  2. 重新渲染:重排和重绘。渲染耗时(粗略地认为渲染耗时为紫色 Rendering 事件和绿色 Painting 事件耗时之和)

# 得到 undefined 的情况

  • 引用已声明但未初始化的变量;
  • 引用未定义的对象属性;
  • 执行无返回值函数;
  • 执行 void 表达式;
  • 全局常量 window.undefined 或 undefined

# new 实现

new 在执行操作时的步骤

  1. 开辟一块新的堆空间,用来保存创建出来的对象
  2. 将这个对象中的地址设置给 this
  3. 执行构造函数(给对象添加属性和方法)
  4. 将对象的地址返回给实例
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 的绑定对象

  1. 由 call 或 apply(或 bind)调用?绑定到指定对象
  2. 由上下文对象调用?绑定到那个上下文对象
  3. 由 new 调用?绑定到新创建的对象
  4. 默认:在严格模式下绑定到 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}`
}

# 自定义事件

Event (opens new window)

<button class="btn">触发事件</button>
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
  1. 创建一个 look 自定义事件
  2. 给 li 元素绑定 look 事件
  3. 通过点击 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()}`
  })
})