JavaScript-question-2

https://github.com/lydiahallie/javascript-questions

https://github.com/lydiahallie/javascript-questions/blob/master/README-zh_CN.md

let

111:let的作用区块

What’s the output?

1
2
3
4
5
6
7
8
9
10
let name = 'Lydia'

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

getName()

// ReferenceError

Each function has its own execution context (or scope). The getName function first looks within its own context (scope) to see if it contains the variable name we’re trying to access. In this case, the getName function contains its own name variable: we declare the variable name with the let keyword, and with the value of 'Sarah'.

Variables with the let keyword (and const) are hoisted, but unlike var, don’t get initialized. They are not accessible before the line we declare (initialize) them. This is called the “temporal dead zone”. When we try to access the variables before they are declared, JavaScript throws a ReferenceError.

If we wouldn’t have declared the name variable within the getName function, the javascript engine would’ve looked down the scope chain. The outer scope has a variable called name with the value of Lydia. In that case, it would’ve logged Lydia.

yield

112:yield的相关性质,待学习

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'] and a

With the yield keyword, we yield values in a generator function. With the yield* keyword, we can yield values from another generator function, or iterable object (for example an array).

In generatorOne, we yield the entire array ['a', 'b', 'c'] using the yield keyword. The value of value property on the object returned by the next method on one (one.next().value) is equal to the entire array ['a', 'b', 'c'].

1
2
console.log(one.next().value) // ['a', 'b', 'c']
console.log(one.next().value) // undefined

In generatorTwo, we use the yield* keyword. This means that the first yielded value of two, is equal to the first yielded value in the iterator. The iterator is the array ['a', 'b', 'c']. The first yielded value is a, so the first time we call two.next().value, a is returned.

1
2
3
4
console.log(two.next().value) // 'a'
console.log(two.next().value) // 'b'
console.log(two.next().value) // 'c'
console.log(two.next().value) // undefined

operator

113:中的立即执行函数

What’s the output?

1
2
3
console.log(`${(x => x)('I love')} to program`)

// I love to program

Expressions within template literals are evaluated first. This means that the string will contain the returned value of the expression, the immediately invoked function (x => x)('I love') in this case. We pass the value 'I love' as an argument to the x => x arrow function. x is equal to 'I love', which gets returned. This results in I love to program.

objects to null setInterval

114:垃圾回收机制,和setInterval的性质

What will happen?

1
2
3
4
5
6
7
8
9
let config = {
alert: setInterval(() => {
console.log('Alert!')
}, 1000)
}

config = null

// The setInterval callback will still be called every second

Normally when we set objects equal to null, those objects get garbage collected as there is no reference anymore to that object. However, since the callback function within setInterval is an arrow function (thus bound to the config object), the callback function still holds a reference to the config object. As long as there is a reference, the object won’t get garbage collected. Since it’s not garbage collected, the setInterval callback function will still get invoked every 1000ms (1s).

Map

115:Map中key的特性

Which method(s) will return the value 'Hello world!'?

1
2
3
4
5
6
7
8
9
10
11
12
13
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

When adding a key/value pair using the set method, the key will be the value of the first argument passed to the setfunction, and the value will be the second argument passed to the set function. The key is the function () => 'greeting'in this case, and the value 'Hello world'. myMap is now { () => 'greeting' => 'Hello world!' }.

1 is wrong, since the key is not 'greeting' but () => 'greeting'. 3 is wrong, since we’re creating a new function by passing it as a parameter to the get method. Object interact by reference. Functions are objects, which is why two functions are never strictly equal, even if they are identical: they have a reference to a different spot in memory.

reference

116:默认参数和引用,这里可以给person添加一个 food:[1,2,3]

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const person = {
name: "Lydia",
age: 21
}

const changeAge = (x = { ...person }) => x.age += 1
const changeAgeAndName = (x = { ...person }) => {
x.age += 1
x.name = "Sarah"
}

changeAge(person)
changeAgeAndName()

console.log(person)

// {name: "Lydia", age: 22}

Both the changeAge and changeAgeAndName functions have a default parameter, namely a newly created object { ...person }. This object has copies of all the key/values in the person object.

First, we invoke the changeAge function and pass the person object as its argument. This function increases the value of the age property by 1. person is now { name: "Lydia", age: 22 }.

Then, we invoke the changeAgeAndName function, however we don’t pass a parameter. Instead, the value of x is equal to a new object: { ...person }. Since it’s a new object, it doesn’t affect the values of the properties on the person object. person is still equal to { name: "Lydia", age: 22 }.

setter

121.setter的返回值

What’s the output?

1
2
3
4
5
6
7
8
9
10
const config = {
languages: [],
set language(lang) {
return this.languages.push(lang);
}
};

console.log(config.language);

// undefined

The language method is a setter. Setters don’t hold an actual value, their purpose is to modify properties. When calling a setter method, undefined gets returned.

async await

124.async 和 await 相关

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

The generator function range returns an async object with promises for each item in the range we pass: Promise{1}, Promise{2}, Promise{3}. We set the variable gen equal to the async object, after which we loop over it using a for await ... of loop. We set the variable item equal to the returned Promise values: first Promise{1}, then Promise{2}, then Promise{3}. Since we’re awaiting the value of item, the resolved promsie, the resolved values of the promises get returned: 1, 2, then 3.

reference

127.reference destructuring 相关

What’s the output?

1
2
3
4
5
6
const spookyItems = ["👻", "🎃", "🕸"];
({ item: spookyItems[3] } = { item: "💀" });

console.log(spookyItems);

// ["👻", "🎃", "🕸", "💀"]

By destructuring objects, we can unpack values from the right-hand object, and assign the unpacked value to the value of the same property name on the left-hand object. In this case, we’re assigning the value “💀” to spookyItems[3]. This means that we’re modifying the spookyItems array, we’re adding the “💀” to it. When logging spookyItems, ["👻", "🎃", "🕸", "💀"] gets logged.

const

129.const 的暂时性死区

What’s the output?

1
2
3
4
5
6
7
8
9
10
const randomValue = 21;

function getInfo() {
console.log(typeof randomValue);
const randomValue = "Lydia Hallie";
}

getInfo();

// ReferenceError

Variables declared with the const keyword are not referencable before their initialization: this is called the temporal dead zone. In the getInfo function, the variable randomValue is scoped in the functional scope of getInfo. On the line where we want to log the value of typeof randomValue, the variable randomValue isn’t initialized yet: a ReferenceError gets thrown! The engine didn’t go down the scope chain since we declared the variable randomValue in the getInfo function.

async await

130.async await 加上 try catch finally

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const myPromise = Promise.resolve("Woah some cool data");

(async () => {
try {
console.log(await myPromise);
} catch {
throw new Error(`Oops didn't work`);
} finally {
console.log("Oh finally!");
}
})();

// Woah some cool data
// Oh finally!

In the try block, we’re logging the awaited value of the myPromise variable: "Woah some cool data". Since no errors were thrown in the try block, the code in the catch block doesn’t run. The code in the finally block always runs, "Oh finally!" gets logged.

call stack

133.考察的是代码的执行顺序

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();

// Last line! Promise! Promise! Last line! Timeout! Timeout!

First, we invoke funcOne. On the first line of funcOne, we call the myPromise promise, which is an asynchronous operation. While the engine is busy completing the promise, it keeps on running the function funcOne. The next line is the asynchronous setTimeout function, from which the callback is sent to the Web API. (see my article on the event loop here.)

Both the promise and the timeout are asynchronous operations, the function keeps on running while it’s busy completing the promise and handling the setTimeout callback. This means that Last line! gets logged first, since this is not an asynchonous operation. This is the last line of funcOne, the promise resolved, and Promise! gets logged. However, since we’re invoking funcTwo(), the call stack isn’t empty, and the callback of the setTimeout function cannot get added to the callstack yet.

In funcTwo we’re, first awaiting the myPromise promise. With the await keyword, we pause the execution of the function until the promise has resolved (or rejected). Then, we log the awaited value of res (since the promise itself returns a promise). This logs Promise!.

The next line is the asynchronous setTimeout function, from which the callback is sent to the Web API.

We get to the last line of funcTwo, which logs Last line! to the console. Now, since funcTwo popped off the call stack, the call stack is empty. The callbacks waiting in the queue (() => console.log("Timeout!") from funcOne, and () => console.log("Timeout!") from funcTwo) get added to the call stack one by one. The first callback logs Timeout!, and gets popped off the stack. Then, the second callback logs Timeout!, and gets popped off the stack. This logs Last line! Promise! Promise! Last line! Timeout! Timeout!

export default

134.考察 export default 和 import * as

How can we invoke sum in index.js from sum.js?

1
2
3
4
5
6
7
8
9
// sum.js
export default function sum(x) {
return x + x;
}

// index.js
import * as sum from "./sum";

// sum.default(4)

For the sum example, it means that the imported value sum looks like this:

1
{ default: function sum(x) { return x + x } }

We can invoke this function, by calling sum.default

class

139.考察 class 中的变量

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Counter {
#number = 10

increment() {
this.#number++
}

getNum() {
return this.#number
}
}

const counter = new Counter()
counter.increment()

console.log(counter.#number)

// SyntaxError

In ES2020, we can add private variables in classes by using the #. We cannot access these variables outside of the class. When we try to log counter.#number, a SyntaxError gets thrown: we cannot acccess it outside the Counter class!

yield

140.考察 yield

✨ SOMETHING IS MISSING HERE ✨ 这里应该填什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const teams = [
{
name: "Team 1",
members: ["Paul", "Lisa"]
},
{
name: "Team 2",
members: ["Laura", "Tim"]
}
];

function* getMembers(members) {
for (let i = 0; i < members.length; i++) {
yield members[i];
}
}

function* getTeams(teams) {
for (let i = 0; i < teams.length; i++) {
// ✨ SOMETHING IS MISSING HERE ✨
yield* getMembers(teams[i].members)
}
}

const obj = getTeams(teams);
console.log(obj.next()) // { value: "Paul", done: false }
console.log(obj.next()) // { value: "Lisa", done: false }

In order to iterate over the members in each element in the teams array, we need to pass teams[i].members to the getMembers generator function. The generator function returns a generator object. In order to iterate over each element in this generator object, we need to use yield*.

If we would’ve written yield, return yield, or return, the entire generator function would’ve gotten returned the first time we called the next method.

博主更新到了 144 题。