js常见知识点补充

193 阅读6分钟

1. 暂时性死区

在变量声明前使用变量,如此一来便会造成变量的提前使用,这种情况下会出现暂时性死区。

  • ES6中的const、let语法
console.log(i)
const i // let i
  • typeof
typeof x
const x
  • 函数参数
function(x = y, y = 2) {}
  • 同变量名赋值
const x = x

2. Promise

2.1 Promise定义

Promise是异步调用的统一接口。每个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。

Promise有三种状态:pending(等待)、fulfilled(完成)、rejected(已拒绝)。每个Promise必须实现then方法,也就是说then方法是Promise的核心。 then方法可传入两个回调函数,第一个即成功时的回调函数,状态由pending -> fulfilled;第二个为失败时的回调函数,状态由pending -> rejected;此外,then方法还可以传入Promise对象或者类then方法,即thenable方法。

2.2 Promise解决回调陷阱的链式写法

  • 不使用Promise
// 假设有两个异步函数:func1 和 func2

// func1 接收一个回调函数作为参数
function func1(callback) {
  // 异步操作
  setTimeout(() => {
    const result = "Func1"
    callback(null, result) // 成功时调用回调函数,并传递结果作为参数
    // callback(new Error('Error in func1')); // 失败时调用回调函数,并传递错误作为参数
  }, 1000)
}

// func2 接收一个参数和一个回调函数作为参数
function func2(data, callback) {
  // 异步操作
  setTimeout(() => {
    const result = data + " -> Func2"
    callback(null, result) // 成功时调用回调函数,并传递结果作为参数
    // callback(new Error('Error in func2')); // 失败时调用回调函数,并传递错误作为参数
  }, 1000)
}

// 使用回调函数实现异步操作的串行执行
func1((err, result1) => {
  if (err) {
    console.error(err)
  } else {
    console.log(result1) // 输出:Func1
    func2(result1, (err, result2) => {
      if (err) {
        console.error(err)
      } else {
        console.log(result2) // 输出:Func1 -> Func2
      }
    })
  }
})

  • 使用Promise
// 假设有两个异步函数:func1 和 func2

// func1 返回一个 Promise 对象
function func1() {
  return new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      const result = "Func1"
      resolve(result) // 成功时调用 resolve
      // reject(new Error('Error in func1')); // 失败时调用 reject
    }, 1000)
  })
}

// func2 返回一个 Promise 对象
function func2(data) {
  return new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      const result = data + " -> Func2"
      resolve(result)
      // reject(new Error('Error in func2'));
    }, 1000)
  })
}

// 使用 Promise 链式写法
func1()
  .then(result => {
    console.log(result) // 输出:Func1
    return func2(result) // 返回一个 Promise 对象,作为下一个 then 的输入
  })
  .then(result => {
    console.log(result) // 输出:Func1 -> Func2
  })
  .catch(error => {
    console.error(error) // 捕捉链中的任何一个 Promise 的错误
  })

2.3 resolve 不同值的区别

  1. 参数传入一个普通的值或对象

    • 这个值回作为 then 回调的参数
    new Promise((resolve, reject) => {
      resolve("normal resolve")
    }).then(res => {
      console.log("res: ", res)
    })
    
  2. 参数传入的是另一个 Promise

    • 这个新 Promise 会决定原 Promise 的状态
     new Promise((resolve, reject) => {
       resolve(new Promise((resolve, reject) => {
         setTimeout(() => {
           resolve("第二个 Promise 的 resolve")
         })
       }))
     }).then(res => {
       console.log("res: ", res)
     }).catch(err => {
       console.log("err: ", err)
     })
    
  3. 参数传入一个对象,并且这个对象有实现 then 方法

    • 会执行该 then 方法,并且根据 then 方法的结果来决定 Promise 的状态
     new Promise((resolve, reject) => {
       resolve({
         then: function(resolve, reject) {
           resolve("thenable value")
         }
       })
     }).then(res => {
       console.log("res: ", res)
     })
    

2.4 await/async

await 与 async 是用在异步函数中的一对关键字,本质上是生成器的语法糖。

await 本质上就是 Promise.resolve()

3. js 内存泄漏

  1. 意外的全局变量
  2. 未清除的定时器
  3. 脱离DOM元素的引用
    • 一个 dom 容器删除之后,变量未置为 null,则其内部的 dom 元素则不会释放
  4. 持续绑定的事件
    • 函数中 addEventListener 绑定事件,函数多次执行,绑定便会产生多次,产生内存泄露
  5. 闭包引起的内存泄露
    • 比如事件处理回调,导致 DOM 对象和脚本中对象双向引用
  6. console.log
    • console.log 的对象是不能被垃圾回收的

4. js判断类型的几种方法

  1. typeof 对象。它返回一个字符串,表示对象的类型。typeof 123 === 'number',typeof undefined === 'undefined',typeof null === 'object'
  2. Object.prototype.toString.call(obj)。它返回一个字符串 '[object type]',其中,type 是对象的类型。
  3. instanceof 运算符。它判断一个对象是否是某个类的实例。'hello' instanceof String === false, new String('hello') instanceof String === true。
  4. constructor 属性。每个对象都有一个 constructor 属性,指向创建对象的构造函数。例如,'hello'.constructor 返回 String,(123).constructor 返回 Number,true.constructor 返回 Boolean。

5. Event Loop 与 宏任务微任务

官网地址

Event Loop 至少有 6 个阶段,不同的操作系统上阶段数不同,不过至少是有这6个阶段的,分别如下:

image.png

最重要的是 timers、poll、check 阶段。

  • timers:执行 setTimeout、setInterval的回调函数
  • poll:检索 I/O 事件,执行 I/O 相关的回调。
    • 计算这一阶段阻塞以及轮询I/O的时间(应该是计算 setTimeout 中阻塞的时间,如果时间到了,那么就会执行 timers 中的回调)
    • 执行 poll 队列中的事件

当 event loop 进入到 poll 阶段并且没有其他 timers 回调执行,那么就会发生以下两件事:

  1. poll 队列不为空,event loop 将会一直同步执行队列中的回调,直到队列中的回调执行完,或者此阶段的时间用完
  2. 如果 poll 队列为空,又回执行以下两件事中的其中一件:
    • 有 setImmediate 任务,event loop 将会结束 poll 轮询阶段执行 check 阶段中的回调
    • 如果没有 setImmediate 任务,eventloop 将会等待添加到队列中的回调,然后立即执行。

一旦 poll 队列为空,event loop 将会一直检查 timers 的最小开始回调时间何时发生,如果有 timers 准备好,那么 event loop 将会返回到 timers 阶段执行回调。

  • check:执行 setImmediate 的回调函数

5.1 宏任务与微任务

  • 事件循环中并非只维护着一个队列,实际上有两个队列:
    • 宏任务队列: Ajax、setTimeout、setInterval、DOM监听、UI Rendering等。
    • 微任务队列: Promise 的 then 回调、Mutation Observer API、queueMicrotask()等。
  • 事件循环对于两个队列的优先级
      1. main script 中的代码先执行(编写的顶层 script 代码)
      1. 执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
      • 也就是宏任务执行之前,必须保证微任务队列是空的;
      • 如果不为空,那么就要优先执行微任务队列中的任务(回调)

5.2 微任务宏任务思路:

  • 每次在执行宏任务之前,需要先将微任务队列中的回调清空;

  • async/await

    • 紧挨着 await 的回调立即执行,而后面的会放到 then 回调中。
    async fn() {
      await foo()
      console.log(1)
    
      // 可以将上述改写:
      foo().then(() => {console.log(1)})
    }
    
console.log('script start')

function requestData(url) {
  console.log("requestData")
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('setTimeout')
      resolve(url)
    }, 2000)
  })
}

function getData() {
  console.log('getData start')
  requestData('why').then(res => {
    console.log('then1-res: ', res)
  })
  console.log('getData end')
}

getData()

console.log('script end')


// 代码结果
// script start
// getData start
// requestData
// getData end
// script end
// setTimeout
// then1-res: why

image.png

  • setTimeout() vs setImmediate()
    • 这两个都是在主模块(main module)中被调用的,那么回调的执行顺序取决于当前进程的性能,这个性能受到其他程序进程(libuv、nodejs 启动顺序)的影响

      // timeout_vs_immediate.js
      setTimeout(() => {
        console.log('timeout')
      }, 0)
      
      setImmediate(() => {
        console.log('immediate')
      })
      
      $ node timeout_vs_immediate.js
      timeout
      immediate
      
      $ node timeout_vs_immediate.js
      immediate
      timeout
      
  • 一个 return Promise.resolve() 等于两个 then。

6. 判断对象为空对象的几种方法

  1. Object.keys()

    Object.keys(obj).length === 0 ? '空对象' : '非空对象'

  2. 使用 for...in 遍历对象

      const obj = {}
      let isEmpty = true
      
      for(let key in obj) {
        isEmpty = false
        break
      }
    
  3. JSON.stringify()

    JSON.stringify(obj) === '{}' ? '空对象' : '非空对象'

  4. 使用第三方库

    • lodash
    • underscore

      const isEmpty = _.isEmpty(obj)

  5. Object.getOwnPropertyNames === 0 && Object.getOwnPropertySymbols === 0

  6. Reflect.ownKeys(obj) === 0