面了某大厂同一个部门两个不同的岗位,第一个岗位三面挂了。第二个岗位估计二面挂了。一共是五轮技术面,以下是本人总结的一些面试题答案解析和面试心得。
代码输出结果篇之浏览器事件循环机制&堆栈内存与闭包
事件循环机制
-
考察js事件循环机制,输出下面函数的执行结果:
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log( 'async2'); } console.log("script start"); setTimeout(function () { console.log("setTimeout"); },0); async1(); new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("promise2"); }); console.log('script end');这道题出现的频率非常高,感觉基本上是面试必问的题。我一共参与了五轮技术面,被问了三次,还不懂的小伙伴,长点心把!
答案如图所示:
在哔哩哔哩看到一个图文并茂的解读事件循环机制的视频,传送门,小伙伴快去看吧!
然后我的解析:
1. 创建函数async1,压入栈中 2. 创建函数async2,压入栈中 3. 执行console.log("script start"), 打印出 “script start” 4. 调用setTimeOut函数,立即将console.log("setTimeout")语句加入任务队列中 5. 执行async1函数console.log("async1 start");语句,打印出:async1 start 6. 执行async2函数,打印出async2,async2函数被释放 7. wait 后面的语句相当于promise中的.then,属于微任务,因此console.log('async1 end') 加入微任务队列中。async1执行完毕,被释放 8. 执行new promise,打印出promise1,然后将.then中的函数加入微任务队列中 9. 执行console.log('script end')。打印出script end。支持,执行栈已经被清空,接下来执行微任务队列 10. 打印async1 end 11. 打印出promise2 12. 至此,微任务队列被清空,执行宏任务队列,打印 setTimeout事件循环机制的总结:当前执行栈执行完毕时,会处理完所有的微任务,再去宏任务队列里取出一个事件执行。同一次事件循环中,微任务永远在宏任务之前执行
-
那么,新的题来了(本题是我想到的题),输出下面代码的执行结果:
setTimeout(function () { console.log("setTimeout1"); new Promise(function (resolve) { console.log("promise1-1"); resolve(); }).then(function () { console.log("promise1-2"); }); },0); setTimeout(function () { console.log("setTimeout2"); new Promise(function (resolve) { console.log("promise2-1"); resolve(); }).then(function () { console.log("promise2-2"); }); },0);在浏览器中和node版本v12+的执行结果:
setTimeout1
promise1-1
promise1-2
setTimeout2
promise2-1
promise2-2
但是在node版本v8.9.3 中的结果:
setTimeout1
promise1-1
setTimeout2
promise1-2
promise2-1
promise2-2
由此可见,在node版本12之前,它会在执行宏任务队列时,先把宏任务队列执行完毕,再去执行后面加入的微任务。但是在node12以后的版本,和Chrome浏览器中,一旦产生微任务,就会先把微任务执行完,再去执行宏任务。
-
那么继续来吧,本题是面试中遇到的原题:
setTimeout(function () { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for (var i = 0; i < 10000; i++) { i == 9999 && resolve(); } console.log(3); }).then(function () { console.log(4); }); console.log(5);输出的顺序是:23541。和第一道比其实简单很多,唯一有点歧义的就是在 i== 9999的时候,才执行resolve,但是这里都是同步任务,因此并不会造成什么影响。
朋友们,宏任务与微任务,你懂了吗?
堆内存与闭包
-
考察函数作用域和闭包,输出下面函数的执行结果:
var result = []; var a = 3; var total = 0; function foo(a) { var i = 0; for (; i < 3; i++) { result[i] = function() { total += i * a; console.log(total); } } } foo(1); result[0](); result[1](); result[2]();下面是我的解析:
-
变量提升:声明result、a、total 变量,并创建一个堆内存,存储函数foo;
-
在堆内存中创建一个空数组,并将地址指向result变量
-
在执行上下文中创建值3,将其赋给a;在执行上下文中创建值0,将其赋给total;
-
执行foo(1);
创建一个函数作用域foo;
- 变量提升,定义i;传入参数a= 1;创建值0,将值赋给i;
- 执行for循环,i为0时,将匿名函数function() {total += i * a;console.log(total);}赋值给result[0],然后i自增,此时i为1
- i为1,将匿名函数function() {total += i * a;console.log(total);}赋值给result[1],然后i自增,此时i为2
- i为2,将匿名函数function() {total += i * a;console.log(total);}赋值给result[2],然后i自增,此时i为3。不满足循环条件,跳出循环
-
由于函数foo中的变量被赋给result中每项的匿名函数引用了,所以不能被释放。这样就形成了一个闭包。此时函数中的值:a为1,i为3;
-
执行result[0]:
- total 未在当前函数内定义,向上一个作用域找,此时为全局的total,值为0;
- i为闭包的foo中的i变量,为3;
- a为闭包的foo中传过来的值,为1;
- 因此 total = 0 + 3*1 = 3,打印出3
-
执行result[1]:
- total 为全局变量total值为3,
- i和a同上,为3和1
- total = 3 + 1*3 = 6
- 打印出6
-
执行result[2]:
- total 为全局变量total值为6,
- i和a同上,为3和1
- total = 6 + 1*3 = 9
- 打印出9
朋友们,你们答对了吗?关于js运行堆栈内存和闭包这一块儿,推荐以下周老师的视频讲解,传送门,看完感觉理解了好多,希望对大家有帮助。
-
-
继续,还是原题:
window.name = 'aaa'; function A() { this.name = 123; } A.prototype.getA = function () { return this.name + 1; } let a = new A(); let funcA = a.getA; console.log(funcA());我的解析:
- 老规矩,变量提升创建函数A
- 创建字符串值'aaa',将这个值赋给window的name属性
- 创建匿名函数1,将其地址赋给函数A的原型上的getA属性
- 执行函数A,创建一个空对象aaa(我随便起的名字),将函数中的this指向这个空对象,this.a = 123,没有设置返回值,所以new A() 最后返回这个对象,也就是将对象aaa的地址,赋给了块级变量a;
- 创建变量funcA,因为a中没有getA属性,因此会去它的原型链上找,最后找到A的原型链上的getA,也就是匿名函数1的地址赋给funcA
- 执行funcA,就是执行匿名函数1:return this.name + 1; 在全局中执行,此时this指向window,所以最后打印出来的结果是:aaa1
-
原题again:
// 下面代码输出的结果是什么? var length = 10; function fn() { return this.length+1; } var obj = { length: 5, test1: function() { return fn(); } }; obj.test2=fn; //下面代码输出是什么 console.log(obj.test1()) console.log(fn()===obj.test2())解析:
- 变量提升,声明变量length,函数fn,变量obj
- 创建值10,将其赋给变量length;
- 在堆内存中创建一个对象,属性length值为5,属性test1为一个匿名函数1;将这个对象的地址aaa赋给变量obj
- 给obj新增一个test2的属性,且将函数fn的地址指给它
- 执行obj.test1(),返回函数fn()的执行结果,fn执行时,返回this.length + 1 这里的this指向全局,因此这里打印的是 10 + 1 = 11,打印11;
- 执行函数fn() 和函数obj.test2(),并比较它们的值。执行函数fn,这里的this指向全局,因此是10+1 = 11;执行函数obj.test2(),返回this.length + 1,但是此时的this指向调用方法的主体obj,因此这里length的值为6.打印结果为false。
好了。本次真题分享+解析到此就结束了,希望对大家有所帮助。 喜欢的点个关注吧!我还会继续更新的~