[JavaScript]关于JS运行机制的一些探究和总结

207 阅读5分钟

summary 概览

本文为我对JavaScript运行机制相关的一些探究和总结, 分为以下几个部分

  • 宏任务与微任务及其应用
  • 用TypeScript实现简单的Promise
  • 用TypeScript实现一个简单的async await
  • 用setTimeout实现requestAnimationFrame

宏任务与微任务及其应用

Macro Task 宏任务

  • setTimeout
  • setInterval
  • requestAnimationFrame
  • setImmediate (Node.js)

Micro Task 微任务

  • Promise.then catch finally
  • MutationObserver
  • process.nextTick (Node.js)

宏任务与微任务的关系

微任务在宏任务结束之前执行

注意

  1. Promise.then catch finally可实现为宏任务, 但一般认为是微任务
  2. setTimeout(fn, 0), 在任务队列尾部添加事件, HTML5标准中规定延迟时间不低于4ms, 所以你写的0, 实际执行时在4ms以上
  3. Node.js中process.nextTick方法可以在当前执行栈的尾部和下一次Event Loop(主线程读取任务队列)之前触发回调函数,它指定的任务优先于所有异步任务之前执行
  4. Node.js中setImmediate方法则是在当前任务队列的尾部添加事件, 相当于window中的setTimeout(fn, 0)

vue批量更新的遇到的问题

<template lang="pug">
  .template
    .num {{ number }}
    .click(@click="handleClick") click
</template>

<script>
export default {
    data () {
        return {
            number: 0
        };
    },
    methods: {
        handleClick () {
            for(let i = 0; i < 1000; i++) {
                this.number++;
            }
        }
    }
}
</script>

若点击按钮, dom更新1000次, 将是对性能的极大损耗, 所以有了nextTick

nextTick 批量异步更新策略

vue nextTick 源码


import { noop } from 'shared/util'
import { handleError } from './error'
import { isIOS, isNative } from './env'

const callbacks = []
let pending = false

// flushCallbacks拷贝并遍历callbacks并执行相应的回调函数
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let microTimerFunc
let macroTimerFunc
let useMacroTask = false

// 依次判断是否支持原生的setImmediate和原生的MessageChannel
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  macroTimerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = flushCallbacks
  macroTimerFunc = () => {
    port.postMessage(1)
  }
} else {
  // 否则降级为setTimeout(flushCallbacks, 0)
  macroTimerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

//  对micro task的实现, 判断是否支持原生的Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  microTimerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
} else {
  // 否则降级为macro task
  microTimerFunc = macroTimerFunc
}

export function withMacroTask (fn: Function): Function {
  return fn._withTask || (fn._withTask = function () {
    useMacroTask = true
    const res = fn.apply(null, arguments)
    useMacroTask = false
    return res
  })
}

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 把传入的回调函数cb压入callbacks数组
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    // 根据useMacroTask条件执行macroTimerFunc或者是 microTimerFunc, 它们都会在下一个tick执行flushCallbacks
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // 如果nextTick函数没有接收到cb而且支持Promise, 则提供Promise化的调用
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

用TypeScript实现简单的Promise

base frame 基本框架

declare type PromiseExecutor = (resolve: Function, reject: Function) => {}

export default class MyPromise {

  executor: PromiseExecutor

  constructor (executor: PromiseExecutor) {
    this.executor = executor
    try {
      this.executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  resolve (value: any) {}

  reject (reason: any) {}

}

state machine 状态机

declare type PromiseExecutor = (resolve: Function, reject: Function) => {}

export default class MyPromise {

  private executor: PromiseExecutor
  private PENDING: string = 'PENDING'
  private FULFILLED: string = 'FULFILLED'
  private REJECTED: string = 'REJECTED'
  private state: string

  constructor (executor: PromiseExecutor) {
    this.state = this.PENDING
    this.executor = executor
    try {
      this.executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  resolve (value: any) {
    if (this.state === this.PENDING) {
      this.state = this.FULFILLED
    }
  }

  reject (reason: any) {
    if (this.state === this.PENDING) {
      this.state = this.REJECTED
    }
  }

}

then

declare type PromiseExecutor = (resolve: Function, reject: Function) => {}

export default class MyPromise {

  private executor: PromiseExecutor
  private readonly PENDING: string = 'PENDING'
  private readonly FULFILLED: string = 'FULFILLED'
  private readonly REJECTED: string = 'REJECTED'
  private state: string
  private value: any
  private reason: any

  constructor (executor: PromiseExecutor) {
    this.state = this.PENDING
    this.executor = executor
    try {
      this.executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  resolve (value: any) {
    if (this.state === this.PENDING) {
      this.state = this.FULFILLED
      this.value = value
    }
  }

  reject (reason: any) {
    if (this.state === this.PENDING) {
      this.state = this.REJECTED
      this.reason = reason
    }
  }

  then (onFulfilled: Function, onRejected: Function) {
    if (this.state === this.FULFILLED) {
      onFulfilled(this.value)
    } else if (this.state === this.REJECTED) {
      onRejected(this.reason)
    }
  }

}

async callback 异步回调

declare type PromiseExecutor = (resolve: Function, reject: Function) => {}

export default class MyPromise {

  private executor: PromiseExecutor
  private readonly PENDING: string = 'PENDING'
  private readonly FULFILLED: string = 'FULFILLED'
  private readonly REJECTED: string = 'REJECTED'
  private state: string
  private value: any
  private reason: any
  private onFulfilledCallbacks: Array<{():any}> = []
  private onRejectedCallbacks: Array<{():any}> = []

  constructor (executor: PromiseExecutor) {
    this.state = this.PENDING
    this.executor = executor
    try {
      this.executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  resolve (value: any) {
    if (this.state === this.PENDING) {
      this.state = this.FULFILLED
      this.value = value
      this.onFulfilledCallbacks.forEach(onFulfilledCallback => onFulfilledCallback())
    }
  }

  reject (reason: any) {
    if (this.state === this.PENDING) {
      this.state = this.REJECTED
      this.reason = reason
      this.onRejectedCallbacks.forEach(onRejectedCallback => onRejectedCallback())
    }
  }

  then (onFulfilled: Function, onRejected: Function) {

    if (this.state === this.PENDING) {
      this.onFulfilledCallbacks.push(() => {
        onFulfilled(this.value)
      })
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    } else if (this.state === this.FULFILLED) {
      onFulfilled(this.value)
    } else if (this.state === this.REJECTED) {
      onRejected(this.reason)
    }
  }

}

comment 注释

declare type PromiseExecutor = (resolve: Function, reject: Function) => {}

export default class MyPromise {

  private executor: PromiseExecutor
  private readonly PENDING: string = 'PENDING'
  private readonly FULFILLED: string = 'FULFILLED'
  private readonly REJECTED: string = 'REJECTED'
  private state: string
  private value: any
  private reason: any
  private handleValueList: Array<{():any}> = []
  private handleReasonList: Array<{():any}> = []


  constructor (executor: PromiseExecutor) {
    this.state = this.PENDING
    this.executor = executor
    try {
      // 立即执行函数调用, 并传入resolve和reject
      this.executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  // 接收在立即执行函数里传入的值
  resolve (value: any) {
    if (this.state === this.PENDING) {
      this.state = this.FULFILLED
      this.value = value
      // 执行then里暂存的handleValue
      this.handleValueList.forEach(handleValue => handleValue())
    }
  }

  // 接收在立即执行函数里传入的值
  reject (reason: any) {
    if (this.state === this.PENDING) {
      this.state = this.REJECTED
      this.reason = reason
      // 执行then里暂存的handleReson
      this.handleReasonList.forEach(handleReason => handleReason())
    }
  }

  then (handleValue: Function, handleReason: Function) {
    // 待定状态, 暂存handleValue和handleReason
    if (this.state === this.PENDING) {
      this.handleValueList.push(() => {
        handleValue(this.value)
      })
      this.handleReasonList.push(() => {
        handleReason(this.reason)
      })
      // 履行状态, 执行handleValue, 传入resovle接收的值
    } else if (this.state === this.FULFILLED) {
      handleValue(this.value)
      // 拒绝状态, 执行handleReason, 传入reject接收的值
    } else if (this.state === this.REJECTED) {
      handleReason(this.reason)
    }
  }

}

用TypeScript实现一个简单的async await

code

// 模拟axios
function _axios (num): Promise<any> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(++num)
    })
  })
}

function myAsync (generatorFn) {
  function asyncFun (...params: any[]) {
    const args = Array.prototype.slice.call(arguments, 0)
    const iterator = generatorFn.apply(this, args)
    function autoRun (data) {
      const result = iterator.next(data)
      if (result.done){
        return result.value
      } else {
        result.value.then(value => {
          autoRun(value)
        })
      }
    }
    autoRun.apply(this, args)
  }
  return asyncFun
}

// Generator 函数
function * G (n: number) {
  const res1 = yield _axios(n)
  console.log(res1) // 2
  const res2 = yield _axios(res1)
  console.log(res2) // 3
}

myAsync(G)(1)

以上G函数调用, 等价于

async function G (n: number) {
  const res1 = await _axios(n)
  console.log(res1) // 2
  const res2 = await _axios(res1)
  console.log(res2) // 3
}

G(1)

* -> async
yield -> await
可见async await 为Generator函数的语法糖

用setTimeout实现requestAnimationFrame

需求背景

请求动画画面API兼容到IE10及其以上, 若IE9及其以下浏览器需做兼容处理

这是requestAnimationFrame的pollyfill

function pollyfillRAF (): void {
    const vendors: string [] = ['webkit', 'moz']
    for(let x: number = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
      window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']
      window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']
    }
    if (!window.requestAnimationFrame) { 
      let lastTime: number = 0
      window.requestAnimationFrame = callback => { 
        const currTime: number = new Date().getTime()
        const timeToCall: number = Math.max(0, 16.7 - (currTime - lastTime))
        return window.setTimeout(() => { 
          lastTime = currTime + timeToCall
          callback(lastTime)
        }, timeToCall)
      } 
    } 
    if (!window.cancelAnimationFrame) { 
      window.cancelAnimationFrame = id =>  { clearTimeout(id) } 
    }
  }

重点注释一下这段优(feng)秀(sao)的代码

const timeToCall: number = Math.max(0, 16.7 - (currTime - lastTime))

等价于如下的代码

let timeToCall: number
// 如果间隔超过16.7ms则尽快执行
if (currTime - lastTime >= 16.7) {
    timeTocall = 0
// 否则按间隔的差值补足16.7ms执行
} else {
    timeTocall = 16.7 - (currTime - lastTime))
}