手动触发点击事件和JavaScript函数式触发点击事件的区别

在寻找这个问题的答案之前,首先得掌握JavaScript的事件循环机制(Event Loop)相关知识。

JavaScript是一门单线程的编程语言,因为其作为浏览器脚本语言,主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如:假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程。因此JavaScript中的所有任务都需要排队依次完成,但这样的设计存在一个问题:如果碰到一个需要耗费很多时间才完成的事件,就可能会造成线程的阻塞。

JavaScript的开发者为解决这个问题将所有的任务分为两种:
① 同步任务:没有被引擎挂起,在主线程中排队执行的任务,它们需要按顺序执行,只有上一个任务执行完成后,下一个任务才会开始执行。
② 异步任务:被引擎挂起,不进入主线程,而进入"任务队列"(task queue)的任务,只有引擎认为某个异步任务可以执行了(主线程的同步任务全部执行完毕),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。

异步任务又分为宏任务(macro-task)与微任务(micro-task)。
常见的宏任务:script(整块代码)、setTimeout、setInterval、用户交互(事件)、DOM操作、网络任务(ajax)、setImmediate(node环境)…
常见的微任务:new promise().then(回调)、MutationObserver(html5新特性,用来监听DOM节点变化的)、process.nextTick(node环境的异步操作)…
注:Promise并不是完全的同步,在Promise中的代码是同步任务,执行resolve或者reject回调的时候,此时是异步操作,会先将then/catch放到异步任务中的微任务队列。若同时存在promise和nextTick,则先执行nextTick。

执行过程:
1、先执行script(宏任务)中所有的同步任务,遇到微任务,就将它添加到微任务的任务队列中,遇到宏任务,就将它添加到宏任务的任务队列中;
2、当同步任务执行完毕后,就会被弹出执行栈,然后会从任务队列中按照先进先出的规则依次从任务队列中取出当前宏任务内包含的微任务执行;
3、当当前宏任务内的微任务也全部执行完毕后,取出任务队列中的下一个宏任务入栈继续执行,重复以上步骤,直到任务队列为空则停止循环。

以上便是JavaScript的事件循环机制(Event Loop)相关知识,将其掌握后便回归本篇博客的主题(写着写着差点忘了正事。。。)

先上代码和运行结果(vue代码,假设当前页面存在一个类名为btn的按钮):

		mounted() {
			const btn = document.querySelector('.btn');
			btn.addEventListener('click', () => {
				Promise.resolve().then(() => {
					console.log('微任务1')
				})
				console.log('1')
			})
			btn.addEventListener('click', () => {
				Promise.resolve().then(() => {
					console.log('微任务2')
				})
				console.log('2')
			})
			btn.click();
		}

1、当程序运行起来后(仅仅是运行,我并没有点击该按钮),运行结果:
在这里插入图片描述
2、当我点击按钮后:
在这里插入图片描述
由运行结果可以看出,手动触发点击事件和JavaScript函数式触发点击事件是有区别的。
为什么会有不同的运行结果呢?

原因:
1、程序刚运行起来的时候,执行的是script这一个宏任务,当执行到btn.click()时,因为是JavaScript调用的所以属于script中的同步任务(或者说btn.click() 会导致事件同步调度,因此调用btn.click()的script仍然在执行栈中)。当第一个click任务入栈的时候script任务并没有出栈,此时执行栈中有两个任务script和click。执行第一个点击事件,将Promise.resolve().then()放入微任务队列中,打印 “1” ,第一个点击事件click任务执行完毕,出栈。然后本应该执行此次点击事件的微任务队列的,但是此时执行栈中的script任务并没有执行完成,微任务队列继续排队。当执行栈为空时会立即执行微任务,但是当执行栈不为空时,微任务就会继续排队。 第二个点击事件被触发入栈,将Promise.resolve().then()放入微任务队列中,打印 “2” ,第二个点击事件click任务执行完毕出栈,script的任务执行完毕出栈。此时执行栈为空,开始根据 先进先出 的规则执行微任务队列,打印 “微任务1”、“微任务2”,故打印顺序为:1、2、微任务1、微任务2。

2、在程序运行完毕后(即“1、2、微任务1、微任务2”打印结束后)到我手动点击按钮前,这段时间间隔内执行栈是为空的,当我手动点击按钮后,第一个click任务入栈,此时执行栈中只有当前这一个任务,执行第一个点击事件,将Promise.resolve().then()放入微任务队列中,打印 “1” ,第一个点击事件click任务执行完毕,出栈。此时执行栈为空会立即执行微任务,打印 “微任务1”;第二个click任务入栈,此时执行栈中只有当前这一个任务,执行第二个点击事件,将Promise.resolve().then()放入微任务队列中,打印 “2” ,第二个点击事件click任务执行完毕,出栈。此时执行栈为空会立即执行微任务,打印 “微任务2”;故打印顺序为:1、微任务1、2、微任务2。

总结:
手动触发点击事件和JavaScript函数式触发点击事件的区别就是在执行的时候执行栈是否为空, 当执行栈为空时会立即执行微任务,但是当执行栈不为空时,微任务就会继续排队 。JavaScript函数式触发点击事件时,执行栈会多出script这一宏任务,而手动触发点击事件时,执行栈为空。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值