JS 进阶 155 题
写在前面: 这是实习时同事推荐的一份 JavaScript 进阶题目,对巩固 JS 基础很有帮助。我对部分解析稍加整理,并在下面附上原仓库地址和本文的 Markdown 源码。
每个题目均标记了 tag,但目录并未显示,可以使用 Ctrl + F 搜索 tag 来快速定位。例如:变量声明this 指向原型方法Promiseasync-await模块化 等。
使用 Ctrl + F 搜索 复习,可以快速定位到几个重点题目。
参考:
# 1. 输出是什么? 变量声明
function sayHi() {
console.log(name)
console.log(age)
var name = 'Lydia'
let age = 21
}
sayHi()
2
3
4
5
6
7
8
- A:
Lydia和undefined - B:
Lydia和ReferenceError - C:
ReferenceError和21 - D:
undefined和ReferenceError
答案:D
在函数内部,
var声明的变量会被 提升到函数顶端,赋值语句保持在原位置,因此var声明变量等同于下列代码:function sayHi() { var name console.log(name) name = 'Lydia' }1
2
3
4
5因此输出
undefined。
let和const声明的变量也会提升,但在实际声明它之前,它是不可访问的(称为 暂时性死区),尝试访问它会抛出ReferenceError: Cannot access 'age' before initialization。
扩展:
我们可以通过以下例子来说明
let声明的变量确实进行了提升:let x = 1; function fun() { console.log(x); // ReferenceError let x = 2; } fun();1
2
3
4
5
6如果函数
fun中的let x没有提升,console.log(x)应返回外层的 1 才对,但它抛出了ReferenceError: Cannot access 'x' before initialization。这说明let x确实提升到了fun顶端,并形成暂时性死区。
# 2. 输出是什么? 变量声明setTimeout
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1)
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1)
}
2
3
4
5
6
7
- A:
0 1 2和0 1 2 - B:
0 1 2和3 3 3 - C:
3 3 3和0 1 2
答案:C
setTimeout回调会在循环结束后再执行,这涉及到宏任务与微任务。在第一个循环中,
i是通过var定义的,具有全局作用域,setTimeout回调执行时,取i的值,i已经成为 3 了,因此输出 3 个 3。在第二个循环中,
i是通过let定义的,具有块级作用域,每次遍历时,i都有新值,并在循环作用域内。另外,6 个数字是在 1 ms 后同时(按顺序)输出的。
第一个循环,若想输出
0 1 2,可通过闭包修改为:for (var i = 0; i < 3; i++) { (function (i) { setTimeout(() => console.log(i), 1) })(i) }1
2
3
4
5解释:这是一个立即执行函数,传入了参数
i,相当于改写了setTimeout回调里console.log(i)的i为0 1 2。
# 3. 输出是什么? this 指向
const shape = {
radius: 10,
diameter() {
return this.radius * 2
},
perimeter: () => 2 * Math.PI * this.radius
}
shape.diameter()
shape.perimeter()
2
3
4
5
6
7
8
9
10
- A:
20and62.83185307179586 - B:
20andNaN - C:
20and63 - D:
NaNand63
答案:B
diameter函数作为 对象的方法 被调用,其中的this指向调用它的对象,即shape对象。
perimeter函数是箭头函数,其中的this指向 定义它的作用域指向的this,由于perimeter外层没有常规函数,因此this指向全局变量window。window.radius为undefined,参与计算后结果返回NaN。
# 4. 输出是什么? + 运算符真值假值
+true;
!"Lydia";
2
- A:
1andfalse - B:
falseandNaN - C:
falseandfalse
答案:A
单目运算符
+可将后面的变量/字面量转换为number类型(等同于Number()函数),布尔值true被转换为数字1。逻辑非
!运算符可将后面的变量/字面量转换为boolean类型的相反值,字符串"Lydia"被视为 truthy,转换为false。
扩展:
下列值被视为 falsy:
false、0、-0、0n、""、''、null、undefined、NaN。下列值被视为 truthy:
true、{}、[]、42、"0"、"false"、new Date()、-42、12n、3.14、-3.14、Infinity、-Infinity等。
# 5. 哪一个是正确的? 对象属性
const bird = {
size: 'small'
}
const mouse = {
name: 'Mickey',
small: true
}
2
3
4
5
6
7
8
- A:
mouse.bird.size是无效的 - B:
mouse[bird.size]是无效的 - C:
mouse[bird["size"]]是无效的 - D: 以上三个选项都是有效的
答案:A
所有对象的属性(
Symbol除外)都被视为字符串。在 B、C 中,
bird.size和bird["size"]都是'small',因此mouse的'small'属性为true。在 A 中,
mouse没有bird(或者说是'bird')属性,因此得到undefined,对undefined尝试取属性会抛出TypeError: Cannot read property 'size' of undefined。
# 6. 输出是什么? 对象引用
let c = { greeting: 'Hey!' }
let d
d = c
c.greeting = 'Hello'
console.log(d.greeting)
2
3
4
5
6
- A:
Hello - B:
undefined - C:
ReferenceError - D:
TypeError
答案:A
当
c被赋值给d时,实际上是把c代表的对象的引用赋值给了d,这样d和c具有同一个对象的引用。当改变其中一个对象时,其实是改变了所有对象。
# 7. 输出是什么? 包装类
let a = 3
let b = new Number(3)
let c = 3
console.log(a == b)
console.log(a === b)
console.log(b === c)
2
3
4
5
6
7
- A:
truefalsetrue - B:
falsefalsetrue - C:
truefalsefalse - D:
falsetruetrue
答案:C
new Number()创造了一个对象,这个对象看起来是number,但实际上是object,它有一些额外功能。因此对于
a === b和b === c,因为两者类型不同,返回false。对于
a == b,由于b是对象,调用valueOf()得到 3,再与a比较,返回true。
扩展:
new Number(3)与Number(3)不同,前者返回一个包装对象object,后者是类型转换函数(等同于单目运算符+,返回数字number。对于相等运算符
==,如果一个操作数为对象,另一个不是,则对象调用valueOf()再比较。
# 8. 输出是什么? 静态方法
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor
return this.newColor
}
constructor({ newColor = 'green' } = {}) {
this.newColor = newColor
}
}
const freddie = new Chameleon({ newColor: 'purple' })
freddie.colorChange('orange')
2
3
4
5
6
7
8
9
10
11
12
13
- A:
orange - B:
purple - C:
green - D:
TypeError
答案:D
colorChange()是Chameleon类的静态方法,只能通过类调用,即Chameleon.colorChange(),无法通过实例调用。
扩展:
constructor({ newColor = 'green' } = {})是一种对象解构写法,同时给newColor和{ newColor }都设置了默认参数。
# 9. 输出是什么? 变量声明
let greeting
greetign = {} // Typo!
console.log(greetign)
2
3
- A:
{} - B:
ReferenceError: greetign is not defined - C:
undefined
答案:A
第2行将
greeting拼写错误为greetign。在 JS 中,不通过var、let、const直接声明变量将被视为声明了全局变量(即全局对象的属性),如window.greetign,因此输出它的值{}。如果在最前面加上
"use strict"声明,则会抛出ReferenceError: greetign is not defined。
# 10. 当我们这么做时,会发生什么? 对象
function bark() {
console.log('Woof!')
}
bark.animal = 'dog'
2
3
4
5
- A: 正常运行!
- B:
SyntaxError,你不能通过这种方式给函数增加属性。 - C:
undefined - D:
ReferenceError
答案:A
bark是一个函数对象,可以通过这种方式给它添加属性。
# 11. 输出是什么? 构造函数原型方法
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
console.log(member.getFullName());
2
3
4
5
6
7
8
9
10
11
- A:
TypeError - B:
SyntaxError - C:
Lydia Hallie - D:
undefinedundefined
答案: A
Person.getFullName为Person函数添加了属性,但当Person作为构造函数使用时,它的函数并不能被它的实例使用。当尝试对
member调用getFullName时,会抛出TypeError: member.getFullName is not a function。要想对构造函数添加全体实例可用的方法,可以在构造函数内使用
this关键字:function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.getFullName = function () { return `${this.firstName} ${this.lastName}`; } }1
2
3
4
5
6
7或在
Person的原型上定义函数,这样更省空间:Person.prototype.getFullName = function () { return `${this.firstName} ${this.lastName}`; }1
2
3
# 12. 输出是什么? 构造函数this 指向
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
const lydia = new Person('Lydia', 'Hallie')
const sarah = Person('Sarah', 'Smith')
console.log(lydia)
console.log(sarah)
2
3
4
5
6
7
8
9
10
- A:
Person {firstName: "Lydia", lastName: "Hallie"}andundefined - B:
Person {firstName: "Lydia", lastName: "Hallie"}andPerson {firstName: "Sarah", lastName: "Smith"} - C:
Person {firstName: "Lydia", lastName: "Hallie"}and{} - D:
Person {firstName: "Lydia", lastName: "Hallie"}andReferenceError
答案:A
lydia的声明是标准的根据构造函数生成实例。
sarah的声明没有使用new关键字,因此Person被当做常规函数使用。这里的this指向全局变量window,Person作为常规函数,给window添加两个属性后没有返回值,因此sarah为undefined。
# 13. 事件传播的三个阶段是什么? 事件
- A: Target > Capturing > Bubbling
- B: Bubbling > Target > Capturing
- C: Target > Bubbling > Capturing
- D: Capturing > Target > Bubbling
答案:D
先从外向内捕获(capturing),到达目标(target),然后从内向外冒泡(bubbling)。
查看下面的例子:
<body> <div onclick="divClick()"> <p onclick="pClick()"> <span onclick="spanClick()">点击事件</span> </p> </div> </body> <script> function divClick() { console.log('divClick'); } function pClick() { console.log('pClick'); } function spanClick() { console.log('spanClick'); } </script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18点击后:输出顺序为
spanClick、pClick、divClick(冒泡顺序)。要阻止事件冒泡,可以使用
stopPropagation函数:function pClick(event) { console.log('pClick'); event.stopPropagation(); }1
2
3
4这样冒泡到
pClick就会停止。
# 14. 所有对象都有原型。 原型
- A: 对
- B: 错
答案:B
通过
Object.create(null)创造的对象没有原型,实际上,它没有任何属性。注意:{}的原型指向Object.prototype。const emp1 = Object.create(null), emp2 = {}; console.log(emp1); // {} console.log(emp2); // {} console.log(emp1.__proto__); // undefined console.log(emp2.__proto__); // {} console.log(emp2.__proto__ === Object.prototype); // true1
2
3
4
5
6
# 15. 输出是什么? + 运算符
function sum(a, b) {
return a + b
}
sum(1, '2')
2
3
4
5
- A:
NaN - B:
TypeError - C:
"12" - D:
3
答案:C
当相加的两个操作元素有一个为字符串时,相加操作为字符串拼接,
1被视为字符串"1",答案得到字符串"12"。
# 16. 输出是什么? 自增运算符
let number = 0
console.log(number++)
console.log(++number)
console.log(number)
2
3
4
- A:
112 - B:
122 - C:
022 - D:
012
答案:C
后自增运算符:先返回值,后自增,此时输出
0,number变为1。前自增运算符:先自增,后返回值,此时
number变为2,输出2。最后输出
number的值为2。
# 17. 输出是什么?字符串模板复习
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`
2
3
4
5
6
7
8
9
10
- A:
"Lydia"21["", " is ", " years old"] - B:
["", " is ", " years old"]"Lydia"21 - C:
"Lydia"["", " is ", " years old"]21
答案:B
如果使用字符串模板作为参数调用函数,则函数的第一个参数为 被字符串插值分隔的字符串数组,剩余参数为每个字符串表达式的值。
# 18. 输出是什么? 相等运算符
function checkAge(data) {
if (data === { age: 18 }) {
console.log('You are an adult!')
} else if (data == { age: 18 }) {
console.log('You are still an adult.')
} else {
console.log(`Hmm.. You don't have an age I guess`)
}
}
checkAge({ age: 18 })
2
3
4
5
6
7
8
9
10
11
- A:
You are an adult! - B:
You are still an adult. - C:
Hmm.. You don't have an age I guess
答案:C
每次新建的对象字面量即使内容一样,也是不同的对象,拥有不同的对象引用。因此无论使用
===还是==,data与它们的比较都是false。
# 19. 输出是什么? 剩余参数typeof
function getAge(...args) {
console.log(typeof args)
}
getAge(21)
2
3
4
5
- A:
"number" - B:
"array" - C:
"object" - D:
"NaN"
答案:C
扩展运算符会将实参作为数组传递,数组的
typeof为"object"。
# 20. 输出是什么? 变量声明
function getAge() {
'use strict'
age = 21
console.log(age)
}
getAge()
2
3
4
5
6
7
- A:
21 - B:
undefined - C:
ReferenceError - D:
TypeError
答案:C
在 JS 中,不通过
var、let、const直接声明变量将被视为声明了全局变量(即全局对象的属性)。使用"use strict"声明可以避免这一操作,而抛出ReferenceError: age is not defined。
# 21. 输出是什么? eval
const sum = eval('10*10+5')
- A:
105 - B:
"105" - C:
TypeError - D:
"10*10+5"
答案:A
eval函数对传进来的字符串视为表达式,对其求值。
# 22. cool_secret可访问多长时间? Storage待补充
sessionStorage.setItem('cool_secret', 123)
- A: 永远,数据不会丢失。
- B: 当用户关掉标签页时。
- C: 当用户关掉整个浏览器,而不只是关掉标签页。
- D: 当用户关闭电脑时。
答案:B
用户关闭标签页后,
sessionStorage存储的数据被清除。如果使用
localStorage,除非调用localStorage.clear(),否则数据将永远存在。
# 23. 输出是什么? 变量声明
var num = 8
var num = 10
console.log(num)
2
3
4
- A:
8 - B:
10 - C:
SyntaxError - D:
ReferenceError
答案:B
var声明的变量可以重复声明,同时保存最新的值。因为var会进行 变量提升,因此上述代码看起来就像这样:var num num = 8 num = 101
2
3
let声明的变量不可以重复声明。
# 24. 输出是什么? 对象属性Set集合
const obj = { 1: 'a', 2: 'b', 3: 'c' }
const set = new Set([1, 2, 3, 4, 5])
obj.hasOwnProperty('1')
obj.hasOwnProperty(1)
set.has('1')
set.has(1)
2
3
4
5
6
7
- A:
falsetruefalsetrue - B:
falsetruetruetrue - C:
truetruefalsetrue - D:
truetruetruetrue
答案:C
所有对象的属性(
Symbol除外)都被视为字符串。无论是'1'还是1都表示obj中为1的键。
Set不会将1和'1'视为同一个键。
# 25. 输出是什么? 对象属性
const obj = { a: 'one', b: 'two', a: 'three' }
console.log(obj)
2
- A:
{ a: "one", b: "two" } - B:
{ b: "two", a: "three" } - C:
{ a: "three", b: "two" } - D:
SyntaxError
答案:C
JS 允许对象字面量定义时有重复的键,当然只保留最后一个作为键值,并使用第一个的位置。
# 26. JavaScript 全局执行上下文为你做了两件事:全局对象和 this 关键字。 全局上下文
- A: 对
- B: 错
- C: 看情况
答案:A
JS 代码中随处可访问全局上下文。
# 27. 输出是什么? continue
for (let i = 1; i < 5; i++) {
if (i === 3) continue
console.log(i)
}
2
3
4
- A:
12 - B:
123 - C:
124 - D:
134
答案:C
continue的基本用法。
# 28. 输出是什么? 原型方法
String.prototype.giveLydiaPizza = () => {
return 'Just give Lydia pizza already!'
}
const name = 'Lydia'
name.giveLydiaPizza()
2
3
4
5
6
7
- A:
"Just give Lydia pizza already!" - B:
TypeError: not a function - C:
SyntaxError - D:
undefined
答案:A
通过
String.prototype添加原型方法后,该方法可被全体string类型或对象使用。但是,如果给
string或其他基本类型添加属性或方法,该属性或方法会被忽略。const str = 'Hello' str.name = 'World' console.log(str.name) // undefined1
2
3
# 29. 输出是什么? 对象属性
const a = {}
const b = { key: 'b' }
const c = { key: 'c' }
a[b] = 123
a[c] = 456
console.log(a[b])
2
3
4
5
6
7
8
- A:
123 - B:
456 - C:
undefined - D:
ReferenceError
答案:B
所有对象的属性(
Symbol除外)都被视为字符串,如果不是字符串会调用toString。
b是一个对象,当尝试将它作为a的属性时,b会调用toString,即得到"[object Object]"。操作a[b] = 123实际上是a["[object Object]"] = 123。同理,
c也是一个对象,操作a[c] = 456实际上是a["[object Object]"] = 456。再次打印
a[b]时,a["[object Object]"]已经是456。
# 30. 输出是什么? setTimeout
const foo = () => console.log('First')
const bar = () => setTimeout(() => console.log('Second'))
const baz = () => console.log('Third')
bar()
foo()
baz()
2
3
4
5
6
7
- A:
FirstSecondThird - B:
FirstThirdSecond - C:
SecondFirstThird - D:
SecondThirdFirst
答案:B
尽管
bar被最先调用,但它的setTimeout里的回调函数会被作为新的宏任务先储存起来,等其他宏任务执行完毕后执行。当
foo和baz调用完毕后,回调函数才被取出来加入到队列中,并一一调用。我们将这些结构称为调用栈(call stack)、Web API、队列(queue)。
# 31. 当点击按钮时,event.target 是什么? 事件
<div onclick="console.log('first div')">
<div onclick="console.log('second div')">
<button onclick="console.log('button')">
Click!
</button>
</div>
</div>
2
3
4
5
6
7
- A: Outer
div - B: Inner
div - C:
button - D: 一个包含所有嵌套元素的数组。
答案:C
最深层的元素是事件的
target。
# 32. 当您单击该段落时,日志输出是什么? 事件
<div onclick="console.log('div')">
<p onclick="console.log('p')">
Click here!
</p>
</div>
2
3
4
5
- A:
pdiv - B:
divp - C:
p - D:
div
答案:A
事件处理程序在冒泡阶段执行,也就是从内到外。
# 33. 输出是什么? this 指向callbind
const person = { name: 'Lydia' }
function sayHi(age) {
console.log(`${this.name} is ${age}`)
}
sayHi.call(person, 21)
sayHi.bind(person, 21)
2
3
4
5
6
7
8
- A:
undefined is 21Lydia is 21 - B:
functionfunction - C:
Lydia is 21Lydia is 21 - D:
Lydia is 21function
答案:D
apply、call和bind都可以改变函数内this的指向,不同之处在于:
apply的第一个参数是this指向的对象,第二个参数是一个可迭代对象,如Math.max.apply(null, [1, 2, 3, 4, 5])call的第一个参数是this指向的对象,剩余参数数量不固定,如Math.max.apply(null, 1, 2, 3, 4, 5)bind与call一致,但它返回一个改写了this指向的函数,并不立即执行
# 34. 输出是什么? 立即执行函数typeof
function sayHi() {
return (() => 0)()
}
typeof sayHi()
2
3
4
5
- A:
"object" - B:
"number" - C:
"function" - D:
"undefined"
答案:B
要判断
sayHi()的typeof返回什么,首先看sayHi()的返回值。它返回一个 立即执行函数,也就是将函数() => 0立即执行,即得到返回值0,因此typeof 0返回"number"。
# 35. 下列哪些值是 falsy? 真值假值
0
new Number(0)
('')
(' ')
new Boolean(false)
undefined
2
3
4
5
6
- A:
0,'',undefined - B:
0,new Number(0),'',new Boolean(false),undefined - C:
0,'',new Boolean(false),undefined - D: All of them are falsy
答案:B
字符串
' '长度不为 0,被认为是 truthy。
# 36. 输出是什么? typeof
console.log(typeof typeof 1)
- A:
"number" - B:
"string" - C:
"object" - D:
"undefined"
答案:B
typeof 1返回"number",而typeof "number"则返回"string"。
# 37. 输出是什么? 数组
const numbers = [1, 2, 3]
numbers[10] = 11
console.log(numbers)
2
3
- A:
[1, 2, 3, 7 x null, 11] - B:
[1, 2, 3, 11] - C:
[1, 2, 3, 7 x empty, 11] - D:
SyntaxError
答案:C
当直接操作
array.length或对超出数组长度的元素位置赋值时,数组长度会随之发生改变,多余的元素被empty slots覆盖,它们实际上是undefined。这些
empty slots可以被for、for-of打印为undefined,但不可响应for-in、forEach、map等。类似地,创建二维数组只能用
new Array(m).fill(0).map(() => new Array(n)),当中的fill(0)不可省略。
# 38. 输出是什么? try-catch变量作用域
(() => {
let x, y
try {
throw new Error()
} catch (x) {
(x = 1), (y = 2)
console.log(x)
}
console.log(x)
console.log(y)
})()
2
3
4
5
6
7
8
9
10
11
- A:
1undefined2 - B:
undefinedundefinedundefined - C:
112 - D:
1undefinedundefined
答案:A
常见的
try-catch结构或许更好理解:try { throw new Error() } catch (e) { console.log(e) }1
2
3
4
5这里
e是一个参数,表示try块中抛出的错误或其他值。e的作用域是catch的块级作用域,类似于函数形参。因此在题目中,
catch(x)中的x是catch块级作用域的x,它被赋值为1后,打印为1,但在外层,x是外层作用域的x,仍为undefined。
y无论在catch作用域还是在外层都指外层作用域的y,被赋值为2后,值就为2。
# 39. JavaScript 中的一切都是? 数据类型
- A: 基本类型与对象
- B: 函数与对象
- C: 只有对象
- D: 数字与对象
答案:A
基本类型:
number、boolean、string、null、undefined、symbol、bigint。引用类型:
object,array、function也是对象。
typeof会返回的类型:"number"、"boolean"、"string"、"undefined"、"symbol"、"bigint"、"object"、"function"。
NaN、Infinity的typeof返回"number"- 未定义的变量做
typeof,不会报错,而返回"undefined"null的typeof返回"object",array的typeof返回"object"(判断array可用Array.isArray()),基本类型的包装类对象的typeof返回"object"class的typeof返回"function"
# 40. 输出是什么? reduce
[[0, 1], [2, 3]].reduce(
(acc, cur) => {
return acc.concat(cur)
},
[1, 2]
)
2
3
4
5
6
- A:
[0, 1, 2, 3, 1, 2] - B:
[6, 1, 2] - C:
[1, 2, 0, 1, 2, 3] - D:
[1, 2, 6]
答案:C
reduce回调中的两个参数acc和cur,每次取出数组元素给cur,并将回调返回值赋给acc,最后返回acc,回调后的参数表示acc的初值。这个
reduce将数组的每个子数组与[1, 2]做拼接,最终返回[1, 2, 0, 1, 2, 3]。
# 41. 输出是什么? 真值假值
!!null
!!''
!!1
2
3
- A:
falsetruefalse - B:
falsefalsetrue - C:
falsetruetrue - D:
truetruefalse
答案:B
null和''都被认为是 falsy,1被认为是 truthy。
# 42. setInterval 方法的返回值是什么? setInterval复习
setInterval(() => console.log('Hi'), 1000)
- A: 一个唯一的 id
- B: 该方法指定的毫秒数
- C: 传递的函数
- D:
undefined
答案:A
在浏览器中,
setInterval返回一个唯一的 id。此 id 可被用于clearInterval函数来取消定时。在 node 中,
setInterval返回一个对象。
# 43. 输出是什么? 扩展运算符
[...'Lydia']
- A:
["L", "y", "d", "i", "a"] - B:
["Lydia"] - C:
[[], "Lydia"] - D:
[["L", "y", "d", "i", "a"]]
答案:A
扩展运算符作用于字符串时,返回由单个字符组成的字符串数组,作用等同于
'Lydia'.split('')。
# 44. 输出是什么? 生成器
function* generator(i) {
yield i;
yield i * 2;
}
const gen = generator(10);
console.log(gen.next().value);
console.log(gen.next().value);
2
3
4
5
6
7
8
9
- A:
[0, 10], [10, 20] - B:
20, 20 - C:
10, 20 - D:
0, 10 and 10, 20
答案:C
generator是一个生成器函数,每次遇到yield它都会暂停执行,并向外传递yield表达式的值,除非生成器对象调用next()方法使其继续执行。使用
10初始化生成器后,两次调用生成器的next(),它依次返回两个值10和20。
# 45. 返回值是什么? Promise待补充
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));
2
3
4
5
6
7
8
9
- A:
"one" - B:
"two" - C:
"two" "one" - D:
"one" "two"
答案:B
Promise.race()传入一个可迭代参数,它将可迭代参数中的每个元素包装为Promise并进行 优先解析,即先处理落定的Promise(无论状态为resolved或rejected),其他的Promise静默处理,并返回落定Promise的镜像。本题将
firstPromise和secondPromise传入Promise.race()后,由于secondPromise的回调 0.1 秒后执行res,也就是先落定为resolved,因此Promise.race()返回secondPromise的镜像,调用then()后,promise的res()向then()的回调传入"two",因此打印出"two"。
扩展:
setTimeout可以接受如下的参数:
- 第一个参数
fn,是一个回调函数,该函数在timeout时间后被调用- 第二个参数
timeout,指延时时间毫秒数,当为0或省略时表示立即调用(但也要等宏任务处理完)- 剩余参数
args,是传递给fn调用时的实参,因为fn在定义时只有形参
Promise回调中的res和rej都可以带参数,会被then和catch捕捉。
# 46. 输出是什么? 对象引用复习
let person = { name: "Lydia" };
const members = [person];
person = null;
console.log(members);
2
3
4
5
- A:
null - B:
[null] - C:
[{}] - D:
[{ name: "Lydia" }]
答案:D
第一行,我们声明对象
person。第二行,我们将数组members的第一个元素赋值为person,这实际上执行了浅拷贝,表示members[0]和person指向了同一个对象。第三行,将
person置为null后,members[0]依然保持对对象的引用。这有点类似于:
let obj1 = { a: 1, b: 2, c: 3 }; let obj2 = obj1; obj1 = null; console.log(obj2); // { a: 1, b: 2, c: 3 }1
2
3
4
# 47. 输出是什么? for-in
const person = {
name: "Lydia",
age: 21
};
for (const item in person) {
console.log(item);
}
2
3
4
5
6
7
8
- A:
{ name: "Lydia" }, { age: 21 } - B:
"name", "age" - C:
"Lydia", 21 - D:
["name", "Lydia"], ["age", 21]
答案:B
for-in循环遍历的是对象的 key(Symbol)除外。
# 48. 输出是什么? + 运算符
console.log(3 + 4 + "5");
- A:
"345" - B:
"75" - C:
12 - D:
"12"
答案:B
+运算符是从左到右的运算顺序,无论执行算术加法或字符串拼接。首先计算
3 + 4得到7,然后计算7 + "5"得到"75"。
# 49. num 的值是什么? parseInt
const num = parseInt("7*6", 10);
- A:
42 - B:
"42" - C:
7 - D:
NaN
答案:C
parseInt将字符串转为数字的逻辑是,从左到右解析字符串,直到遇到无法解释的字符为止。本题中,
"*"是无法解释的字符,所以返回7。
# 50. 输出是什么? maptypeof
[1, 2, 3].map(num => {
if (typeof num === "number") return;
return num * 2;
});
2
3
4
- A:
[] - B:
[null, null, null] - C:
[undefined, undefined, undefined] - D:
[ 3 x empty ]
答案:C
三个数都满足
if条件,返回undefined。
# 51. 输出的是什么? 函数参数对象引用
function getInfo(member, year) {
member.name = "Lydia";
year = "1998";
}
const person = { name: "Sarah" };
const birthYear = "1997";
getInfo(person, birthYear);
console.log(person, birthYear);
2
3
4
5
6
7
8
9
10
11
- A:
{ name: "Lydia" }, "1997" - B:
{ name: "Sarah" }, "1998" - C:
{ name: "Lydia" }, "1998" - D:
{ name: "Sarah" }, "1997"
答案:A
基本参数是值传递,将函数中的
year改为"1998",不会影响birthYear的值。对象是引用传递,将函数中
member的name属性改为"Lydia",实际上就改变了person的name属性。
扩展:
严格意义上,对象也是值传递。在函数中直接更改对象参数的引用,不会更改原对象的引用,参考第 46 题。
const obj = { a: 1, b: 2 }; function foo(o) { o = { a: 4, b: 5 }; } foo(obj); console.log(obj); // { a: 1, b: 2 }1
2
3
4
5
6
# 52. 输出是什么? 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();
2
3
4
5
6
7
8
9
10
11
12
13
14
- 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
greeting()执行后抛出异常,异常是字符串,异常能被catch捕捉为e,e就是这个字符串。
# 53. 输出是什么? this 指向
function Car() {
this.make = "Lamborghini";
return { make: "Maserati" };
}
const myCar = new Car();
console.log(myCar.make);
2
3
4
5
6
7
- A:
"Lamborghini" - B:
"Maserati" - C:
ReferenceError - D:
TypeError
答案:B
由于构造函数
Car返回了一个对象,myCar就指向这个对象。因此myCar的make属性应该是"Maserati"。
# 54. 输出是什么? typeof变量声明
(() => {
let x = (y = 10);
})();
console.log(typeof x);
console.log(typeof y);
2
3
4
5
6
- A:
"undefined", "number" - B:
"number", "number" - C:
"object", "number" - D:
"number", "undefined"
答案:A
立即执行函数中,
let x = (y = 10)实际上是两个语句的缩写:y = 10和let x = y。不通过
var、let、const直接声明变量将被视为声明了全局变量(即全局对象的属性),因此y是一个全局变量,typeof y返回"number"。
x是let声明的,只在块作用域起作用,因此typeof x里的x是未声明变量,返回"undefined"。
# 55. 输出是什么? 原型方法delete
class Dog {
constructor(name) {
this.name = name;
}
}
Dog.prototype.bark = function() {
console.log(`Woof I am ${this.name}`);
};
const pet = new Dog("Mara");
pet.bark();
delete Dog.prototype.bark;
pet.bark();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- A:
"Woof I am Mara",TypeError - B:
"Woof I am Mara","Woof I am Mara" - C:
"Woof I am Mara",undefined - D:
TypeError,TypeError
答案:A
delete可以删除对象的属性和方法,原型对象当然也可以。因此,在delete后再尝试调用bark时,会抛出TypeError: pet.bark is not a function。
# 56. 输出是什么? Set 集合
const set = new Set([1, 1, 2, 3, 4]);
console.log(set);
2
3
- A:
[1, 1, 2, 3, 4] - B:
[1, 2, 3, 4] - C:
{1, 1, 2, 3, 4} - D:
{1, 2, 3, 4}
答案:D
将数组转为
Set集合时,集合会自动去重。
# 57. 输出是什么? 模块化待补充
// counter.js
let counter = 10;
export default counter;
2
3
// index.js
import myCounter from "./counter";
myCounter += 1;
console.log(myCounter);
2
3
4
5
6
- A:
10 - B:
11 - C:
Error - D:
NaN
答案:C
import导入的数据是只读的,不能被修改,尝试修改会抛出TypeError: Assignment to constant variable。
# 58. 输出是什么? delete
const name = "Lydia";
age = 21;
console.log(delete name);
console.log(delete age);
2
3
4
5
- A:
false,true - B:
"Lydia",21 - C:
true,true - D:
undefined,undefined
答案:A
delete的返回值是true或false,表示是否删除成功。
age是全局变量,即全局对象的属性,可以删除成功。name是const声明的,无法被delete删除。以下情况
delete会返回true:删除全局变量或对象的属性、方法,即使它不存在或进行了重复删除。以下情况
delete会返回false:删除let、const、var声明的变量;删除的属性设置了configurable: false。注意,在严格模式下,
delete不会返回false,而是会抛出SyntaxError: Delete of an unqualified identifier in strict mode或TypeError: Cannot delete property 'xxx' of #<Object>。
# 59. 输出是什么? 解构
const numbers = [1, 2, 3, 4, 5];
const [y] = numbers;
console.log(y);
2
3
4
- A:
[[1, 2, 3, 4, 5]] - B:
[1, 2, 3, 4, 5] - C:
1 - D:
[1]
答案:C
这是数组的解构赋值语法。使用
[a, b] = [1, 2]可以将a赋值为1,b赋值为2;如果左边变量比右边多,则多余的变量赋为undefined;如果左边变量比右边少,则按顺序赋值后多余的值忽略,如:let [a, b] = [1, 2]; let [c, d, e] = [3, 4]; let [f] = [5, 6, 7, 8]; console.log(a, b); // 1 2 console.log(c, d, e); // 3 4 undefined console.log(f); // 51
2
3
4
5
6
# 60. 输出是什么? 扩展运算符
const user = { name: "Lydia", age: 21 };
const admin = { admin: true, ...user };
console.log(admin);
2
3
4
- A:
{ admin: true, user: { name: "Lydia", age: 21 } } - B:
{ admin: true, name: "Lydia", age: 21 } - C:
{ admin: true, user: ["Lydia", 21] } - D:
{ admin: true }
答案:B
将对象通过扩展运算符操作后,对象的每个键值对都被复制,并可用于其他对象的赋值。
# 61. 输出是什么? defineProperty复习
const person = { name: "Lydia" };
Object.defineProperty(person, "age", { value: 21 });
console.log(person);
console.log(Object.keys(person));
2
3
4
5
6
- A:
{ name: "Lydia", age: 21 },["name", "age"] - B:
{ name: "Lydia", age: 21 },["name"] - C:
{ name: "Lydia"},["name", "age"] - D:
{ name: "Lydia"},["age"]
答案:B
通过
defineProperty可以给对象添加属性或修改属性。但在默认情况下,该属性是 不可枚举的,即无法被for-in遍历,也无法被Object.keys()获取。可以通过以下方式控制
defineProperty添加的属性:Object.defineProperty(person, "age", { value: 21, // 属性值 enumerable: true, // 是否可枚举,即是否可以被for-in遍历或被Object.keys()获取,默认为false writable: true, // 是否可写,即是否可以被修改,默认为false configurable: true // 是否可配置,即是否可以被删除或是否可以修改属性的特性(writable, enumerable, configurable),默认为false });1
2
3
4
5
6注意,在 node 下,
console.log(person)在age不可枚举的情况下,输出{ name: "Lydia"}。
# 62. 输出是什么? JSON复习
const settings = {
username: "lydiahallie",
level: 19,
health: 90
};
const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
2
3
4
5
6
7
8
- A:
"{"level":19, "health":90}" - B:
"{"username": "lydiahallie"}" - C:
"["level", "health"]" - D:
"{"username": "lydiahallie", "level":19, "health":90}"
答案:A
JSON.stringify()的第二个参数称为replacer。当
replacer是数组时,只有包含在数组里的属性会被转为字符串,并使用数组顺序,如本题所示。当
replacer是函数时,JSON.stringify()会把每个属性都调用一次函数,函数返回值将成为属性值写入 JSON 字符串,注意:
JSON.stringify()首先会以key为""、value为原对象调用一次函数,如果函数返回一个对象,则以该对象进行接下来的JSON.stringify(),否则停止操作,将整个返回值作为字符串返回(undefined返回undefined而非"undefined")。- 如果函数返回值遇到
undefined,则忽略该键。const settings = { username: "lydiahallie", level: 19, health: 90 }; function rp1(key, value) { if (key === "") { return { a: 1, b: 2, c: 3 }; } return key === "a" ? 5 : value; } function rp2(key, value) { return 5; } function rp3(key, value) { if (typeof value === "number") { return value * 2; } return value; } console.log(JSON.stringify(settings, rp1)); // {"a":5,"b":2,"c":3} console.log(JSON.stringify(settings, rp2)); // 5 console.log(JSON.stringify(settings, rp3)); // {"username":"lydiahallie","level":38,"health":180}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
# 63. 输出是什么? 自增运算符
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = number => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);
console.log(num1);
console.log(num2);
2
3
4
5
6
7
8
9
10
- A:
10,10 - B:
10,11 - C:
11,11 - D:
11,12
答案:A
第一个函数访问到的
num是外层num,即10,它的返回值是num自增前的值,即10。第二个函数将
num1 = 10传入,它的返回值也是number自增前的值,即10。
# 64. 输出什么? 默认参数扩展运算符
const value = { number: 10 };
const multiply = (x = { ...value }) => {
console.log(x.number *= 2);
};
multiply();
multiply();
multiply(value);
multiply(value);
2
3
4
5
6
7
8
9
10
- A:
20,40,80,160 - B:
20,40,20,40 - C:
20,20,20,40 - D:
NaN,NaN,20,40
答案:C
前两次没有传入参数,
x使用默认参数{ ...value }。这个对象字面量使用扩展运算符操作value,相当于value的复制品。两次调用都是一个新的对象,因此都返回20。后两次传入
value,x相当于直接对value做操作,value.x的值被改变,因此返回20和40。
# 65. 输出什么? reduce
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
- A:
12and33and64 - B:
12and23and34 - C:
1undefinedand2undefinedand3undefinedand4undefined - D:
12andundefined3andundefined4
答案:D
reduce的前两个参数acc(即x)和cur(即y),当acc没有设置初始值时,acc将被设置为数组第一个值,reduce从数组第二个值开始遍历。所以第一次输出1和2。每次
reduce回调的返回值作为下一次调用的acc,题目中回调函数的返回值是console.log的返回值undefined,因此后两次的acc都为undefined。
# 66. 使用哪个构造函数可以成功继承 Dog 类? 类的继承
class Dog {
constructor(name) {
this.name = name;
}
};
class Labrador extends Dog {
// 1
constructor(name, size) {
this.size = size;
}
// 2
constructor(name, size) {
super(name);
this.size = size;
}
// 3
constructor(size) {
super(name);
this.size = size;
}
// 4
constructor(name, size) {
this.name = name;
this.size = size;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- A: 1
- B: 2
- C: 3
- D: 4
答案:B
在子类中使用
this之前,应调用父类super初始化。同时name也应传入子类的构造函数。
# 67. 输出什么? 模块化复习
// 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;
2
3
4
5
6
7
8
- 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在编译阶段就执行。因此代码运行之前,导入的模块就已运行。如果使用
require(),则输出顺序为running index.js,running sum.js,3。
# 68. 输出什么? 包装类Symbol
console.log(Number(2) === Number(2))
console.log(Boolean(false) === Boolean(false))
console.log(Symbol('foo') === Symbol('foo'))
2
3
- A:
true,true,false - B:
false,true,false - C:
true,false,true - D:
true,true,true
答案:A
前两个包装类没有加
new关键字,因此都是强转,当然返回true。对于
Symbol,每个Symbol都是唯一的,并且不可被new。
# 69. 输出什么? padStart
const name = "Lydia Hallie"
console.log(name.padStart(13))
console.log(name.padStart(2))
2
3
- A:
"Lydia Hallie","Lydia Hallie" - B:
" Lydia Hallie"," Lydia Hallie"("[13x whitespace]Lydia Hallie","[2x whitespace]Lydia Hallie") - C:
" Lydia Hallie","Lydia Hallie"("[1x whitespace]Lydia Hallie","Lydia Hallie") - D:
"Lydia Hallie","Lyd"
答案:C
padStart方法可以在字符串前填充空格,参数n表示填充空格后字符串的长度为n。本题中字符串长度为 12,n = 13时就在字符串前填充 1 个空格。当
n小于等于字符串长度时,不做任何操作,返回原字符串。
# 70. 输出什么? + 运算符
console.log("🥑" + "💻");
- A:
"🥑💻" - B:
257548 - C: A string containing their code points
- D: Error
答案:A
emoji 也是字符串,当然执行字符串拼接。
# 71. 如何能打印出 console.log 语句后注释掉的值? 生成器
function* startGame() {
const 答案 = yield "Do you love JavaScript?";
if (答案 !== "Yes") {
return "Oh wow... Guess we're gone here";
}
return "JavaScript loves you back ❤️";
}
const game = startGame();
console.log(/* 1 */); // Do you love JavaScript?
console.log(/* 2 */); // JavaScript loves you back ❤️
2
3
4
5
6
7
8
9
10
11
- A:
game.next("Yes").valueandgame.next().value - B:
game.next.value("Yes")andgame.next.value() - C:
game.next().valueandgame.next("Yes").value - D:
game.next.value()andgame.next.value("Yes")
答案:C
当生成器执行到
yield语句时暂停执行,并向外传递yield表达式后的值,因此第 1 个位置可以使用game.next().value。生成器继续执行时,
next()可以传入参数,这个参数作为yield表达式的返回值,如果为空就是undefined。因此这里传入"Yes",也就是给生成器中的答案赋值"Yes",函数的if判断返回false,并返回第二句话。
# 72. 输出什么? String.raw
console.log(String.raw`Hello\nworld`);
- A:
Hello world! - B:
Helloworld - C:
Hello\nworld - D:
Hello\nworld
答案:C
String.raw()返回一个模板字符串的原始字符串,防止转义符被转义。例如:const path = `C:\temp\my\file.txt`; console.log(String.raw`${path}`); // C: empmy ile.txt console.log(String.raw`C:\temp\my\file.txt`); // C:\temp\my\file.txt1
2
3
# 73. 输出什么? async-await待补充
async function getData() {
return await Promise.resolve("I made it!");
}
const data = getData();
console.log(data);
2
3
4
5
6
- A:
"I made it!" - B:
Promise {<resolved>: "I made it!"} - C:
Promise {<pending>} - D:
undefined
答案:C
函数运行到
await时,将后面的Promise加入微任务队列,暂停执行函数,并开始执行外面的任务。因为异步函数始终返回一个Promise,函数即使还未返回值,但data表示getData()挂起的一个待定状态的Promise。要想落定期约,可以在后面加上:
data.then(res => console.log(res)),这会给微任务队列加上等待data表示的期约落定的任务,直至函数返回值。下面的例子说明了执行顺序:
async function getData() { console.log(1); const p = await Promise.resolve("I made it!"); console.log(2); return p; } const data = getData(); console.log(3); data.then(res => console.log(res)); console.log(4); // 输出顺序:1 => 3 => 4 => 2 => I made it!1
2
3
4
5
6
7
8
9
10
11
12异步函数执行 => 打印 1 => 队列添加
await后的期约,异步函数暂停执行 => 打印 3 => 队列添加函数期约落定任务 => 打印 4 => 宏任务队列空,期约解决,函数恢复执行,字符串赋给p=> 打印 2 => 字符串被异步函数返回,并包装为期约 => 函数期约落定,访问期约解决的值
# 74. 输出什么? 数组 push
function addToList(item, list) {
return list.push(item);
}
const result = addToList("apple", ["banana"]);
console.log(result);
2
3
4
5
6
- A:
['apple', 'banana'] - B:
2 - C:
true - D:
undefined
答案:B
数组的
push方法返回一个数字,这个数字是改变后的数组的长度。
# 75. 输出什么? 对象 freeze复习
const box = { x: 10, y: 20 };
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape)
2
3
4
5
6
7
- A:
{ x: 100, y: 20 } - B:
{ x: 10, y: 20 } - C:
{ x: 100 } - D:
ReferenceError
答案:B
Object.freeze()可以使得一个对象的属性无法被添加、修改或删除,也无法修改对象原型和配置。在非严格模式下,对象被冻结后,上述操作不会影响对象;在严格模式下,会抛出
TypeError: Cannot assign to read only property 'x' of object '#<Object>'。这个冻结是浅冻结,意味着如果对象的属性是对象,则子对象无法冻结。
可以通过
Object.isFrozen()检查对象是否被冻结。当然,数组也可以被冻结。
# 76. 输出什么? 解构
const { name: myName } = { name: "Lydia" };
console.log(name);
2
3
- A:
"Lydia" - B:
"myName" - C:
undefined - D:
ReferenceError
答案:A
这是对象解构语法,等同于
const myName = { name: "Lydia" }.name。当然也可以不用冒号,这样键名就是变量名,即
const { name } = { name: "Lydia" }。
# 77. 以下是个纯函数么? 纯函数
function sum(a, b) {
return a + b;
}
2
3
- A: Yes
- B: No
答案:A
纯函数 是不会带来 副作用 的函数。它的输入输出信息都是显式的,只通过参数输入信息,只通过返回值输出信息。
以下是几个纯函数和非纯函数的例子:
function fun1(x) { return x + 1; } // 是纯函数 let a = 5; function fun2() { let b = a; } // 不是纯函数 function fun3() { return Math.random(); } // 不是纯函数 function fun4() { console.log('hello'); } // 不是纯函数1
2
3
4
5
# 78. 输出什么? 闭包
const add = () => {
const cache = {};
return num => {
if (num in cache) {
return `From cache! ${cache[num]}`;
} else {
const result = num + 10;
cache[num] = result;
return `Calculated! ${result}`;
}
};
};
const addFunction = add();
console.log(addFunction(10));
console.log(addFunction(10));
console.log(addFunction(5 * 2));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- A:
Calculated! 20Calculated! 20Calculated! 20 - B:
Calculated! 20From cache! 20Calculated! 20 - C:
Calculated! 20From cache! 20From cache! 20 - D:
Calculated! 20From cache! 20Error
答案:C
首先我们看到
add返回的是一个函数,这个函数被赋给addFunction,随后add调用结束。在调用
addFunction时,注意到函数里使用了add函数中的局部变量cache,虽然add已经调用结束,但局部变量cache没有被销毁,依然可以访问,这就是典型的 闭包。这个cache类似于一个全局变量。理解这之后,就很好做了。首先将
10存入cache,cache此时形如{ 10: 20 }。后面两次调用都能得到cache中的10。
# 79. 输出什么? for-infor-of
const myLifeSummedUp = ["☕", "💻", "🍷", "🍫"]
for (let item in myLifeSummedUp) {
console.log(item)
}
for (let item of myLifeSummedUp) {
console.log(item)
}
2
3
4
5
6
7
8
9
- A:
0123and"☕""💻""🍷""🍫" - B:
"☕""💻""🍷""🍫"and"☕""💻""🍷""🍫" - C:
"☕""💻""🍷""🍫"and0123 - D:
0123and{0: "☕", 1: "💻", 2: "🍷", 3: "🍫"}
答案:A
for-in可以遍历一个对象 自有的、继承的、可枚举的、非Symbol的 属性,对于数组,它遍历数组的下标。
for-of可以遍历一个可迭代对象(Array、Map、Set、string等)的值,对于数组,它遍历数组的元素。
# 80. 输出什么? 数组
const list = [1 + 2, 1 * 2, 1 / 2]
console.log(list)
2
- A:
["1 + 2", "1 * 2", "1 / 2"] - B:
["12", 2, 0.5] - C:
[3, 2, 0.5] - D:
[1, 1, 1]
答案:C
数组可以包含任何值。如果是表达式,则进行计算。
# 81. 输出什么? 函数参数
function sayHi(name) {
return `Hi there, ${name}`
}
console.log(sayHi())
2
3
4
5
- A:
Hi there, - B:
Hi there, undefined - C:
Hi there, null - D:
ReferenceError
答案:B
如果不给函数传实参,函数的形参将被默认赋为
undefined。
# 82. 输出什么? this 指向
var status = "😎"
setTimeout(() => {
const status = "😍"
const data = {
status: "🥑",
getStatus() {
return this.status
}
}
console.log(data.getStatus())
console.log(data.getStatus.call(this))
}, 0)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- A:
"🥑"and"😍" - B:
"🥑"and"😎" - C:
"😍"and"😎" - D:
"😎"and"😎"
答案:B
谁调用,
this就指向谁。第一个输出中,
getStatus()被data调用,this指向data,因此this.status就是data.status。第二个输出中,
getStatus()的this指向被call修改为箭头函数里的this,箭头函数this的指向等同于外层this的指向,即全局对象。var声明的变量被视为全局对象的属性,因此this.status就是window.status也就是开头声明的var status。
扩展:
浏览器和 node 对全局的
this、var定义的变量处理逻辑不同。浏览器的 JS 代码是嵌入script标签直接运行的,因此本身就是全局;node 的 JS 代码是作为一个子模块运行的,类似于函数块。因此:
- 浏览器的
this是全局对象window,node 的this是一个空对象{}而非全局对象global。- 浏览器下
var定义的变量就是全局变量,是window对象的属性;node 下var定义的变量是局部变量(函数作用域),不会成为global也不会成为this的属性。- 直接定义的变量在浏览器和 node 都被认为是全局变量(全局对象的属性),因为函数中如此定义的变量会突破函数作用域,作用于全局。
# 83. 输出什么? 对象属性
const person = {
name: "Lydia",
age: 21
}
let city = person.city
city = "Amsterdam"
console.log(person)
2
3
4
5
6
7
8
9
- A:
{ name: "Lydia", age: 21 } - B:
{ name: "Lydia", age: 21, city: "Amsterdam" } - C:
{ name: "Lydia", age: 21, city: undefined } - D:
"Amsterdam"
答案:A
person.city是undefined,所以变量city是undefined。把city重新赋值后,由于没有使用person的引用,因此并没有直接修改person,返回的是原对象。
# 84. 输出什么? 变量声明
function checkAge(age) {
if (age < 18) {
const message = "Sorry, you're too young."
} else {
const message = "Yay! You're old enough!"
}
return message
}
console.log(checkAge(21))
2
3
4
5
6
7
8
9
10
11
- A:
"Sorry, you're too young." - B:
"Yay! You're old enough!" - C:
ReferenceError - D:
undefined
答案:C
const定义的变量是块作用域,因此两个定义的message都只是在其大括号内的作用域,return后面尝试使用message会抛出ReferenceError: message is not defined。
# 85. 什么样的信息将被打印? Promise复习
fetch('https://www.website.com/api/user/1')
.then(res => res.json())
.then(res => console.log(res))
2
3
- A:
fetch方法的结果 - B: 第二次调用
fetch方法的结果 - C: 前一个
.then()中回调方法返回的结果 - D: 总是
undefined
答案:C
当一个
Promise解决后,调用.then()可以获取Promise解决的值,同时继续返回一个Promise,这个Promise以此次回调的返回值作为解决值。例如:Promise.resolve("123").then(parseInt).then(console.log); // 1231
# 86. 哪个选项是将 hasName 设置为 true 的方法,前提是不能将 true 作为参数传递? 真值假值
function getName(name) {
const hasName = //
}
2
3
- A:
!!name - B:
name - C:
new Boolean(name) - D:
name.length
答案:A
两次逻辑非运算符可以将变量或常量转变为对应的布尔值。
new Boolean()返回的是包装类对象,而非基本类型布尔值。
# 87. 输出什么? 字符串
console.log("I want pizza"[0])
- A:
""" - B:
"I" - C:
SyntaxError - D:
undefined
答案:B
字符串可以使用中括号索引,类似于数组,等同于字符串的
charAt()函数。
# 88. 输出什么? 默认参数
function sum(num1, num2 = num1) {
console.log(num1 + num2)
}
sum(10)
2
3
4
5
- A:
NaN - B:
20 - C:
ReferenceError - D:
undefined
答案:B
默认参数可以将参数的默认值设置为函数其他参数,只要其他参数在该参数之前定义即可。
本题中,传入
num1 = 10,num2默认使用num1的值即10,因此打印20。如果其他参数定义在默认参数之后,则会抛出
ReferenceError,例如:function fun(x = y, y = 2) { console.log(x, y); } fun(); // ReferenceError: Cannot access 'y' before initialization fun(1); // 1 2 fun(1, 3); // 1 31
2
3
4
5
6
7
# 89. 输出什么? 模块化待补充
// module.js
export default () => "Hello world"
export const name = "Lydia"
// index.js
import * as data from "./module"
console.log(data)
2
3
4
5
6
7
8
- A:
{ default: function default(), name: "Lydia" } - B:
{ default: function default() } - C:
{ default: "Hello world", name: "Lydia" } - D: Global object of
module.js
答案:A
使用
import * as data语法时,模块所有的export都被导入到文件,并创建一个名为data的对象。模块的默认导出在data对象中被命名为default属性,命名导出在data对象被命名为其变量名的属性。
# 90. 输出什么? typeof
class Person {
constructor(name) {
this.name = name
}
}
const member = new Person("John")
console.log(typeof member)
2
3
4
5
6
7
8
- A:
"class" - B:
"function" - C:
"object" - D:
"string"
答案:C
member是个对象,当然返回"object"。如果打印
typeof Person则会返回"function",因为class是构造函数的语法糖。
# 91. 输出什么? 数组 push
let newList = [1, 2, 3].push(4)
console.log(newList.push(5))
2
3
- A:
[1, 2, 3, 4, 5] - B:
[1, 2, 3, 5] - C:
[1, 2, 3, 4] - D:
Error
答案:D
数组的
push方法返回一个数字,这个数字是改变后的数组的长度。因此newList是数组长度也就是4,再尝试对它调用push方法会抛出TypeError: newList.push is not a function。
# 92. 输出什么? 原型
function giveLydiaPizza() {
return "Here is pizza!"
}
const giveLydiaChocolate = () => "Here's chocolate... now go hit the gym already."
console.log(giveLydiaPizza.prototype)
console.log(giveLydiaChocolate.prototype)
2
3
4
5
6
7
8
- A:
{ constructor: ...}{ constructor: ...} - B:
{}{ constructor: ...} - C:
{ constructor: ...}{} - D:
{ constructor: ...}undefined
答案:D
每一个函数都会有一个属性
prototype,它指向一个原型对象,函数作为构造函数使用时,它的实例也指向这个原型对象,即A.prototype === a.__proto__;同时,这个原型对象有一个属性constructor,指向原函数,即A.prototype.constructor === A。对于
giveLydiaPizza,虽然其可能本意并不是当做构造函数使用,但其确实可以使用new关键字构造一个实例,因此它当然有prototype属性,指向它的原型对象。(在 node 下,由于原型对象的constructor属性是不可枚举的,因此会打印{})对于
giveLydiaChocolate,箭头函数不可当做构造函数使用(因为它没有自己的this),使用new关键字会报错,因此自然也没有prototype属性,返回undefined。
# 93. 输出什么? 解构
const person = {
name: "Lydia",
age: 21
}
for (const [x, y] of Object.entries(person)) {
console.log(x, y)
}
2
3
4
5
6
7
8
- A:
nameLydiaandage21 - B:
["name", "Lydia"]and["age", 21] - C:
["name", "age"]andundefined - D:
Error
答案:A
Object.entries返回一个二维数组,每个子数组长度为 2,分别是对象的键和值。在
for-of循环时,令[x, y]为循环项,实际上是执行了数组解构,将x赋值为键,y赋值为值。
# 94. 输出什么? 剩余参数
function getItems(fruitList, ...args, favoriteFruit) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
2
3
4
5
- A:
["banana", "apple", "pear", "orange"] - B:
[["banana", "apple"], "pear", "orange"] - C:
["banana", "apple", ["pear"], "orange"] - D:
SyntaxError
答案:D
剩余参数只能位于函数参数的最后,否则会抛出
SyntaxError: Rest parameter must be last formal parameter。
# 95. 输出什么? 自动插入分号
function nums(a, b) {
if
(a > b)
console.log('a is bigger')
else
console.log('b is bigger')
return
a + b
}
console.log(nums(4, 2))
console.log(nums(1, 2))
2
3
4
5
6
7
8
9
10
11
12
- A:
a is bigger,6andb is bigger,3 - B:
a is bigger,undefinedandb is bigger,undefined - C:
undefinedandundefined - D:
SyntaxError
答案:C
JS 里不用显示书写分号,JS 引擎会自动在语句后插入分号。
但是,
return后面添加了回车,JS 引擎认为return是一句完整的语句,就像这样:return; a + b;1
2此时
a + b永远不会执行,而空return实际上返回的是undefined,因此输出两个undefined。
# 96. 输出什么? 类
class Person {
constructor() {
this.name = "Lydia"
}
}
Person = class AnotherPerson {
constructor() {
this.name = "Sarah"
}
}
const member = new Person()
console.log(member.name)
2
3
4
5
6
7
8
9
10
11
12
13
14
- A:
"Lydia" - B:
"Sarah" - C:
Error: cannot redeclare Person - D:
SyntaxError
答案:B
class是构造函数的语法糖,以上代码等同于:function Person () { this.name = "Lydia" } Person = function AnotherPerson () { this.name = "Sarah" }1
2
3
4
5
6
7这当然是允许的。
# 97. 输出什么? Symbol对象属性
const info = {
[Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))
2
3
4
5
6
- A:
{Symbol('a'): 'b'}and["{Symbol('a')"] - B:
{}and[] - C:
{ a: "b" }and["a"] - D:
{Symbol('a'): 'b'}and[]
答案:D
Symbol属性是 不可枚举的,它无法被Object.keys()获取,也无法通过for-in遍历,但可以被Object.getOwnPropertySymbols()获取。虽然
Symbol属性不可枚举,但在浏览器和 node 下打印的对象均可以显示它。
# 98. 输出什么? 扩展运算符解构
const getList = ([x, ...y]) => [x, y]
const getUser = user => { name: user.name, age: user.age }
const list = [1, 2, 3, 4]
const user = { name: "Lydia", age: 21 }
console.log(getList(list))
console.log(getUser(user))
2
3
4
5
6
7
8
- A:
[1, [2, 3, 4]]andSyntaxError - B:
[1, [2, 3, 4]]and{ name: "Lydia", age: 21 } - C:
[1, 2, 3, 4]and{ name: "Lydia", age: 21 } - D:
Errorand{ name: "Lydia", age: 21 }
答案:A
将
list传入getList参数时,执行数组解构,即[x, ...y] = [1, 2, 3, 4],此时x为1,y为数组[2, 3, 4],返回[x, y]即[1, [2, 3, 4]]。对于
getUser的箭头函数,虽然仅返回一个值的情况下可以不写大括号,但这时返回一个对象,箭头函数会把对象的大括号认为是函数体的块语句,因此抛出SyntaxError: Unexpected token ':'。要想箭头函数顺利返回一个对象,应将对象用小括号包起来,即:
const getUser = user => ({ name: user.name, age: user.age })1
# 99. 输出什么? 异常
const name = "Lydia"
console.log(name())
2
3
- A:
SyntaxError - B:
ReferenceError - C:
TypeError - D:
undefined
答案:C
name不是一个函数,如果尝试将它以函数形式调用,则会抛出TypeError: name is not a function。如果编写的 JS 语句非法,会抛出
SyntaxError,例如拼写错误;如果 JS 语句合法但出现了不恰当的引用,会抛出ReferenceError,例如先使用后定义let变量。
# 100. 输出什么? 字符串模板真值假值逻辑与运算符
// 🎉✨ This is my 100th question! ✨🎉
const output = `${[] && 'Im'}possible!
You should${'' && `n't`} see a therapist after so much JavaScript lol`
2
3
4
- A:
possible! You should see a therapist after so much JavaScript lol - B:
Impossible! You should see a therapist after so much JavaScript lol - C:
possible! You shouldn't see a therapist after so much JavaScript lol - D:
Impossible! You shouldn't see a therapist after so much JavaScript lol
答案:B
[]被视为 truthy,逻辑与运算符会返回后者,即'Im'。
''被视为 falsy,逻辑与运算符会直接返回前者,即''。
# 101. 输出什么? 真值假值逻辑或运算符
const one = (false || {} || null)
const two = (null || false || "")
const three = ([] || 0 || true)
console.log(one, two, three)
2
3
4
5
- A:
falsenull[] - B:
null""true - C:
{}""[] - D:
nullnulltrue
答案:C
false || {}返回后者{},{} || null返回前者{}。
null || false返回后者false,false || ""返回后者""。
[] || 0返回前者[],[] || true返回前者[]。
# 102. 输出什么? Promiseasync-await复习
const myPromise = () => Promise.resolve('I have resolved!')
function firstFunction() {
myPromise().then(res => console.log(res))
console.log('second')
}
async function secondFunction() {
console.log(await myPromise())
console.log('second')
}
firstFunction()
secondFunction()
2
3
4
5
6
7
8
9
10
11
12
13
14
- A:
I have resolved!,secondandI have resolved!,second - B:
second,I have resolved!andsecond,I have resolved! - C:
I have resolved!,secondandsecond,I have resolved! - D:
second,I have resolved!andI have resolved!,second
答案:D
我们分析一下这个程序的执行过程:
- 调用
firstFunction(),遇到myPromise()返回的这个Promise解决后的任务,将其加入微任务队列中- 继续向下执行,输出
"second",firstFunction()调用结束,退出- 调用
secondFunction()(这也是宏任务),遇到await关键字,将恢复函数运行的任务以及后面的myPromise()返回的这个Promise解决值加入微任务队列,函数退出- 宏任务队列空,开始执行微任务队列
- 首先解决第一个
Promise,打印"I have resolved!"。- 然后恢复
secondFunction()的运行,此时await已取回Promise解决值,打印"I have resolved!"。- 最后打印
"second"。
# 103. 输出什么? Set 集合+ 运算符
const set = new Set()
set.add(1)
set.add("Lydia")
set.add({ name: "Lydia" })
for (let item of set) {
console.log(item + 2)
}
2
3
4
5
6
7
8
9
- A:
3,NaN,NaN - B:
3,7,NaN - C:
3,Lydia2,[Object object]2 - D:
"12",Lydia2,[Object object]2
答案:C
Set集合在遍历时会按添加顺序遍历。对于
1 + 2,+运算符进行数学运算,返回3。对于
"Lydia" + 2,+运算符进行字符串拼接,返回"Lydia2"。对于
{ name: "Lydia" } + 2,如果+运算符的两个操作数都不是数值,+运算符则会执行字符串拼接,两侧操作数调用toString方法。对象调用toString()后是"[object Object]",因此返回"[object Object]2"。经试验,对象在浏览器和 node 的
toString()结果都是"[object Object]"而非"[Object object]"。
# 104. 结果是什么? Promise复习
Promise.resolve(5)
- A:
5 - B:
Promise {<pending>: 5} - C:
Promise {<fulfilled>: 5} - D:
Error
答案:C
静态方法
Promise.resolve()返回一个解决状态的Promise。它可以传递解决值,但要通过.then()方法或await关键字才能取得。事实上,
Promise.resolve(5)等同于:new Promise((resolve, reject) => { resolve(5); })1
2
3只是写起来更为简略了。
# 105. 输出什么? 默认参数对象引用
function compareMembers(person1, person2 = person) {
if (person1 !== person2) {
console.log("Not the same!")
} else {
console.log("They are the same!")
}
}
const person = { name: "Lydia" }
compareMembers(person)
2
3
4
5
6
7
8
9
10
11
- A:
Not the same! - B:
They are the same! - C:
ReferenceError - D:
SyntaxError
答案:B
只传入一个参数后,
person1和person2都指向person所代表的对象,用===比较它们时,比较的是它们的引用,返回true。
# 106. 输出什么? 对象属性
const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
}
const colors = ["pink", "red", "blue"]
console.log(colorConfig.colors[1])
2
3
4
5
6
7
8
9
10
11
- A:
true - B:
false - C:
undefined - D:
TypeError
答案:D
JS 中获取对象的属性可以使用点表示法或括号表示法,例如
a.b或a["b"],这两者优先级相同。因此上述代码中,首先获取
colorConfig.colors得到undefined,再尝试访问undefined[1]时抛出TypeError: Cannot read property '1' of undefined。要想返回预期的结果,可以使用
colorConfig[colors[1]]。
# 107. 输出什么? 字符串
console.log('❤️' === '❤️')
- A:
true - B:
false
答案:A
与其他语言不同,字符串在 JS 中是基本类型,基本类型只比较值是否相同,因此返回
true。
# 108. 哪些方法修改了原数组? 数组
const emojis = ['✨', '🥑', '😍']
emojis.map(x => x + '✨')
emojis.filter(x => x !== '🥑')
emojis.find(x => x !== '🥑')
emojis.reduce((acc, cur) => acc + '✨')
emojis.slice(1, 2, '✨')
emojis.splice(1, 2, '✨')
2
3
4
5
6
7
8
- A:
All of them - B:
mapreduceslicesplice - C:
mapslicesplice - D:
splice
答案:D
map、filter均返回一个新数组。find返回第一个满足条件的元素本身。reduce返回一个数组逐一计算后压缩的值。slice返回原数组的切片(实际上只要两个参数),不改变原数组。splice将原数组特定位置删除和添加一定的元素,改变了原数组。
# 109. 输出什么? 字符串对象引用
const food = ['🍕', '🍫', '🥑', '🍔']
const info = { favoriteFood: food[0] }
info.favoriteFood = '🍝'
console.log(food)
2
3
4
5
6
- A:
['🍕', '🍫', '🥑', '🍔'] - B:
['🍝', '🍫', '🥑', '🍔'] - C:
['🍝', '🍕', '🍫', '🥑', '🍔'] - D:
ReferenceError
答案:A
字符串是基本类型,当改变
info的favorite属性时,food[0]并没有被改变。因此返回原数组。第 46 题与此相似但有不同之处。它是两个地方引用了同一个对象,两者之间并没有关联,当其中一个地方改变引用时,另一个地方依然保持对原对象的引用。
# 110. 这个函数干了什么? JSON
JSON.parse()
- A: Parses JSON to a JavaScript value
- B: Parses a JavaScript object to JSON
- C: Parses any JavaScript value to JSON
- D: Parses JSON to a JavaScript object only
答案:A
JSON.parse()可以将JSON字符串转为 JS 值。注意,JSON本质上是字符串,是 JS 对象的字符串表示形式。
# 111. 输出什么? 变量声明
let name = 'Lydia'
function getName() {
console.log(name)
let name = 'Sarah'
}
getName()
2
3
4
5
6
7
8
- A: Lydia
- B: Sarah
- C:
undefined - D:
ReferenceError
答案:D
我们可能认为
console.log(name)中的name是外部的name。但是,let声明的变量会进行提升并形成 暂时性死区。函数中的let name被提升到console.log(name)之前,这时 JS 认为这里的name是函数内部的name,会抛出ReferenceError: Cannot access 'name' before initialization。
# 112. 输出什么? 生成器复习
function* generatorOne() {
yield ['a', 'b', 'c'];
}
function* generatorTwo() {
yield* ['a', 'b', 'c'];
}
const one = generatorOne()
const two = generatorTwo()
console.log(one.next().value)
console.log(two.next().value)
2
3
4
5
6
7
8
9
10
11
12
13
- A:
aanda - B:
aandundefined - C:
['a', 'b', 'c']anda - D:
aand['a', 'b', 'c']
答案:C
yield可以暂停生成器函数的执行,并将yield表达式的值传递给迭代对象。因此输出['a', 'b', 'c']。
yield*也会暂停生成器函数的执行,不同的是,生成器这时每次yield的值是yield*对其后面的表达式(可迭代对象或生成器)进行yield的值。因此输出数组['a', 'b', 'c']的第一个yield值即'a'。对
one和two依次迭代会得到:console.log(one.next().value) // ['a', 'b', 'c'] console.log(one.next().value) // undefined console.log(two.next().value) // 'a' console.log(two.next().value) // 'b' console.log(two.next().value) // 'c' console.log(two.next().value) // undefined1
2
3
4
5
6
7
# 113. 输出什么? 立即执行函数字符串模板
console.log(`${(x => x)('I love')} to program`)
- A:
I love to program - B:
undefined to program - C:
${(x => x)('I love') to program - D:
TypeError
答案:A
字符串模板内是一个立即执行函数,返回结果为
'I love',插值后得到结果。
# 114. 将会发生什么? 字符串待补充
let config = {
alert: setInterval(() => {
console.log('Alert!')
}, 1000)
}
config = null
2
3
4
5
6
7
- A:
setInterval的回调不会被调用 - B:
setInterval的回调被调用一次 - C:
setInterval的回调仍然会被每秒钟调用 - D: 我们从没调用过
config.alert(), config 为null
答案:C
一般情况下,如果对象被赋值为
null,对象会被垃圾回收,因为没有指向对象的引用了。之前遇到过的一些例外是,如果两个地方引用了同一个对象,当其中一个地方赋值为null时,另一个地方依然保持对原对象的引用,因此对象不会被回收。而
setInterval里的回调函数的上下文被绑定到了config对象上,即使config被置为null,但对象不会被回收,因此回调依然会被调用。
# 115. 哪一个方法会返回 'Hello world!'?Map 集合对象引用
const myMap = new Map()
const myFunc = () => 'greeting'
myMap.set(myFunc, 'Hello world!')
//1
myMap.get('greeting')
//2
myMap.get(myFunc)
//3
myMap.get(() => 'greeting')
2
3
4
5
6
7
8
9
10
11
- A: 1
- B: 2
- C: 2 and 3
- D: All of them
答案:B
myMap里添加了一个键值对,键是myFunc即() => 'greeting'函数,值是'Hello world!'。1 的键是
'greeting'而非() => 'greeting',因此无法取得。3 的键虽然也是() => 'greeting',但它与myFunc不是一个对象,因此无法取得。
# 116. 输出什么? 默认参数扩展运算符
const person = {
name: "Lydia",
age: 21
}
const changeAge = (x = { ...person }) => x.age += 1
const changeAgeAndName = (x = { ...person }) => {
x.age += 1
x.name = "Sarah"
}
changeAge(person)
changeAgeAndName()
console.log(person)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- A:
{name: "Sarah", age: 22} - B:
{name: "Sarah", age: 23} - C:
{name: "Lydia", age: 22} - D:
{name: "Lydia", age: 23}
答案:C
changeAge()传入了参数,因此改变的x.age就是person.age。
changeAgeAndName()没有传入参数,x使用默认值{ ...person },也就是person的复制品,它不会修改原对象person。
# 117. 下面哪个选项将会返回 6? 扩展运算符
function sumValues(x, y, z) {
return x + y + z;
}
2
3
- A:
sumValues([...1, 2, 3]) - B:
sumValues([...[1, 2, 3]]) - C:
sumValues(...[1, 2, 3]) - D:
sumValues([1, 2, 3])
答案:C
扩展运算符可以拆开可迭代对象,例如将
[1, 2, 3]拆成1, 2, 3,它们可以传递给函数做参数。
# 118. 输出什么? 赋值
let num = 1;
const list = ["🥳", "🤠", "🥰", "🤪"];
console.log(list[(num += 1)]);
2
3
4
- A:
🤠 - B:
🥰 - C:
SyntaxError - D:
ReferenceError
答案:B
赋值表达式的返回值是赋值结果,因此
(num += 1)的值就是num的结果2,因此返回list[2]。
# 119. 输出什么? ?. 操作符
const person = {
firstName: "Lydia",
lastName: "Hallie",
pet: {
name: "Mara",
breed: "Dutch Tulip Hound"
},
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(person.pet?.name);
console.log(person.pet?.family?.name);
console.log(person.getFullName?.());
console.log(member.getLastName?.());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- A:
undefinedundefinedundefinedundefined - B:
MaraundefinedLydia HallieReferenceError - C:
MaranullLydia Hallienull - D:
nullReferenceErrornullReferenceError
答案:B
ES10 提出可选链操作符
?.,可以防止在比较长的属性链上(如a.b.c),出现对undefined或null求属性的情况,类似于以前a.b && a.b.c或a.b ? a.b.c : undefined的写法。在值不为undefined或null时,?.相当于.,可以获得属性值,但当遇到undefined或null时,表达式会短路并返回undefined。?.还可用于函数或数组,当对象的函数名属性不为undefined或null时,?.()相当于(),?.[]相当于[]。因此对于
person.pet?.name,输出person.pet.name即"Mara"。对于person.pet?.family?.name,由于person.pet.family为undefined,输出undefined。对于person.getFullName?.(),输出person.getFullName()即Lydia Hallie。对于member.getLastName?.(),由于member没有定义,抛出ReferenceError: member is not defined。
# 120. 输出什么? 数组 indexOf
const groceries = ["banana", "apple", "peanuts"];
if (groceries.indexOf("banana")) {
console.log("We have to buy bananas!");
} else {
console.log(`We don't have to buy bananas!`);
}
2
3
4
5
6
7
- A: We have to buy bananas!
- B: We don't have to buy bananas
- C:
undefined - D:
1
答案:B
数组的
indexOf方法返回所找元素第一次在数组出现的索引。groceries.indexOf("banana")返回0,0被认为是 falsy,因此输出"We don't have to buy bananas!"。如果想要通过
if判断元素是否在数组中出现,可以用if (groceries.indexOf("banana") > -1),因为indexOf未找到时返回-1。
# 121. 输出什么? getter-setter复习
const config = {
languages: [],
set language(lang) {
return this.languages.push(lang);
}
};
console.log(config.language);
2
3
4
5
6
7
8
- A:
function language(lang) { this.languages.push(lang } - B:
0 - C:
[] - D:
undefined
答案:D
JS 可以为对象设置
getter和setter伪属性,在属性名前加get和set即可。当调用属性名时,getter函数起作用,返回getter的返回值。当给属性赋值时,setter函数起作用,执行相关操作。本题中,虽然
setter返回了值,但config.language这个操作是无法返回其值的(因为它调用的是getter),所以这里返回undefined。下面的例子可以帮助理解
getter和setter的调用:const obj = { a: 1, get b() { console.log('getter called'); return this.a * 3; }, set b(value) { console.log('setter called'); this.a = value / 3; } } console.log(obj.b); // getter called, 3 obj.b = 30; // setter called console.log(obj.a); // 101
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 122. 输出什么? typeof逻辑非运算符
const name = "Lydia Hallie";
console.log(!typeof name === "object");
console.log(!typeof name === "string");
2
3
4
- A:
falsetrue - B:
truefalse - C:
falsefalse - D:
truetrue
答案:C
typeof name为"string"。但是!逻辑非运算符!比全等运算符===具有更高的优先级,它先与typeof name结合,返回!typeof name即!"string"即false,因此false与一个字符串做全等判断,始终返回false。要想判断
name的属性,应该使用typeof name !== "object"这样的形式。
# 123. 输出什么? 箭头函数
const add = x => y => z => {
console.log(x, y, z);
return x + y + z;
};
add(4)(5)(6);
2
3
4
5
6
- A:
456 - B:
654 - C:
4functionfunction - D:
undefinedundefined6
答案:A
add是一个函数,它的参数是x,返回值是一个函数,记为f1。f1的参数是y,返回值是一个函数,记为f2。f2的参数是z,函数体是大括号里的内容。因此
add(4)(5)(6)是依次调用了add(4)、f1(5)、f2(6),因此输出4 5 6。注意,
f2函数体是可以访问x和y的,因为函数在其作用域内。
# 124. 输出什么?async-await生成器复习
async function* range(start, end) {
for (let i = start; i <= end; i++) {
yield Promise.resolve(i);
}
}
(async () => {
const gen = range(1, 3);
for await (const item of gen) {
console.log(item);
}
})();
2
3
4
5
6
7
8
9
10
11
12
- A:
Promise {1}Promise {2}Promise {3} - B:
Promise {<pending>}Promise {<pending>}Promise {<pending>} - C:
123 - D:
undefinedundefinedundefined
答案:C
对于
gen,它是一个依次生成Promise {1}、Promise {2}、Promise {3}的生成器。对于
for await ... of的迭代,由于函数外没有宏任务,所以它依次完成3个微任务,即取得三个Promise的解决值作为item,然后循环体打印。如果函数外还有宏任务,就会先执行宏任务,例如:
console.log(1); (async () => { console.log(2); const gen = range(1, 3); console.log(3); for await (const item of gen) { console.log(4, item); } console.log(5); })(); console.log(6); // 输出顺序:1 => 2 => 3 => 6 => 4 1 => 4 2 => 4 3 => 51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 125. 输出什么? 解构函数参数
const myFunc = ({ x, y, z }) => {
console.log(x, y, z);
};
myFunc(1, 2, 3);
2
3
4
5
- A:
123 - B:
{1: 1}{2: 2}{3: 3} - C:
{ 1: undefined }undefinedundefined - D:
undefinedundefinedundefined
答案:D
函数只有一个形参,多余传进的参数会被忽略,实际上只把
1传给了{ x, y, z }。
{ x, y, z }希望被一个有x、y、z三个属性的对象传递,但1没有这三个属性,因此都返回undefined。如果我们传递的是
1有的属性,则会输出,例如:const myFunc = ({ x, y, toString: z }) => { console.log(x, y, z); }; myFunc(1, 2, 3); // undefined undefined [Function: toString]1
2
3
4
5
# 126. 输出什么? Intl复习
function getFine(speed, amount) {
const formattedSpeed = new Intl.NumberFormat(
'en-US',
{ style: 'unit', unit: 'mile-per-hour' }
).format(speed)
const formattedAmount = new Intl.NumberFormat(
'en-US',
{ style: 'currency', currency: 'USD' }
).format(amount)
return `The driver drove ${formattedSpeed} and has to pay ${formattedAmount}`
}
console.log(getFine(130, 300))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- A: The driver drove 130 and has to pay 300
- B: The driver drove 130 mph and has to pay $300.00
- C: The driver drove undefined and has to pay undefined
- D: The driver drove 130.00 and has to pay 300.00
答案:B
Intl是 JS 中的一个国际化类,可以对数字、日期等进行格式化。
Intl.NumberFormat()是一个构造器,用来实例化一个数字格式化工具对象,它调用format方法就可以格式化一个数字,返回一个字符串。
Intl.NumberFormat()有两个参数,分别是locales和options:
locales是表示地区的字符串,如"en-us"、zh-Hans-CN。options是表示选项的对象,包含"style"、"decimal"、"currency"、"percent"、"unit"等属性。第一个例子中,130 的
format结果为单位,即"130 mph";第二个例子中,300的format结果为货币,即"$300.00"。
# 127. 输出什么? 解构
const spookyItems = ["👻", "🎃", "🕸"];
({ item: spookyItems[3] } = { item: "💀" });
console.log(spookyItems);
2
3
4
- A:
["👻", "🎃", "🕸"] - B:
["👻", "🎃", "🕸", "💀"] - C:
["👻", "🎃", "🕸", { item: "💀" }] - D:
["👻", "🎃", "🕸", "[object Object]"]
答案:B
第二行是一个解构语法,相当于
spookyItems[3] = "💀",因此就是对数组元素赋值,更改了原数组。
# 128. 输出什么? NaN复习
const name = "Lydia Hallie";
const age = 21;
console.log(Number.isNaN(name));
console.log(Number.isNaN(age));
console.log(isNaN(name));
console.log(isNaN(age));
2
3
4
5
6
7
8
- A:
truefalsetruefalse - B:
truefalsefalsefalse - C:
falsefalsetruefalse - D:
falsetruefalsetrue
答案:C
Number.isNaN()方法是 ES6 新加的,它首先判断是否为number类型,对于非number一律返回false;对于number类型再判断是否是NaN。全局的
isNaN()则会先将参数转换为number类型,再判断是否是NaN。因此字符串"Lydia Hallie"转为Number类型为NaN,返回true。
# 129. 输出什么? 变量声明
const randomValue = 21;
function getInfo() {
console.log(typeof randomValue);
const randomValue = "Lydia Hallie";
}
getInfo();
2
3
4
5
6
7
8
- A:
"number" - B:
"string" - C:
undefined - D:
ReferenceError
答案:D
同 第 111 题。
# 130. 输出什么? async-awaittry-catch
const myPromise = Promise.resolve("Woah some cool data");
(async () => {
try {
console.log(await myPromise);
} catch {
throw new Error(`Oops didn't work`);
} finally {
console.log("Oh finally!");
}
})();
2
3
4
5
6
7
8
9
10
11
- A:
Woah some cool data - B:
Oh finally! - C:
Woah some cool dataOh finally! - D:
Oops didn't workOh finally!
答案:C
当函数运行到
await时,函数退出运行并等待Promise完成,由于没有其他宏任务(catch和finally都只能等待try块完成才能进行),所以函数马上恢复运行并输出Promise解决值"Woah some cool data"。catch没有捕捉到错误,不执行,执行finally块,即输出"Oh finally!"。
# 131. 输出什么? 数组 flat复习
const emojis = ["🥑", ["✨", "✨", ["🍕", "🍕"]]];
console.log(emojis.flat(1));
2
3
- A:
['🥑', ['✨', '✨', ['🍕', '🍕']]] - B:
['🥑', '✨', '✨', ['🍕', '🍕']] - C:
['🥑', ['✨', '✨', '🍕', '🍕']] - D:
['🥑', '✨', '✨', '🍕', '🍕']
答案:B
flat是数组新的方法,它可以使一个数组扁平化,也就是数组嵌套数组时,将内层数组拆开,它的使用方法如下:
flat()接收一个参数depth,表示拍平层数,当depth = Infinity时flat将数组直接拍平为一维数组,当depth省略时默认只拍平一层flat()会自动忽略数组中的空位
扩展:
利用
reduce实现自己的flatten函数:const flatten = (arr, depth) => { if (depth === 0) return arr; return arr.reduce((acc, val) => { if (Array.isArray(val)) { return acc.concat(flatten(val, depth - 1)); } return acc.concat(val); }, []); }1
2
3
4
5
6
7
8
9
# 132. 输出什么? this 指向对象引用
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
}
}
const counterOne = new Counter();
counterOne.increment();
counterOne.increment();
const counterTwo = counterOne;
counterTwo.increment();
console.log(counterOne.count);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- A:
0 - B:
1 - C:
2 - D:
3
答案:D
counterOne和counterTwo指向同一个Counter对象,counterTwo对this.count的操作会影响到counterOne。
# 133. 输出什么? async-awaitPromise复习
const myPromise = Promise.resolve(Promise.resolve("Promise!"));
function funcOne() {
myPromise.then(res => res).then(res => console.log(res));
setTimeout(() => console.log("Timeout!"), 0);
console.log("Last line!");
}
async function funcTwo() {
const res = await myPromise;
console.log(await res);
setTimeout(() => console.log("Timeout!"), 0);
console.log("Last line!");
}
funcOne();
funcTwo();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- A:
Promise! Last line! Promise! Last line! Last line! Promise! - B:
Last line! Timeout! Promise! Last line! Timeout! Promise! - C:
Promise! Last line! Last line! Promise! Timeout! Timeout! - D:
Last line! Promise! Promise! Last line! Timeout! Timeout!
答案:D
分析程序运行过程:
funcOne()运行,将myPromise的解决值(即"Promise!",原因参考扩展)加入微任务队列- 将
setTimeout回调加入新的宏任务队列,延时为立即执行- 输出
"Last line!",funcOne()结束运行- 主线程宏任务没有结束,
funcTwo()运行- 遇到
await,将myPromise的解决值(即"Promise!")加入微任务队列,函数退出运行- 此时主线程已经没有宏任务,开始执行微任务
- 微任务1:
funcOne()取得myPromise的解决值,即"Promise!",形成一个解决值为"Promise!"的Promise,继续调用then,加入微任务队列- 微任务2:
funcTwo()取得myPromise的解决值,即"Promise!",函数恢复运行,赋值给res;运行到await res,将res的值加入微任务队列,函数退出运行- 微任务3:
funcOne()取得新Promise的解决值,即"Promise!",将其打印,输出"Promise!"- 微任务4:
funcTwo()取得res的值,即"Promise!",函数恢复运行,将其打印,输出"Promise!"funcTwo()继续运行,将setTimeout回调加入新的宏任务队列,延时为立即执行- 输出
"Last line!",funcTwo()结束运行- 微任务队列已经清空,开始执行新的宏任务队列
- 执行
funcOne()中setTimeout的回调,输出"Timeout!"- 执行
funcTwo()中setTimeout的回调,输出"Timeout!"
扩展:
当
Promise.resolve()的参数仍是一个Promise时,等同于空包装,即Promise.resolve(Promise.resolve("Promise!"))全等于Promise.resolve("Promise!"),并且新包装的Promise保持内层Promise的状态。
# 134. 我们怎样才能在 index.js 中调用 sum.js? 中的 sum? 模块化复习
// sum.js
export default function sum(x) {
return x + x;
}
// index.js
import * as sum from "./sum";
2
3
4
5
6
7
- A:
sum(4) - B:
sum.sum(4) - C:
sum.default(4) - D: 默认导出不用
*来导入,只能具名导出
答案:C
import * as sum的语法会为index.js创建一个名为sum的对象,默认导出会作为sum的default属性,命名导出会作为sum对应属性名的属性。本题中,
sum是这样的一个对象:{ default: function sum(x) { return x + x } }1因此要使用
sum函数,就通过sum.default()调用即可。
# 135. 输出什么? Proxy复习
const handler = {
set: () => console.log("Added a new property!"),
get: () => console.log("Accessed a property!")
};
const person = new Proxy({}, handler);
person.name = "Lydia";
person.name;
2
3
4
5
6
7
8
9
- A:
Added a new property! - B:
Accessed a property! - C:
Added a new property!Accessed a property! - D: 没有任何输出
答案:C
代理是 ES6 新增的模式。通过
Proxy构造函数新增一个代理对象,代理对象可以通过代理对目标对象进行操作。
Proxy构造函数接收两个参数target和handler。target代表目标对象,对代理对象的任意操作可以应用到目标对象,如:const obj = { id: 'target' }; const handler = {}; const proxy = new Proxy(obj, handler); console.log(proxy.id); // target proxy.id = 'foo'; console.log(proxy.id); // foo console.log(obj.id); // foo console.log(obj instanceof Object); // true console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check console.log(proxy === obj); // false1
2
3
4
5
6
7
8
9
10由于
Proxy.prototype是undefined,所以无法通过instanceof得到proxy是否是Proxy的实例。
handler中可以设置捕获器,如set()和get()捕获器会在设置属性值和获取属性值时被调用,如:const obj = { id: 'target' }; const handler = { get: function (target, name) { console.log('get', name); return name in target ? target[name] : 'default'; }, set: function (target, name, value) { console.log('set', name, value); target[name] = value; } }; const proxy = new Proxy(obj, handler); console.log(proxy.id); // get id => target console.log(proxy.name); // get name => default proxy.id = 'proxy'; // set id proxy1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 136. 以下哪一项会对对象 person 有副作用? 对象 seal复习
const person = { name: "Lydia Hallie" };
Object.seal(person);
2
3
- A:
person.name = "Evan Bacon" - B:
person.age = 21 - C:
delete person.name - D:
Object.assign(person, { age: 21 })
答案:A
Object.seal()静态方法使对象 密封,它阻止像对象添加、删除属性,但可以更新或修改对象的现有属性。可以通过
Object.isSealed()静态方法检查对象是否被密封。它与
Object.freeze()类似,但freeze使对象也无法修改现有属性。
# 137. 以下哪一项会对对象 person 有副作用?对象 freeze复习
const person = {
name: "Lydia Hallie",
address: {
street: "100 Main St"
}
};
Object.freeze(person);
2
3
4
5
6
7
8
- A:
person.name = "Evan Bacon" - B:
delete person.address - C:
person.address.street = "101 Main St" - D:
person.pet = { name: "Mara" }
答案:C
参考 第 75 题,
Object.freeze()执行的是浅冻结,也就是person.address没有被冻结,我们仍可以对其内部的属性进行修改。
# 138. 输出什么? 默认参数
const add = x => x + x;
function myFunc(num = 2, value = add(num)) {
console.log(num, value);
}
myFunc();
myFunc(3);
2
3
4
5
6
7
8
- A:
24and36 - B:
2NaNand3NaN - C:
2Errorand36 - D:
24and3Error
答案:A
默认参数不仅可以是常量、变量,还可以是表达式。不传入参数时,相当于传入
2和add(2)即4,传入3时,相当于传入3和add(3)即6。
# 139. 输出什么? 类的成员复习
class Counter {
#number = 10
increment() {
this.#number++
}
getNum() {
return this.#number
}
}
const counter = new Counter()
counter.increment()
console.log(counter.#number)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- A:
10 - B:
11 - C:
undefined - D:
SyntaxError
答案:D
ES2020 中,
#前缀的变量是类的私有成员,在类的外部无法调用,尝试调用会抛出SyntaxError: Private field '#number' must be declared in an enclosing class。
# 140. 选择哪一个? 生成器复习
const teams = [
{ name: "Team 1", members: ["Paul", "Lisa"] },
{ name: "Team 2", members: ["Laura", "Tim"] }
];
function* getMembers(members) {
for (let i = 0; i < members.length; i++) {
yield members[i];
}
}
function* getTeams(teams) {
for (let i = 0; i < teams.length; i++) {
// ✨ SOMETHING IS MISSING HERE ✨
}
}
const obj = getTeams(teams);
obj.next(); // { value: "Paul", done: false }
obj.next(); // { value: "Lisa", done: false }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- A:
yield getMembers(teams[i].members) - B:
yield* getMembers(teams[i].members) - C:
return getMembers(teams[i].members) - D:
return yield getMembers(teams[i].members)
答案:B
首先观察生成器
getMembers(),它的作用是给定一个members数组,依次yield它的每个成员。再观察生成器
getTeams()和obj的输出结果,它输出了每个team的members里每个member的值。因此注释处应使用
yield*,表达式为getMembers(team[i].members),因为yield*对可迭代对象依次做yield,这样obj每次yield的就是yield*后面的每个yield值。
# 141. 输出什么? 默认参数
const person = {
name: "Lydia Hallie",
hobbies: ["coding"]
};
function addHobby(hobby, hobbies = person.hobbies) {
hobbies.push(hobby);
return hobbies;
}
addHobby("running", []);
addHobby("dancing");
addHobby("baking", person.hobbies);
console.log(person.hobbies);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- A:
["coding"] - B:
["coding", "dancing"] - C:
["coding", "dancing", "baking"] - D:
["coding", "running", "dancing", "baking"]
答案:C
对于
"running",传入的hobbies是[],不会影响原数组。而对于
"dancing"和"baking",hobbies都表示person.hobbies本身,因此会影响原数组。
# 142. 输出什么? 类的继承
class Bird {
constructor() {
console.log("I'm a bird. 🦢");
}
}
class Flamingo extends Bird {
constructor() {
console.log("I'm pink. 🌸");
super();
}
}
const pet = new Flamingo();
2
3
4
5
6
7
8
9
10
11
12
13
14
- A:
I'm pink. 🌸 - B:
I'm pink. 🌸I'm a bird. 🦢 - C:
I'm a bird. 🦢I'm pink. 🌸 - D: Nothing, we didn't call any method
答案:B
当使用
new创造Flamingo的实例时,调用Flamingo的构造函数,首先输出"I'm pink. 🌸",然后调用super()即父类Bird的构造函数,输出"I'm a bird. 🦢"。
# 143. 哪一个选项会导致报错? 数组变量声明
const emojis = ["🎄", "🎅🏼", "🎁", "⭐"];
/* 1 */ emojis.push("🦌");
/* 2 */ emojis.splice(0, 2);
/* 3 */ emojis = [...emojis, "🥂"];
/* 4 */ emojis.length = 0;
2
3
4
5
6
- A: 1
- B: 1 and 2
- C: 3 and 4
- D: 3
答案:D
emojis是const变量,它不可以被重新赋值。但是修改数组元素、长度都是可以的。
# 144. 我们需要向对象 person 添加什么,以致执行 [...person] 时获得形如 ["Lydia Hallie", 21] 的输出? Symbol生成器扩展运算符复习
const person = {
name: "Lydia Hallie",
age: 21
}
[...person] // ["Lydia Hallie", 21]
2
3
4
5
6
- A: 不需要,对象默认就是可迭代的
- B:
*[Symbol.iterator]() { for (let x in this) yield* this[x] } - C:
*[Symbol.iterator]() { yield* Object.values(this) } - D:
*[Symbol.iterator]() { for (let x in this) yield this }
答案:C
对象默认是不可迭代的。想要其迭代,可为其添加
Symbol.iterator方法,并赋一个生成器函数。观察到
...person的结果为"Lydia Hallie", 21,即生成器函数应yield对象的每一个属性值。使用yield* Object.values(this)即可实现。如果使用 B 的写法,应把
yield*改为yield。
# 145. 输出什么? forEach真值假值
let count = 0;
const nums = [0, 1, 2, 3];
nums.forEach(num => {
if (num) count += 1
})
console.log(count)
2
3
4
5
6
7
8
- A: 1
- B: 2
- C: 3
- D: 4
答案:C
只有
0被认为是 falsy,其他三个数都被认为是 truthy,因此返回3。
# 146. 输出什么? ?. 操作符
function getFruit(fruits) {
console.log(fruits?.[1]?.[1])
}
getFruit([['🍊', '🍌'], ['🍍']])
getFruit()
getFruit([['🍍'], ['🍊', '🍌']])
2
3
4
5
6
7
- A:
null,undefined, 🍌 - B:
[],null, 🍌 - C:
[],[], 🍌 - D:
undefined,undefined, 🍌
答案:D
参考 第 119 题。
# 147. 输出什么? 对象引用
class Calc {
constructor() {
this.count = 0
}
increase() {
this.count ++
}
}
const calc = new Calc()
new Calc().increase()
console.log(calc.count)
2
3
4
5
6
7
8
9
10
11
12
13
14
- A:
0 - B:
1 - C:
undefined - D:
ReferenceError
答案:A
calc后的new Calc()和下一行的new Calc()并不是同一个对象。calc的count在原型链上并没有被分享出去,而是属于它自己,因此没有被修改。
# 148. 输出什么? 解构对象 assgin
const user = {
email: "e@mail.com",
password: "12345"
}
const updateUser = ({ email, password }) => {
if (email) {
Object.assign(user, { email })
}
if (password) {
user.password = password
}
return user
}
const updatedUser = updateUser({ email: "new@email.com" })
console.log(updatedUser === user)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- A:
false - B:
true - C:
TypeError - D:
ReferenceError
答案:B
函数
updateUser的目的是修改user中的password属性,并返回user,因此updatedUser和user是同一个对象。
Object.assign将多个对象合并到第一个对象,有同名属性将覆盖,因此Object.assign(user, { email })等同于user.email = email。
# 149. 输出什么? 数组
const fruit = ['🍌', '🍊', '🍎']
fruit.slice(0, 1)
fruit.splice(0, 1)
fruit.unshift('🍇')
console.log(fruit)
2
3
4
5
6
7
- A:
['🍌', '🍊', '🍎'] - B:
['🍊', '🍎'] - C:
['🍇', '🍊', '🍎'] - D:
['🍇', '🍌', '🍊', '🍎']
答案:C
slice返回原数组的切片,不改变原数组。
splice将数组中的元素删除并替换,splice(0, 1)从数组0位置开始删除1个元素。
unshift向数组前端添加元素。
# 150. 输出什么? 扩展运算符对象属性
const animals = {};
let dog = { emoji: '🐶' }
let cat = { emoji: '🐈' }
animals[dog] = { ...dog, name: "Mara" }
animals[cat] = { ...cat, name: "Sara" }
console.log(animals[dog])
2
3
4
5
6
7
8
- A:
{ emoji: "🐶", name: "Mara" } - B:
{ emoji: "🐈", name: "Sara" } - C:
undefined - D:
ReferenceError
答案:B
首先
...扩展运算符将dog、cat的属性拆开并添加到新对象中。由于
dog、cat是对象,它们作为animals的属性都会转为字符串"[object Object]",所以两步操作其实都是对animals["[object Object]"]赋值,打印的也是animals["[object Object]"],最终输出{ emoji: "🐈", name: "Sara" }。
# 151. 输出什么? this 指向
const user = {
email: "my@email.com",
updateEmail: email => {
this.email = email
}
}
user.updateEmail("new@email.com")
console.log(user.email)
2
3
4
5
6
7
8
9
- A:
my@email.com - B:
new@email.com - C:
undefined - D:
ReferenceError
答案:A
updateEmail是一个箭头函数,它的this应指向作用域上下文的this,即全局对象。因此无论如何调用,updateEmail都只会改变全局对象的obj的
# 152. 输出什么? async-awaitPromise复习
const promise1 = Promise.resolve('First')
const promise2 = Promise.resolve('Second')
const promise3 = Promise.reject('Third')
const promise4 = Promise.resolve('Fourth')
const runPromises = async () => {
const res1 = await Promise.all([promise1, promise2])
const res2 = await Promise.all([promise3, promise4])
return [res1, res2]
}
runPromises()
.then(res => console.log(res))
.catch(err => console.log(err))
2
3
4
5
6
7
8
9
10
11
12
13
14
- A:
[['First', 'Second'], ['Fourth']] - B:
[['First', 'Second'], ['Third', 'Fourth']] - C:
[['First', 'Second']] - D:
'Third'
答案:D
Promise.all()静态方法返回一个新Promise,新Promise等待内部的所有Promise解决后再解决。如果有一个Promise为rejected,则最终返回以它为reason的Promise <rejected>。如果所有Promise都resolved,返回的Promise解决值为所有Promise解决值按迭代顺序的排列的数组。本题中,
Promise.all([promise1, promise2])应返回一个resolved的Promise {['First', 'Second']},res1为解决值['First, Second']。Promise.all([promise3, promise4])应返回一个rejected的Promise <rejected> {'Third'}。由于
await获取到了一个rejected的Promise,它将 直接结束执行 并传递这个Promise,因此runPromises()返回的是Promise { <rejected> 'Third' }。因此,这个拒绝值
'Third'被下面的catch捕获,输出'Third'。
# 153. 哪个作为 method 的值可以打印 { name: "Lydia", age: 22 }? 对象复习
const keys = ["name", "age"]
const values = ["Lydia", 22]
const method = /* ?? */
Object[method](keys.map((_, i) => {
return [keys[i], values[i]]
})) // { name: "Lydia", age: 22 }
2
3
4
5
6
7
- A:
entries - B:
values - C:
fromEntries - D:
forEach
答案:C
Object.fromEntries()静态方法可以返回一个对象,它接收一个二维数组,二维数组的每一个子数组都是键值对,形如[["name", "Lydia"], ["age", 22]]。
# 154. 输出什么? 解构正则表达式默认参数真值假值
const createMember = ({ email, address = {}}) => {
const validEmail = /.+\@.+\..+/.test(email)
if (!validEmail) throw new Error("Valid email pls")
return {
email,
address: address ? address : null
}
}
const member = createMember({ email: "my@email.com" })
console.log(member)
2
3
4
5
6
7
8
9
10
11
12
- A:
{ email: "my@email.com", address: null } - B:
{ email: "my@email.com" } - C:
{ email: "my@email.com", address: {} } - D:
{ email: "my@email.com", address: undefined }
答案:C
参数传入函数后被解构,即
email = "my@email.com"和address = {}。
test后返回true。{}被认为是 truthy,因此返回本身。
# 155. 输出什么? typeof逻辑非运算符
let randomValue = { name: "Lydia" }
randomValue = 23
if (!typeof randomValue === "string") {
console.log("It's not a string!")
} else {
console.log("Yay it's a string!")
}
2
3
4
5
6
7
8
- A:
It's not a string! - B:
Yay it's a string! - C:
TypeError - D:
undefined
答案:B
同 第 122 题。
typeof randomValue为"number"。但是!逻辑非运算符!比全等运算符===具有更高的优先级,它先与typeof randomValue结合,返回!typeof randomValue即!"number"即false,因此false与一个字符串做全等判断,始终返回false。