阅读 25

【JS系列3】异步和性能

异步

JavaScript 程序总是至少分为两个块:第一块现在运行;下一块将 来运行,以响应某个事件。尽管程序是一块一块执行的,但是所有这些块 共享对程序作用域和状态的访问,所以对状态的修改都是在之前累积的修 改之上进行的。

一旦有事件需要运行,事件循环就会运行,直到队列清空。事件循环的每 一轮称为一个 tick。用户交互、IO 和定时器会向事件队列中加入事件。

任意时刻,一次只能从队列中处理一个事件。执行事件的时候,可能直接 或间接地引发一个或多个后续事件。

并发是指两个或多个事件链随时间发展交替执行,以至于从更高的层次来 看,就像是同时在运行(尽管在任意时刻只处理一个事件)。

通常需要对这些并发执行的“进程”(有别于操作系统中的进程概念)进行某种形式的交互协调,比如需要确保执行顺序或者需要防止竞态出现。这 些“进程”也可以通过把自身分割为更小的块,以便其他“进程”插入进来。

(页码235).

事件循环

JS引擎并不是独立运行的,运行在宿主环境中,通常来讲就是Web 浏览器,当然现在JS也进入了其他环境。但是他们都土工了一种机制来处理程序中多个块的执行,且执行每块时调用JS引擎,这种机制被称为事件循环

事件循环&Promise 好文

eventloop伪代码

// eventloop 是一个用作队列的数组
// 保持先进先出

let eventloop = []

let event 

// 永远执行
while (true) {
    // 一次tick(一次循环)
    if (eventloop.length > 0) {
        // 拿到队列中优先级最高的事件
        event = eventloop.shift()

        try {
            event() // 就是回调函数
        } catch (err) {
            reportError(err)
        }
    } 
}
复制代码

Tips

setTimeout(...)并没有把回调函数挂在EventLoop中。而是设置一个定时器,到时后,运行环境才会把回调函数放入时间循环中,然后再未来某个时刻的下一轮循环(tick)中才会执行并摘下。那么当事件循环中已经存在N个回调事件时,setTimeout 的回调只能排队等候,这也就是为什么定时器精确度不高的原因。

进程 & 线程

对于操作系统来说,一个任务就是一个进程,一个进程上又至少有一个线程。一个进程是操作系统分配资源的最小单元,进程是操作系统调度的最小单元。

进程和线程独立运行,并可能同 时运行:在不同的处理器,甚至不同的计算机上,但多个线程能够共享耽搁进程的内存。

并行线程

事件循环是将JS分成一个个代码块/任务顺序执行。但是可以通过分立线程彼此合作的事件循环,让并行和顺序执行共存。

回调

回调函数是 JavaScript 异步的基本单元。但是随着 JavaScript 越来越成熟, 对于异步编程领域的发展,回调已经不够用了。

continuation

C部分可以说是回调函数包裹或者封装了程序的延续(continuation)

// A 
setTimeout(function () {
// C
})
// B
复制代码

Promise

我们用回调函数来封装程序中的continue,然后把回调交给第三方,接着期待第三方能调用回调,从而实现功能。

Promise作为第三方只是给我们提供了解任务何时结束的能力,然后由我们自己的代码来决定什么下一步做什么。

识别Promise

定义某种称为thenable的东西,将其定义为任何具有then(...)方法的对象和函数

鸭子类型表示如果看起来像只鸭子,叫起来像只鸭子,那么它一定就是只鸭子。即为根据一个值的类型(具有哪些属性)对整个值得类型做出一些假定。

那么我们如何判别是否为Promise呢?可以理解为针对thenable值的鸭子类型检测:

if ( p !== null && 
(typeof p === "object" || typeof p === "function") && 
typeof p.then === "function") {
    // 假定这是一个thenabble
} else {
    // 不是
}
复制代码

所以 如果有任何代码有意或无意给对象或者函数添加了then方法,并且指定的是不调用其参数作为回调的函数,那么如果有Promise决议到这样的值时,就会永远被挂起!

生成器

生成器函数是一种特殊的函数。

文章分类
前端
文章标签