javascript前端工程师面试题:精辟前端31道JavaScript面试题
javascript前端工程师面试题:精辟前端31道JavaScript面试题3. 下面代码的输出是什么?当我们向 Promise.race方法中传入多个 Promise时,会进行 优先 解析。在这个例子中,我们用 setTimeout给 firstPromise和 secondPromise分别设定了500ms和100ms的定时器。这意味着 secondPromise会首先解析出字符串 two。那么此时 res参数即为 two,是为输出结果。然后,我们再执行 next()方法。生成器会从刚才暂停的地方继续,这个时候 i还是 10。于是我们走到了第二个 yield关键字处,这时候需要生成的值是 i*2, i为 10,那么此时生成的值便是 20。所以这道题的最终结果是 10 20。2. 下面代码的返回值是什么?const firstPromise = new Promise((res rej) => { setTimeout(res 500 "one")
1. 下面代码的输出是什么?
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 10and10 20
答案: C
一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到 yield关键字的时候,会生成 yield后面的值。注意,生成器在这种情况下不 返回(return )值,而是 生成 (yield)值。
首先,我们用 10作为参数 i来初始化生成器函数。然后使用 next()方法一步步执行生成器。第一次执行生成器的时候, i的值为 10,遇到第一个 yield关键字,它要生成 i的值。此时,生成器“暂停”,生成了 10。
然后,我们再执行 next()方法。生成器会从刚才暂停的地方继续,这个时候 i还是 10。于是我们走到了第二个 yield关键字处,这时候需要生成的值是 i*2, i为 10,那么此时生成的值便是 20。所以这道题的最终结果是 10 20。
2. 下面代码的返回值是什么?
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时,会进行 优先 解析。在这个例子中,我们用 setTimeout给 firstPromise和 secondPromise分别设定了500ms和100ms的定时器。这意味着 secondPromise会首先解析出字符串 two。那么此时 res参数即为 two,是为输出结果。
3. 下面代码的输出是什么?
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数组时,第一个元素会将引用的对象打印出来。
4. 下面代码的输出是什么?
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来进行迭代 也就是这里的 name和 age。在底层,对象的key都是字符串(如果他们不是Symbol的话)。在每次循环中,我们将 item设定为当前遍历到的key.所以一开始, item是 name,之后 item输出的则是 age。
5. 下面代码的输出是什么?
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".
6. num的值是什么?
const num = parseInt("7*6" 10);
- A: 42
- B: "42"
- C: 7
- D: NaN
答案: C
只返回了字符串中第一个字母. 设定了 进制 后 (也就是第二个参数,指定需要解析的数字是什么进制: 十进制、十六机制、八进制、二进制等等……) parseInt 检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。
*就是不合法的数字字符。所以只解析到 "7",并将其解析为十进制的 7. num的值即为 7.
7. 下面代码的输出是什么?
[1 2 3].map(num => { if (typeof num === "number") return; return num * 2; });
- A: []
- B: [null null null]
- C: [undefined undefined undefined]
- D: [3x empty]
答案: C
对数组进行映射的时候 num就是当前循环到的元素. 在这个例子中,所有的映射都是number类型,所以if中的判断 typeofnum==="number"结果都是 true.map函数创建了新数组并且将函数的返回值插入数组。
但是,没有任何值返回。当函数没有返回任何值时,即默认返回 undefined.对数组中的每一个元素来说,函数块都得到了这个返回值,所以结果中每一个元素都是 undefined.
8. 下面代码输出的是什么?
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的相应属性也被修改了 因为他们引用了相同的对象. person的 name属性也变成了 "Lydia".
9. 下面代码的输出是什么?
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 onlythrowErrorobjects
- D: "Oh no an error: Hello world!
答案: D
通过 throw语句,我么可以创建自定义错误。而通过它,我们可以抛出异常。异常可以是一个字符串 一个 数字 一个 布尔类型 或者是一个 对象。在本例中,我们的异常是字符串 'Hello world'.
通过 catch语句,我们可以设定当 try语句块中抛出异常后应该做什么处理。在本例中抛出的异常是字符串 'Hello world'. e就是这个字符串,因此被输出。最终结果就是 'Oh an error: Hello world'.
10. 下面代码的输出是什么?
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".
11. 下面代码的输出是什么?
(() => { 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
letx=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(typeofx)返回 "undefined".
而我们创建了全局变量 y,并且设定 y等于 10.这个值在我们的代码各处都访问的到。y已经被定义了,而且有一个 "number"类型的值。console.log(typeofy)返回 "number".
12. 下面代码的输出是什么?
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在执行了 deleteDog.prototype.bark后不可用 然而后面的代码还在调用它。
当我们尝试调用一个不存在的函数时 TypeError异常会被抛出。在本例中就是 TypeError:pet.barkisnotafunction,因为 pet.bark是 undefined.
13. 下面代码的输出是什么?
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}.
14. 下面代码的输出是什么?
// 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是只读的,不能被修改。
15. 下面代码的输出是什么?
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的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以 deleteage返回 true.
16. 下面代码的输出是什么?
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的值现在是 1, b的值现在是 2.而在题目中,我们是这么做的:
[y] = [1 2 3 4 5];
也就是说, y等于数组的第一个值就是数字 1.我们输出 y, 返回 1.
17. 下面代码的输出是什么?
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}.
18. 下面代码的输出是什么?
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方法添加的属性有了更多的控制权。
19. 下面代码的输出是什么?
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,则该属性会被排除在外。
20. 下面代码的输出是什么?
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的累加。
num2是 10因为我们将 num1传入 increasePassedNumber. number等于 10( num1的值。同样道理, 先返回 操作值 再累加 操作值。) number是 10,所以 num2也是 10.
21. 下面代码输出什么?
const value = { number: 10 }; const multiply = (x = { ...value }) => { console.log(x.number *= 2); }; multiply(); multiply(); multiply(value); multiply(value);
- 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。
22. 下面代码输出什么?
[1 2 3 4].reduce((x y) => console.log(x y));
- A: 12 and 33 and 64
- B: 12 and 23 and 34
- C: 1undefined and 2undefined and 3undefined and 4undefined
- D: 12 and undefined3 and undefined4
答案: 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被打印出。
23. 使用哪个构造函数可以成功继承 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; } };
- A: 1
- B: 2
- C: 3
- D: 4
答案: B
在子类中,在调用 super之前不能访问到 this关键字。如果这样做,它将抛出一个 ReferenceError:1和4将引发一个引用错误。
使用 super关键字,需要用给定的参数来调用父类的构造函数。父类的构造函数接收 name参数,因此我们需要将 name传递给 super。
Labrador类接收两个参数, name参数是由于它继承了 Dog, size作为 Labrador类的额外属性,它们都需要传递给 Labrador的构造函数,因此使用构造函数2正确完成。
24. 下面代码输出什么?
// 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会被依次打印。
25. 下面代码输出什么?
console.log(Number(2) === Number(2)) console.log(Boolean(false) === Boolean(false)) console.log(Symbol('foo') === Symbol('foo'))
- A: true true false
- B: false true false
- C: true false true
- D: true true true
答案: A
每个 Symbol都是完全唯一的。传递给 Symbol的参数只是给 Symbol的一个描述。Symbol的值不依赖于传递的参数。当我们测试相等时,我们创建了两个全新的符号:第一个 Symbol('foo'),第二个 Symbol('foo') 这两个值是唯一的,彼此不相等,因此返回 false。
26. 下面代码输出什么?
const name = "Lydia Hallie" console.log(name.padStart(13)) console.log(name.padStart(2))
- 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方法,我们可以在字符串的开头添加填充。传递给此方法的参数是字符串的总长度(包含填充)。字符串 LydiaHallie的长度为 12 因此 name.padStart(13)在字符串的开头只会插入1( 13-12=1)个空格。
如果传递给 padStart方法的参数小于字符串的长度,则不会添加填充。
27. 下面代码输出什么?
console.log("" "");
- A: ""
- B: 257548
- C: A string containing their code points
- D: Error
答案: A
使用 运算符,您可以连接字符串。上述情况,我们将字符串 “”与字符串 ”“连接起来,产生 ”“。
28. 如何能打印出 console.log语句后注释掉的值?
function* startGame() { const answer = yield "Do you love JavaScript?"; if (answer !== "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 ❤️
- A: game.next("Yes").value and game.next().value
- B: game.next.value("Yes") and game.next.value()
- C: game.next().value and game.next("Yes").value
- D: game.next.value() and game.next.value("Yes")
答案: C
generator函数在遇到 yield关键字时会“暂停”其执行。首先,我们需要让函数产生字符串Doyou loveJavaScript?,这可以通过调用 game.next().value来完成。上述函数的第一行就有一个 yield关键字,那么运行立即停止了, yield表达式本身没有返回值,或者说总是返回 undefined 这意味着此时变量 answer 为 undefined
next方法可以带一个参数,该参数会被当作上一个 yield 表达式的返回值。当我们调用 game.next("Yes").value时,先前的 yield 的返回值将被替换为传递给 next()函数的参数 "Yes"。此时变量 answer 被赋值为 "Yes", if语句返回 false,所以 JavaScriptloves you back❤️被打印。
29. 下面代码输出什么?
console.log(String.raw`Hello\nworld`);
- A: Helloworld!
- 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被打印出。
30.下面代码输出什么?
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!"
31. 下面代码输出什么?
function addToList(item list) { return list.push(item); } const result = addToList("apple" ["banana"]); console.log(result);
- A: ['apple' 'banana']
- B: 2
- C: true
- D: undefined
答案: B
push()方法返回新数组的长度。一开始,数组包含一个元素(字符串 "banana"),长度为1。 在数组中添加字符串 "apple"后,长度变为2,并将从 addToList函数返回。
push方法修改原始数组,如果你想从函数返回数组而不是数组长度,那么应该在push item之后返回 list。
关注 私信《资料》获取!
感谢您的朗读。想学习前端的小伙伴们可以关注 私信回复:【资料】已经为大家准备好最新的前端资料。