题目地址:https://github.com/lydiahallie/javascript-questions/blob/master/zh-CN/README-zh_CN.md
1. 输出是什么?
function sayHi() {
console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
- A:
Lydia和undefined - B:
Lydia和ReferenceError - C:
ReferenceError和21 - D:
undefined和ReferenceError
答案
答案:D
在函数内部,我们首先通过 var 关键字声明了 name 变量。这意味着变量被提升了(内存空间在创建阶段就被设置好了),直到程序运行到定义变量位置之前默认值都是 undefined。因为当我们打印 name 变量时还没有执行到定义变量的位置,因此变量的值保持为 undefined。
通过 let 和 const 关键字声明的变量也会提升,但是和 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 2和0 1 2 - B:
0 1 2和3 3 3 - C:
3 3 3和0 1 2
答案
答案:C
由于 JavaScript 的事件循环,setTimeout 回调会在遍历结束后才执行。因为在第一个遍历中遍历 i 是通过 var 关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++ 来每次递增 i 的值。当 setTimeout 回调执行的时候,i 的值等于 3。
在第二个遍历中,遍历 i 是通过 let 关键字声明的:通过 let 和 const 关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i 都有一个新值,并且每个值都在循环内的作用域中。
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:
undefinedundefined
答案
答案: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"}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
对于 sarah,我们没有使用 new 关键字。当使用 new 时,this 引用我们创建的空对象。当未使用 new 时,this 引用的是全局对象(global object)。
我们说 this.firstName 等于 "Sarah",并且 this.lastName 等于 "Smith"。实际上我们做的是,定义了 global.firstName = 'Sarah' 和 global.lastName = 'Smith'。而 sarah 本身是 undefined。
29. 输出是什么?
const a = {};
const b = { key: "b" };
const c = { key: "c" };
a[b] = 123;
a[c] = 456;
console.log(a[b]);
- A:
123 - B:
456 - C:
undefined - D:
ReferenceError
答案
答案:B
对象的键被自动转换为字符串。我们试图将一个对象 b 设置为对象 a 的键,且相应的值为 123。
然而,当字符串化一个对象时,它会变成 "[object Object]"。因此这里说的是,a["[object Object]"] = 123。然后,我们再一次做了同样的事情,c 是另外一个对象,这里也有隐式字符串化,于是,a["[object Object]"] = 456。
然后,我们打印 a[b],也就是 a["[object Object]"]。之前刚设置为 456,因此返回的是 456。
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 21Lydia is 21 - B:
functionfunction - C:
Lydia is 21Lydia is 21 - D:
Lydia is 21function
34. 输出是什么?
function sayHi() {
return (() => 0)();
}
typeof sayHi();
- A:
"object" - B:
"number" - C:
"function" - D:
"undefined"
答案
答案:B
sayHi 方法返回的是立即执行函数 (IIFE) 的返回值。此立即执行函数的返回值是 0,类型是 number
参考:只有 7 种内置类型:null,undefined,boolean,number,string,object, symbol 和 bigint。function 不是一种类型,函数是对象,它的类型是object。
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:
1undefined2 - B:
undefinedundefinedundefined - C:
112 - D:
1undefinedundefined
答案
答案:A
catch 代码块接收参数 x。当我们传递参数时,这与之前定义的变量 x 不同。这个 x 是属于 catch 块级作用域的。
然后,我们将块级作用域中的变量赋值为 1,同时也设置了变量 y 的值。现在,我们打印块级作用域中的变量 x,值为 1。
catch 块之外的变量 x 的值仍为 undefined,y 的值为 2。当我们在 catch 块之外执行 console.log(x) 时,返回 undefined,y 返回 2。
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".
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}.
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, const 或 let 关键字声明的变量无法用 delete 操作符来删除。
name变量由const关键字声明,所以删除不成功:返回 false. 而我们设定age等于21时,我们实际上添加了一个名为age的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以delete age返回true.
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, configurable 和 enumerable属性来改变这一行为。这样,defineProperty方法可以让您更好地控制要添加到对象的属性。
65. 输出什么?
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
- A:
12and33and64 - B:
12and23and34 - C:
1undefinedand2undefinedand3undefinedand4undefined - D:
12andundefined3andundefined4
答案
答案:D
reducer 函数接收 4 个参数:
- Accumulator (acc) (累计器)
- Current Value (cur) (当前值)
- Current Index (idx) (当前索引)
- Source Array (src) (源数组)
reducer 函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
reducer 函数还有一个可选参数initialValue,该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供initialValue,则将使用数组中的第一个元素。
在上述例子,reduce方法接收的第一个参数 (Accumulator) 是x,第二个参数 (Current Value) 是y。
在第一次调用时,累加器x为1,当前值“y”为2,打印出累加器和当前值:1和2。
例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回undefined。在下一次调用时,累加器为undefined,当前值为“3”,因此undefined和3被打印出。
在第四次调用时,回调函数依然没有返回值。累加器再次为 undefined,当前值为“4”。undefined和4被打印出。
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;
- 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会被依次打印。
72. 输出什么?
console.log(String.raw`Hello\nworld`);
- A:
Hello world! - B:
Hello
world - C:
Hello\nworld - D:
Hello\n
world
答案
答案:C
String.raw函数是用来获取一个模板字符串的原始字符串的,它返回一个字符串,其中忽略了转义符(\n,\v,\t等)。但反斜杠可能造成问题,因为你可能会遇到下面这种类似情况:
const path = `C:\Documents\Projects\table.html`;
String.raw`${path}`;
这将导致:
"C:DocumentsProjects able.html"
直接使用String.raw
String.raw`C:\Documents\Projects\table.html`;
它会忽略转义字符并打印:C:\Documents\Projects\table.html
上述情况,字符串是Hello\nworld被打印出。
73. 输出什么?
async function getData() {
return await Promise.resolve("I made it!");
}
const data = getData();
console.log(data);
- 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!"
77. 以下是个纯函数么?
function sum(a, b) {
return a + b;
}
- A: Yes
- B: No
答案
答案:A
纯函数在相同的输入值时,需产生相同的输出,其输出的结果,与输入值以外的其他隐藏信息或状态无关,也和由 I/O 设备产生的外部输出无关。 纯函数不会产生副作用。
纯函数与副作用的定义可参考: https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)
89. 输出什么?
// module.js
export default () => "Hello world";
export const name = "Lydia";
// index.js
import * as data from "./module";
console.log(data);
- 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 name语法,我们将module.js文件中所有export导入到index.js文件中,并且创建了一个名为data的新对象。在module.js文件中,有两个导出:默认导出和命名导出。默认导出是一个返回字符串“Hello World”的函数,命名导出是一个名为name的变量,其值为字符串“Lydia”。
data对象具有默认导出的default属性,其他属性具有指定 exports 的名称及其对应的值。
91. 输出什么?
let newList = [1, 2, 3].push(4);
console.log(newList.push(5));
- 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。
94. 输出什么?
function getItems(fruitList, ...args, favoriteFruit) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
- A:
["banana", "apple", "pear", "orange"] - B:
[["banana", "apple"], "pear", "orange"] - C:
["banana", "apple", ["pear"], "orange"] - D:
SyntaxError
答案
答案:D
... args是剩余参数,剩余参数的值是一个包含所有剩余参数的数组,并且只能作为最后一个参数。上述示例中,剩余参数是第二个参数,这是不可能的,并会抛出语法错误。
function getItems(fruitList, favoriteFruit, ...args) {
return [...fruitList, ...args, favoriteFruit];
}
getItems(["banana", "apple"], "pear", "orange");
上述例子是有效的,将会返回数组:[ 'banana', 'apple', 'orange', 'pear' ]
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));
- A:
a is bigger,6andb is bigger,3 - B:
a is bigger,undefinedandb is bigger,undefined - C:
undefinedandundefined - D:
SyntaxError
答案
答案:B
在 JavaScript 中,我们不必显式地编写分号 (;),但是 JavaScript 引擎仍然在语句之后自动添加分号。这称为自动分号插入。例如,一个语句可以是变量,或者像throw、return、break这样的关键字。
在这里,我们在新的一行上写了一个return语句和另一个值a + b。然而,由于它是一个新行,引擎并不知道它实际上是我们想要返回的值。相反,它会在return后面自动添加分号。你可以这样看:
return;
a + b;
这意味着永远不会到达a + b,因为函数在return关键字之后停止运行。如果没有返回值,就像这里,函数返回undefined。注意,在if/else语句之后没有自动插入!
97. 输出什么?
const info = {
[Symbol("a")]: "b",
};
console.log(info);
console.log(Object.keys(info));
- A:
{Symbol('a'): 'b'}and["{Symbol('a')"] - B:
{}and[] - C:
{ a: "b" }and["a"] - D:
{Symbol('a'): 'b'}and[]
答案
答案:D
Symbol类型是不可枚举的。Object.keys方法返回对象上的所有可枚举的键属性。Symbol类型是不可见的,并返回一个空数组。记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。
这是Symbol的众多特性之一:除了表示完全唯一的值(防止对象意外名称冲突,例如当使用 2 个想要向同一对象添加属性的库时),您还可以隐藏这种方式对象的属性(尽管不完全。你仍然可以使用Object.getOwnPropertySymbols()方法访问 Symbol。
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))
- 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
getList函数接收一个数组作为其参数。在getList函数的括号之间,我们立即解构这个数组。您可以将其视为:
[x, ...y] = [1, 2, 3, 4]
使用剩余的参数... y,我们将所有剩余参数放在一个数组中。在这种情况下,其余的参数是2,3和4。 y的值是一个数组,包含所有其余参数。在这种情况下,x的值等于1,所以当我们打印[x,y]时,会打印[1,[2,3,4]]。
getUser函数接收一个对象。对于箭头函数,如果只返回一个值,我们不必编写花括号。但是,如果您想从一个箭头函数返回一个对象,您必须将它写在圆括号之间,否则两个花括号之间的所有内容都将被解释为一个块语句!在这种情况下,花括号之间的代码不是有效的 JavaScript 代码,因此会抛出 SyntaxError。
以下函数将返回一个对象:
const getUser = user => ({ name: user.name, age: user.age })
102. 依次输出什么?
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();
- 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
有了 promise,我们通常会说:当我想要调用某个方法,但是由于它可能需要一段时间,因此暂时将它放在一边。只有当某个值被 resolved/rejected,并且执行栈为空时才使用这个值。
我们可以在async函数中通过.then和await关键字获得该值。尽管我们可以通过.then和await获得 promise 的价值,但是它们的工作方式有所不同。
在 firstFunction中,当运行到myPromise方法时我们将其放在一边,即 promise 进入微任务队列,其他后面的代码(console.log('second'))照常运行,因此second被打印出,firstFunction方法到此执行完毕,执行栈中宏任务队列被清空,此时开始执行微任务队列中的任务,I have resolved被打印出。
在secondFunction方法中,我们通过await关键字,暂停了后面代码的执行,直到异步函数的值被解析才开始后面代码的执行。这意味着,它会等着直到 myPromise 以值I have resolved被解决之后,下一行second才开始执行。
111. 输出什么?
let name = "Lydia";
function getName() {
console.log(name);
let name = "Sarah";
}
getName();
- 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
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);
- A:
aanda - B:
aandundefined - C:
['a', 'b', 'c']anda - D:
aand['a', 'b', 'c']
答案
答案:C
通过 yield 关键字,我们在 Generator 函数里执行yield表达式。通过 yield* 关键字,我们可以在一个Generator 函数里面执行(yield表达式)另一个 Generator 函数,或可遍历的对象 (如数组).
在函数 generatorOne 中,我们通过 yield 关键字 yield 了一个完整的数组 ['a', 'b', 'c']。函数one通过next方法返回的对象的value 属性的值 (one.next().value) 等价于数组 ['a', 'b', 'c'].
console.log(one.next().value); // ['a', 'b', 'c']
console.log(one.next().value); // undefined
在函数 generatorTwo 中,我们使用 yield* 关键字。就相当于函数two第一个yield的值,等价于在迭代器中第一个 yield 的值。数组['a', 'b', 'c']就是这个迭代器。第一个 yield 的值就是 a,所以我们第一次调用 two.next().value时,就返回a。
console.log(two.next().value); // 'a'
console.log(two.next().value); // 'b'
console.log(two.next().value); // 'c'
console.log(two.next().value); // undefined
115
