# JS 面试题(一)

# 7. 输出是什么

let a = 3;
let b = new Number(3);
let c = 3;

console.log(a == b);
console.log(a === b);
console.log(b === c);
1
2
3
4
5
6
7
  • 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。

# 9. 输出是什么

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

答案: A

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

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

# 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());
1
2
3
4
5
6
7
8
9
10
11
  • A: TypeError
  • B: SyntaxError
  • C: Lydia Hallie
  • D: undefined undefined
答案

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

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

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

# 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`;
1
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

如果使用标记模板字面量,第一个参数的值总是包含字符串的数组。其余的参数获取的是传递的表达式的值!

# 19. 输出是什么

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

getAge(21);
1
2
3
4
5
  • A: "number"
  • B: "array"
  • C: "object"
  • D: "NaN"
答案

答案: C

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

# 22. sessionStorage 可访问多长时间

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

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

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

# 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);
1
2
3
4
5
6
7
8
9
10
  • A: Person {firstName: "Lydia", lastName: "Hallie"} and undefined
  • B: Person {firstName: "Lydia", lastName: "Hallie"} and Person {firstName: "Sarah", lastName: "Smith"}
  • C: Person {firstName: "Lydia", lastName: "Hallie"} and {}
  • D:Person {firstName: "Lydia", lastName: "Hallie"} and 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)才开始。 img

# 27. 输出是什么

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

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

# 28. 输出是什么

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

const name = "Lydia";

name.giveLydiaPizza();
1
2
3
4
5
6
7
  • A: "Just give Lydia pizza already!"
  • B: TypeError: not a function
  • C: SyntaxError
  • D: undefined
answer

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

# 29. 输出是什么

const a = {};
const b = { key: "b" };
const c = { key: "c" };

a[b] = 123;
a[c] = 456;

console.log(a[b]);
1
2
3
4
5
6
7
8
  • 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。

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

<div onclick="console.log('first div')">
  <div onclick="console.log('second div')">
    <button onclick="console.log('button')">Click!</button>
  </div>
</div>
1
2
3
4
5
  • 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!</p>
</div>
1
2
3
  • A: p div
  • B: div p
  • C: p
  • D: div
答案

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

# 38. 输出是什么

(() => {
  let x, y;
  try {
    throw new Error();
  } catch (x) {
    (x = 1), (y = 2);
    console.log(x);
  }
  console.log(x);
  console.log(y);
})();
1
2
3
4
5
6
7
8
9
10
11
  • 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 的值仍为 undefined, y 的值为 2。当我们在 catch 块之外执行 console.log(x) 时,返回 undefined,y 返回 2。

# 49. num 的值是什么

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

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

# 51. 输出的是什么

function getInfo(member, year) {
  member.name = "Lydia";
  year = "1998";
}

const person = { name: "Sarah" };
const birthYear = "1997";

getInfo(person, birthYear);

console.log(person, birthYear);
1
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"
answer

答案: A primitive value is passed by its copy, object is passed by it's copy of reference

# 53. 输出是什么

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

const myCar = new Car();
console.log(myCar.make);
1
2
3
4
5
6
7
  • 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);
1
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; 复制代码我们设定 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".

# 64. 输出什么

const value = { number: 10 };

const multiply = (x = { ...value }) => {
  console.log((x.number *= 2));
};

multiply();
multiply();
multiply(value);
multiply(value);
1
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 在 ES6 中,我们可以使用默认值初始化参数。如果没有给函数传参,或者传的参值为 "undefined" ,那么参数的值将是默认值。上述例子中,我们将 value 对象进行了解构并传到一个新对象中,因此 x 的默认值为 {number:10} 。 默认参数在调用时才会进行计算,每次调用函数时,都会创建一个新的对象。我们前两次调用 multiply 函数且不传递值,那么每一次 x 的默认值都为 {number:10} ,因此打印出该数字的乘积值为 20。 第三次调用 multiply 时,我们传递了一个参数,即对象 value。 *=运算符实际上是 x.number = x.number * 2 的简写,我们修改了 x.number 的值,并打印出值 20。 第四次,我们再次传递 value 对象。 x.number 之前被修改为 20,所以 x.number * = 2 打印为 40。

# 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;
1
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 命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行。 这是 CommonJS 中 require()和 import 之间的区别。使用 require(),您可以在运行代码时根据需要加载依赖项。 如果我们使用 require 而不是 import,running index.js,running sum.js,3 会被依次打印。

# 73. 输出什么

async function getData() {
  return await Promise.resolve("I made it!");
}

const data = getData();
console.log(data);
1
2
3
4
5
6
  • A: "I made it!"
  • B: Promise {< resolved >: "I made it!"}
  • C: Promise {< pending >}
  • D: undefined
答案

答案: C 异步函数始终返回一个 promise。await 仍然需要等待 promise 的解决:当我们调用 getData()并将其赋值给 data,此时 data 为 getData 方法返回的一个挂起的 promise,该 promise 并没有解决。 如果我们想要访问已解决的值"I made it!",可以在 data 上使用.then()方法: data.then(res => console.log(res)) 这样将打印 "I made it!"

# 74. 输出什么

function addToList(item, list) {
  return list.push(item);
}

const result = addToList("apple", ["banana"]);
console.log(result);
1
2
3
4
5
6
  • A: ['apple', 'banana']
  • B: 2
  • C: true
  • D: undefined
答案

答案: B push()方法返回新数组的长度。一开始,数组包含一个元素(字符串"banana"),长度为 1。 在数组中添加字符串"apple"后,长度变为 2,并将从 addToList 函数返回。 push 方法修改原始数组,如果你想从函数返回数组而不是数组长度,那么应该在 push item 之后返回 list。

# 75. 输出什么

const box = { x: 10, y: 20 };

Object.freeze(box);

const shape = box;
shape.x = 100;
console.log(shape);
1
2
3
4
5
6
7
  • A: { x: 100, y: 20 }
  • B: { x: 10, y: 20 }
  • C: { x: 100 }
  • D: ReferenceError
答案

答案: B Object.freeze 使得无法添加、删除或修改对象的属性(除非属性的值是另一个对象)。 当我们创建变量 shape 并将其设置为等于冻结对象 box 时,shape 指向的也是冻结对象。你可以使用 Object.isFrozen 检查一个对象是否被冻结,上述情况,Object.isFrozen(shape)将返回 true。 由于 shape 被冻结,并且 x 的值不是对象,所以我们不能修改属性 x。 x 仍然等于 10,{x:10,y:20}被打印。 注意,上述例子我们对属性 x 进行修改,可能会导致抛出 TypeError 异常(最常见但不仅限于严格模式下时)。

# 91. 输出什么

let newList = [1, 2, 3].push(4);

console.log(newList.push(5));
1
2
3
  • A: [1, 2, 3, 4, 5]
  • B: [1, 2, 3, 5]
  • C: [1, 2, 3, 4]
  • D: Error
答案

答案: D .push 方法返回数组的长度,而不是数组本身! 通过将 newList 设置为[1,2,3].push(4),实际上 newList 等于数组的新长度:4。 然后,尝试在 newList 上使用.push 方法。 由于 newList 是数值 4,抛出 TypeError。

# 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);
1
2
3
4
5
6
7
8
9
  • A: { constructor: ...} { constructor: ...}
  • B: {} { constructor: ...}
  • C: { constructor: ...} {}
  • D: { constructor: ...} undefined
答案

答案: D 常规函数,例如 giveLydiaPizza 函数,有一个 prototype 属性,它是一个带有 constructor 属性的对象(原型对象)。 然而,箭头函数,例如 giveLydiaChocolate 函数,没有这个 prototype 属性。 尝试使用 giveLydiaChocolate.prototype 访问 prototype 属性时会返回 undefined。

# 100. 输出什么

// 🎉✨ This is my 100th question! ✨🎉

const output = `${[] && "Im"}possible!
You should${"" && `n't`} see a therapist after so much JavaScript lol`;
1
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 []是一个真值。 使用&&运算符,如果左侧值是真值,则返回右侧值。 在这种情况下,左侧值[]是一个真值,所以返回 Im。 ""是一个假值。 如果左侧值是假的,则不返回任何内容。 n't 不会被退回。

# 111. 输出什么

let name = "Lydia";

function getName() {
  console.log(name);
  let name = "Sarah";
}

getName();
1
2
3
4
5
6
7
8
  • A: Lydia
  • B: Sarah
  • C: undefined
  • D: ReferenceError
答案

答案: D 每个函数都有其自己的执行上下文。 getName 函数首先在其自身的上下文(范围)内查找,以查看其是否包含我们尝试访问的变量 name。 上述情况,getName 函数包含其自己的 name 变量:我们用 let 关键字和 Sarah 的值声明变量 name。 带有 let 关键字(和 const)的变量被提升,但是与 var 不同,它不会被初始化。 在我们声明(初始化)它们之前,无法访问它们。 这称为“暂时性死区”。 当我们尝试在声明变量之前访问变量时,JavaScript 会抛出 ReferenceError: Cannot access 'name' before initialization。 如果我们不在 getName 函数中声明 name 变量,则 javascript 引擎会查看原型练。会找到其外部作用域有一个名为 name 的变量,其值为 Lydia。 在这种情况下,它将打印 Lydia:

let name = "Lydia";

function getName() {
  console.log(name);
}

getName(); // Lydia
1
2
3
4
5
6
7

# 114. 将会发生什么

let config = {
  alert: setInterval(() => {
    console.log("Alert!");
  }, 1000)
};
config = null;
1
2
3
4
5
6
  • A: setInterval 的回调不会被调用
  • B: setInterval 的回调被调用一次
  • C: setInterval 的回调仍然会被每秒钟调用
  • D: 我们从没调用过 config.alert(), config 为 null
答案

答案: C 一般情况下当我们将对象赋值为 null, 那些对象会被进行 垃圾回收(garbage collected) 因为已经没有对这些对象的引用了。然而,setInterval 的参数是一个箭头函数(所以上下文绑定到对象 config 了),回调函数仍然保留着对 config 的引用。只要存在引用,对象就不会被垃圾回收。因为没有被垃圾回收,setInterval 的回调每 1000ms (1s)会被调用一次。

# 115. 哪一个方法会返回 'Hello world!'

const myMap = new Map();
const myFunc = () => "greeting";
myMap.set(myFunc, "Hello world!");
//1
myMap.get("greeting");
//2
myMap.get(myFunc);
//3
myMap.get(() => "greeting");
1
2
3
4
5
6
7
8
9
  • A: 1
  • B: 2
  • C: 2 and 3
  • D: All of them
答案

答案: B 当通过 set 方法添加一个键值对,一个传递给 set 方法的参数将会是键名,第二个参数将会是值。在这个 case 里,键名为 函数 () => 'greeting',值为'Hello world'。 myMap 现在就是 { () => 'greeting' => 'Hello world!' }。 1 是错的,因为键名不是 'greeting' 而是 () => 'greeting'。 3 是错的,因为我们给 get 方法传递了一个新的函数。对象受 引用 影响。函数也是对象,因此两个函数严格上并不等价,尽管他们相同:他们有两个不同的内存引用地址。

# 122. 输出什么

const name = "Lydia Hallie";
console.log(!typeof name === "object");
console.log(!typeof name === "string");
1
2
3
  • A: false true
  • B: true false
  • C: false false
  • D: true true
答案

答案: C typeof name 返回 "string"。字符串 "string" 是一个 truthy 的值,因此 !typeof name 返回一个布尔值 false。 false === "object" 和 false === "string" 都返回 false。

# 124. 输出什么

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);
  }
})();
1
2
3
4
5
6
7
8
9
10
11
  • A: Promise {1} Promise {2} Promise {3}
  • B: Promise {< pending>} Promise {< pending>} Promise {< pending>}
  • C: 1 2 3
  • D: undefined undefined undefined
答案

答案: C 我们给 函数 range 传递: Promise{1}, Promise{2}, Promise{3},Generator 函数 range 返回一个全是 async object promise 数组。我们将 async object 赋值给变量 gen,之后我们使用 for await ... of 进行循环遍历。我们将返回的 Promise 实例赋值给 item: 第一个返回 Promise{1}, 第二个返回 Promise{2},之后是 Promise{3}。因为我们正 awaiting item 的值,resolved 状态的 promise,promise 数组的 resolved 值 以此为: 1,2,3.

# 128. 输出什么

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));
1
2
3
4
5
6
  • A: true false true false
  • B: true false false false
  • C: false false true false
  • D: false true false true
答案

答案: C 通过方法 Number.isNaN,你可以检测你传递的值是否为 数字值 并且是否等价于 NaN。name 不是一个数字值,因此 Number.isNaN(name) 返回 false。age 是一个数字值,但它不等价于 NaN,因此 Number.isNaN(age) 返回 false. 通过方法 isNaN, 你可以检测你传递的值是否一个 number。name 不是一个 number,因此 isNaN(name) 返回 true. age 是一个 number 因此 isNaN(age) 返回 false.

# 133. 输出什么

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();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 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。在函数 funcOne 的第一行,我们调用 myPromise promise 异步操作。当 JS 引擎在忙于执行 promise,它继续执行函数 funcOne。下一行 异步操作 setTimeout,其回调函数被 Web API 调用。 (详情请参考我关于 event loop 的文章.) promise 和 timeout 都是异步操作,函数继续执行当 JS 引擎忙于执行 promise 和 处理 setTimeout 的回调。相当于 Last line! 首先被输出, 因为它不是异步操作。执行完 funcOne 的最后一行,promise 状态转变为 resolved,Promise! 被打印。然而,因为我们调用了 funcTwo(), 调用栈不为空,setTimeout 的回调仍不能入栈。 我们现在处于 funcTwo,先 awaiting myPromise。通过 await 关键字, 我们暂停了函数的执行直到 promise 状态变为 resolved (或 rejected)。然后,我们输出 res 的 awaited 值(因为 promise 本身返回一个 promise)。 接着输出 Promise!。 下一行就是 异步操作 setTimeout,其回调函数被 Web API 调用。 我们执行到函数 funcTwo 的最后一行,输出 Last line!。现在,因为 funcTwo 出栈,调用栈为空。在事件队列中等待的回调函数(() => console.log("Timeout!") from funcOne, and () => console.log("Timeout!") from funcTwo)以此入栈。第一个回调输出 Timeout!,并出栈。然后,第二个回调输出 Timeout!,并出栈。得到结果 Last line! Promise! Promise! Last line! Timeout! Timeout!