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)
宏任务与微任务的关系
微任务在宏任务结束之前执行
注意
- Promise.then catch finally可实现为宏任务, 但一般认为是微任务
- setTimeout(fn, 0), 在任务队列尾部添加事件, HTML5标准中规定延迟时间不低于4ms, 所以你写的0, 实际执行时在4ms以上
- Node.js中process.nextTick方法可以在当前执行栈的尾部和下一次Event Loop(主线程读取任务队列)之前触发回调函数,它指定的任务优先于所有异步任务之前执行
- 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))
}