事件循环 执行栈 异步 缺陷 原理 (详解) (精华帖)

事件循环

JS运行环境称为宿主环境。

JS运行的环境我们叫做数组环境,为神么我们要这样说呢是因为我们的目前的整个前端里面JS代码也就是ES标准代码它不仅能够运行在浏览器它还能够运行在服务器,比如说node.js,也可以运行在客户端,也可以运行在移动端,所以我们吧以前的那种刻板印象应该去掉了,他能够运行在各种环境里面,你只要给它配备了相关的js执行引擎,我们学的ES6其实是语言本身里面的东西,因为是语言本身的东西,而与原本身是可以运行在各种环境里面的。

我们学的循环,判断,像一些数组的基础操作,原型他是属于ES环境的,也就是语言本身的是去教你怎么说话的,而运行环境里面他说的话又有一些差异,但是他们的基础都是这个ES语言,而JS就像当于一个环境,浏览器环境,我们在浏览器环境里,我们把它称之为Javascript

js语言和ES语言实际上是有些差异的,只不过我们平常说习惯了他们是一个东西,但是你心里面要清楚这两个其实是两个东西,JavaScript他是ES里的一个超集,

在这里插入图片描述

浏览器端会给他们提供一个环境,这个环境里面不止包含了ES里面本身的规范而且还新加入了一些东西叫做WebAPI里面的dom操作,bom操作,setTimeout,dom + bom就是WebAPI,这就是浏览器为他提供的新的API,语法是没有变化的,所以浏览器端我们把他称为数组环境

而服务器端,我们把他称为node.js,他也是用得一些ES语言,循环判断等在node是完全一样的,只不过在node里面新增了一些对象,方法,所以叫做node api,我们只需要去学新增的方法就行了

执行栈(call stack)

这是一个内存空间,我们吧这块内存空间当做一个栈的话,它有点类似于一个数组,他跟那个数组有一点差异,他加入数据时始终把它加入到栈的末尾,当然这不可能是一个纯数据他有可能是一个函数有可能是任何一个东西

在这里插入图片描述

他加入数据时是不能穿插的一定得是加入到末尾,那么我们将最后插入的数据叫做栈顶,最先插入的叫做栈底,我们把往里面加入的这个过程叫做入栈,在我们的这个栈里面的数据不可能永远存在有时候我们需要吧这些数据扔掉,但是扔掉不能从中间扔掉要按照顺序从栈顶开始依次去除,我们管这种过程叫做出站(pop),入栈叫做(push),其实你看这不就跟数组的一样嘛。

执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。

JS引擎永远执行的是执行栈的最顶部。

异步

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码
  2. GUI线程:负责渲染页面
  3. 事件监听线程:负责监听各种事件
  4. 计时线程:负责计时
  5. 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
  • 微任务(队列):MutationObserver,Promise产生的回调进入微队列

MutationObserver用于监听某个DOM对象的变化

当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。

事件和回调函数的缺陷

我们习惯于使用传统的回调或事件处理来解决异步问题

事件:某个对象的属性是一个函数,当发生某一件事时,运行该函数

	dom.onclick = function(){
	
	}

回调:运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数。

	dom.addEventListener("click", function(){
	
	})

本质上,事件和回调并没有本质的区别,只是把函数放置的位置不同而已。

一直以来,该模式都运作良好。

直到前端工程越来越复杂…

目前,该模式主要面临以下两个问题:

  1. 回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套
  2. 异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增

我们来举个例子


	 <p>
        <button id="btn1">按钮1:给按钮2注册点击事件</button>
        <button id="btn2">按钮2:给按钮3注册点击事件</button>
        <button id="btn3">按钮3:点击后弹出hello</button>
    </p>
    <script>
        const btn1 = document.getElementById("btn1"),
            btn2 = document.getElementById("btn2"),
            btn3 = document.getElementById("btn3");
        btn1.addEventListener("click", function() {
            //按钮1的其他事情
            btn2.addEventListener("click", function() {
                //按钮2的其他事情
                btn3.addEventListener("click", function() {
                    alert("hello");
                })
            })
        })
    </script>

这样的话就会各种回调各种循环会很麻烦形成回调地狱,别看这是三个事件少,如果是100呢~
以上就是本篇博客的全部内容了,有什么问题欢迎评论,希望对您有用

发布了27 篇原创文章 · 获赞 5 · 访问量 2712

猜你喜欢

转载自blog.csdn.net/function_zzc/article/details/105324384
今日推荐