JavaScript 系列八:同步与异步

快来加入我们吧!

"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。

"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!

同步与异步

前言

在开始学习之前,我们想要告诉您的是,本文章是对JavaScript语言知识中 “异步操作” 部分的总结,如果您已掌握下面知识事项,则可跳过此环节直接进入题目练习

  • 单线程
  • 同步概念
  • 异步概念
  • 异步操作的模式
  • 异步操作的流程控制
  • 定时器的创建和清除

如果您对某些部分有些遗忘,👇🏻 已经为您准备好了!

汇总总结

单线程

单线程指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。

JavaScript 之所以采用单线程,而不是多线程,跟历史有关系。JavaScript 从诞生起就是单线程,原因是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。

单线程的好处

  • 实现起来比较简单
  • 执行环境相对单纯

单线程的坏处

  • 坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行

如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 IO 操作(输入输出)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。JavaScript 语言的设计者意识到,这时 CPU 完全可以不管 IO 操作,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是 JavaScript 内部采用的**“事件循环”机制**(Event Loop)。

单线程虽然对 JavaScript 构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果用得好,JavaScript 程序是不会出现堵塞的,这就是为什么 Node 可以用很少的资源,应付大流量访问的原因。

为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。

同步

同步行为对应内存中顺序执行的处理器指令。每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息。这样的执行流程容易分析程序在执行到代码任意位置时的状态(比如变量的值)。

同步操作的例子可以是执行一次简单的数学计算:

let xhs = 3

xhs = xhs + 4

在程序执行的每一步,都可以推断出程序的状态。这是因为后面的指令总是在前面的指令完成后才会执行。等到最后一条指定执行完毕,存储在 xhs 的值就立即可以使用。

首先,操作系统会在栈内存上分配一个存储浮点数值的空间,然后针对这个值做一次数学计算,再把计算结果写回之前分配的内存中。所有这些指令都是在单个线程中按顺序执行的。在低级指令的层面,有充足的工具可以确定系统状态。

异步

异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的(同步操作则必须要等)。如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待。

异步操作的例子可以是在定时回调中执行一次简单的数学计算:

let xhs = 3

setTimeout(() => (xhs = xhs + 4), 1000)

这段程序最终与同步代码执行的任务一样,都是把两个数加在一起,但这一次执行线程不知道 xhs 值何时会改变,因为这取决于回调何时从消息队列出列并执行。

异步代码不容易推断。虽然这个例子对应的低级代码最终跟前面的例子没什么区别,但第二个指令块(加操作及赋值操作)是由系统计时器触发的,这会生成一个入队执行的中断。到底什么时候会触发这个中断,这对 JavaScript 运行时来说是一个黑盒,因此实际上无法预知(尽管可以保证这发生在当前线程的同步代码执行之后,否则回调都没有机会出列被执行)。无论如何,在排定回调以后基本没办法知道系统状态何时变化。

为了让后续代码能够使用 xhs ,异步执行的函数需要在更新 xhs 的值以后通知其他代码。如果程序不需要这个值,那么就只管继续执行,不必等待这个结果了。

任务队列和事件循环

JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)

首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。

JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。

维基百科对事件循环的定义是:“事件循环是一个程序结构,用于等待和发送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。

异步操作的模式

回调函数

回调函数是异步操作最基本的方法。

下面是两个函数 f1f2 ,编程的意图是 f2 必须等到 f1 执行完成,才能执行。

function f1() {
   
  // ...
}

function f2() {
   
  // ...
}

f1()
f2()

上面代码的问题在于,如果 f1 是异步操作,f2 会立即执行,不会等到 f1 结束再执行。

这时,可以考虑改写 f1 ,把 f2 写成 f1 的回调函数。

function f1(callback) {
   
  // ...
  callback()
}

function f2() {
   
  // ...
}

f1(f2)

回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。

异步操作的流程控制

如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守这种顺序。

function async(arg,
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript 是一门单线程的编程语言,意味着它在任意给定的时刻只能执行一个任务。这是因为 JavaScript 在最初设计时是作为浏览器脚本语言而诞生的,用于操作网页的 DOM(文档对象模型)。 在 JavaScript 中,任务按照它们被调用的顺序执行,这种方式称为同步执行。当一个任务执行时,其他任务必须等待它的完成才能继续执行。这种同步执行的特性可以确保数据的一致性,但也可能导致阻塞,特别是在执行耗时较长的任务时。 为了解决阻塞问题,JavaScript 引入了异步执行的概念。通过异步执行,可以让某些任务在后台执行,而不会阻塞其他任务的执行。常见的异步操作包括网络请求、文件读写和定时器等。在 JavaScript 中,通常使用回调函数、Promise、async/await 等方式来处理异步操作。 回调函数是最早被广泛使用的异步处理方式。通过将一个函数作为参数传递给异步操作,在操作完成后调用该函数来处理结果。然而,使用回调函数嵌套多层会导致代码可读性和维护性的降低,这就是所谓的"回调地狱"问题。 为了解决回调地狱问题,Promise 和 async/await 出现了。Promise 是一种用于处理异步操作的对象,它可以链式调用,避免了回调函数嵌套的问题。而 async/await 是基于 Promise 的语法糖,使异步代码看起来更像同步代码,更易于理解和编写。 总结起来,JavaScript 是单线程的,但通过异步执行可以提高程序的性能和响应速度。同步执行保证了数据的一致性,而异步执行允许在后台处理耗时操作,提高了用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值