Skip to content

Các câu hỏi phỏng vấn JavaScript - part 2

Posted on:January 14, 2020

Part 1: https://kiendt.me/2019/06/19/javascript-questions/

Trên đời vốn chỉ có 2 loại ngôn ngữ lập trình: loại bị nhiều người chê và loại không ai thèm dùng.


102. Output là gì?

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();

Đáp án: D

Có thể tưởng tượng đơn giản cách promise thực thi như sau: bây giờ tôi sẽ để tạm nó sang một bên vì nó tính toán mất thời gian. Chỉ khi nào nó được hoàn thành (resolved) hay bị hủy bỏ (rejected) hay khi call stack trở nên rỗng thì tôi sẽ lấy giá trị trả về ra.

Dù chúng ta có thể sử dụng giá trị thu được bằng cú pháp .then, hoặc sử dụng cặp cú pháp await/async, nhưng, cách chúng hoạt động là khác nhau.

Trong firstFunction, chúng ta đưa promise qua một bên chờ cho nó tính toán xong, và vẫn tiếp tục chạy những code tiếp sau đó, theo đó console.log('second') sẽ được chạy. Sau đó promise được hoàn thành trả về giá trị I have resolved, giá trị này sẽ được log ra khi call stack trở nên rỗng.

Với từ khóa await trong secondFunction, ta đã tạm dừng một hàm bất đồng bộ cho tới khi chúng trả về giá trị, sau đó ta mới đi tiếp đến các câu lệnh tiếp theo.

Do đó nó sẽ chờ cho tới khi myPromise được hoàn thành và trả về giá trị I have resolved, sau đó chúng ta sẽ chạy tiếp câu lệnh tiếp theo in ra second.


103. Output là gì?

const set = new Set();

set.add(1);
set.add("Lydia");
set.add({ name: "Lydia" });

for (let item of set) {
  console.log(item + 2);
}

Đáp án: C

Phép toán + không chỉ dùng để cộng các số, mà nó còn dùng để nối chuỗi nữa. Mỗi khi Javascript engine gặp một giá trị trong phép toán không phải dạng số, nó sẽ chuyển các số trong phép toán đó sang dạng chuỗi.

Phép toán đầu tiên item là một số 1, nên 1 + 2 trả về 3.

Ở phép toán thứ hai, item là một chuỗi "Lydia". trong khi đó 2 là một số, nên 2 sẽ bị chuyển sang dạng chuỗi, sau khi nối vào ta có chuỗi "Lydia2".

Ở phép toán thứ ba, { name: "Lydia" } là một object. Tuy nhiên dù có là object hay gì đi nữa thì nó cũng sẽ bị chuyển sang dạng chuỗi. Đối với object thì khi chuyển sang dạng chuỗi nó sẽ trở thành "[object Object]". "[object Object]" nối với "2" trở thành "[object Object]2".


104. Output là gì?

Promise.resolve(5);

Đáp án: C

Ta có thể truyền vào giá trị bất kì cho Promise.resolve, dù có là promise hay không promise. Bản thân nó sẽ là một hàm trả về một promise với giá trị đã được resolved.

Trong trường hợp này ta đưa vào giá trị 5. Nó sẽ trả về một resolved promise với giá trị 5.


105. Output là gì?

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);

Đáp án: B

Object sẽ được truyền vào hàm theo reference. Khi chúng ta nói so sánh strict equal (===), nghĩa là ta đang so sánh các reference của chúng.

Ta set giá trị mặc định của person2 là object person, và đưa object person vào làm giá trị cho đối số person1.

Điều đó có nghĩa là chúng cùng trỏ đến một object trong bộ nhớ, do đó chúng bằng nhau, và They are the same! được in ra.


106. Output là gì?

const colorConfig = {
  red: true,
  blue: false,
  green: true,
  black: true,
  yellow: false,
};

const colors = ["pink", "red", "blue"];

console.log(colorConfig.colors[1]);

Đáp án: D

Trong Javascript ta có hai cách để truy cập thuộc tính của một object: sử dụng ngoặc vuông [], hoặc sử dụng chấm .. Trong trương hợp này chúng ta sử dụng chấm (colorConfig.colors) thay cho ngoặc vuông (colorConfig["colors"]).

Với cách sử dụng chấm, Javascript sẽ tìm kiếm một thuộc tính có tên chính xác như tên ta đưa vào. Trong trường hợp này nó là thuộc tính colors trong object colorConfig Tuy nhiên trong object này không có thuộc tính nào tên là colors, nên nó sẽ trả về undefined. Sau đó chúng ta cố truy cậ vào thuộc tính 1 của nó bằng cách gọi [1]. Chúng ta không thể làm như vậy trên giá trị undefined, nên nó sẽ trả về TypeError: Cannot read property '1' of undefined.

Javascript thông dịch theo câu lệnh. Khi ta sử dụng ngoặc vuông, Nnó sẽ tìm mở ngoặc đầu tiên [ và tiếp tục cho tới khi gặp đóng ngoặc tương ứng ]. Chỉ khi đó nó mới đánh giá câu lệnh. Nếu chúng ta sử dụng cú pháp colorConfig[colors[1]], nó sẽ trả về giá trị của thuộc tính red trong object colorConfig.


107. Ouput là gì?

console.log("❤️" === "❤️");

Đáp án: A

Về cơ bản, emoji vẫn là các ký tự unicode mà thôi. Mã unicode cho hình trái tim là "U+2764 U+FE0F". Chúng luôn luôn là một, nên phép toán đơn giản trả về true.


108. Phép toán nào sau đây làm thay đổi mảng gốc?

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, "✨");

Đáp án: D

Với splice, ta thay đổi mảng gốc bằng cách thêm sửa xóa các phần tử. Trong trường hợp này ta xóa 2 phần tử kể từ index 1 (ta xóa '🥑''😍') và thêm vào ✨ emoji.

map, filterslice trả về một mảng mới, find trả về một phần tử, và reduce trả về giá trị tích lũy.


109. Output là gì?

const food = ["🍕", "🍫", "🥑", "🍔"];
const info = { favoriteFood: food[0] };

info.favoriteFood = "🍝";

console.log(food);

Đáp án: A

Trong Javascript tất cả các kiểu cơ bản (mọi thứ không phải object) đều tương tác bằng giá trị. Chúng ta set giá trị của thuộc tính favoriteFood trong object info bằng ký tự bánh pizza, '🍕'. Chuỗi trong javascript là một kiểu cơ bản, nên nó cũng sẽ tương tác bằng giá trị.

Bản thân mảng food không hề thay đổi, do giá trị của favoriteFood chỉ là một bản copy của giá trị đầu tiên trong mảng mà thôi, và không hề trỏ tới reference của food[0]. Do đó khi ghi ra, giá trị của mảng vẫn là giá trị ban đầu, ['🍕', '🍫', '🥑', '🍔'].


110. Phép toán này dùng để làm gì?

JSON.parse();

Đáp án: A

Với phương thức JSON.parse(), ta sẽ parse một chuỗi JSON thành một giá trị JavaScript.

Ví dụ:

// Chuyển một số thành một chuỗi JSON, sau đó parse chuỗi JSON đó để trả về một giá trị JavaScript:
const jsonNumber = JSON.stringify(4); // '4'
JSON.parse(jsonNumber); // 4

// Chuyển một mảng thành một chuỗi JSON, sau đó parse chuỗi JSON để trả về một giá trị JavaScript:
const jsonArray = JSON.stringify([1, 2, 3]); // '[1, 2, 3]'
JSON.parse(jsonArray); // [1, 2, 3]

// Chuyển một object thành một chuỗi JSON, sau đó parse chuỗi JSON để trả về một giá trị JavaScript:
const jsonArray = JSON.stringify({ name: "Lydia" }); // '{"name":"Lydia"}'
JSON.parse(jsonArray); // { name: 'Lydia' }

111. Ouput là gì?

let name = "Lydia";

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

getName();

Đáp án: D

Mỗi hàm sẽ có một context thực thi (hay scope) của riêng nó. Hàm getName đầu tiên sẽ tìm trong context của nó (scope) để tìm xem có biến nào tên là name hay không. Trong trường hợp này, hàm getName có biến name được khai báo với từ khóa let, giá trị là 'Sarah'.

Một biến được khai báo với từ khóa let (hoặc const) sẽ được hoisted, nhưng không giống như var, nó sẽ không được khởi tạo. Nó sẽ không thể truy cập được trước dòng ta khai báo (initialize). Nó được gọi là “temporal dead zone”. Khi ta cố truy cập một biến trước khi nó được khai báo, JavaScript sẽ throw ra ReferenceError.

Nếu ta không khai báo biến name bên trong hàm getName, thì Javascript engine sẽ tiếp tục tìm kiếm trong scope chain. Nó sẽ tìm thấy ở scope phía ngoài một biến name với giá trị là Lydia. Trong trường hợp này nó sẽ log ra Lydia.

let name = "Lydia";

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

getName(); // Lydia

112. Output là gì?

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);

Đáp án: C

Với từ khóa yield, ta sẽ trả về các giá trị trong một generator. Với từ khóa yield*, ta có thể trả về giá trị từ một engerator khác, hoặc một iterable object (ví dụ mảng).

Trong generatorOne, ta trả về toàn bộ mảng ['a', 'b', 'c'] sử dụng từ khóa yield. Giá trị của thuộc tính value trong object thu được bởi phương thức next trong one (one.next().value) là toàn bộ mảng ['a', 'b', 'c'].

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

Trong generatorTwo, ta sử dụng từ khóa yield*. Có nghĩa là giá trị đầu tiên mà two trả về là giá trị đầu tiên trong iterator. Trong trường hợp này iterator của chúng ta là mảng ['a', 'b', 'c']. Giá trị đầu tiên của mảng là a, nên lần đầu tiên khi ta gọi two.next().value, a sẽ được trả về.

console.log(two.next().value); // 'a'
console.log(two.next().value); // 'b'
console.log(two.next().value); // 'c'
console.log(two.next().value); // undefined

113. Output là gì?

console.log(`${(x => x)("I love")} to program`);

Đáp án: A

Biểu thức bên trong chuỗi template (tức chuỗi nằm trong hai dấu “, gọi là template literals) sẽ được đánh giá trước. Sau đó kết quả của biểu thức sẽ được đưa vào chuỗi, trong trường hợp này biểu thức là (x => x)('I love'). Chúng ta truyền giá trị đối số 'I love'cho một arrow functionx => x. xlúc này là'I love', và trả về chính nó. Cuối cùng kết quả của chuỗi là I love to program.


114. Điều gì sẽ xảy ra?

let config = {
  alert: setInterval(() => {
    console.log("Alert!");
  }, 1000),
};

config = null;

Đáp án: C

Thông thường khi ta set một object bằng null, thì object này sẽ được bộ dọn rác dọn đi do không còn gì reference đến nó nữa (garbage collected). Tuy nhiên, do callback trong setInterval là một arrow function (do đó nó sẽ gắn với object config), nên callback này vẫn sẽ giữ reference đến object config. Vì vẫn còn giữ reference, nên object sẽ không bị dọn đi. Do đó nó vẫn sẽ được gọi sau mỗi 1000ms (tức 1 giây).


115. Những hàm nào sẽ trả về '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");

Đáp án: B

Khi ta thêm vào một cặp key/value với từ khóa set, key sẽ là đối số đầu tiên đưa vào trong hàm set function, và value sẽ là đối số thứ hai.Trong trường hơp này key chính là hàm () => 'greeting', value là 'Hello world'. myMap trở thành { () => 'greeting' => 'Hello world!' }.

1 sai, vì key là () => 'greeting' chứ không phải là 'greeting'. 3 sai, vì khi chúng ta đưa một hàm vào làm đối số trong phương thức get, nó sẽ được đưa vào dưới dạng reference. Function vốn là object, do đó 2 hàm sẽ không bao giờ là strictly equal, mặc dù chúng có trông giống nhau đi chăng nữa thì chúng vẫn trỏ đến các vùng nhớ khác nhau.


116. Output là gì?

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);

Đáp án: C

Cả hai hàm changeAgechangeAgeAndName đều có tham số mặc định - nó là một bản copy mới của object { ...person }. Object này sẽ copy tất cả những cặp key/values bên trong object person.

Đầu tiên, chúng ta gọi hàm changeAge và đưa chính object person vào làm đối số. Hàm này sẽ tăng giá trị của thuộc tính age lên 1. person lúc này là { name: "Lydia", age: 22 }.

Sau đó, chúng ta gọi hàm changeAgeAndName tuy nhiên không đưa vào đối số nào cả. Do đó giá trị của x lúc này sẽ là giá trị mặc định, tức một bản copy của object { ...person }. Do nó chỉ là một bản copy (tức object mới), nên nó không ảnh hưởng gì tới giá trị của object person gốc, giá trị của person gốc sẽ vẫn là { name: "Lydia", age: 22 }.


117. Phép tính nào dưới đây trả về 6?

function sumValues(x, y, z) {
  return x + y + z;
}

Đáp án: C

Với toán tử ba chấm (spread operator) ..., chúng ta có thể unpack một iterable thành từng các phần tử riêng biệt. Hàm sumValues nhận vào 3 giá trị: x, yz. ...[1, 2, 3] sẽ trả về 1, 2, 3, đưa vào sumValues sẽ cho ta kết quả là 6.


118. Output là gì?

let num = 1;
const list = ["🥳", "🤠", "🥰", "🤪"];

console.log(list[(num += 1)]);

Đáp án: B

Với phép toán +=, Ta tăng giá trị của num lên 1. num có giá trị khởi tạo là 1, do đó 1 + 12. Phần tử thứ hai của list là 🥰, do đó console.log(list[2]) sẽ in ra 🥰.


119. Output là gì?

const person = {
  firstName: "Lydia",
  lastName: "Hallie",
  pet: {
    name: "Mara",
    breed: "Dutch Tulip Hound",
  },
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  },
};

console.log(person.pet?.name);
console.log(person.pet?.family?.name);
console.log(person.getFullName?.());
console.log(member.getLastName?.());

Đáp án: B

Với phép toán optional chaining ?., chúng ta sẽ không cần phải check xem giá trị phía sau nó có được phép truy cập hay có tồn tại hay không. Nếu ta cố lấy một thuộc tính của undefined hay null (nullish), biểu thức sẽ dừng lại và trả về undefined.

person.pet?.name: person có thuộc tính pet: do đó person.pet không phải là một nullish. Nó có một thuộc tính name, với giá trị Mara.

person.pet?.family?.name: person có thuộc tính pet: do đó person.pet không phải là nullish. Tuy nhiên pet không có thuộc tính family, nên person.pet.family là nullish. Biểu thức sẽ trả về undefined.

person.getFullName?.(): person có thuộc tính getFullName: do đó person.getFullName() không phải nullish và có thể gọi ra, trả về Lydia Hallie.

member.getLastName?.(): member không được định nghĩa: do đó member.getLastName() là nullish. Biểu thức trả về undefined.


120. Ouput là gì?

const groceries = ["banana", "apple", "peanuts"];

if (groceries.indexOf("banana")) {
  console.log("We have to buy bananas!");
} else {
  console.log(`We don't have to buy bananas!`);
}

Đáp án: B

Ta đưa một điều kiện groceries.indexOf("banana") vào câu lệnh if. groceries.indexOf("banana") trả về 0, là một giá trị falsy. Do đó điệu kiện if sẽ chạy vào khối else và in ra We don't have to buy bananas!.


121. Ouput là gì?

const config = {
  languages: [],
  set language(lang) {
    return this.languages.push(lang);
  },
};

console.log(config.language);

Đáp án: D

Phương thức language là một setter. Setter không mang giá trị, nhiệm vụ của nó chỉ đơn giản là thay đổi thuộc tính. Khi ta gọi một phương thức setter nó sẽ trả về undefined.


122. Output là gì?

const name = "Lydia Hallie";

console.log(!typeof name === "object");
console.log(!typeof name === "string");

Đáp án: C

typeof name trả về "string". Chuỗi "string" là một giá trị truthy, do đó !typeof name sẽ trả về một giá trị bool là false. Do đó false === "object"false === "string" đều trả vềfalse.

(Nếu chúng ta muốn check xem một kiểu dữ liệu không phải là một kiểu nào đó, chúng ta nên viết !== thay vì !typeof)


123. Output là gì?

const add = x => y => z => {
  console.log(x, y, z);
  return x + y + z;
};

add(4)(5)(6);

Đáp án: A

Hàm add trả về một arrow function, arrow function này lại trả về một arrow function khác, arrow function này lại trả về một arrow function khác nữa. Hàm đầu tiên nhận vào một tham số x với giá trị là 4 4. Chúng ta gọi hàm thứ hai, nhận vào giá trị của y5. Sau đó chúng ta gọi hàm thứ 3, nhận vào giá trị của z6. Sau đó ta truy cập các giá trị của x, yz bên trong arrow function cuối cùng, khi này JS engine sẽ lần ngược lại scope chain để tìm các giá trị xy tương ứng. Do đó cuối cùng nó sẽ trả về 4 5 6.


124. Output là gì?

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);
  }
})();

Đáp án: C

Generator range trả về một async object với các promise tương ứng với mỗi phần tử ta đưa vào: Promise{1}, Promise{2}, Promise{3}. Ta set giá trị gen bằng với một async object để thực hiện vòng lặp for await ... of sau đó. Tiếp đó ta lại set giá trị của item bằng với giá trị trả về của mỗi promise: đầu tiên là Promise{1}, sau đó Promise{2}, sau đó Promise{3}. Do chúng ta sử dụng cú pháp async/await nên sẽ trả về giá trị đã được resolve của promise item, do đó lần lượt 1, 2, và 3 được in ra.


125. Output là gì?

const myFunc = ({ x, y, z }) => {
  console.log(x, y, z);
};

myFunc(1, 2, 3);

Đáp án: D

myFunc nhận vào một object có các thuộc tính x, yz làm đối số của nó. Do chúng ta đưa vào 3 số riêng biệt (1, 2, 3) chứ không phải một object với các thuộc tính x, y, z như ({x: 1, y: 2, z: 3}), nên x, y, z đều có giá trị là undefined.


126. Output là gì?

function getFine(speed, amount) {
  const formattedSpeed = new Intl.NumberFormat({
    'en-US',
    { style: 'unit', unit: 'mile-per-hour' }
  }).format(speed)

  const formattedAmount = new Intl.NumberFormat({
    'en-US',
    { style: 'currency', currency: 'USD' }
  }).format(amount)

  return `The driver drove ${formattedSpeed} and has to pay ${formattedAmount}`
}

console.log(getFine(130, 300))

Đáp án: B

Với phương thức Intl.NumberFormat, chúng ta có thể format bất cứ số nào theo định dạng ta mong muốn. Ở đây ta format giá trị 130 theo định dạng en-US, kiểu unit, đơn vị là mile-per-hour, và nó sẽ trả về 130 mph. Tiếp theo số 300 sẽ được format theo định dạng en-US, kiểu currentcy, đơn vị USD, và kết quả là $300.00.


127. Output là gì?

const spookyItems = ["👻", "🎃", "🕸"];
({ item: spookyItems[3] } = { item: "💀" });

console.log(spookyItems);

Đáp án: B

Khi tiến hành cú pháp destructuring object, chúng ta có thể unpack các giá trị ở phía phải của một object, và đưa giá trị đã được unpack đó làm giá trị của thuộc tính tương ứng của object phía trái. Trong trường hợp này, ta đã gán giá trị ”💀” cho spookyItems[3]. Có nghĩa là mảng spookyItems đã bị thay đổi, chúng ta đã thêm vào nó một phần tử ”💀“. Do đó khi in ra thì kết quả sẽ là ["👻", "🎃", "🕸", "💀"] .


128. Output là gì?

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));

Đáp án: C

Với phương thức Number.isNaN, ta có thể check một giá trị có phải là dạng số và bằng NaN hay không. name không phải là một số, do đó Number.isNaN(name) sẽ trả về false. age là một số, nhưng không bằng NaN, do đó Number.isNaN(age) cũng trả về false. Với phương thức isNaN, ta đơn thuần chỉ check xem giá trị đưa vào không phải là dạng số hay không. name không phải là dạng số, nên isNaN(name) trả về true. age là số, nên isNaN(age) trả về false.


129. Output là gì?

const randomValue = 21;

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

getInfo();

Đáp án: D

Một biến được khai báo với từ khóa const sẽ không thể truy cập trước khi nó được khởi tạo: nó gọi là temporal dead zone. Trong hàm getInfo, giá trị randomValue sẽ được tìm kiếm đầu tiên trong scope của hàm getInfo. Tại dòng ta muốn lấy ra typeof randomValue, giá trị randomValue chưa được khởi tạo, do đó một ReferenceError sẽ được throw ra! Lưu ý nhỏ là Javascript engine sẽ không tìm kiếm ở scope khác nữa do randomValue đã được khai báo bên trong hàm getInfo.


130. Ouput là gì?

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!");
  }
})();

Đáp án: C

Trong khối try, ta in ra giá trị của biến myPromise: "Woah some cool data". Do không có lỗi gì xảy ra ở đây cả, nên các lệnh trong khối catch sẽ không được chạy. Tuy nhiên các lệnh trong khối finally thì sẽ luôn luôn chạy, nên "Oh finally!" sẽ được in ra.


131. Output là gì?

const emojis = ["🥑", ["✨", "✨", ["🍕", "🍕"]]];

console.log(emojis.flat(1));

Đáp án: B

Với phương thức flat, ta có thể tạo một mảng mới với các phần tử đã được flattened (làm phẳng). Độ sâu của mảng đã làm phẳng sẽ phụ thuộc vào giá trị ta đưa vào. Trong trường hợp này ta đưa vào là 1 (thực ra đây là giá trị default, ta không đưa vào cũng không sao), có nghĩa là chỉ những phần tử ở độ sâu 1 sẽ được làm phẳng. Chúng là['🥑']['✨', '✨', ['🍕', '🍕']] trong trường hợp này. Nối lại ta sẽ có mảng mới ['🥑', '✨', '✨', ['🍕', '🍕']].


132. Output là gì?

class Counter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count++;
  }
}

const counterOne = new Counter();
counterOne.increment();
counterOne.increment();

const counterTwo = counterOne;
counterTwo.increment();

console.log(counterOne.count);

Đáp án: D

counterOne là một instance của class Counter. Trong counter class có thuộc tính count bên trong constructor, và một phương thức increment. Đầu tiên chúng ta gọi phương thức increment hai lần bằng counterOne.increment(). Nên hiện tại giá trị của counterOne.count2.

Sau đó chúng ta có thêm một biến mới là counterTwo, và set cho nó giá trị bằng với counterOne. Do object được tương tác bằng reference, nên việc này tương ứng với ta đã tạo thêm một reference đến bộ nhớ mà biến counterOne đã trỏ vào. Do chúng có chung bộ nhớ, bất cứ thay đổi nào trên counterTwo cũng sẽ thay đổi trên counterOne. Lúc này counterTwo.count cũng sẽ là 2.

Ta gọi hàm counterTwo.increment() để tăng count lên 3. Sau đó chúng ta in ra countcounterOne, kết quả là 3.


133. Output là gì?

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();

Đáp án: D

Đầu tiên chúng ta gọi funcOne. Trong dòng đầu tiên của funcOne, chúng ta gọi myPromise, đây là một hàm bất đồng bộ. Trong khi chờ promise này hoàn thành, nó sẽ tiếp tục thực thi các dòng khác trong funcOne. Dòng tiếp theo là cũng là một hàm bất đồng bộ setTimeout, phần callback của nó sẽ được gửi tới Web API (các bạn có thể tham khảo câu hỏi trước đó để hiểu về callstack và Web API).

Do cả promise và timeout đều là những hàm xử lý bất đồng bộ, nên trong khi chờ chúng hoàn thành thì các dòng tiếp theo vẫn tiếp tục được thực thi. Có nghĩa là Last line! sẽ được in ra đầu tiên, do nó là một hàm chạy đồng bộ. Và đây cũng là dòng cuối cùng của hàm funcOne, khi này promise sẽ được resolve, trả về Promise!. Tuy nhiên do ta tiếp tục gọi hàm funcTwo(), call stack của ta vẫn chưa rỗng, nên callback của setTimeout vẫn chưa thể được đưa vào callstack (vẫn đang năm ở Web API).

Trong hàm funcTwo đầu tiên ta sẽ awaiting myPromise. Với từ khóa await, Ta sẽ tạm dừng thực thi cho tới khi n ào promise được resolved (hay rejected). Khi này ta sẽ in ra giá trị của res (do bản thân hàm promise lại trả về một promise). Nó sẽ in ra Promise!.

Dòng tiếp theo lại là một hàm bất đồng bộ setTimeout, callback khi này tiếp tục được gửi tới Web API.

Ta tiếp tục thực thi dòng cuối cùng của funcTwo, trả về Last line!. Khi này funcTwo đã làm rỗng call stack. Các callback khi nãy (() => console.log("Timeout!") từ funcOne, và () => console.log("Timeout!") từ funcTwo) lần lượt được đưa vào trong call stack. Callback đầu tiên in ra Timeout!. Callback thứ hai in ra Timeout!. Kết quả cuối cùng sẽ là Last line! Promise! Promise! Last line! Timeout! Timeout!


134. Làm thế nào có thể gọi hàm sum trong index.js từ sum.js?

// sum.js
export default function sum(x) {
  return x + x;
}

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

Đáp án: C

Với dấu hoa thị *, ta sẽ import tất cả những gì đã được export ra bởi file đó, cả default lẫn những hàm có tên. Nếu ta có một dòng như sau:

// info.js
export const name = "Lydia";
export const age = 21;
export default "I love JavaScript";

// index.js
import * as info from "./info";
console.log(info);

Thì kết quả sẽ là:

{
  default: "I love JavaScript",
  name: "Lydia",
  age: 21
}

Trong ví dụ hàm sum, nó giống với chúng ta đã import hàm sum như thế này:

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

Ta có thể gọi hàm này bằng cách sử dụng sum.default


135. Output là gì?

const handler = {
  set: () => console.log("Added a new property!"),
  get: () => console.log("Accessed a property!"),
};

const person = new Proxy({}, handler);

person.name = "Lydia";
person.name;

Đáp án: C

Với Proxy object, ta có thể add thêm được các hành vi (behavior) cho object bằng cách đưa nó vào làm đối số thứ hai. Trong trường hợp này, chúng ta đưa vào object handler có hai thuộc tính: setget. set sẽ được gọi mỗi khi ta thay đổi giá trị của thuộc tính, get sẽ được gọi mỗi khi ta truy cập giá trị của thuộc tính.

Giá trị của person sẽ là đối số đầu tiên đưa vào, là một object rỗng {}. Hành vi của person là đối số thứ hai, tức handler. Do đó môi khi ta thêm thuộc tính của obejct person, set sẽ được gọi. Nếu ta truy cập thuộc tính của person thì get sẽ được gọi.

Đầu tiên ra thêm vào thuộc tính name cho proxy object (person.name = "Lydia"). set được gọi và in ra "Added a new property!".

Sau đó chúng truy cập thuộc tính này, get được gọi và in ra "Accessed a property!".


136. Cách nào sau đây sẽ thay đổi object person?

const person = { name: "Lydia Hallie" };

Object.seal(person);

Đáp án: A

Với Object.seal ta có thể ngăn thêm vào các thuộc tính mới, hay xóa đi các thuộc tính cũ.

Tuy nhiên ta vẫn có thể thay đổi các thuộc tính cũ.


137. Cách nào sau đây có thể thay đổi object person?

const person = {
  name: "Lydia Hallie",
  address: {
    street: "100 Main St",
  },
};

Object.freeze(person);

Đáp án: C

Phương thức Object.freeze sẽ đóng băng object. Ta không thể thêm/sửa/xóa bất kì thuộc tính nào.

Tuy nhiên trên thực tế đây chỉ là đóng băng nông (shallowly) object, có nghĩa là nó chỉ đóng băng các thuộc tính trực tiếp của object mà thôi. Nếu thuộc tính lại là một object khác, như address trong trường hợp này, thuộc tính bên trong của address sẽ không bị đóng băng, và ta vẫn có thể chỉnh sửa như bình thường.


138. Cách nào sau đây có thể thay đổi object person?

const person = {
  name: "Lydia Hallie",
  address: {
    street: "100 Main St",
  },
};

Object.freeze(person);

Đáp án: C

Phương thức Object.freeze sẽ đóng băng object. Ta không thể thêm/sửa/xóa bất kì thuộc tính nào.

Tuy nhiên trên thực tế đây chỉ là đóng băng nông (shallowly) object, có nghĩa là nó chỉ đóng băng các thuộc tính trực tiếp của object mà thôi. Nếu thuộc tính lại là một object khác, như address trong trường hợp này, thuộc tính bên trong của address sẽ không bị đóng băng, và ta vẫn có thể chỉnh sửa như bình thường.


139. Output là gì?

const add = x => x + x;

function myFunc(num = 2, value = add(num)) {
  console.log(num, value);
}

myFunc();
myFunc(3);

Đáp án: A

Đầu tiên, ta gọi hàm myFunc() nhưng không đưa vào đối số nào. Do đó numvalue sẽ nhận các giá trị mặc định: num2, và value sẽ là giá trị trả về của hàm add. Với hàm add, ta đưa num vào làm đối số, tức 2. add trả về 4, đây sẽ là giá trị của value.

Sau đó ta gọi hàm myFunc(3), khi này 3 sẽ là giá trị của num. Ta không đưa vào giá trị cho value. Lúc này value tiếp tục nhận giá trị mặc định: giá trị trả về của hàm add. Trong add, ta đưa vào num, khi này là 3. add sẽ trả về 6, đây sẽ là giá trị của value.


140. Output là gì?

class Counter {
  #number = 10;

  increment() {
    this.#number++;
  }

  getNum() {
    return this.#number;
  }
}

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

console.log(counter.#number);

Đáp án: D

Với cú pháp ES2020, ta có thể thêm các thuộc tính private vào class bằng cách sử dụng #. Ta không thể truy cập được biến này bên ngoài class. Khi ta in ra counter.#number, một SyntaxError sẽ được throw: ta không thể truy cập từ phía ngoài class Counter!


141. Câu lệnh còn thiếu là gì?

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 ✨
  }
}

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

Đáp án: B

Ta duyệt và in ra giá trị của từng member bên trong members, mà members lại nằm bên trong mảng teams, ta cần đưa vào đối số teams[i].members cho hàm generator getMembers trong phần code thiếu. Hàm generator sẽ trả về một generator object. Để duyệt qua từng phần tử của một generator object, ta dùng từ khóa yield*.

Nếu ta dùng yield, return yield, hay return, toàn bộ generator sẽ được trả về trong lần đầu tiên chúng ta gọi phương thức next.


142. Output là gì?

const person = {
  name: "Lydia Hallie",
  hobbies: ["coding"],
};

function addHobby(hobby, hobbies = person.hobbies) {
  hobbies.push(hobby);
  return hobbies;
}

addHobby("running", []);
addHobby("dancing");
addHobby("baking", person.hobbies);

console.log(person.hobbies);

Đáp án: C

Hàm addHobby nhận vào hai đối số, hobby, và hobbies với giá trị default là mảng hobbies của object person.

Đầu tiên chúng ta gọi hàm addHobby và đưa vào "running" làm giá trị cho hobby, và một mảng rỗng cho hobbies. Do chúng ta đưa vào một mảng rỗng cho hobbies, "running" sẽ được add vào một mảng rỗng.

Sau đó chúng ta tiếp tục gọi hàm addHobby, đưa "dancing" vào làm giá trị cho hobby. Chúng ta không hề đưa vào giá trị nào cho hobbies, do đó nó sẽ sử dụng giá trị mặc định, tức mảng hobbies trong thuộc tính của object person. Có nghĩa là ta đã thêm dancing vào trong mảng person.hobbies.

Cuối cùng chúng ta lại gọi addHobby, đưa "baking" vào làm giá trị cho hobby, và mảng person.hobbies làm giá trị cho hobbies. Có nghĩa là ta đã thêm baking vào trong mảng person.hobbies.

Sau khi thêm dancingbaking, giá trị của person.hobbies["coding", "dancing", "baking"]


143. Output là gì?

class Bird {
  constructor() {
    console.log("I'm a bird. 🦢");
  }
}

class Flamingo extends Bird {
  constructor() {
    console.log("I'm pink. 🌸");
    super();
  }
}

const pet = new Flamingo();

Đáp án: B

Chúng ta tạo ra biến pet là một instance của clas Flamingo. Khi ta tạo ra instance, constructor bên trong Flamingo sẽ được gọi. Đầu tiên, "I'm pink. 🌸" được in ra, sau đó chúng ta gọi super(). super() sẽ gọi constructor ở class cha, tức Bird. Hàm constructor trong Bird được gọi và in ra "I'm a bird. 🦢".


144. Câu lệnh nào sẽ bị lỗi?

const emojis = ["🎄", "🎅🏼", "🎁", "⭐"];

/* 1 */ emojis.push("🦌");
/* 2 */ emojis.splice(0, 2);
/* 3 */ emojis = [...emojis, "🥂"];
/* 4 */ emojis.length = 0;

Đáp án: D

Từ khóa const làm cho ta không thể định nghĩa lại giá trị của biến, nó là read-only. Tuy nhiên giá trị của bên trong nó thì không phải là bất biến. Các thuộc tính bên trong mảng emojis vẫn có thể được sửa đổi, ví dụ thêm phần tử, cắt, hoặc là đưa độ dài mảng về 0.


145. Ta cần thêm gì vào object person để khi gọi [...person] sẽ cho kết quả là ["Lydia Hallie", 21]?

const person = {
  name: "Lydia Hallie",
  age: 21
}

[...person] // ["Lydia Hallie", 21]

Đáp án: C

Mặc định ta không thể duyệt qua được object. Trừ phi nó được cài đặt iterator protocol. Ta có thể cài đặt bằng cách thêm vào một iterator symbol [Symbol.iterator], biến nó trở thành generator object (object có thể duyệt được), ví dụ *[Symbol.iterator]() {}.

Để generator này trả về được mảng các giá trị của các thuộc tính của object person, tức Object.values của object person, ta sẽ sử dụng cấu trúc yield* Object.values(this).