Tổng hợp các câu hỏi phỏng vấn Javascript phần 2

Các câu hỏi sẽ từ cơ bản đến nâng cao: kiểm tra trình độ JavaScript của bạn, làm mới một chút về kiến thức, hay chuẩn bị cho cuộc phỏng vấn lập trình!

Các đáp án được đặt dưới mỗi câu hỏi, hãy click để tham khảo chúng. Chúc may mắn :heart:

51. Output là gì?

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"
Đáp án

Đáp án: A

Đối số sẽ được đưa vào hàm dạng tham trị, trừ phi nó là object, khi đó nó sẽ được đưa vào hàm dạng tham chiếu. birthYear là dạng giá trị, vì nó là string chứ không phải object. Khi chúng ta đưa vào dạng giá trị, một bản sao của giá trị đó sẽ được tạo ra (xem thêm câu 46).

birthYear trỏ đến giá trị là "1997". Đối số year cũng sẽ rỏ đến giá trị "1997", nhưng giá trị này chỉ là một bản sao của giá trị mà birthYear trỏ tới mà thôi, hai giá trị đó hoàn toàn khác nhau. Do đó khi ta thay đổi giá trị year bằng "1998", chúng ta chỉ thay đổi giá trị của year mà thôi. birthYear sẽ vẫn giữ giá trị là "1997".

person là một object. Biến member có một tham chiếu tới cùng object mà person trỏ tới. Khi chúng ta thay đổi một thuộc tính của object mà member trỏ tới, giá trị của person cũng sẽ tự động thay đổi theo, vì chúng có chung tham chiếu. name của person khi này sẽ có giá trị mới là "Lydia".


52. Output là gì?

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 only throw Error objects
  • D: "Oh no an error: Hello world!
Đáp án

Đáp án: D

Với lệnh throw, chúng ta có thể tạo ra các errors tùy ý. Với câu lệnh đó, chúng ta có thể throw các exception. Một exeption có thể là một chuỗi, một số, một boolean hoặc một object. Trong trường hợp này thì nó là chuỗi 'Hello world'.

Với lệnh catch chúng ta có thể xử lý những exeption được throw ra khi thực hiện try. Một exeption đã được throw ra: chuỗi 'Hello world'. e chính là chuỗi đó và chúng ta sẽ in ra. Kết quả là 'Oh an error: Hello world'.


53. Output là gì?

function Car() {
  this.make = "Lamborghini";
  return { make: "Maserati" };
}

const myCar = new Car();
console.log(myCar.make);
  • A: "Lamborghini"
  • B: "Maserati"
  • C: ReferenceError
  • D: TypeError
Đáp án

Đáp án: B

Khi chúng ta trả về một thuộc tính, giá trị của thuộc tính bằng với giá trị đã được trả về bởi lệnh return, chứ không phải giá trị được set trong constructor. Chúng ta trả về giá trị là "Maserati", do đó myCar.make sẽ là "Maserati".


54. Output là gì?

(() => {
  let x = (y = 10);
})();

console.log(typeof x);
console.log(typeof y);
  • A: "undefined", "number"
  • B: "number", "number"
  • C: "object", "number"
  • D: "number", "undefined"
Đáp án

Đáp án: A

let x = y = 10; chính là cách viết ngắn gọn của:

y = 10;
let x = y;

Khi ta set y bằng 10, thực tế chúng ta đã sử dụng biến global y (window nếu là trên browser, global nếu là môi trường Node).Trên browser, window.y sẽ là 10.

Sau đó, chúng ta khai báo giá trị của x với giá trị của y, tức 10. Tuy nhiên khi ta khai báo với từ khóa let biến x sẽ chỉ tồn tại trong block scoped; hay trong trường hợp này là hàm thực hiện ngay lập tức (immediately-invoked function - IIFE). Khi ta sử dụng phép toán typeof, x hoàn toàn chưa được định nghĩa: vì x lúc này nằm bên ngoài block nó được định nghĩa lúc trước. Nghĩa là xundefined. Do đó console.log(typeof x) trả về "undefined".

Tuy nhiên với y thì khác, ta đã có giá trị của y khi set y bằng 10. Giá trị đó có thể truy cập được từ bất kì đâu bởi chúng là biến global. y được định nghĩa với kiểu là "number". Do đó console.log(typeof y) trả về "number".


55. Output là gì?

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
Đáp án

Đáp án: A

Chúng ta có thể xóa các thuộc tính khỏe object bằng từ khóa delete, kể cả với prototype. Khi chúng ta xóa một thuộc tính trên prototype, nó sẽ bị vô hiệu hóa hoàn toàn trong chuỗi prototype. Trong trường hợp này, hàm bark sẽ bị vô hiệu hóa ngay sau khi chúng ta thực hiện hàm xóa delete Dog.prototype.bark, tất nhiên ta vẫn có thể truy cập vào nó nhưng giá trị sẽ là undefined.

Khi chúng ta chạy một thứ không phải là hàm, nó sẽ bắn ra một TypeError. Trong trường hợp này là TypeError: pet.bark is not a function, vì bản thân thuộc tính pet.barkundefined.


56. Output là gì?

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}
Đáp án

Đáp án: D

Set là một tập hơp các giá trị không trùng nhau.

Chúng ta đưa đầu vào là một mảng [1, 1, 2, 3, 4] với giá trị 1 bị trùng. Giá trị trùng đó sẽ bị loại bỏ. Kết quả là {1, 2, 3, 4}.


57. Output là gì?

// 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
Đáp án

Đáp án: C

Một module khi được import sẽ là read-only: chúng ta sẽ không thể chỉnh sửa module đó, chỉ có bản thân module đó có thể chỉnh sửa giá trị của nó mà thôi.

Khi ta thay đổi giá trị cuả myCounter, nó sẽ throw ra một lỗi: myCounterread-only và không thể thay đổi.


58. Output là gì?

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
Đáp án

Đáp án: A

Phép toán delete sẽ trả về một giá trị boolean: true nếu xóa thành công, false nếu thất bại. Tuy nhiên, nếu biến được khai báo với các từ khóa var, const hay let thì nó sẽ không thể bị xóa bởi phép toán delete.

Biến name được khai báo với từ khóa const, nên nó sẽ không thể bị xóa và trả về false. Khi ta set age bằng 21, thực tế là ta đang sử dụng biến global age. Ta có thể xóa sử dụng phép toán delete, khi này delete age trả về true.


59. Output là gì?

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]
Đáp án

Đáp án: C

Chúng ta có thể unpack các giá trị từ mảng hoặc thuộc tính từ objects bằng phương pháp destructuring. Ví dụ:

[a, b] = [1, 2];

Giá trị của a sẽ là 1, b sẽ là 2. Thực tế, câu hỏi của chúng ta đơn giản là:

[y] = [1, 2, 3, 4, 5];

Có nghĩa là y chính là giá trị đầu tiên trong mảng, tức số 1. Do đó khi ta in ra y thì sẽ là1.


60. Output là gì?

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 }
Đáp án

Đáp án: B

Ta có thể kết hợp 2 object sử dụng phép toán spread operator .... Nó cho phép ta tạo ra bản sao của từng cặp key/values trong từng object và nối chúng lại với nhau thành một object mới. Trong trường hợp này chúng ta tạo ra các bản sao của các cặp key/value của object user object, và nối chúng vào object admin. admin object khi này sẽ trở thành { admin: true, name: "Lydia", age: 21 }.


61. Output là gì?

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"]
Đáp án

Đáp án: B

Với phương thức defineProperty, chúng ta có thể thêm các thuộc tính mới, cũng như sửa các thuộc tính sẵn có của object. Khi chúng ta thêm thuộc tính vào object bằng defineProperty, chúng sẽ mặc định là thuộc tính not enumerable. Phương thức Object.keys sẽ trả về tất cả các thuộc tính enumerable của object, trong trường hợp này thì chỉ có "name" mà thôi.

Thêm nữa, các thuộc tính được thêm bởi defineProperty là mặc định không thể thay đổi được. Tất nhiên ta có thể override các điều đó bằng các thuộc tính như writable, configurableenumerable. Tức là defineProperty là một cách rất mềm dẻo để tạo ra và điều chỉnh thuộc tính của object.


62. Output là gì?

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}"
Đáp án

Đáp án: A

Đối số thứ hai của JSON.stringifyreplacer. Replacer Có thể là một hàm hoặc một mảng, nó sẽ quy định xem giá trị nào sẽ được chuỗi hóa ra sao.

Nếu replacer là một mảng, chỉ có các thuộc tính có tên trong mảng được convert thành chuỗi JSON. Trong trường hợp này, chỉ có các thuộc tính "level""health" được đưa vào, "username" bị loại bỏ. data giờ sẽ là "{"level":19, "health":90}".

Nếu replacer là function, hàm này sẽ được gọi trên từng thuộc tính của object được chuỗi hóa. Giá trị trả về sẽ là giá trị được đưa vào chuỗi JSON. Nếu trả về undefined, thuộc tính này sẽ bị loại bỏ khỏi chuỗi.


63. Output là gì?

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
Đáp án

Đáp án: A

Phép toán ++ sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của num110, vì increaseNumber sẽ trả về giá trị của num, đang là 10, và sau đó mới tăng giá trị của num lên.

num2 cũng là 10, vì chúng ta đưa num1 vào increasePassedNumber. number bằng 10(tức giá trị của num1). Cũng giống như trên, phép toán ++ sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của number10, do đó num2 cũng sẽ là 10.


64. Output là gì?

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
Đáp án

Đáp án: C

Trong ES6 thì chúng ta có thể khởi tạo tham số với giá trị mặc định. Giá trị của tham số sẽ là giá trị mặc định nếu ta không truyền gì vào hàm, hoặc khi giá trị truyền vào là "undefined". Trong trường hợp này, ta dùng spread operator (toán tử mở rộng) để biến value thành một object mới, do đó x sẽ có giá trị mặc định là { number: 10 }.

Chú ý một điều là đối số sẽ được xét giá trị tại call time! Có nghĩa là mỗi khi chúng ta gọi hàm, một object mới sẽ được tạo ra. Chúng ta gọi hàm multiply hai lần mà không truyền vào đối số nào cả: x sẽ nhận giá trị mặc định { number: 10 }. Sau đó chúng ta sẽ ghi ra giá trị là 20.

Lần gọi thứ ba chúng ta truyền vào một đối số: chính là value. Toán tử *= chính là cách viết gọn của x.number = x.number * 2: chúng ta thay đổi giá trị của x.number, và ghi ra giá trị 20.

Tại lần gọi thứ tư, chúng ta truyền vào value một lần nữa. x.number trước đó đã bị thay đổi thành 20, nên x.number *= 2 sẽ ghi ra 40.


65. Output là gì?

[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
  • A: 1 2 and 3 3 and 6 4
  • B: 1 2 and 2 3 and 3 4
  • C: 1 undefined and 2 undefined and 3 undefined and 4 undefined
  • D: 1 2 and undefined 3 and undefined 4
Đáp án

Đáp án: D

Đối số đầu tiên của hàm reduce chính là accumulator (tổng tích lũy), trong trường hợp này là x. Đối số thứ 2 chính là giá trị hiện tại, tức y. Với hàm reduce, ta sẽ gọi callback trên mỗi phần tử của mảng, cứ vậy cho tới khi ra đến một giá trị cuối cùng.

Trong trường hợp này, chúng ta không trả về bất cứ giá trị nào cả, mà đơn thuần chỉ là ghi ra giá trị của tổng tích lũygiá trị hiện tại mà thôi.

Giá trị của tổng tích lũy chính là giá trị được hàm callback trả về tại vòng lặp trước đó. Nếu ta không đặt giá trị khởi tạo cho đối số trong hàm reduce, thì tổng tích lũy sẽ chính bằng giá trị đầu tiên tại lời gọi đầu tiên.

Trong lời gọi đầu tiên, tổng tích lũy (x) là 1, và giá trị hiện tại (y) là 2. Chúng ta không trả về giá trị cho hàm callback, mà đơn thuần chỉ ghi chúng ta, vậy nên 12 được ghi ra.

Nếu ta không trả về giá trị trong một function, thì nó sẽ mặc định trả về là undefined. Do đó trong lời gọi tiếp theo tổng tích lũy sẽ là undefined, và giá trị hiện tại là 3. undefined3 sẽ được ghi ra.

Tiếp tục như vậy, trong lời gọi thứ tư thì tổng tích lũy sẽ vẫn là undefined, giá trị hiện tại là 4. undefined4 sẽ được ghi ra.


66. Với cách nào chúng ta có thể kế thừa Dog class?

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
Đáp án

Đáp án: B

Trong class dẫn xuất, ta không thể truy cập từ khóa this trước khi gọi super. Nếu bạn chạy thử, nó sẽ throw ra một ReferenceError, do đó 1 và 4 sẽ throw ra reference error.

Với việc gọi super, chúng ta đã gọi hàm constructor của class cha với tham số truyền vào. Trong trường hợp này, class cha nhận name làm đối số trong constructor, do đó chúng cần đưa name vào hàm super.

Labrador class nhận vào hai đối số, name vì nó kế thừa Dog, và size - một thuộc tính của Labrador class. Cả hai đều cần thiết để đưa vào trong constructor của class Labrador, do đó cách khởi tạo đúng là 2.


67. Output là gì?

// 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
Đáp án

Đáp án: B

Với import, tất cả các module import vào đều sẽ được pre-parsed (đánh giá trước). Có nghĩa là những module được import vào sẽ được chạy trước, còn code trong file sẽ chạy sau.

Đó chính là điều khác biệt giữa require() trong CommonJS và import! Với require(), ta có thể load các dependencies tại bất cứ khi nào ta cần. Nếu ta sử dụng require thay thế cho import thì running index.js, running sum.js, 3 sẽ được ghi ra.


68. Output là gì?

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
Đáp án

Đáp án: A

Mỗi Symbol là một thứ hoàn toàn khác biệt. Giá trị truyền vào làm đối số trong Symbol chỉ đơn thuần là phần giải thích cho Symbol đó mà thôi, và nó không liên quan gì tới giá trị của Symbol đó cả. Chúng ta kiểm tra tính bằng nhau của hai Symbol hoàn toàn khác biệt: Symbol('foo') thứ nhất, và Symbol('foo') thứ hai. Mỗi giá trị đều là riêng biệt và duy nhất, nên Symbol('foo') === Symbol('foo') sẽ trả về false.


69. Output là gì?

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",
Đáp án

Đáp án: C

Với hàm padStart chúng ta có thể thêm vào khoảng trắng đằng trước mỗi chuỗi. Giá trị đưa vào trong hàm là tổng độ dài của chuỗi sau khi thêm vào khoảng trắng. Chuỗi "Lydia Hallie" có độ dài là 12 nên name.padStart(13) sẽ thêm vào một khoảng trắng đằng trước chuỗi.

Nếu đối số truyền vào cho hàm padStart nhỏ hơn độ dài của chuỗi, không có khoảng trắng nào được thêm vào.


70. Output là gì?

console.log("🥑" + "💻");
  • A: "🥑💻"
  • B: 257548
  • C: A string containing their code points
  • D: Error
Đáp án

Đáp án: A

Với phép toán +, ta có thể nối các xâu chuỗi. Trong trường hợp này, ta nối chuỗi "🥑" với chuỗi "💻", kết quả tạo ra "🥑💻".


71. Làm thế nào có thể ghi ra giá trị giống như trong comment khi 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")
Đáp án

Đáp án: C

Một generator sẽ "tạm dừng" khi nhìn thấy từ khóa yield. Đầu tiên ra sẽ đưa ra chuỗi "Do you love JavaScript?", bằng cách gọi game.next().value.

Chương trình sẽ chạy từng dòng, cho tới khi nó tìm thấy từ khóa yield. Có một từ khóa yield tại dòng đầu tiên của hàm: chương trình sẽ dừng tại đâ! Điều đó có nghĩa là biến answer chưa hề được định nghĩa!

Khi ta gọi game.next("Yes").value, yield trước đó sẽ được thay thế bởi giá trị được truyền vào hàm next(), trong trường hợp này là"Yes". Theo đó giá trị của biến answer giờ sẽ là "Yes". Điều kiện if sẽ trả về false, và JavaScript loves you back ❤️ sẽ được ghi ra.



72. Output là gì?

console.log(String.raw`Hello\nworld`);
  • A: Hello world!
  • B: Hello
         world
  • C: Hello\nworld
  • D: Hello\n
         world
Đáp án

Đáp án: C

String.raw trả về chuỗi nguyên bản, các ký tự (\n, \v, \t etc.) sẽ vẫn là nguyên bản và không biến thành xuống dòng hay khoảng trắng! Nếu ta không để là chuỗi nguyên bản, sẽ có trường hợp xảy ra lỗi không mong muốn, ví dụ với đường dẫn:

const path = `C:\Documents\Projects\table.html`

Sẽ cho ta chuỗi là:

"C:DocumentsProjects able.html"

Với String.raw, nó sẽ trả về là:

C:\Documents\Projects\table.html

Do đó, trong trường hợp này Hello\nworld sẽ được ghi ra.


73. Output là gì?

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
Đáp án

Đáp án: C

Một hàm async luôn luôn trả về một promise. await sẽ chờ cho tới khi promise đó được hoàn thành: một pending promise sẽ được trả về khi ta gọi getData() bằng cách gán nó cho biến data.

Nếu ta muốn truy cập giá trị đã hoàn thành của promise, trong trường hợp này là "I made it", ta có thể sử dụng hàm .then() ngay sau data như sau:

data.then(res => console.log(res))

Khi này nó sẽ ghi ra "I made it!"


74. Output là gì?

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
Đáp án

Đáp án: B

Hàm .push() trả về độ dài của mảng mới! Trước đó, mảng chỉ hồm một phần tử là "banana" và có độ dài là 1. Sau khi thêm chuỗi "apple" vào mảng, mảng lúc này có hai chuỗi và có độ dài là 2. Do đó hàm addToList sẽ trả về 2.

Hàm push sẽ thay đổi chính bản thân mảng truyền vào. Do đó nếu chúng ta muốn trả về mảng thay vì chỉ trả về độ dài, chúng ta nên trả về trực tiếp mảng list sau khi đã thêm item vào đó.


75. Output là gì?

const box = { x: 10, y: 20 };

Object.freeze(box);

const shape = box;
shape.x = 100;

console.log(shape);
  • A: { x: 100, y: 20 }
  • B: { x: 10, y: 20 }
  • C: { x: 100 }
  • D: ReferenceError
Đáp án

Đáp án: B

Object.freeze khiến cho chúng ta không thể thêm vào, xóa đi hay thay đổi bất kì thuộc tính nào của object (trừ phi giá trị của thuộc tính lại chính là một object khác).

Khi chúng ta tạo ra biến shape và set cho nó giá trị bằng với một object đã được đóng băng là box, thì shape cũng sẽ trỏ tới một object đã được đóng băng. Ta có thể check một object có đang bị đóng băng hay không bằng Object.isFrozen. Trong trường hợp này, Object.isFrozen(shape) trả về true, vì shape đang trỏ tới một object bị đóng băng.

Do đó, cộng với việc x không phải là object, ta sẽ không thể thay đổi giá trị của x. x sẽ vẫn là 10, và { x: 10, y: 20 } được ghi ra.


76. Output là gì?

const { name: myName } = { name: "Lydia" };

console.log(name);
  • A: "Lydia"
  • B: "myName"
  • C: undefined
  • D: ReferenceError
Đáp án

Đáp án: D

Khi ta tiến hành unpack giá trị name từ object ở phía bên phải, ta đã gán giá trị "Lydia" của nó cho biến có tên là myName.

Với cú pháp { name: myName }, chúng ta muốn khai báo một biến myName với giá trị là giá trị của thuộc tính name trong object phía bên phải.

Do name chưa được định nghĩa, nên ghi log ra, nó sẽ throw ra một ReferenceError.


77. Đây có phải là một pure function không?

function sum(a, b) {
  return a + b;
}
  • A: Yes
  • B: No
Đáp án

Đáp án: A

Một hàm được gọi là pure function khi nó luôn luôn trả về một giá trị giống nhau, nếu đối số đưa vào là giống nhau.

Hàm sum luôn trả về giá trị giống nhau. Nếu ta đưa vào 12, nó sẽ luôn trả về 3. Nếu ta đưa vào 510, nó luôn trả về 15. Cứ như vậy, đây là một pure function.


78. Output là gì?

const add = () => {
  const cache = {};
  return (num) => {
    if (num in cache) {
      return `From cache! ${cache[num]}`;
    } else {
      const result = num + 10;
      cache[num] = result;
      return `Calculated! ${result}`;
    }
  };
};

const addFunction = add();
console.log(addFunction(10));
console.log(addFunction(10));
console.log(addFunction(5 * 2));
  • A: Calculated! 20 Calculated! 20 Calculated! 20
  • B: Calculated! 20 From cache! 20 Calculated! 20
  • C: Calculated! 20 From cache! 20 From cache! 20
  • D: Calculated! 20 From cache! 20 Error
Đáp án

Đáp án: C

Hàm add chính là một hàm memoized (hàm có nhớ). Với việc có nhớ, chúng ta có thể cache lại kết quả của function để tăng tốc độ tính toán lên. Trong trường hợp này, chúng ta tạo ra một cache object để lưu trữ những kết quả tính toán trước đó.

Mỗi lần chúng ta gọi hàm addFunction với đối số giống nhau, đầu tiên nó sẽ check xem đối số đó có tồn tại trong cache hay không. Nếu có, giá trị trong cache sẽ được trả về luôn, tiết kiệm thời gian tính toán. Còn nếu không thì nó sẽ tiến hành tính toán kết quả và tiếp tục lưu vào cache.

Chúng ta gọi hàm addFunction ba lần với cùng một đối số: trong lần gọi đầu tiên, giá trị của num10 và chưa có mặt trong cache. Do đó num in cache trả về false, và sẽ chạy vào else block: Calculated! 20 sẽ được ghi ra, và 10 sẽ được đưa vào cạche. cache khi này sẽ là { 10: 20 }.

Tại lần gọi thứ hai, cache object đã có giá trị 10. num in cache trả về true, và 'From cache! 20' được ghi ra.

Tại lần gọi thứ ba, ta đưa vào 5 * 2, tức 10 vào hàm. Tiếp tục giống như trên, 'From cache! 20' sẽ được ghi ra.


79. Output là gì?

const myLifeSummedUp = ["☕", "💻", "🍷", "🍫"];

for (let item in myLifeSummedUp) {
  console.log(item);
}

for (let item of myLifeSummedUp) {
  console.log(item);
}
  • A: 0 1 2 3 and "☕" "💻" "🍷" "🍫"
  • B: "☕" "💻" "🍷" "🍫" and "☕" "💻" "🍷" "🍫"
  • C: "☕" "💻" "🍷" "🍫" and 0 1 2 3
  • D: 0 1 2 3 and {0: "☕", 1: "💻", 2: "🍷", 3: "🍫"}
Đáp án

Đáp án: A

Với vòng lặp for-in, chúng ta có thể duyệt qua các thuộc tính enumerable của object. Với mảng, thuộc tính enumerable chính là các "key" của mảng, hay chính là các index của mảng đó. Ta có thể coi mảng như là:

{0: "☕", 1: "💻", 2: "🍷", 3: "🍫"}

Do đó 0 1 2 3 được ghi ra.

Với vòng lặp for-of, chúng ta sẽ duyệt qua các phần tử của một iterable. Một mảng chính là một iterable. Khi chúng ta duyệt qua mảng, biến "item" chính là phần tử mà nó đang duyệt qua, do đó "☕" "💻" "🍷" "🍫" được ghi ra.


80. Output là gì?

const list = [1 + 2, 1 * 2, 1 / 2];
console.log(list);
  • A: ["1 + 2", "1 * 2", "1 / 2"]
  • B: ["12", 2, 0.5]
  • C: [3, 2, 0.5]
  • D: [1, 1, 1]
Đáp án

Đáp án: C

Mảng có thể nhận bất cứ giá trị nào. Số, chuỗi, objects, mảng khác, null, boolean, undefined, và nhiều dạng biểu thức nữa như ngày tháng, hàm, và các tính toán.

Giá trị của phần tử chính là giá trị trả về. 1 + 2 trả về 3, 1 * 2 trả về 2, và 1 / 2 trả về 0.5.


81. Output là gì?

function sayHi(name) {
  return `Hi there, ${name}`;
}

console.log(sayHi());
  • A: Hi there,
  • B: Hi there, undefined
  • C: Hi there, null
  • D: ReferenceError
Đáp án

Đáp án: B

Mặc định, đối số sẽ có giá trị là undefined, trừ phi ta gán giá trị cho nó khi đưa vào hàm. Trong trường hợp này, ta không đưa vào giá trị nào cho đối số name cả. Do đó name sẽ là undefined và được ghi ra.

Với cú pháp ES6, ta có thể thay đổi giá trị mặc định undefined bằng một giá trị mặc định khác. Ví dụ:

function sayHi(name = "Lydia") { ... }

Trong trường hợp này, nếu ta không đưa giá trị nào vào hoặc đưa vào undefined, name cũng sẽ nhận giá trị mặc định là Lydia.


82. Output là gì?

var status = "😎";

setTimeout(() => {
  const status = "😍";

  const data = {
    status: "🥑",
    getStatus() {
      return this.status;
    },
  };

  console.log(data.getStatus());
  console.log(data.getStatus.call(this));
}, 0);
  • A: "🥑" and "😍"
  • B: "🥑" and "😎"
  • C: "😍" and "😎"
  • D: "😎" and "😎"
Đáp án

Đáp án: B

Giá trị của this phụ thuộc vào vị trí mà nó được gọi. Trong một phương thức, ví dụ getStatus, this trỏ tới object chứa phương thức đó. Phương thức này thuộc data object, do đó this trỏ tới data object. Khi chúng ta gọi this.status thì thuộc tính status của data sẽ được ghi ra, chính là "🥑".

Với phương thức call, chúng ta thay đổi tham chiếu mà this trỏ tới. Trong hàm, từ khóa this trỏ tới object chứa hàm đó. Chúng ta khai báo hàm setTimeout trong global object, do đó bên trong hàm setTimeout thì this sẽ trỏ tới global object. Tại biến global object, có một biến status với giá trị "😎". Do đó khi gọi this.status, "😎" sẽ được ghi ra.


83. Output là gì?

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

let city = person.city;
city = "Amsterdam";

console.log(person);
  • A: { name: "Lydia", age: 21 }
  • B: { name: "Lydia", age: 21, city: "Amsterdam" }
  • C: { name: "Lydia", age: 21, city: undefined }
  • D: "Amsterdam"
Đáp án

Đáp án: A

Chúng ta set biến city bằng với giá trị của thuộc tính city của object person. Nhưng object này không có thuộc tính nào là city cả, nên giá trị của biến city sẽ là undefined.

Chú ý là chúng ta không tham chiếu tới bản thân object person! Chúng ta chỉ đơn giản là set giá trị của biến city bằng với giá trị của thuộc tính city trong object person mà thôi.

Sau đó chúng ta set biến city bằng với chuỗi "Amsterdam". Điều này không hề ảnh hưởng gì tới object person vì không có tham chiếu nào ở đây cả.

Do đó khi ghi object person ra, Tất cả các thuộc tính vẫn như cũ không hề thay đổi gì cả.


84. Output là gì?

function checkAge(age) {
  if (age < 18) {
    const message = "Sorry, you're too young.";
  } else {
    const message = "Yay! You're old enough!";
  }

  return message;
}

console.log(checkAge(21));
  • A: "Sorry, you're too young."
  • B: "Yay! You're old enough!"
  • C: ReferenceError
  • D: undefined
Đáp án

Đáp án: C

Biến số được khai báo với các từ khóa như const hay let đều là block-scoped. Block chính là những khối được bao bọc bởi cặp ngoặc nhọn ({ }). Trong trường hợp này nó chính là cặp ngoặc bao quanh những câu lệnh if/else. Chúng ta không thể truy cập đến biến đó bên ngoài block, và kết quả là throw một ReferenceError.


85. Những thông tin nào sẽ được ghi ra?

fetch("https://www.website.com/api/user/1")
  .then((res) => res.json())
  .then((res) => console.log(res));
  • A: The result of the fetch method.
  • B: The result of the second invocation of the fetch method.
  • C: The result of the callback in the previous .then().
  • D: It would always be undefined.
Đáp án

Đáp án: C

Giá trị của res trong .then thứ hai chính là giá trị trả về từ .then trước đó. Chúng ta có thể thực hiện .then liên tiếp như vậy, các giá trị sẽ liên tiếp được truyền tới hàm xử lý tiếp theo.


86. Bằng cách nào chúng ta có thể set hasName bằng true, nếu chúng ta không đưa true vào đối số?

function getName(name) {
  const hasName = //
}
  • A: !!name
  • B: name
  • C: new Boolean(name)
  • D: name.length
Đáp án

Đáp án: A

Với !!name, chúng ta sẽ đánh giá giá trị của nametruthy hay falsy. Nếu name là truthy, thì !name sẽ trả về false. !false (hay chính là !!name khi này) sẽ trả về true.

Bằng cách set hasName bằng với name, chúng ta set hasName bằng với giá trị đối số đưa vào trong hàm getName, không thỏa mãn vì ta đã giới hạn là không đưa true vào.

new Boolean(true) trả về một object wrapper, chứ không phải là một giá trị boolean.

name.length trả về độ dài của đối số, chứ không phải true.


87. Output là gì?

console.log("I want pizza"[0]);
  • A: """
  • B: "I"
  • C: SyntaxError
  • D: undefined
Đáp án

Đáp án: B

Trong trường hợp ta muốn lấy ra một ký tự trong một chuỗi, ta có thể sử dụng toán tử ngoặc vuông. Ký tự đầu tiên sẽ có thứ tự là 0, và cứ tiếp tục như vậy. Trong trường hợp này chúng ta lấy ra ký tự có thứ tự là 0, đó chính là ký tự "I'.

Chú ý là phương thức này không hoạt động với IE7 trở xuống. Thay vì thế ta sử dụng .charAt()


88. Output là gì?

function sum(num1, num2 = num1) {
  console.log(num1 + num2);
}

sum(10);
  • A: NaN
  • B: 20
  • C: ReferenceError
  • D: undefined
Đáp án

Đáp án: B

Ta có thể set giá trị mặc định của một tham số là tham số khác trong hàm, miễn là tham số đó được khai báo trước tham số mặc định. Chúng ta đưa 10 vào hàm sum. Nếu hàm sum chỉ nhận 1 đối số, nó có nghĩa là giá trị của num2 không được đưa vào, nên nó sẽ lấy giá trị mặc định là giá trị của num1 đã được đưa vào, hay chính là 10 trong trường hợp này. Khi này num1 + num2 trả về 20.

Nếu chúng ta thử set một giá trị mặc định của tham số bằng với giá trị của tham số khai báo sau (về bên phải), giá trị của tham số đó sẽ không được khởi tạo và dẫn đến throw ra lỗi.


89. Output là gì?

// 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
Đáp án

Đáp án: A

Cú pháp import * as data sẽ import tất cả những gì được export từ module.js vào trong index.js và lưu trữ dưới một object có tên là data. Trong file module.js, có hai thứ được export ra: default export và một named export. Default export là một hàm trả về chuỗi "Hello World", và named export là một biến name nhận giá trị là chuỗi "Lydia".

Do đó data object có thuộc tính default cho default export, các thuộc tính khác sẽ có tên chính là tên của named exports và giá trị đi kèm.


90. Output là gì?

class Person {
  constructor(name) {
    this.name = name;
  }
}

const member = new Person("John");
console.log(typeof member);
  • A: "class"
  • B: "function"
  • C: "object"
  • D: "string"
Đáp án

Đáp án: C

Classes chỉ đơn thuần là syntactical sugar (cú pháp đặc biệt) của function constructors mà thôi. Nó tương đương với việc ta viết một function thế này:

function Person() {
  this.name = name;
}

Gọi một constructor với từ khóa new sẽ tạo ra một instance của class Person, typeof sẽ trả về là "object" cho các instance. Do đó typeof member trả về "object".


91. Output là gì?

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
Đáp án

Đáp án: D

Hàm .push trả về độ dài mới của mảng, chứ không phải bản thân mảng đó! Bằng cách set newList bằng với [1, 2, 3].push(4), ta đã set cho newList giá trị là 4 - tức độ dài của mảng lúc này.

Sau đó chúng ta tiến hành .push trên newList. Vì newList là một số thông thường, ta không thể dùng .push được, nên sẽ throw ra một TypeError.


92. Output là gì?

function giveLydiaPizza() {
  return "Here is pizza!";
}

const giveLydiaChocolate = () =>
  "Here's chocolate... now go hit the gym already.";

console.log(giveLydiaPizza.prototype);
console.log(giveLydiaChocolate.prototype);
  • A: { constructor: ...} { constructor: ...}
  • B: {} { constructor: ...}
  • C: { constructor: ...} {}
  • D: { constructor: ...} undefined
Đáp án

Đáp án: D

Hàm thông thường giống như giveLydiaPizza, sẽ có thuộc tính prototype là một object (prototype object) với một thuộc tính là constructor. Còn arrow functions giống như giveLydiaChocolatethì không có thuộc tính prototype này. undefined trả về khi ta truy cập thuộc tính prototype bằng cách gọi giveLydiaChocolate.prototype.


93. Output là gì?

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

for (const [x, y] of Object.entries(person)) {
  console.log(x, y);
}
  • A: name Lydiaage 21
  • B: ["name", "Lydia"]["age", 21]
  • C: ["name", "age"]undefined
  • D: Error
Đáp án

Đáp án: A

Object.entries(person) sẽ trả về một mảng của mảng, bao gồm các key và các object:

[ [ 'name', 'Lydia' ], [ 'age', 21 ] ]

Khí sử dụng for-of, chúng ta sẽ duyệt qua từng thành phần của mảng, trong trường hợp này chính là những mảng con. Đồng thời tiến hành gán giá trị luôn trong vongf lặp for-of, bằng cách sử dụng const [x, y]. Khi này x sẽ là phần tử đầu tiên trong mảng con, y chính là phần tử thứ hai trong mảng con.

Mảng con đầu tiên là [ "name", "Lydia" ], nên x sẽ là "name", và y sẽ là "Lydia", và được ghi ra. Mảng con thứ hai là [ "age", 21 ], nên x sẽ là "age", và y sẽ là 21, và được ghi ra.


94. Output là gì?

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
Đáp án

Đáp án: D

...args là cú pháp tham số cuối cùng. Giá trị của tham số cuối cùng chính là toàn bộ các đối số còn lại, và nó là tham số cuối cùng duy nhất! Trong trường hợp này, tham số cuối cùng lại là tham số thứ hai. Điều đó là không thể được, và sẽ throw ra một lỗi cú pháp.

function getItems(fruitList, favoriteFruit, ...args) {
  return [...fruitList, ...args, favoriteFruit];
}

getItems(["banana", "apple"], "pear", "orange");

Nếu ta code như thế này thì lại đúng. Giá trị trả về sẽ là [ 'banana', 'apple', 'orange', 'pear' ]


95. Output là gì?

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, 6b is bigger, 3
  • B: a is bigger, undefinedb is bigger, undefined
  • C: undefinedundefined
  • D: SyntaxError
Đáp án

Đáp án: B

Với JavaScript, ta không bắt buộc phải viết dấu chấm phẩy (;), JavaScript engine sẽ tự động thêm vào sau mỗi câu lệnh. Nó gọi là Automatic Semicolon Insertion. Một câu lệnh có thể là khai báo biến, hoặc từ khóa như throw, return, break, vv.

Ở đây ta sử dụng câu lệnh return ở một dòng và giá trị a + b ở một dòng khác. Tuy nhiên do khác dòng nên JS engine không thể biết đâu là giá trị ta thực sự muốn trả về. Thay vì thế, nó sẽ tự động thêm vào dấu chấm phẩy ngay sau return giống như này:

return;
a + b;

Có nghĩa là a + b sẽ không bao giờ được thực hiện, vì hàm đã được return rồi. Do không giá trị nào được trả về, nên giá trị trả về của hàm sẽ là undefined. Lưu ý là sẽ không tự động thêm dấu chấm phẩy ngay sau if/else đâu nhé!


96. Output là gì?

class Person {
  constructor() {
    this.name = "Lydia";
  }
}

Person = class AnotherPerson {
  constructor() {
    this.name = "Sarah";
  }
};

const member = new Person();
console.log(member.name);
  • A: "Lydia"
  • B: "Sarah"
  • C: Error: cannot redeclare Person
  • D: SyntaxError
Đáp án

Đáp án: B

Chúng ta có thể set một class với giá trị là một classes/function constructor khác. Trong trường hợp này, ta set Person bằng với AnotherPerson. Trong constructor, this.nameSarah, do đó giá trị của thuộc tính này trong instance member chính là "Sarah".


97. Output là gì?

const info = {
  [Symbol("a")]: "b",
};

console.log(info);
console.log(Object.keys(info));
  • A: {Symbol('a'): 'b'}["{Symbol('a')"]
  • B: {}[]
  • C: { a: "b" }["a"]
  • D: {Symbol('a'): 'b'}[]
Đáp án

Đáp án: D

Phương thức Object.keys sẽ trả về toàn bộ những key của các thuộc tính enumerable trên một object. Nhưng Symbol không phải dạng enumerable. do đó nó sẽ trả về một mảng rỗng. Tuy nhiên khi ta log ra toàn bộ object, thì ta sẽ ghi ra toàn bộ các thuộc tính, cho dù đó có là thuộc tính enumerable hay không.

Đó chính là một đặc trưng của Symbol: Bên cạnh việc nó biểu diễn một giá trị duy nhất (tránh việc xảy ra xung đột tên gọi, ví dụ khi ta sử dụng 2 thư viện mà muốn thêm thuộc tính vào cho cùng một object chẳng hạn), nó còn giúp "ẩn" thuộc tính đó đi (dù không hoàn toàn, ta vẫn có thể truy cập được bằng cách sử dụng phương thức Object.getOwnPropertySymbols()).


98. Output là gì?

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]]undefined
  • B: [1, [2, 3, 4]]{ name: "Lydia", age: 21 }
  • C: [1, 2, 3, 4]{ name: "Lydia", age: 21 }
  • D: Error{ name: "Lydia", age: 21 }
Đáp án

Đáp án: A

Hàm getList nhận vào một mảng các đối số, và tiến hành xử lý mảng đó luôn khi đưa vào hàm:

[x, ...y] = [1, 2, 3, 4]

Với việc sử dụng cú pháp tham số cuối cùng ...y, chúng ta đưa toàn bộ "những đối số còn lại" vào một mảng y. Trong trường hợp này đó là mảng gồm các phần tử 2, 34. Do đó giá trị của y lúc này chính là mảng [2, 3, 4]. Giá trị của x1, nên khi ghi [x, y] ra, kết quả sẽ là [1, [2, 3, 4]].

Hàm getUser nhận vào một object. Với cú pháp arrow function, chúng ta sẽ không phải viết trong ngoặc nhọn {} nữa nếu ta chỉ muốn đơn thuần trả về giá trị. Tuy nhiên, nếu ta muốn trả về một object t arrow function, ta sẽ phải viết chúng trong dấu ngoặc tròn (), nếu không thì sẽ không có giá trị nào được trả về! Ví dụ như sau:

const getUser = user => ({ name: user.name, age: user.age })

Do không giá trị nào được trả về, kết quả sẽ là undefined.


99. Output là gì?

const name = "Lydia";

console.log(name());
  • A: SyntaxError
  • B: ReferenceError
  • C: TypeError
  • D: undefined
Đáp án

Đáp án: C

Biến name có giá trị là một chuỗi, không phải hàm, vì thế không thể gọi được.

TypeErrors sẽ được throw ra nếu một giá trị không được sử dụng đúng kiểu. JavaScript muốn name là một hàm khi ta tiến hành gọi nó. Nhưng nó là chuỗi, nên sẽ throw ra một TypeError.

SyntaxErrors sẽ được throw khi ta viết code không đúng cú pháp của JavaScript, ví dụ thay vì return ta viết retrun.

ReferenceErrors s��� được throw ra khi Javascript không thể tìm được tham chiếu nào đến giá trị mà ta đang cố truy cập.


100. Output là gì?

// 🎉✨ Đây là câu hỏi thứ 100 của tôi! ✨🎉

const output = `${[] && "Im"}possible!
You should${"" && `n't`} see a therapist after so much JavaScript lol`;
  • A: possible! You should see a therapist after so much JavaScript lol
  • B: Impossible! You should see a therapist after so much JavaScript lol
  • C: possible! You shouldn't see a therapist after so much JavaScript lol
  • D: Impossible! You shouldn't see a therapist after so much JavaScript lol
Đáp án

Đáp án: B

[] là một giá trị truthy. Với phép toán && , giá trị bên phải sẽ được trả về nếu giá trị bên trái là truthy. Trong trường hợp này giá trị bên trái [] là truthy, nên "Im' sẽ được trả về.

"" là một giá trị falsy. Nếu giá trị bên trái là falsy, không có gì được trả về cả. Do đó n't sẽ không được trả về.


Avatar Thaycacac

Thaycacac

@thaycacac
Cuộc sống không phải là phim ảnh, không có nhiều đến thế... những lần không hẹn mà gặp.
Logo thể loại Câu hỏi phỏng vấn HTML

Câu hỏi phỏng vấn HTML

Chuyên mục các câu hỏi phỏng vấn
Tổng hợp các câu hỏi phỏng vấn HTML.
hello