24

经常写业务就容易忽视对基础知识的补充和加强,但在面试中,基础知识点是非常重要的考核部分。本文要分享的是,一位开发者每天都会发布的 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 20 1 2
  • B: 0 1 23 3 3
  • C: 3 3 30 1 2

答案: C

由于 JavaScript 的事件循环,setTimeout 回调会在遍历结束后才执行。因为在第一个遍历中遍历 i 是通过 var 关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++ 来每次递增 i 的值。当 setTimeout 回调执行的时候,i 的值等于 3。

在第二个遍历中,遍历 i 是通过 let 关键字声明的:通过 letconst 关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,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种内置类型:nullundefinedbooleannumberstringobjectsymbolfunction 不是一种类型,函数是对象,它的类型是object

4. 对象作为key值的问题

const a = {}
const b = { key: 'b' }
const c = { key: 'c' }

a[b] = 123
a[c] = 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 and 3 3 and 6 4
  • B: 1 2 and 2 3 and 3 4
  • C: 1 undefined and 2 undefined and 3 undefined and 4 undefined
  • D: 1 2 and undefined 3 and undefined 4

答案: D

reducer 函数接收4个参数:

  1. Accumulator (acc) (累计器)
  2. Current Value (cur) (当前值)
  3. Current Index (idx) (当前索引)
  4. Source Array (src) (源数组)

reducer 函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

reducer 函数还有一个可选参数initialValue, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供initialValue,则将使用数组中的第一个元素。

在上述例子,reduce方法接收的第一个参数(Accumulator)是x, 第二个参数(Current Value)是y

在第一次调用时,累加器x1,当前值“y”2,打印出累加器和当前值:12

例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回undefined。 在下一次调用时,累加器为undefined,当前值为“3”, 因此undefined3被打印出。

在第四次调用时,回调函数依然没有返回值。 累加器再次为 undefined ,当前值为“4”。 undefined4被打印出。

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时,会进行 优先 解析(注:只要有一个成功或失败,就立马结束)。在这个例子中,我们用setTimeoutfirstPromisesecondPromise分别设定了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而不是importrunning index.jsrunning sum.js3会被依次打印。

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

如果对你有帮助,请关注【前端技能解锁】:
qrcode_for_gh_d0af9f92df46_258.jpg


戎马
2.4k 声望346 粉丝

前端码农一枚,上班一族,爱文学一本。ส็็็็็็็็็็็็็็ ส้้้้้้้้้้้้้้้้้้้。