乐趣区

JavaScript-进阶问题列表你掌握了多少

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

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

退出移动版