经常写业务就容易忽视对基础知识的补充和加强,但在面试中,基础知识点是非常重要的考核部分。本文要分享的是,一位开发者每天都会发布的 JavaScript 问题。有的容易,有的会有难度,对基础知识的查缺补漏非常有帮助,也是你进阶路上必然要掌握的知识。
以下挑选了 10 个问题,紧跟其后的就是对这道题的详细解答。如果你想看所有的题目,最后有链接直达。
1. for 循环中套定时器
for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 1)
}
for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 1)
}
- A:
0 1 2
和0 1 2
- B:
0 1 2
和3 3 3
- C:
3 3 3
和0 1 2
答案: C
由于 JavaScript 的事件循环,setTimeout
回调会在 遍历结束后 才执行。因为在第一个遍历中遍历 i
是通过 var
关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++
来每次递增 i
的值。当 setTimeout
回调执行的时候,i
的值等于 3。
在第二个遍历中,遍历 i
是通过 let
关键字声明的:通过 let
和 const
关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i
都有一个新值,并且每个值都在循环内的作用域中。
2. 标记模板字面量
function getPersonInfo(one, two, three) {console.log(one)
console.log(two)
console.log(three)
}
const person = 'Lydia'
const age = 21
getPersonInfo`${person} is ${age} years old`
- A:
"Lydia"
21
[""," is "," years old"]
- B:
[""," is "," years old"]
"Lydia"
21
- C:
"Lydia"
[""," is "," years old"]
21
答案: B
如果使用标记模板字面量,第一个参数的值总是包含字符串的数组。其余的参数获取的是传递的表达式的值!
3. typeof 运算符返回值
function sayHi() {return (() => 0)()}
typeof sayHi()
- A:
"object"
- B:
"number"
- C:
"function"
- D:
"undefined"
答案: B
sayHi
方法返回的是立即执行函数 (IIFE) 的返回值. 此立即执行函数的返回值是 0
,类型是 number
参考:只有 7 种内置类型:null
,undefined
,boolean
,number
,string
,object
和 symbol
。function
不是一种类型,函数是对象,它的类型是object
。
4. 对象作为 key 值的问题
const a = {}
const b = {key: 'b'}
const c = {key: 'c'}
a[b] = 123
a = 456
console.log(a[b])
- A:
123
- B:
456
- C:
undefined
- D:
ReferenceError
答案: B
对象的键被自动转换为字符串。我们试图将一个对象 b
设置为对象 a
的键,且相应的值为 123
。
然而,当字符串化一个对象时,它会变成 "[object Object]"
。因此这里说的是,a["[object Object]"] = 123
。然后,我们再一次做了同样的事情,c
是另外一个对象,这里也有隐式字符串化,于是,a["[object Object]"] = 456
。
然后,我们打印 a[b]
,也就是 a["[object Object]"]
。之前刚设置为 456
,因此返回的是 456
。
5. call 和 bind 的使用
const person = {name: 'Lydia'}
function sayHi(age) {console.log(`${this.name} is ${age}`)
}
sayHi.call(person, 21)
sayHi.bind(person, 21)
- A:
undefined is 21
Lydia is 21
- B:
function
function
- C:
Lydia is 21
Lydia is 21
- D:
Lydia is 21
function
答案: D
使用这两种方法,我们都可以传递我们希望 this
关键字引用的对象。但是,.call
是 立即执行 的。
.bind
返回函数的 副本,但带有绑定上下文!它不是立即执行的。
6. reduce 方法的使用
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
- A:
1
2
and3
3
and6
4
- B:
1
2
and2
3
and3
4
- C:
1
undefined
and2
undefined
and3
undefined
and4
undefined
- D:
1
2
andundefined
3
andundefined
4
答案: D
reducer
函数接收 4 个参数:
- Accumulator (acc) (累计器)
- Current Value (cur) (当前值)
- Current Index (idx) (当前索引)
- Source Array (src) (源数组)
reducer
函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
reducer
函数还有一个可选参数initialValue
, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供initialValue
,则将使用数组中的第一个元素。
在上述例子,reduce
方法接收的第一个参数 (Accumulator) 是x
, 第二个参数 (Current Value) 是y
。
在第一次调用时,累加器 x
为1
,当前值 “y”
为2
,打印出累加器和当前值:1
和2
。
例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回 undefined
。在下一次调用时,累加器为undefined
,当前值为“3”, 因此undefined
和3
被打印出。
在第四次调用时,回调函数依然没有返回值。累加器再次为 undefined
,当前值为“4”。undefined
和 4
被打印出。
7. Promise.race 的用法
const firstPromise = new Promise((res, rej) => {setTimeout(res, 500, "one");
});
const secondPromise = new Promise((res, rej) => {setTimeout(res, 100, "two");
});
Promise.race([firstPromise, secondPromise]).then(res => console.log(res));
- A:
"one"
- B:
"two"
- C:
"two" "one"
- D:
"one" "two"
答案: B
当我们向 Promise.race
方法中传入多个 Promise
时,会进行 优先 解析(注:只要有一个成功或失败,就立马结束)。在这个例子中,我们用setTimeout
给firstPromise
和 secondPromise
分别设定了 500ms 和 100ms 的定时器。这意味着 secondPromise
会首先解析出字符串 two
。那么此时res
参数即为two
,是为输出结果。
8. try…catch 捕获错误
function greeting() {throw "Hello world!";}
function sayHi() {
try {const data = greeting();
console.log("It worked!", data);
} catch (e) {console.log("Oh no an error!", e);
}
}
sayHi();
- A:
"It worked! Hello world!"
- B:
"Oh no an error: undefined
- C:
SyntaxError: can only throw Error objects
- D:
"Oh no an error: Hello world!
答案: D
通过 throw
语句,我么可以创建自定义错误。而通过它,我们可以抛出异常。异常可以是一个 字符串 , 一个 数字 , 一个 布尔类型 或者是一个 对象。在本例中,我们的异常是字符串'Hello world'
.
通过 catch
语句,我们可以设定当 try
语句块中抛出异常后应该做什么处理。在本例中抛出的异常是字符串 'Hello world'
. e
就是这个字符串,因此被输出。最终结果就是'Oh an error: Hello world'
.
9. import 执行顺序问题
// index.js
console.log('running index.js');
import {sum} from './sum.js';
console.log(sum(1, 2));
// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;
- A:
running index.js
,running sum.js
,3
- B:
running sum.js
,running index.js
,3
- C:
running sum.js
,3
,running index.js
- D:
running index.js
,undefined
,running sum.js
答案: B
import
命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行。
这是 CommonJS 中 require()
和import
之间的区别。使用 require()
,您可以在运行代码时根据需要加载依赖项。如果我们使用require
而不是 import
,running index.js
,running sum.js
,3
会被依次打印。
10. JSON.stringify 过滤需要的字段
const settings = {
username: "lydiahallie",
level: 19,
health: 90
};
const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
- A:
"{"level":19,"health":90}"
- B:
"{"username":"lydiahallie"}"
- C:
"["level","health"]"
- D:
"{"username":"lydiahallie","level":19,"health":90}"
答案: A
JSON.stringify
的第二个参数是 _替代者 (replacer)_. 替代者(replacer) 可以是个函数或数组,用以控制哪些值如何被转换为字符串。
如果替代者 (replacer) 是个 数组 ,那么就只有包含在数组中的属性将会被转化为字符串。在本例中,只有名为"level"
和 "health"
的属性被包括进来,"username"
则被排除在外。data
就等于 "{"level":19,"health":90}"
.
而如果替代者 (replacer) 是个 _函数_,这个函数将被对象的每个属性都调用一遍。
函数返回的值会成为这个属性的值,最终体现在转化后的 JSON 字符串中(译者注:Chrome 下,经过实验,如果所有属性均返回同一个值的时候有异常,会直接将返回值作为结果输出而不会输出 JSON 字符串),而如果返回值为undefined
,则该属性会被排除在外。
题目来源:https://github.com/lydiahallie/javascript-questions/blob/master/README-zh_CN.md
如果对你有帮助,请关注【前端技能解锁】: