JavaScript引擎运行原理( 三 )

  • 调用 setTimeout
  • 打印 Message 3
  • 打印 Message 2
  • 它记录消息3
    稍后,它会记录消息2
    setTimeout是一个 API,和大多数浏览器 API一样,当它被调用时,它会向浏览器发送一些数据和回调 。我们这边是延迟一秒打印 Message 2 。
    调用完 setTimeout 后,我们的代码继续运行,没有暂停,打印 Message 3 并执行一些必须先执行的操作 。
    浏览器等待一秒钟,它就会将数据传递给我们的回调函数并将其添加到事件/回调队列中( event/callback queue) 。然后停留在队列中,只有当调用堆栈(call stack)为空时才会被压入堆栈 。
    JavaScript引擎运行原理

    文章插图
     
    代码示例
    要熟悉JS引擎,最好的方法就是使用它,再来些有意义的例子 。
    简单的闭包
    这个例子中 有一个返回函数的函数,并在返回的函数中使用外部的变量,这称为闭包 。
    function exponent (x) { return function (y) { //和math.pow() 或者x的y次方是一样的 return y ** x }}const square = exponent(2)console.log(square(2), square(3)) // 4, 9console.log(exponent(3)(2)) // 8块代码
    我们使用无限循环将将调用堆栈塞满,会发生什么,回调队列被会阻塞,因为只能在调用堆栈为空时添加回调队列 。
    function blockingCode() { const startTime = new Date().getSeconds() // 延迟函数250毫秒 setTimeout(function() { const calledAt = new Date().getSeconds() const diff = calledAt - startTime // 打印调用此函数所需的时间 console.log(`Callback called after: ${diff} seconds`) }, 250) // 用循环阻塞堆栈2秒钟 while(true) { const currentTime = new Date().getSeconds() // 2 秒后退出 if(currentTime - startTime >= 2) break }}blockingCode() // 'Callback called after: 2 seconds'我们试图在 250毫秒之后调用一个函数,但因为我们的循环阻塞了堆栈所花了 两秒钟,所以回调函数实际是两秒后才会执行,这是JavaScript应用程序中的常见错误 。
    setTimeout不能保证在设置的时间之后调用函数 。相反,更好的描述是,在至少经过这段时间之后调用这个函数 。
    延迟函数
    当 setTimeout 的设置为0,情况是怎么样?
    function defer () { setTimeout(() => console.log('timeout with 0 delay!'), 0) console.log('after timeout') console.log('last log')}defer()你可能期望它被立即调用,但是,事实并非如此 。
    执行结果:
    after timeoutlast logtimeout with 0 delay!它会立即被推到回调队列,但它仍然会等待调用堆栈为空才会执行 。
    用闭包来缓存
    Memoization是缓存函数调用结果的过程 。
    例如,有一个添加两个数字的函数 add 。调用 add(1,2)返回 3,当再次使用相同的参数add(1,2)调用它,这次不是重新计算,而是记住1 +2是3的结果并直接返回对应的结果 。Memoization可以提高代码运行速度,是一个很好的工具 。
    我们可以使用闭包实现一个简单的memoize函数 。
    // 缓存函数,接收一个函数const memoize = (func) => { // 缓存对象 // keys 是 arguments, values are results const cache = {} // 返回一个新的函数 // it remembers the cache object & func (closure) // ...args is any number of arguments return (...args) => { // 将参数转换为字符串,以便我们可以存储它 const argStr = JSON.stringify(args) // 如果已经存,则打印 console.log('cache', cache, !!cache[argStr]) cache[argStr] = cache[argStr] || func(...args) return cache[argStr] }}const add = memoize((a, b) => a + b)console.log('first add call: ', add(1, 2))console.log('second add call', add(1, 2))执行结果:
    cache {} falsefirst add call: 3cache { '[1,2]': 3 } truesecond add call 3第一次 add 方法,缓存对象是空的,它调用我们的传入函数来获取值 3.然后它将 args/value键值对存储在缓存对象中 。
    在第二次调用中,缓存中已经有了,查找到并返回值 。
    对于 add函数来说,有无缓存看起来无关紧要,甚至效率更低,但是对于一些复杂的计算,它可以节省很多时间 。这个示例并不是一个完美的缓存示例,而是闭包的实际应用 。




    推荐阅读