【前端】20道JavaScript进阶问题(3-完结)
来源:
javascript-questions/zh-CN/README-zh_CN.md at master · lydiahallie/javascript-questions · GitHub
记录一些有趣的题。
系列:
【前端】35道JavaScript进阶问题(1)
【前端】20道JavaScript进阶问题(2)
1
const info = {
[Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))
答:
{ [Symbol(a)]: 'b' }
[]
第一个 console.log(info)
语句打印出整个对象,包括符号键。然而,第二个 console.log(Object.keys(info))
语句返回一个空数组,因为 Object.keys()
只返回可枚举属性的名称作为一个字符串数组,它不包括 Symbol
属性。
如果你想获取用作对象键的符号,你可以使用 Object.getOwnPropertySymbols()
。
console.log(Object.getOwnPropertySymbols(info))
[ Symbol('a') ]
2
const output = `${[] && 'Im'}possible!
You should${'' && `n't`} see a therapist after so much JavaScript lol`
答:
Impossible!
You should see a therapist after so much JavaScript lol
解析:
[]
是一个真值。使用&&
运算符,如果左侧值是真值,则返回右侧值。在这种情况下,左侧值[]
是一个真值,所以返回Im
。
""
是一个假值。如果左侧值是假的,则不返回任何内容。n't
不会被退回。
使用
||
运算符,我们可以返回第一个真值。如果所有值都是假值,则返回最后一个值。
3
Promise.resolve(5)
答:
Promise {<fulfilled>: 5}
4
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)
答:
They are the same!
解析:
使用同一个引用person
。
5
const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
}
const colors = ["pink", "red", "blue"]
console.log(colorConfig.colors[1])
答:
TypeError
解析:
colorConfig.colors
是undefined,undefined[1]
会报错。
6
哪些方法修改了原数组:
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, '✨')
答:
splice
修改原数组。
解析:
map
,filter
和slice
返回一个新数组,find
返回一个元素,而reduce
返回一个减小的值。
7
这个函数干了什么?
JSON.parse()
答:
将 JSON 字符串解析为 JavaScript 值。(不会只解析为对象)
8
let name = 'Lydia'
function getName() {
console.log(name)
let name = 'Sarah'
}
getName()
答:
ReferenceError
解析:
name变量定义在函数作用域里。let没有作用域的提升,在console.log(name)
时还没有name的声明。会报错。
但是:
let name = 'Lydia'
function getName() {
console.log(name)
}
getName() // Lydia
根据作用域链,会找到外面的name,输出Lydia
9
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', 'b', 'c' ]
a
解析:
generatorOne 和 generatorTwo 都是生成器函数,它们使用 yield 关键字来产生一系列的值。但是,它们在处理数组时有所不同。
generatorOne 使用 yield [‘a’, ‘b’, ‘c’],这会直接返回整个数组 [‘a’, ‘b’, ‘c’]。因此,当你调用 one.next().value,你得到的是整个数组 [‘a’, ‘b’, ‘c’]。
generatorTwo 使用 yield* [‘a’, ‘b’, ‘c’],yield*
是一个特殊的语法,它会遍历可迭代对象(在这种情况下是数组)并逐个产生其元素。 因此,当你首次调用 two.next().value,你得到的是数组的第一个元素 ‘a’。如果你再次调用 two.next().value,你将得到 ‘b’,然后是 ‘c’。
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) // undefined
为什么第一个会直接返回整个数组?
在JavaScript中,yield关键字用于从生成器函数返回一个值,并暂停执行。当你使用yield关键字与一个数组一起时,它会将整个数组视为一个单一的值,并直接返回这个数组。
在generatorOne函数中,yield ['a', 'b', 'c']
语句就是这样工作的。它返回了整个数组[‘a’, ‘b’, ‘c’],并且暂停了函数的执行。所以,当你调用one.next().value,你得到的是整个数组[‘a’, ‘b’, ‘c’]。
如果你想要逐个返回数组中的元素,你可以使用yield*
语句,如generatorTwo函数所示。yield*
语句会遍历可迭代对象(例如数组),并逐个返回其元素。
10
console.log(`${(x => x)('I love')} to program`)
答:
I love to program
解析:
${(x => x)('I love')}
是一个模板字符串中的表达式。在这个表达式中,(x => x)
是一个箭头函数,它接收一个参数 x 并直接返回这个参数。当你调用这个函数并传入 ‘I love’ 作为参数时,它会返回 ‘I love’。
因此,${(x => x)(‘I love’)} 这个表达式的值就是 ‘I love’,所以整个模板字符串的值就是 ‘I love to program’。
11
let config = {
alert: setInterval(() => {
console.log('Alert!')
}, 1000)
}
config = null
- A:
setInterval
的回调不会被调用 - B:
setInterval
的回调被调用一次 - C:
setInterval
的回调仍然会被每秒钟调用 - D: 我们从没调用过
config.alert()
, config 为null
答:
C。
解析:
这段代码首先创建了一个名为config的对象,并在该对象中定义了一个属性alert。这个alert属性是一个定时器,每隔1000毫秒(1秒)就会打印出’Alert!'。
然后,你将config设置为null。这意味着config不再引用之前的对象,看起来好像它已经被删除了。但实际上,虽然config现在是null,定时器仍然在运行,并且每隔1秒就会打印出’Alert!'。
这是因为setInterval()函数创建的定时器并不依赖于创建它的对象或变量。 即使你将config设置为null,定时器仍然存在并持续运行,直到你明确地使用clearInterval()函数来停止它,或者关闭页面(在浏览器环境中)或结束进程(在Node.js环境中)。
省流:只有显式地调用clearInterval()
来停止它,定时器才会停止。
12
哪一个方法会返回 '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')
答:
2
解析:
在这段代码中,你使用函数myFunc
(而不是字符串’greeting’)作为键来设置Map的值。因此,当你使用相同的函数myFunc
作为键来获取值时,它会返回 ‘Hello world!’。
对于方法1 myMap.get('greeting')
和方法3 myMap.get(() => 'greeting')
,它们都不会返回 ‘Hello world!’,因为它们使用的键与设置Map时使用的键不同。在JavaScript的Map对象中,键的比较是基于同一性(即必须完全相同),而不是基于等价性。
13
const config = {
languages: [],
set language(lang) {
return this.languages.push(lang);
}
};
console.log(config.language);
答:
undefined。
解析:
language是一个setter
,不是一个值。所以是undefined
。
14
const name = "Lydia Hallie";
console.log(!typeof name === "object");
console.log(!typeof name === "string");
答:
false
false
解析:
!
操作符的优先级高于 ===
操作符。因此,问题相当于:
(!'string') === 'object'
即:
false === 'object'
因此两个都是false。
15
const add = x => y => z => {
console.log(x, y, z);
return x + y + z;
};
add(4)(5)(6);
答:
4 5 6
解析:
add 是一个柯里化函数,它接收一个参数并返回一个新的函数,这个新的函数也接收一个参数并返回另一个新的函数。最后这个新的函数接收一个参数,打印出所有的参数,并返回这三个参数的和。
所以当你调用 add(4)(5)(6) 时,首先调用 add(4) 返回一个新的函数,这个新的函数等待接收第二个参数。然后你调用这个新的函数并传入 5 作为参数,它又返回另一个新的函数,等待接收第三个参数。最后你调用这个新的函数并传入 6 作为参数,它打印出所有的参数 4, 5, 6,并返回这三个参数的和 15。
16
哪个作为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 }
- A:
entries
- B:
values
- C:
fromEntries
- D:
forEach
答:
C。
解析:
Object.fromEntries()
是一个JavaScript的静态方法,它接收一个键值对的列表(通常是一个二维数组或者一个可迭代对象)并返回一个新的对象,这个新的对象的属性由这些键值对定义。
例如:
const entries = [['name', 'John'], ['age', 30]];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'John', age: 30 }
在这个例子中,Object.fromEntries()
接收一个二维数组 entries 并返回一个新的对象 obj。这个新的对象有两个属性:name 和 age,它们的值分别是 ‘John’ 和 30。
注意,Object.fromEntries()
是在ES2019中引入的,所以在旧版本的JavaScript环境中可能不可用。
17
const user = {
email: "my@email.com",
updateEmail: email => {
this.email = email
}
}
user.updateEmail("new@email.com")
console.log(user.email)
答:
"my@email.com"
解析:
在JavaScript中,箭头函数并不绑定this。所以,在updateEmail方法中,this并不指向user对象,而是指向了该箭头函数被定义时的上下文(通常是全局对象,在浏览器中是window,在Node.js中是global)。
因此,user.updateEmail("new@email.com")
实际上并没有改变user对象的email属性,而是尝试在全局对象上设置一个email属性。然后,当你打印user.email时,它仍然是原来的值。
如果你想要在方法中访问和修改对象的属性,你应该使用普通的函数,而不是箭头函数,像这样:
const user = {
email: "my@email.com",
updateEmail: function(email) {
this.email = email;
}
}
user.updateEmail("new@email.com")
console.log(user.email) // "new@email.com"
在这个版本的代码中,updateEmail是一个普通的函数,所以this指向user对象。因此,user.updateEmail("new@email.com")
正确地改变了user对象的email属性,然后打印user.email会得到新的值。
18
const animals = {};
let dog = { emoji: '🐶' }
let cat = { emoji: '🐈' }
animals[dog] = { ...dog, name: "Mara" }
animals[cat] = { ...cat, name: "Sara" }
console.log(animals[dog])
答:
{ emoji: '🐈', name: 'Sara' }
解析:
对象的键会被转换为字符串。
因为 dog
的值是一个对象,animals[dog]
实际上意味着我们创建了一个叫做 "object Object"
的属性来代表新的对象。animals["object Object"]
现在等于 { emoji: "🐶", name: "Mara"}
。
cat
也是一个对象,animals[cat]
实际上意味着我们在用新的 cat 的属性覆盖animals["object Object"]
的值。
打印 animals[dog]
,实际上是animals["object Object"]
,这是因为转化dog
对象为一个字符串结果 "object Object"
,所以返回 { emoji: "🐈", name: "Sara" }
。
19
哪一个选项会导致报错?
const emojis = ["🎄", "🎅🏼", "🎁", "⭐"];
/* 1 */ emojis.push("🦌");
/* 2 */ emojis.splice(0, 2);
/* 3 */ emojis = [...emojis, "🥂"];
/* 4 */ emojis.length = 0;
答:
3
解析:
这是一个const
常量,不能用=
赋值。
20
const randomValue = 21;
function getInfo() {
console.log(typeof randomValue);
const randomValue = "Lydia Hallie";
}
getInfo();
答:
ReferenceError
解析:
通过 const
关键字声明的变量在被初始化之前不可被引用:这被称之为 暂时性死区。
如果没有const randomValue = "Lydia Hallie";
,会输出number
。