javaScript事件处理机制

一个简单的事件循环的阐释

天都在写JavaScript的你,是否清楚JavaScript引擎的原理呢?

想要了解JavaScript引擎,首先我们从它的运行机制Event Loop来说起。

首先科普一些基础知识。

进程和线程

进程
应用程序的执行实例,每一个进程都是由私有的虚拟地址空间、代码、数据和其他系统资源所组成。

线程

线程是进程内的一个独立执行单元,在不同的线程之间是可以共享进程资源的。

有句老话是这样说的,穷养儿子富养女。

进程就是一个富二代爸爸,它选择了穷养线程儿子。
在这里插入图片描述

进程拥有独立的堆栈空间和数据段,每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段。

线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高。

一句话解释进程和线程
进程:资源分配的最小单位

线程:程序执行的最小单位

关于进程和线程方面的知识我们先了解到这,感兴趣的同学们可以移步
https://www.cnblogs.com/Jones-dd/p/8858995.html

Q&A再来回答一个问题:

在多线程操作下可以实现应用的并行处理,从而以更高的 CPU 利用率提高整个应用程序的性能和吞吐量。特别是现在很多语言都支持多核并行处理技术,然而 JavaScript 却以单线程执行,为什么呢?

答:JavaScript作为脚本语言,最初被设计用于浏览器。为了避免复杂的同步问题(做人嘛,还是简单点好,语言也一样),如果JavaScript同时有两个线程,一个线程中执行在某个DOM节点上添加内容,另一个线程执行删除这个节点,这时浏览器会……在这里插入图片描述

所以JavaScript的单线程是这门语言的核心,未来也不会改变。

有人说,那HTML5的新特性Web Worker,可以创建多线程呀~

是的,为了解决不可避免的耗时操作(多重循环、复杂的运算),HTML5提出了Web Worker,它会在当前的js执行主线程中开辟出一个额外的线程来运行js文件,这个新的线程和js主线程之间不会互相影响,同时提供了数据交换的接口:postMessage和onMessage。

但是因为它创建的子线程完全受控于主线程,且位于外部文件中,无法访问DOM。所以它并没有改变js单线程的本质。

单线程就意味着,所有的任务都需要排队。

就像还不能自助点餐的时候你去肯德基需要排队,有的人没想好点什么或者点的东西很多,耗时就会长,那么后面的人也只好排队等待。有了自助点餐服务后,一切问题迎刃而解。
在这里插入图片描述

语言的设计和生活中的现实情况很像,IO设备(输入输出)很慢(比如Ajax),那么语言的设计者意识到这一点,就在主线程中挂起处于等待中的任务,先运行后面的任务,等IO设备有了结果,再把挂起的任务执行下去。

Event Loop

在这里插入图片描述

从上图中我们可以看到,在主线程运行时,会产生堆(heap)和栈(stack)。

堆中存的是我们声明的object类型的数据,栈中存的是基本数据类型以及函数执行时的运行空间。

栈中的代码会调用各种外部API,它们在任务队列中加入各种事件(onClick,onLoad,onDone),只要栈中的代码执行完毕(js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空),主线程就回去读取任务队列,在按顺序执行这些事件对应的回调函数。

也就是说主线程从任务队列中读取事件,这个过程是循环不断的,所以这种运行机制又成为Event Loop(事件循环)。

同步任务和异步任务
我们可以将任务分为同步任务和异步任务。
同步任务就是在主线程上排队执行的任务,只能执行完一个再执行下一个。
异步任务则不进入主线程,而是先在event table中注册函数,当满足触发条件后,才可以进入任务队列来执行。只有任务队列通知主线程说,我这边异步任务可以执行了,这个时候此任务才会进入主线程执行。

宏任务和微任务
同步任务和异步任务的划分其实并不准确,准确的分类方式是宏任务(Macrotask)和微任务(Microtask)。

宏任务包括:
script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

微任务包括:
process.nextTick, Promise,
Object.observe(已废弃), MutationObserver(html5新特性)。

这种分类的执行方式就是,执行一个宏任务,过程中遇到微任务时,将其放到微任务的事件队列里,当前宏任务执行完成后,会查看微任务的事件队列,依次执行里面的微任务。如果还有宏任务的话,再重新开启宏任务……
在这里插入图片描述

调用栈工作流程
举个🌰

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  bar()
  baz()
}

foo()

此代码会如预期地打印:

foo
bar
baz

当运行此代码时,会首先调用 foo()。 在 foo() 内部,会首先调用 bar(),然后调用 baz()。

此时,调用堆栈如下所示:
在这里插入图片描述
每次迭代中的事件循环都会查看调用堆栈中是否有东西并执行它直到调用堆栈为空:
在这里插入图片描述

入队函数执行

上面的示例看起来很正常,没有什么特别的:JavaScript 查找要执行的东西,并按顺序运行它们。

让我们看看如何将函数推迟直到堆栈被清空。

setTimeout(() => {}, 0) 的用例是调用一个函数,但是是在代码中的每个其他函数已被执行之后。

举个例子:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  baz()
}

foo()

该代码会打印:

foo
baz
bar

当运行此代码时,会首先调用 foo()。 在 foo() 内部,会首先调用 setTimeout,将 bar 作为参数传入,并传入 0 作为定时器指示它尽快运行。 然后调用 baz()。

此时,调用堆栈如下所示:
在这里插入图片描述
这是程序中所有函数的执行顺序:
在这里插入图片描述
为什么会这样呢?

消息队列

当调用 setTimeout() 时,浏览器或 Node.js 会启动定时器。 当定时器到期时(在此示例中会立即到期,因为将超时值设为 0),则回调函数会被放入“消息队列”中。

在消息队列中,用户触发的事件(如单击或键盘事件、或获取响应)也会在此排队,然后代码才有机会对其作出反应。 类似 onLoad 这样的 DOM 事件也如此。

事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西。

我们不必等待诸如 setTimeout、fetch、或其他的函数来完成它们自身的工作,因为它们是由浏览器提供的,并且位于它们自身的线程中。 例如,如果将 setTimeout 的超时设置为 2 秒,但不必等待 2 秒,等待发生在其他地方。

ES6 作业队列(微事件)

ECMAScript 2015 引入了作业队列的概念,Promise 使用了该队列(也在 ES6/ES2015 中引入)。 这种方式会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾。

在当前函数结束之前 resolve 的 Promise 会在当前函数之后被立即执行。

有个游乐园中过山车的比喻很好:消息队列将你排在队列的后面(在所有其他人的后面),你不得不等待你的回合,而工作队列则是快速通道票,这样你就可以在完成上一次乘车后立即乘坐另一趟车。

示例:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  new Promise((resolve, reject) =>{
  		console.log('foo后')
    	resolve('应该在 baz 之后、bar 之前')
    }).then(resolve => console.log(resolve))
  baz()
}

foo()

这会打印:

foo
baz
应该在 baz 之后、bar 之前
bar

这是 Promise(以及基于 promise 构建的 async/await)与通过 setTimeout() 或其他平台 API 的普通的旧异步函数之间的巨大区别。

推荐读:
如何解释Event Loop面试官才满意
Node.js 事件循环

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值