动画:《大前端吊打面试官系列》 之原生 JavaScript 精华篇( 八 )


面试官:JS 为什么是单线程?又带来了哪些问题呢?
JS 单线程的特点就是同一时刻只能执行一个任 。这是由一些与用户的互动以及操作 DOM 等相关的操作决定了 JS 要使用单线程,否则使用多线程会带来复杂的同步问题 。如果执行同步问题的话,多线程需要加锁,执行任务造成非常的繁琐 。
虽然 HTML5 标准规定,允许脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM 。
上述开头我们也说到了,单线程带来的问题就是会导致阻塞问题,为了解决这个问题,就不得不涉及 JS 的两种任务,分别为同步任务和异步任务 。
面试官:JS 如何实现异步编程?
最早的解决方案是使用回调函数,回调函数不是直接调用,而是在特定的事件或条件发生时另一方调用的,用于对该事件或条件进行响应 。比如 Ajax 回调:
// jQuery 中的 ajax$.ajax({ type : "post", url : 'test.json', dataType : 'json', success : function(res) { // 响应成功回调},fail: function(err){// 响应失败回调}});
但是如果某个请求存在依赖性,如下:
$.ajax({type:"post",success: function(res){//成功回调//再次异步请求$.ajax({type:"post",url:"...?id=res.id,success:function(res){$.ajax({type:"post",url:"...?id=res.id,success:function(){// 往复循环}})}})}})
就会形成不断的循环嵌套,我们称之为回调地狱 。我们可以看出回调地狱有以下缺点:
以上有两个地方俺需要再进一步详细说明一下:
其实这跟 js 的运行机制相关,异步任务执行完成会加入任务队列,当执行栈中没有可执行任务了,主线程取出任务队列中的异步任务并入栈执行,当异步任务执行的时候,捕获异常的函数已经在执行栈内退出了,所以异常无法被捕获 。
只能终止回调的函数的执行,而不能终止外部代码的执行 。
面试官:如何解决回调地狱问题呢?
既然回调函数存在回调地狱问题,那我们如何解决呢?ES6 给我们提供了三种解决方案,分别是 、、async/await(ES7) 。
由于这部分涉及到 ES6 部分的知识,这一期是有关 JS 的,所以会在下一期进行延伸,这里不多涉及 。
【留下一个传送门~】
面试官:说说异步代码的执行顺序?Event Loop 的运行机制是如何的运行的?
上边我们说到 JS 是单线程且使用同步和异步任务解决 JS 的阻塞问题,那么异步代码的执行顺序以及是如何运作的呢?
在深入事件循环机制之前,需要弄懂一下几个概念:
执行上下文
执行上下文是一个抽象的概念,可以理解为是代码执行的一个环境 。JS 的执行上下文分为三种,全局执行上下文、函数(局部)执行上下文、Eval 执行上下文 。
执行栈
执行栈,就是我们数据结构中的“栈”,它具有“先进后出”的特点,正是因为这种特点,在我们代码进行执行的时候,遇到一个执行上下文就将其依次压入执行栈中 。
当代码执行的时候,先执行位于栈顶的执行上下文中的代码,当栈顶的执行上下文代码执行完毕就会出栈,继续执行下一个位于栈顶的执行上下文 。
function foo() {console.log('a');bar();console.log('b');}function bar() {console.log('c');}foo();
宏任务
对于宏任务一般包括:
微任务
对于微任务一般包括:
注意: 队列会比队列先执行 。
以上概念弄明白之后,再来看循环机制是如何运行的呢?以下涉及到的任务执行顺序都是靠函数调用栈来实现的 。