JS 读代�? 读懂面试的代码,才能读懂工作中的代码�? ::: tip 如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务 �?专业、系统、高效、全流程 准备前端面试 :::
JS 编译 以下代码,执行结果是什么?
1 2 3 var func = 1 function func ( ) {}console .log (func + func)
答案
::: details
这题考察�?GO,也就是全局的预编译�?
创建 GO 对象
找变量声明,将变量声明作�?key,值赋�?undefined
找函数声明,将函数名作为 GO 对象的key,值赋为函数体
编译阶段:创�?GO 对象后,func 作为 key,值为 undefined,然�?func 变成�?函数体,所以在编译结束时,func 还是一�?function
运行阶段:func 被赋值为 1,所�?func + func 就是 2
:::
JS 引用类型 以下代码,执行结果是什么?
1 2 3 4 5 let obj1 = { x : 1 }let obj2 = obj1obj2.y = 2 obj2 = { y : 20 } console .log ('obj1' , obj1)
答案
::: details
ECMAScript 变量可能包含两种不同类型的值:基本类型值和引用类型值�? 基本类型包括:Undefined、Null、Boolean、Number、String、Symbol 引用类型包括:Object、Array
引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 并不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象�? 换言之,变量实际保存的是一个指针,这个指针指向放在内存中的对象
当运�?let obj2 = obj1; 的时候,实际上是复制了一份指针,而不是复制了一份对象�?
所�?obj2.y = 2 能够修改对象的值,obj1 能够访问修改后的对象�? 运行 obj2 = { y: 20 }; 时,只是�?obj2 指向了新的对象,obj1 还是指向原来的对象�?:::
JS parseInt 以下代码,执行结果是什么?
1 ;['1' , '2' , '3' ].map (parseInt )
答案
::: details
查看 MDN 数组�?map 方法,它的参数是一个函数,函数的参数是 (element, index, array),element 是数组的元素,index 是元素的索引,array 是数组本身�? 查看 MDN �?parseInt 函数,它的参数是 (string, radix),string 是要解析的字符串,radix �?2-36 之间的整数,表示被解析字符串的基数�? 所以当遍历的时候,实际效果是:
parseInt(‘1’, 0) // radix 假如指定 0 或未指定,基数将会根据字符串的值进行推算。所以结果为 1�? parseInt(‘2’, 1) // radix 应该�?2-36 之间的整数,此时�?1,无法解析,返回 NaN
parseInt(‘3’, 2) // radix �?2,表�?2 进制,注意这里不是指将其解析�?2 进制,而是按照 2 进制进行解析,但 3 不是 2 进制里的数字,所以无法解析,返回 NaN :::
JS this 1 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 const User = { count : 1 , getCount : function ( ) { return this .count }, } console .log ('a ' , User .getCount ()) const func = User .getCount console .log ('b' , func ())
答案
::: details
本题考察 this 的指向�? this 是一个指向对象的指针,this 的指向与所在方法的调用位置有关,而与方法的声明位置无关�? 作为方法调用时,this 指向调用它所在方法的对象;所�?a �?1
作为函数调用时,this 指向 window。所�?b �?undefined.
:::
JS this 2 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const obj = { f1 ( ) { const fn = ( ) => { console .log ('this1' , this ) } fn () fn.call (window ) }, f2 : () => { function fn ( ) { console .log ('this2' , this ) } fn () fn.call (this ) }, } obj.f1 () obj.f2 ()
答案
::: details
1 2 this1 obj �?this1 obj this2 window(严格模式下�?undefined�?�?this2 window
箭头函数没有自己�?this 对象
对于普通函数来说,内部�?this 指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己�?this 对象,内部的 this 就是定义时上层作用域中的 this。也就是说,箭头函数内部�?this 指向是固定的,相比之下,普通函数的 this 指向是可变的,比如通过 call 来改变�? obj.f1() 时,fn 是箭头函数,内部�?this 是定义时上层作用域中�?this,也就是 obj。箭头函数修改不�?this,所�?fn.call(window) 不会修改 this 指向�? obj.f2() 时,fn 是普通函数,�?f2 是箭头函数,如果 f2 是普通函数,该方法内部的 this 指向 obj,但是写成箭头函数,this 指向全局对象,这是因为对象不构成单独的作用域,导致箭头函数定义时的作用域就是全局作用域。所以都�?windows(在非严格模式下)�? :::
JS 自由变量 1 以下代码,执行结果是什么?
1 2 3 4 5 6 let ifor (i = 1 ; i <= 3 ; i++) { setTimeout (function ( ) { console .log (i) }, 0 ) }
答案
::: details
i 是全局变量,用来控制循环。循环调用了 3 �?setTimeout 延迟执行。当 setTimeout 执行的时候,i 已经变成�?4�? 此外查看 MDN setTimeout ,第二个参数�?delay,表示定时器在执行指定的函数或代码之前应该等待的时间,单位是毫秒。如果省略该参数,则使用�?0,意味着“立即”执行,或者更准确地说,在下一个事件循环执行。所以虽然数值设置为 0,但依然是异步行为�?:::
JS 自由变量 2 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let n = 10 function f1 ( ) { n++ function f2 ( ) { function f3 ( ) { n++ } let n = 20 f3 () n++ } f2 () n++ } f1 ()console .log ('n' , n)
答案
::: details
let 声明�?JavaScript 新增了块级作用域。所�?f2 内部�?n �?f2 内部的变量,不会影响外层 n 的变化�? 关于 let 声明可以参�?《ECMAScript 6 入门》let �?const 命令 :::
JS 闭包 1 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 10 const n = 10 function print ( ) { console .log (n) } function f1 (fn ) { const n = 20 fn () } f1 (print)
答案
::: details
JavaScript 采用词法作用�?lexical scoping),也就是静态作用域。换句话说,说函数的作用域在函数定义的时候就决定了�? 所以当调用 print 的时候,它会根据定义的位置向外查找变量,也就�?n = 10�? 关于词法作用域,参�?《JavaScript 深入之词法作用域和动态作用域》 :::
JS 闭包 2 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 function fn ( ) { let num = 10 return { set : (n ) => (num = n), get : () => num, } } let num = 20 const { get, set } = fn ()console .log ('result1: ' , get ())set (100 )console .log ('result2: ' , num)
答案
::: details
由于 JavaScript 的闭包特性,函数内部的变量可以持续存在�? 所以当调用 get() 的时候,可以访问�?fn 函数作用域中�?num,所�?result1 输出 10�? set(100)修改的是 fn 函数作用域中�?num,而不是全局�?num�? 所以全局�?num 值,仍然�?20�?:::
JS Promise 1 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 const promise = new Promise ((resolve, reject ) => { console .log (1 ) console .log (2 ) }) promise.then (() => { console .log (3 ) }) console .log (4 )
答案
::: details
当创建新�?Promise 时,executor 函数会立即执行,所以打印了 1 �?2�? promise.then() 注册了一个回调函数,但因�?executor 函数没有调用 resolve �?reject,所�?Promise 永远处于 pending 状态,回调函数不会被执行,所以不会打�?3�? 最后执�?console.log(4),打�?4�? :::
JS Promise 2 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 const promise = new Promise ((resolve, reject ) => { console .log (1 ) setTimeout (() => { console .log ('timerStart' ) resolve ('success' ) console .log ('timerEnd' ) }, 0 ) console .log (2 ) }) promise.then ((res ) => { console .log (res) }) console .log (4 )
答案
::: details
1 2 3 4 5 6 1 2 4 timerStart timerEnd success
当创建新�?Promise 时,executor 函数会立即执行,所以打印了 1 �?2�? 即使 setTimeout 的延时设置为 0,它的回调函数依然是异步执行的,会被放入宏任务队列,要等到当前所有同步代码执行完毕后才会执行�? promise.then() 注册了一个回调函数,
按照代码顺序执行 console.log(4),打�?4�? setTimeout 回调开始执行,打印 timerStart。此时调�?resolve 函数,但 Promise.then 回调也是异步的,所以不会立刻调�?then 函数,而是放入微任务队列。代码继续执行,打印 timerEnd�? 最后开始执行微任务,调�?then 函数,打�?success�? :::
JS 异步执行顺序 1 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 console .log ('start' )setTimeout (() => { console .log ('a' ) Promise .resolve ().then (() => { console .log ('c' ) }) }) Promise .resolve ().then (() => { console .log ('b' ) setTimeout (() => { console .log ('d' ) }) }) console .log ('end' )
答案
::: details
本题涉及同步代码、微任务、宏任务的执行顺序�? 首先执行同步代码,打�?start �?end�? setTimeout 回调函数会被放入宏任务队列,等待同步代码执行完毕后执行�? Promise.resolve().then() 注册了一个回调函数,会被放入微任务队列�? 因为微任务优先级高于宏任务,所�?then 回调函数会首先执行,打印 b。回调函数调用了 setTimeout,将函数放入宏任务队列�? 然后执行之前的宏任务,打�?a。然后又注册了一�?then 回调函数,会被放入微任务队列�? 因为微任务优先级高于宏任务,所�?then 回调函数会首先执行,打印 c�? 最后执行宏任务,打�?d�? :::
JS 异步执行顺序 2 以下代码,执行结果是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Promise .resolve () .then (() => { console .log (0 ) return Promise .resolve (4 ) }) .then ((res ) => { console .log (res) }) Promise .resolve () .then (() => { console .log (1 ) }) .then (() => { console .log (2 ) }) .then (() => { console .log (3 ) }) .then (() => { console .log (5 ) }) .then (() => { console .log (6 ) })
答案
::: details
初始状态:
两个 Promise.resolve() 创建两个立即 resolved �?Promise
它们�?.then 回调都被加入到第一轮微任务队列
第一轮微任务�? - 执行第一个链的第一�?then:打�?0,返�?Promise.resolve(4)
第二轮微任务�? - 第二个链的第二个 then 执行:打�?2
第一个链的第二个 then 暂时不执行,因为它在等待 Promise.resolve(4) 的解�?4. 第三轮微任务�?
第二个链的第三个 then 执行:打�?3
第四轮微任务�?
Promise.resolve(4) 完成解析
第一个链的第二个 then 执行:打�?4
第二个链的第四个 then 执行:打�?5
第五轮微任务�? - 第二个链的最后一�?then 执行:打�?6
这道题的难点在于为什�?4 �?3 之后打印,延迟了 2 个微任务才执行�? 这是因为等待 Promise.resolve(4) 的解析需要一个微任务(这期间打印�?2),resolve 过程中发现是 Promise(准确的说是 thenable),V8 会进行一个不同处理,将其入列一个新任务,这期间打印�?3,然后在第四轮微任务中,第一�?Promise 打印 4,第 2 �?Promise 打印 5�? 拓展阅读:promise.then �?return Promise.resolve 后,发生了什么? :::