JS-reading
JS 读代�?
读懂面试的代码,才能读懂工作中的代码�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::
JS 编译
以下代码,执行结果是什么?
1 | var func = 1 |
答案
::: details
1 | 2 |
这题考察�?GO,也就是全局的预编译�?
- 创建 GO 对象
- 找变量声明,将变量声明作�?key,值赋�?undefined
- 找函数声明,将函数名作为 GO 对象的key,值赋为函数体
编译阶段:创�?GO 对象后,func 作为 key,值为 undefined,然�?func 变成�?函数体,所以在编译结束时,func 还是一�?function
运行阶段:func 被赋值为 1,所�?func + func 就是 2
:::
JS 引用类型
以下代码,执行结果是什么?
1 | let obj1 = { x: 1 } |
答案
::: details
1 | obj1 { x: 1, y: 2 } |
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
1 | [1, NaN, NaN] |
查看 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 | const User = { |
答案
::: details
1 | a 1 |
本题考察 this 的指向�?
this 是一个指向对象的指针,this 的指向与所在方法的调用位置有关,而与方法的声明位置无关�?
作为方法调用时,this 指向调用它所在方法的对象;所�?a �?1
作为函数调用时,this 指向 window。所�?b �?undefined.
:::
JS this 2
以下代码,执行结果是什么?
1 | const obj = { |
答案
::: details
1 | this1 obj �?this1 obj |
箭头函数没有自己�?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 | let i |
答案
::: details
1 | 4 4 4 |
i 是全局变量,用来控制循环。循环调用了 3 �?setTimeout 延迟执行。当 setTimeout 执行的时候,i 已经变成�?4�?
此外查看 MDN setTimeout,第二个参数�?delay,表示定时器在执行指定的函数或代码之前应该等待的时间,单位是毫秒。如果省略该参数,则使用�?0,意味着“立即”执行,或者更准确地说,在下一个事件循环执行。所以虽然数值设置为 0,但依然是异步行为�?:::
JS 自由变量 2
以下代码,执行结果是什么?
1 | let n = 10 |
答案
::: details
1 | n 12 |
let 声明�?JavaScript 新增了块级作用域。所�?f2 内部�?n �?f2 内部的变量,不会影响外层 n 的变化�?
关于 let 声明可以参�?《ECMAScript 6 入门》let �?const 命令
:::
JS 闭包 1
以下代码,执行结果是什么?
1 | const n = 10 |
答案
::: details
1 | 10 |
JavaScript 采用词法作用�?lexical scoping),也就是静态作用域。换句话说,说函数的作用域在函数定义的时候就决定了�?
所以当调用 print 的时候,它会根据定义的位置向外查找变量,也就�?n = 10�?
关于词法作用域,参�?《JavaScript 深入之词法作用域和动态作用域》
:::
JS 闭包 2
以下代码,执行结果是什么?
1 | function fn() { |
答案
::: details
1 | 10 20 |
由于 JavaScript 的闭包特性,函数内部的变量可以持续存在�?
所以当调用 get() 的时候,可以访问�?fn 函数作用域中�?num,所�?result1 输出 10�?
set(100)修改的是 fn 函数作用域中�?num,而不是全局�?num�?
所以全局�?num 值,仍然�?20�?:::
JS Promise 1
以下代码,执行结果是什么?
1 | const promise = new Promise((resolve, reject) => { |
答案
::: details
1 | 1 2 4 |
当创建新�?Promise 时,executor 函数会立即执行,所以打印了 1 �?2�?
promise.then() 注册了一个回调函数,但因�?executor 函数没有调用 resolve �?reject,所�?Promise 永远处于 pending 状态,回调函数不会被执行,所以不会打�?3�?
最后执�?console.log(4),打�?4�?
:::
JS Promise 2
以下代码,执行结果是什么?
1 | const promise = new Promise((resolve, reject) => { |
答案
::: details
1 | 1 |
当创建新�?Promise 时,executor 函数会立即执行,所以打印了 1 �?2�?
即使 setTimeout 的延时设置为 0,它的回调函数依然是异步执行的,会被放入宏任务队列,要等到当前所有同步代码执行完毕后才会执行�?
promise.then() 注册了一个回调函数,
按照代码顺序执行 console.log(4),打�?4�?
setTimeout 回调开始执行,打印 timerStart。此时调�?resolve 函数,但 Promise.then 回调也是异步的,所以不会立刻调�?then 函数,而是放入微任务队列。代码继续执行,打印 timerEnd�?
最后开始执行微任务,调�?then 函数,打�?success�?
:::
JS 异步执行顺序 1
以下代码,执行结果是什么?
1 | console.log('start') |
答案
::: details
1 | start end b a c d |
本题涉及同步代码、微任务、宏任务的执行顺序�?
首先执行同步代码,打�?start �?end�?
setTimeout 回调函数会被放入宏任务队列,等待同步代码执行完毕后执行�?
Promise.resolve().then() 注册了一个回调函数,会被放入微任务队列�?
因为微任务优先级高于宏任务,所�?then 回调函数会首先执行,打印 b。回调函数调用了 setTimeout,将函数放入宏任务队列�?
然后执行之前的宏任务,打�?a。然后又注册了一�?then 回调函数,会被放入微任务队列�?
因为微任务优先级高于宏任务,所�?then 回调函数会首先执行,打印 c�?
最后执行宏任务,打�?d�?
:::
JS 异步执行顺序 2
以下代码,执行结果是什么?
1 | Promise.resolve() |
答案
::: details
1 | 0 1 2 3 4 5 6 |
初始状态:
- 两个 Promise.resolve() 创建两个立即 resolved �?Promise
- 它们�?.then 回调都被加入到第一轮微任务队列
第一轮微任务�? - 执行第一个链的第一�?then:打�?0,返�?Promise.resolve(4)
- 执行第二个链的第一�?then:打�?1
第二轮微任务�? - 第二个链的第二个 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 后,发生了什么?
:::