乐趣区

JavaScript-进阶问题列表

作者:Lydia Hallie

翻译:疯狂的技术宅

原文:https://github.com/lydiahalli…

我在我的 Instagram 上每天都会发布 JavaScript 的选择题,并且同时也会在这个仓库中发布。

从基础到进阶,测试你有多了解 JavaScript,刷新你的知识,或者帮助你的 coding 面试!
我每周都会在这个仓库下更新新的问题。

答案在问题的下方。祝你好运


1. 输出是什么?

function sayHi() {console.log(name)
  console.log(age)
  var name = 'Lydia'
  let age = 21
}

sayHi()
  • A: Lydiaundefined
  • B: LydiaReferenceError
  • C: ReferenceError21
  • D: undefinedReferenceError

答案: D

在函数内部,我们首先通过 var 关键字声明了 name 变量。这意味着变量被提升了(内存空间在创建阶段就被设置好了),直到程序运行到定义变量位置之前默认值都是 undefined。因为当我们打印 name 变量时还没有执行到定义变量的位置,因此变量的值保持为 undefined

通过 letconst 关键字声明的变量也会提升,但是和 var 不同,它们不会被初始化。在我们声明(初始化)之前是不能访问它们的。这个行为被称之为暂时性死区。当我们试图在声明之前访问它们时,JavaScript 将会抛出一个 ReferenceError 错误。


2. 输出是什么?

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 都有一个新值,并且每个值都在循环内的作用域中。


3. 输出是什么?

const shape = {
  radius: 10,
  diameter() {return this.radius * 2},
  perimeter: () => 2 * Math.PI * this.radius}

shape.diameter()
shape.perimeter()
  • A: 2062.83185307179586
  • B: 20NaN
  • C: 2063
  • D: NaN63

答案: B

注意 diameter 的值是一个常规函数,但是 perimeter 的值是一个箭头函数。

对于箭头函数,this 关键字指向的是它当前周围作用域(简单来说是包含箭头函数的常规函数,如果没有常规函数的话就是全局对象),这个行为和常规函数不同。这意味着当我们调用 perimeter 时,this 不是指向 shape 对象,而是它的周围作用域(在例子中是 window)。

window 中没有 radius 这个属性,因此返回 undefined


4. 输出是什么?

+true;
!"Lydia";
  • A: 1false
  • B: falseNaN
  • C: falsefalse

答案: A

一元操作符加号尝试将 bool 转为 number。true 转换为 number 的话为 1false0

字符串 'Lydia' 是一个真值,真值取反那么就返回 false


5. 哪一个是无效的?

const bird = {size: 'small'}

const mouse = {
  name: 'Mickey',
  small: true
}
  • A: mouse.bird.size
  • B: mouse[bird.size]
  • C: mouse[bird["size"]]
  • D: 全部有效

答案: A

在 JavaScript 中,所有对象的 keys 都是字符串(除非对象是 Symbol)。尽管我们可能不会定义它们为字符串,但它们在底层总会被转换为字符串。

当我们使用括号语法时([]),JavaScript 会解释(或者 unboxes)语句。它首先看到第一个开始括号 [ 并继续前进直到找到结束括号 ]。只有这样,它才会计算语句的值。

mouse[bird.size]:首先计算 bird.size,这会得到 smallmouse["small"] 返回 true

然后使用点语法的话,上面这一切都不会发生。mouse 没有 bird 这个 key,这也就意味着 mouse.birdundefined。然后当我们使用点语法 mouse.bird.size 时,因为 mouse.birdundefined,这也就变成了 undefined.size。这个行为是无效的,并且会抛出一个错误类似 Cannot read property "size" of undefined


6. 输出是什么?

let c = {greeting: 'Hey!'}
let d

d = c
c.greeting = 'Hello'
console.log(d.greeting)
  • A: Hello
  • B: undefined
  • C: ReferenceError
  • D: TypeError

答案: A

在 JavaScript 中,当设置两个对象彼此相等时,它们会通过 引用 进行交互。

首先,变量 c 的值是一个对象。接下来,我们给 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)
  • A: true false true
  • B: false false true
  • C: true false false
  • D: false true true

答案: C

new Number() 是一个内建的函数构造器。虽然它看着像是一个 number,但它实际上并不是一个真实的 number:它有一堆额外的功能并且它是一个对象。

当我们使用 == 操作符时,它只会检查两者是否拥有相同的 。因为它们的值都是 3,因此返回 true

然后,当我们使用 === 操作符时,两者的值以及 类型 都应该是相同的。new Number() 是一个对象而不是 number,因此返回 false


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')
  • A: orange
  • B: purple
  • C: green
  • D: TypeError

答案: D

colorChange 是一个静态方法。静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon),并且不能传递给实例。因为 freddie 是一个实例,静态方法不能被实例使用,因此抛出了 TypeError 错误。


9. 输出是什么?

let greeting
greetign = {} // Typo!
console.log(greetign)
  • A: {}
  • B: ReferenceError: greetign is not defined
  • C: undefined

答案: A

代码打印出了一个对象,这是因为我们在全局对象上创建了一个空对象!当我们将 greeting 写错成 greetign 时,JS 解释器实际在上浏览器中将它视为 global.greetign = {}(或者 window.greetign = {})。

为了避免这个为题,我们可以使用 "use strict"。这能确保当你声明变量时必须赋值。


10. 当我们这么做时,会发生什么?

function bark() {console.log('Woof!')
}

bark.animal = 'dog'
  • A: 正常运行!
  • B: SyntaxError. 你不能通过这种方式给函数增加属性。
  • C: undefined
  • D: ReferenceError

答案: A

这在 JavaScript 中是可以的,因为函数是对象!(除了基本类型之外其他都是对象)

函数是一个特殊的对象。你写的这个代码其实不是一个实际的函数。函数是一个拥有属性的对象,并且属性也可被调用。


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());
  • A: TypeError
  • B: SyntaxError
  • C: Lydia Hallie
  • D: undefined undefined

答案: A

你不能像常规对象那样,给构造函数添加属性。如果你想一次性给所有实例添加特性,你应该使用原型。因此本例中,使用如下方式:

Person.prototype.getFullName = function () {return `${this.firstName} ${this.lastName}`;
}

这才会使 member.getFullName() 起作用。为什么这么做有益的?假设我们将这个方法添加到构造函数本身里。也许不是每个 Person 实例都需要这个方法。这将浪费大量内存空间,因为它们仍然具有该属性,这将占用每个实例的内存空间。相反,如果我们只将它添加到原型中,那么它只存在于内存中的一个位置,但是所有实例都可以访问它!


12. 输出是什么?

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)
  • A: Person {firstName: "Lydia", lastName: "Hallie"}undefined
  • B: Person {firstName: "Lydia", lastName: "Hallie"}Person {firstName: "Sarah", lastName: "Smith"}
  • C: Person {firstName: "Lydia", lastName: "Hallie"}{}
  • D:Person {firstName: "Lydia", lastName: "Hallie"}ReferenceError

答案: A

对于 sarah,我们没有使用 new 关键字。当使用 new 时,this 引用我们创建的空对象。当未使用 new 时,this 引用的是 全局对象(global object)。

我们说 this.firstName 等于 "Sarah",并且 this.lastName 等于 "Smith"。实际上我们做的是,定义了 global.firstName = 'Sarah'global.lastName = 'Smith'。而 sarah 本身是 undefined


13. 事件传播的三个阶段是什么?

  • A: Target > Capturing > Bubbling
  • B: Bubbling > Target > Capturing
  • C: Target > Bubbling > Capturing
  • D: Capturing > Target > Bubbling

答案: D

捕获 (capturing)阶段中,事件从祖先元素向下传播到目标元素。当事件达到 目标 (target)元素后, 冒泡(bubbling)才开始。


14. 所有对象都有原型。

  • A: true
  • B: false

答案: B

除了 基本对象(base object),所有对象都有原型。基本对象可以访问一些方法和属性,比如 .toString。这就是为什么你可以使用内置的 JavaScript 方法!所有这些方法在原型上都是可用的。虽然 JavaScript 不能直接在对象上找到这些方法,但 JavaScript 会沿着原型链找到它们,以便于你使用。


15. 输出是什么?

function sum(a, b) {return a + b}

sum(1, '2')
  • A: NaN
  • B: TypeError
  • C: "12"
  • D: 3

答案: C

JavaScript 是一种 动态类型语言 :我们不指定某些变量的类型。值可以在你不知道的情况下自动转换成另一种类型,这种类型称为 隐式类型转换(implicit type coercion)。Coercion 是指将一种类型转换为另一种类型。

在本例中,JavaScript 将数字 1 转换为字符串,以便函数有意义并返回一个值。在数字类型(1)和字符串类型('2')相加时,该数字被视为字符串。我们可以连接字符串,比如 "Hello" + "World",这里发生的是 "1" + "2",它返回 "12"


16. 输出是什么?

let number = 0
console.log(number++)
console.log(++number)
console.log(number)
  • A: 1 1 2
  • B: 1 2 2
  • C: 0 2 2
  • D: 0 1 2

答案: C

一元 后自增 运算符 ++

  1. 返回值(返回 0
  2. 值自增(number 现在是 1

一元 前自增 运算符 ++

  1. 值自增(number 现在是 2
  2. 返回值(返回 2

结果是 0 2 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`
  • 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})
  • A: You are an adult!
  • B: You are still an adult.
  • C: Hmm.. You don't have an age I guess

答案: C

在测试相等性时,基本类型通过它们的值(value)进行比较,而对象通过它们的引用(reference)进行比较。JavaScript 检查对象是否具有对内存中相同位置的引用。

题目中我们正在比较的两个对象不是同一个引用:作为参数传递的对象引用的内存位置,与用于判断相等的对象所引用的内存位置并不同。

这也是 {age: 18} === {age: 18}{age: 18} == {age: 18} 都返回 false 的原因。


19. 输出是什么?

function getAge(...args) {console.log(typeof args)
}

getAge(21)
  • A: "number"
  • B: "array"
  • C: "object"
  • D: "NaN"

答案: C

扩展运算符(...args)会返回实参组成的数组。而数组是对象,因此 typeof args 返回 "object"


20. 输出是什么?

function getAge() {
  'use strict'
  age = 21
  console.log(age)
}

getAge()
  • A: 21
  • B: undefined
  • C: ReferenceError
  • D: TypeError

答案: C

使用 "use strict",你可以确保不会意外地声明全局变量。我们从来没有声明变量 age,因为我们使用 "use strict",它将抛出一个引用错误。如果我们不使用 "use strict",它就会工作,因为属性 age 会被添加到全局对象中了。


21. 输出是什么?

const sum = eval('10*10+5')
  • A: 105
  • B: "105"
  • C: TypeError
  • D: "10*10+5"

答案: A

代码以字符串形式传递进来,eval 对其求值。如果它是一个表达式,就像本例中那样,它对表达式求值。表达式是 10 * 10 + 5。这将返回数字 105


22. cool_secret 可访问多长时间?

sessionStorage.setItem('cool_secret', 123)
  • A: 永远,数据不会丢失。
  • B: 当用户关掉标签页时。
  • C: 当用户关掉整个浏览器,而不只是关掉标签页。
  • D: 当用户关闭电脑时。

答案: B

关闭 tab 标签页 后,sessionStorage 存储的数据才会删除。

如果使用 localStorage,那么数据将永远在那里,除非调用了 localStorage.clear()


23. 输出是什么?

var num = 8
var num = 10

console.log(num)
  • A: 8
  • B: 10
  • C: SyntaxError
  • D: ReferenceError

答案: B

使用 var 关键字,你可以用相同的名称声明多个变量。然后变量将保存最新的值。

你不能使用 letconst 来实现这一点,因为它们是块作用域的。


24. 输出是什么?

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)
  • A: false true false true
  • B: false true true true
  • C: true true false true
  • D: true true true true

答案: C

所有对象的键(不包括 Symbol)在底层都是字符串,即使你自己没有将其作为字符串输入。这就是为什么 obj.hasOwnProperty('1') 也返回 true

对于集合,它不是这样工作的。在我们的集合中没有 '1'set.has('1') 返回 false。它有数字类型为 1set.has(1) 返回 true


25. 输出是什么?

const obj = {a: 'one', b: 'two', a: 'three'}
console.log(obj)
  • A: {a: "one", b: "two"}
  • B: {b: "two", a: "three"}
  • C: {a: "three", b: "two"}
  • D: SyntaxError

答案: C

如果你有两个名称相同的键,则键会被替换掉。它仍然位于第一个键出现的位置,但是值是最后出现那个键的值。


26. JavaScript 全局执行上下文为你做了两件事:全局对象和 this 关键字。

  • A: true
  • B: false
  • C: it depends

答案: A

基本执行上下文是全局执行上下文:它是代码中随处可访问的内容。


27. 输出是什么?

for (let i = 1; i < 5; i++) {if (i === 3) continue
  console.log(i)
}
  • A: 1 2
  • B: 1 2 3
  • C: 1 2 4
  • D: 1 3 4

答案: C

如果某个条件返回 true,则 continue 语句跳过本次迭代。


28. 输出是什么?

String.prototype.giveLydiaPizza = () => {return 'Just give Lydia pizza already!'}

const name = 'Lydia'

name.giveLydiaPizza()
  • A: "Just give Lydia pizza already!"
  • B: TypeError: not a function
  • C: SyntaxError
  • D: undefined

答案: A

String 是内置的构造函数,我们可以向它添加属性。我只是在它的原型中添加了一个方法。基本类型字符串被自动转换为字符串对象,由字符串原型函数生成。因此,所有 string(string 对象)都可以访问该方法!


29. 输出是什么?

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


30. 输出是什么?

const foo = () => console.log('First')
const bar = () => setTimeout(() => console.log('Second'))
const baz = () => console.log('Third')

bar()
foo()
baz()
  • A: First Second Third
  • B: First Third Second
  • C: Second First Third
  • D: Second Third First

答案: B

我们有一个 setTimeout 函数,并首先调用它。然而,它是最后打印日志的。

这是因为在浏览器中,我们不仅有运行时引擎,还有一个叫做 WebAPI 的东西。WebAPI 提供了 setTimeout 函数,也包含其他的,例如 DOM。

callback 推送到 WebAPI 后,setTimeout 函数本身 (但不是回调!) 将从栈中弹出。

现在,foo 被调用,打印 "First"

foo 从栈中弹出,baz 被调用. 打印 "Third"

WebAPI 不能随时向栈内添加内容。相反,它将回调函数推到名为 queue 的地方。

这就是事件循环开始工作的地方。一个 事件循环 查看栈和任务队列。如果栈是空的,它接受队列上的第一个元素并将其推入栈。

bar 被调用,打印 "Second",然后它被栈弹出。


31. 当点击按钮时,event.target 是什么?

<div onclick="console.log('first div')">
  <div onclick="console.log('second div')">
    <button onclick="console.log('button')">
      Click!
    </button>
  </div>
</div>
  • A: Outer div
  • B: Inner div
  • C: button
  • D: 一个包含所有嵌套元素的数组。

答案: C

导致事件的最深嵌套的元素是事件的 target。你可以通过 event.stopPropagation 来停止冒泡。


32. 当您单击该段落时,日志输出是什么?

<div onclick="console.log('div')">
  <p onclick="console.log('p')">
    Click here!
  
</div>
  • A: p div
  • B: div p
  • C: p
  • D: div

答案: A

如果我们点击 p,我们会看到两个日志:pdiv。在事件传播期间,有三个阶段:捕获、目标和冒泡。默认情况下,事件处理程序在冒泡阶段执行(除非将 useCapture 设置为 true)。它从嵌套最深的元素向外传播。


33. 输出是什么?

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 返回函数的 副本,但带有绑定上下文!它不是立即执行的。


34. 输出是什么?

function sayHi() {return (() => 0)()}

typeof sayHi()
  • A: "object"
  • B: "number"
  • C: "function"
  • D: "undefined"

答案: B

sayHi 方法返回的是立即执行函数 (IIFE) 的返回值. 此立即执行函数的返回值是 0,类型是 number

参考:只有 7 种内置类型:nullundefinedbooleannumberstringobjectsymbolfunction 不是一种类型,函数是对象,它的类型是object


35. 下面哪些值是 falsy?

0
new Number(0)
('')
(' ')
new Boolean(false)
undefined
  • A: 0, '', undefined
  • B: 0, new Number(0), '', new Boolean(false), undefined
  • C: 0, '', new Boolean(false), undefined
  • D: 全都是 of them are falsy

答案: A

只有 6 种 falsy 值:

  • undefined
  • null
  • NaN
  • 0
  • '' (empty string)
  • false

Function 构造函数, 比如 new Numbernew Boolean,是 truthy。


36. 输出是什么?

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)
  • A: [1, 2, 3, 7 x null, 11]
  • B: [1, 2, 3, 11]
  • C: [1, 2, 3, 7 x empty, 11]
  • D: SyntaxError

答案: C

当你为数组设置超过数组长度的值的时候,JavaScript 会创建名为 “empty slots” 的东西。它们的值实际上是 undefined。你会看到以下场景:

[1, 2, 3, 7 x empty, 11]

这取决于你的运行环境(每个浏览器,以及 node 环境,都有可能不同)


38. 输出是什么?

(() => {
  let x, y
  try {throw new Error()
  } catch (x) {(x = 1), (y = 2)
    console.log(x)
  }
  console.log(x)
  console.log(y)
})()
  • A: 1 undefined 2
  • B: undefined undefined undefined
  • C: 1 1 2
  • D: 1 undefined undefined

答案: A

catch 代码块接收参数 x。当我们传递参数时,这与之前定义的变量 x 不同。这个 x 是属于 catch 块级作用域的。

然后,我们将块级作用域中的变量赋值为 1,同时也设置了变量 y 的值。现在,我们打印块级作用域中的变量 x,值为 1

catch 块之外的变量 x 的值仍为 undefinedy 的值为 2。当我们在 catch 块之外执行 console.log(x) 时,返回 undefinedy 返回 2


39. JavaScript 中的一切都是?

  • A: 基本类型与对象
  • B: 函数与对象
  • C: 只有对象
  • D: 数字与对象

答案: A

JavaScript 只有基本类型和对象。

基本类型包括 boolean, null, undefined, bigint, number, string, symbol


40. 输出是什么?

[[0, 1], [2, 3]].reduce((acc, cur) => {return acc.concat(cur)
  },
  [1, 2]
)
  • A: [0, 1, 2, 3, 1, 2]
  • B: [6, 1, 2]
  • C: [1, 2, 0, 1, 2, 3]
  • D: [1, 2, 6]

答案: C

[1, 2]是初始值。初始值将会作为首次调用时第一个参数 acc 的值。在第一次执行时,acc 的值是 [1, 2]cur 的值是 [0, 1]。合并它们,结果为 [1, 2, 0, 1]
第二次执行,acc 的值是 [1, 2, 0, 1]cur 的值是 [2, 3]。合并它们,最终结果为 [1, 2, 0, 1, 2, 3]


41. 输出是什么?

!!null
!!''
!!1
  • A: false true false
  • B: false false true
  • C: false true true
  • D: true true false

答案: B

null 是 falsy。!null 的值是 true!true 的值是 false

"" 是 falsy。!"" 的值是 true!true 的值是 false

1 是 truthy。!1 的值是 false!false 的值是 true


42. setInterval 方法的返回值是什么?

setInterval(() => console.log('Hi'), 1000)
  • A: 一个唯一的 id
  • B: 该方法指定的毫秒数
  • C: 传递的函数
  • D: undefined

答案: A

setInterval 返回一个唯一的 id。此 id 可被用于 clearInterval 函数来取消定时。


43. 输出是什么?

[...'Lydia']
  • A: ["L", "y", "d", "i", "a"]
  • B: ["Lydia"]
  • C: [[], "Lydia"]
  • D: [["L", "y", "d", "i", "a"]]

答案: A

string 类型是可迭代的。扩展运算符将迭代的每个字符映射成一个元素。


44. 输出是什么?

function* generator(i) {
  yield i;
  yield i * 2;
}

const gen = generator(10);

console.log(gen.next().value);
console.log(gen.next().value);
  • A: [0, 10], [10, 20]
  • B: 20, 20
  • C: 10, 20
  • D: 0, 10 和 10, 20

答案: C

一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到 yield 关键字的时候,会生成 yield 后面的值。注意,生成器在这种情况下不 返回 (_return_) 值,而是 生成 (_yield_) 值。

首先,我们用 10 作为参数 i 来初始化生成器函数。然后使用 next() 方法一步步执行生成器。第一次执行生成器的时候,i的值为 10,遇到第一个yield 关键字,它要生成 i 的值。此时,生成器“暂停”,生成了10

然后,我们再执行 next() 方法。生成器会从刚才暂停的地方继续,这个时候 i 还是 10。于是我们走到了第二个yield 关键字处,这时候需要生成的值是 i*2i10,那么此时生成的值便是20。所以这道题的最终结果是10,20

45. 返回值是什么?

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,是为输出结果。


46. 输出是什么?

let person = {name: "Lydia"};
const members = [person];
person = null;

console.log(members);
  • A: null
  • B: [null]
  • C: [{}]
  • D: [{name: "Lydia"}]

答案: D

首先我们声明了一个拥有 name 属性的对象 person

然后我们又声明了一个变量 members. 将首个元素赋值为变量person。当设置两个对象彼此相等时,它们会通过 引用 进行交互。但是当你将引用从一个变量分配至另一个变量时,其实只是执行了一个 复制 操作。(注意一点,他们的引用 _并不相同_!)

接下来我们让 person 等于null

我们没有修改数组第一个元素的值,而只是修改了变量 person 的值, 因为元素(复制而来)的引用与 person 不同。members的第一个元素仍然保持着对原始对象的引用。当我们输出 members 数组时,第一个元素会将引用的对象打印出来。


47. 输出是什么?

const person = {
  name: "Lydia",
  age: 21
};

for (const item in person) {console.log(item);
}
  • A: {name: "Lydia"}, {age: 21}
  • B: "name", "age"
  • C: "Lydia", 21
  • D: ["name", "Lydia"], ["age", 21]

答案: B

for-in 循环中, 我们可以通过对象的 key 来进行迭代, 也就是这里的 nameage。在底层,对象的 key 都是字符串(如果他们不是 Symbol 的话)。在每次循环中,我们将 item 设定为当前遍历到的 key. 所以一开始,itemname,之后 item 输出的则是age


48. 输出是什么?

console.log(3 + 4 + "5");
  • A: "345"
  • B: "75"
  • C: 12
  • D: "12"

答案: B

当所有运算符的 优先级 相同时,计算表达式需要确定运算符的结合顺序,即从右到左还是从左往右。在这个例子中,我们只有一类运算符+,对于加法来说,结合顺序就死从左到右。

3 + 4首先计算,得到数字7.

由于类型的强制转换,7 + '5'的结果是 "75". JavaScript 将7 转换成了字符串,可以参考问题 15. 我们可以用 + 号把两个字符串连接起来。"7" + "5" 就得到了"75".


49. num的值是什么?

const num = parseInt("7*6", 10);
  • A: 42
  • B: "42"
  • C: 7
  • D: NaN

答案: C

只返回了字符串中第一个字母. 设定了 进制 后 (也就是第二个参数,指定需要解析的数字是什么进制: 十进制、十六机制、八进制、二进制等等……),parseInt 检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。

*就是不合法的数字字符。所以只解析到 "7",并将其解析为十进制的7. num 的值即为7.


50. 输出是什么?

[1, 2, 3].map(num => {if (typeof num === "number") return;
  return num * 2;
});
  • A: []
  • B: [null, null, null]
  • C: [undefined, undefined, undefined]
  • D: [3 x empty]

答案: C

对数组进行映射的时候,num就是当前循环到的元素. 在这个例子中,所有的映射都是 number 类型,所以 if 中的判断 typeof num === "number" 结果都是true.map 函数创建了新数组并且将函数的返回值插入数组。

但是,没有任何值返回。当函数没有返回任何值时,即默认返回undefined. 对数组中的每一个元素来说,函数块都得到了这个返回值,所以结果中每一个元素都是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);
  • A: {name: "Lydia"}, "1997"
  • B: {name: "Sarah"}, "1998"
  • C: {name: "Lydia"}, "1998"
  • D: {name: "Sarah"}, "1997"

答案: A

普通参数都是 传递的,而对象则不同,是 引用 传递。所以说,birthYear 是值传递,因为他是个字符串而不是对象。当我们对参数进行值传递时,会创建一份该值的 复制。(可以参考问题 46)

变量 birthYear 有一个对 "1997" 的引用,而传入的参数也有一个对 "1997" 的引用,但二者的引用并不相同。当我们通过给 year赋值 "1998" 来更新 year 的值的时候我们只是更新了 year(的引用)。此时birthYear 仍然是"1997".

person 是个对象。参数 member 引用与之 相同的 对象。当我们修改member 所引用对象的属性时,person的相应属性也被修改了, 因为他们引用了相同的对象. personname属性也变成了 "Lydia".


52. 输出是什么?

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'.


53. 输出是什么?

function Car() {
  this.make = "Lamborghini";
  return {make: "Maserati"};
}

const myCar = new Car();
console.log(myCar.make);
  • A: "Lamborghini"
  • B: "Maserati"
  • C: ReferenceError
  • D: TypeError

答案: B

返回属性的时候,属性的值等于 返回的 值,而不是构造函数中设定的值。我们返回了字符串 "Maserati",所以 myCar.make 等于"Maserati".


54. 输出是什么?

(() => {let x = (y = 10);
})();

console.log(typeof x);
console.log(typeof y);
  • A: "undefined", "number"
  • B: "number", "number"
  • C: "object", "number"
  • D: "number", "undefined"

答案: A

let x = y = 10; 是下面这个表达式的缩写:

y = 10;
let x = y;

我们设定 y 等于 10 时, 我们实际上增加了一个属性 y 给全局对象 (浏览器里的window, Nodejs 里的global)。在浏览器中,window.y 等于10.

然后我们声明了变量 x 等于 y, 也是10. 但变量是使用 let 声明的,它只作用于 _块级作用域_, 仅在声明它的块中有效;就是案例中的立即调用表达式 (IIFE)。使用typeof 操作符时, 操作值 x没有被定义:因为我们在 x 声明块的外部,无法调用它。这就意味着 x 未定义。未分配或是未声明的变量类型为 "undefined". console.log(typeof x) 返回"undefined".

而我们创建了全局变量 y,并且设定y 等于 10. 这个值在我们的代码各处都访问的到。y 已经被定义了,而且有一个 "number" 类型的值。console.log(typeof y)返回"number".


55. 输出是什么?

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();
  • 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 关键字删除对象的属性,对原型也是适用的。删除了原型的属性后,该属性在原型链上就不可用了。在本例中,函数 bark 在执行了 delete Dog.prototype.bark 后不可用, 然而后面的代码还在调用它。

当我们尝试调用一个不存在的函数时 TypeError 异常会被抛出。在本例中就是 TypeError: pet.bark is not a function,因为 pet.barkundefined.


56. 输出是什么?

const set = new Set([1, 1, 2, 3, 4]);

console.log(set);
  • A: [1, 1, 2, 3, 4]
  • B: [1, 2, 3, 4]
  • C: {1, 1, 2, 3, 4}
  • D: {1, 2, 3, 4}

答案: D

Set对象手机 独一无二 的值:也就是说同一个值在其中仅出现一次。

我们传入了数组[1, 1, 2, 3, 4],他有一个重复值1. 以为一个集合里不能有两个重复的值,其中一个就被移除了。所以结果是 {1, 2, 3, 4}.


57. 输出是什么?

// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from "./counter";

myCounter += 1;

console.log(myCounter);
  • A: 10
  • B: 11
  • C: Error
  • D: NaN

答案: C

引入的模块是 只读 的: 你不能修改引入的模块。只有导出他们的模块才能修改其值。

当我们给 myCounter 增加一个值的时候会抛出一个异常:myCounter是只读的,不能被修改。


58. 输出是什么?

const name = "Lydia";
age = 21;

console.log(delete name);
console.log(delete age);
  • A: false, true
  • B: "Lydia", 21
  • C: true, true
  • D: undefined, undefined

答案: A

delete操作符返回一个布尔值:true指删除成功,否则返回false. 但是通过 var, constlet 关键字声明的变量无法用 delete 操作符来删除。

name变量由 const 关键字声明,所以删除不成功: 返回 false. 而我们设定 age 等于 21 时, 我们实际上添加了一个名为 age 的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以 delete age 返回true.


59. 输出是什么?

const numbers = [1, 2, 3, 4, 5];
const [y] = numbers;

console.log(y);
  • A: [[1, 2, 3, 4, 5]]
  • B: [1, 2, 3, 4, 5]
  • C: 1
  • D: [1]

答案: C

我们可以通过解构赋值来解析来自对象的数组或属性的值,比如说:

[a, b] = [1, 2];

a的值现在是 1b 的值现在是2. 而在题目中,我们是这么做的:

[y] = [1, 2, 3, 4, 5];

也就是说,y等于数组的第一个值就是数字1. 我们输出y,返回1.


60. 输出是什么?

const user = {name: "Lydia", age: 21};
const admin = {admin: true, ...user};

console.log(admin);
  • 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

扩展运算符 ... 为对象的组合提供了可能。你可以复制对象中的键值对,然后把它们加到另一个对象里去。在本例中,我们复制了 user 对象键值对,然后把它们加入到 admin 对象中。admin对象就拥有了这些键值对,所以结果为{admin: true, name: "Lydia", age: 21}.


61. 输出是什么?

const person = {name: "Lydia"};

Object.defineProperty(person, "age", { value: 21});

console.log(person);
console.log(Object.keys(person));
  • A: {name: "Lydia", age: 21}, ["name", "age"]
  • B: {name: "Lydia", age: 21}, ["name"]
  • C: {name: "Lydia"}, ["name", "age"]
  • D: {name: "Lydia"}, ["age"]

答案: B

通过 defineProperty 方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用 defineProperty 方法给对象添加了一个属性之后,属性默认为 不可枚举 (not enumerable)_. Object.keys 方法仅返回对象中 _可枚举(enumerable) 的属性,因此只剩下了"name".

defineProperty 方法添加的属性默认不可变。你可以通过 writable, configurableenumerable 属性来改变这一行为。这样的话,相比于自己添加的属性,defineProperty方法添加的属性有了更多的控制权。


62. 输出是什么?

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,则该属性会被排除在外。


63. 输出是什么?

let num = 10;

const increaseNumber = () => num++;
const increasePassedNumber = number => number++;

const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);

console.log(num1);
console.log(num2);
  • A: 10, 10
  • B: 10, 11
  • C: 11, 11
  • D: 11, 12

答案: A

一元操作符 ++ 先返回 操作值, 再累加 操作值。num1 的值是 10, 因为increaseNumber 函数首先返回 num 的值,也就是 10,随后再进行 num 的累加。

num210 因为我们将 num1传入 increasePassedNumber. number 等于 10num1 的值。同样道理,++ 先返回 操作值, 再累加 操作值。)number10,所以 num2 也是10.


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:

  • 深入理解 Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13 个帮你提高开发效率的现代 CSS 框架
  • 快速上手 BootstrapVue
  • JavaScript 引擎是如何工作的?从调用栈到 Promise 你需要知道的一切
  • WebSocket 实战:在 Node 和 React 之间进行实时通信
  • 关于 Git 的 20 个面试题
  • 深入解析 Node.js 的 console.log
  • Node.js 究竟是什么?
  • 30 分钟用 Node.js 构建一个 API 服务器
  • Javascript 的对象拷贝
  • 程序员 30 岁前月薪达不到 30K,该何去何从
  • 14 个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩展插件
  • Node.js 多线程完全指南
  • 把 HTML 转成 PDF 的 4 个方案及实现

  • 更多文章 …
退出移动版