一道Javascript面试题引发的血案
文章首发于szhshp的第三边境研究所,转载请注明
先来看几道面试题,公司的开发们都尝试做了一下,然而基本没有人能够全部答对。
覆盖的考点很多,也有一些难度,题目挺有意思建议手动执行一边玩玩。
Question 1
for (var i = 0; i <5 ; i++) {
setTimeout(function(){
console.log(i)
),1000}
}
console.log(i)
- Q:这道题目会输出什么?
- A:这道题目还比较简单,如果对Javascript稍微有一点深入的同学都会发现这道题目循环里面出现了闭包,因此输出的数字是完全相同的,最后的输出也是完全相同的。
- 考点:闭包,(伪)异步
Question 2
for (let i = 0; i <5 ; i++) { //注意var变成了let
setTimeout(function(){
console.log(i)
},1000)
}
console.log(i)
- Q:这道题目会输出什么?
A:这道题目其实是个坑。首先题目与Q1的区别就是变量i的定义改为了关键字let,使用let的时候会将变量限制在循环之中,因此第二个输出其实会报错。另外setTimeout实现了(伪)异步,同时因为let将变量作用域进行了控制,破坏了闭包结构,因此会按照正常顺序输出。
关于let关键字[^3]
Use the let statement to declare a variable, the scope of which is restricted to the block in which it is declared. You can assign values to the variables when you declare them or later in your script.
A variable declared using let cannot be used before its declaration or an error will result..考点:闭包,(伪)异步,作用域
Question 3
同样是Q1的代码
for (var i = 0; i <5 ; i++) { //DO NOT MODIFY
setTimeout(function(){ //DO NOT MODIFY
console.log(i)
},1000)
}
console.log(i) //DO NOT MODIFY
- Q:修改上述代码(部分行不允许修改,可以在代码间插入),以实现“每隔一秒输出一个数字并且顺序为0-5”
A
- 首先考到了破坏闭包结构,破坏闭包的方法很多,最简单的是将跨域变量转换成范围内的变量
其次考到了setTimeout事件队列的处理
for (var i = 0; i <5 ; i++) {
(function(i){
setTimeout(function(){
console.log(i)
},1000*i)
})(i) //将i作为参数传入匿名函数,如此破坏了闭包内跨域访问
}
setTimeout(function (){
console.log(i);
}, 5000); //强行将5放到5sec后输出
考点:闭包,(伪)异步,作用域,事件队列
Question 4
window.setTimeout(function (){
console.log(2)
},1); //Ouput for a long time
for (var i = 0; i < 1000; i++) {
console.log('');
}; console.log(1) window.setTimeout(function (){
console.log(3)
},0);
- Q:这道题目会输出什么?
- A:可能有些同学会记得,setTimeout是一个回调函数,因此无论延时多少结果都是最后输出。
- 考点:(伪)异步,事件队列
Question 5
这道题目其实是其他地方抄袭来的[^2],正好和之前考点有一定重叠因此一起放了过来:
setTimeout(function(){console.log(4)},0);
new Promise(function(resolve){
console.log(1) //time consuming ops
for( var i=0 ; i<10000 ; i++ ){
i==9999 && resolve();
} console.log(2)
}).then(function(){
console.log(5)
});
console.log(3);
- Q:这道题目会输出什么?
A:输出是12354
关于这个输出,有如下几个逻辑:
- 4是setTimeOut.callback的输出,加入MacroTask末端,
- 输出1
- 执行Promise.resolve()将输出5的callback放到MicroTask中(注意这里不是MacroTask)
- 输出2
- 输出3
- MacroTask首个任务执行完毕
- 查找MicroTask里面有没有任务,发现有,执行,输出5
- 查找MacroTask里面有没有任务,发现有,执行,输出4
- 查找MicroTask里面有没有任务,发现没有,可以休息了
- 查找MacroTask里面有没有任务,发现没有,可以睡觉了
- 执行完毕
关于事件循环/关于macrotask和microtask[^1]
简介
一个事件循环(EventLoop)中会有一个正在执行的任务(Task),而这个任务就是从 macrotask 队列中来的。在whatwg规范中有 queue 就是任务队列。当这个 macrotask 执行结束后所有可用的 microtask 将会在同一个事件循环中执行,当这些 microtask 执行结束后还能继续添加 microtask 一直到真个 microtask 队列执行结束。
怎么用
基本来说,当我们想以同步的方式来处理异步任务时候就用 microtask(比如我们需要直接在某段代码后就去执行某个任务,就像Promise一样)。
其他情况就直接用 macrotask。
两者的具体实现
- macrotasks: setTimeout setInterval setImmediate I/O UI渲染
- microtasks: Promise process.nextTick Object.observe MutationObserver
从规范中理解
规范:https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
- 一个事件循环(event loop)会有一个或多个任务队列(task queue) task queue 就是 macrotask queue
- 每一个 event loop 都有一个 microtask queue
- task queue == macrotask queue != microtask queue
- 一个任务 task 可以放入 macrotask queue 也可以放入 microtask queue 中
- 当一个 task 被放入队列 queue(macro或micro) 那这个 task 就可以被立即执行了
再来回顾下事件循环如何执行一个任务的流程
当执行栈(call stack)为空的时候,开始依次执行:
- 把最早的任务(task A)放入任务队列
- 如果 task A 为null (那任务队列就是空),直接跳到第6步
- 将 currently running task 设置为 task A
- 执行 task A (也就是执行回调函数)
- 将 currently running task 设置为 null 并移出 task A
- 执行 microtask 队列
- 在 microtask 中选出最早的任务 task X
- 如果 task X 为null (那 microtask 队列就是空),直接跳到 g
- 将 currently running task 设置为 task X
- 执行 task X
- 将 currently running task 设置为 null 并移出 task X
- 在 microtask 中选出最早的任务 , 跳到 b
- 结束 microtask 队列
- 跳到第一步
上面就算是一个简单的 event-loop 执行模型
再简单点可以总结为:
- 在 macrotask 队列中执行最早的那个 task ,然后移出
- 执行 microtask 队列中所有可用的任务,然后移出
- 下一个循环,执行下一个 macrotask 中的任务 (再跳到第2步)
其他
- 当一个task(在 macrotask 队列中)正处于执行状态,也可能会有新的事件被注册,那就会有新的 task 被创建。比如下面两个
1. promiseA.then() 的回调就是一个 task
1. promiseA 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
1. promiseA 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
1. setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况 - microtask queue 中的 task 会在事件循环的当前回合中执行,因此 macrotask queue 中的 task 就只能等到事件循环的下一个回合中执行了
- click ajax setTimeout 的回调是都是 task, 同时,包裹在一个 script 标签中的js代码也是一个 task 确切说是 macrotask。
参考文献
[^3]: let 语句 (JavaScript).aspx)
[^2]: https://www.zhihu.com/question/36972010
[^1]: https://github.com/ccforward/cc/issues/48
一道Javascript面试题引发的血案的更多相关文章
- 一道python面试题引发的血案
这里说的是一道阿里校招的面试题:一行代码实现对列表a中的偶数位置的元素进行加3后求和? 今天去面试同样遇到了这个题目,这道题考察的是对python高阶函数map/filter的灵活运用(具体的使用方法 ...
- 一道JS面试题引发的血案
刚入职新公司,属于公司萌新一枚,一天下午对着屏幕看代码架构时. BI项目组长给我看了一道面试别人的JS面试题. 虽然答对了,但把理由说错了,照样不及格. 话不多说,直接上题: var a = 1; s ...
- 一道 JavaScript 面试题
有一道 JavaScript 面试题. f = function () { return true; }; g = function () { return false; }; (function() ...
- 一道试题引发的血案 int *ptr2=(int *)((int)a+1);
某日,看到一道比较恶心的C语言的试题,考了很多比较绕的知识点,嘴脸如下: int main(void) { int a[4] = {1, 2, 3, 4}; int *ptr1=(int *)(&am ...
- 学生问的一道javascript面试题[来自腾讯]
function Parent() { this.a = 1; this.b = [1, 2, this.a]; this.c = { demo: 5 }; this.show = function ...
- 一道Integer面试题引发的对Integer的探究
面试题: //在jdk1.5的环境下,有如下4条语句: Integer i01 = 59; int i02 = 59; Integer i03 =Integer.valueOf(59); Intege ...
- 一道javascript面试题
下面表达式比较的结果分别是什么? 1. []=="0" 2. []==0 3. "0"==0 4. []==false 5. []==[] 大家可以试试写下自己 ...
- 腾讯的一道JavaScript面试题
//题目:分别弹出什么内容? <!-- function test(){ this.a = 1; alert(this); //[object Window] } test(); var t = ...
- 一道javascript面试题(闭包与函数柯里化)
要求写一个函数add(),分别实现能如下效果: (1)console.log(add(1)(2)(3)(4)()); (2)console.log(add(1,2)(3,4)()); (3)conso ...
随机推荐
- [Asp.net core 2.0]Ueditor 图片上传
摘要 在项目中要用到富文本编辑器,包含上传图片,插入视频等功能.但ueditor只有.net版本,没有支持core.那么上传等接口就需要自己实现了. 一个例子 首先去百度ueditor官网下载简化版的 ...
- SVN的管理方式和git的管理方式
SVN是集中式的管理方式.大致流程如下: 1.从服务器上将整个项目代码检出到本地电脑硬盘中(一般来说,是从主分支上下载的代码).2.然后在svn服务器中建立新的开发分支,将硬盘中的代码提交到该开发分支 ...
- Unity3D实践系列11, 组件的添加和访问
当把一个脚本附加到一个GameObject上的时候,这个GameObject就有了脚本组件. 通过GameObject的属性获取组件 比如如下: [RequireComponent(typeof(Ri ...
- C#各种泛型集合体验
本篇体验除Queue<T>和Stack<T>之外的其它泛型集合. SortedList<TKey, TValue> SortedList<TKey, TVal ...
- datagrid在MVC中的运用01-基本属性并实现分页
本文体验jQuery EasyUI的datagrid在MVC中的应用.主要涉及到: ※ datagrid的基本属性和方法 ※ datagrid分页在前后台的实现 最终效果: 与视图显示对应的view ...
- uistatusBar 详解
成功的方法: 方法1.隐藏应用程序内所有的StatusBar 第一步:在Info.plist,然后添加一个新的row,"View controller-based status bar ap ...
- Swift - 多个mask的动画效果
Swift - 多个mask的动画效果 效果 源码 https://github.com/YouXianMing/Swift-Animations // // TranformFadeView.swi ...
- 《深入理解Android内核设计思想》
<深入理解Android内核设计思想> 基本信息 作者: 林学森 出版社:人民邮电出版社 ISBN:9787115348418 上架时间:2014-4-25 出版日期:2014 年5月 开 ...
- C# 关键字Event
1>>> 在委托实例前,不加event,那这个委托就是一个普通的委托,我们可以用别的函数调用,也可以直接用对象调用,但加上关键字之后,只能用成员函数调用 public delegat ...
- 架构模式数据源模式之:表数据入口(Table Data Gateway)、行数据入口(Row Data Gateway)、活动记录(Active Record)
一:表数据入口(Table Data Gateway) 表数据入口提供了用于访问单个表或者视图(也包含了联表查询)的所有SQL,通常一个表一个类.其它代码通过它来实现对数据库的交互.基于这个特点,表数 ...