Bộ câu hỏi phỏng vấn Javascript phần 11

Temporal Dead Zone trong ES6 là gì?


Trong ES6, letconst được hoisting (giống như var, classfunction), nhưng có một khoảng thời gian giữa phạm vi truy cập và được khai báo là nơi chúng không thể được truy cập. Khoảng thời gian này là Temporal Dead Zone (TDZ).

Chúng ta cùng xem ví dụ dưới đây:

// console.log(aLet) // would throw ReferenceError

let aLet;
console.log(aLet); // undefined
aLet = 10;
console.log(aLet); // 10

Trong ví dụ này, Temporal Dead Zone kết thúc khi aLet được khai báo, thay vì được gán.

Giải thích cách hoạt động của JSONP và tại sao nó không thực sự là Ajax?


JSONP (JSON with Padding) là một phương pháp thường được sử dụng để vượt qua các chính sách cross-domain trong trình duyệt web bởi vì việc gọi Ajax từ trang hiện tại đến một cross-origin domain là không được cho phép.

JSONP hoạt động bằng cách đưa ra một request đến một cross-origin domain thông qua thẻ <script> và thường với một tham số là callback query, ví dụ: https://example.com?callback=printData. Sau đó, máy chủ sẽ bọc dữ liệu trong một hàm gọi là printData và trả lại cho máy khách.

<!-- https://mydomain.com -->
<script>
  function printData(data) {
    console.log(`My name is ${data.name}!`);
  }
</script>
<script src="https://example.com?callback=printData"></script>
// File loaded from https://example.com?callback=printData
printData({ name: "kungfutech.edu.vn" });

Máy khách phải có hàm printData trong phạm vi toàn cục của nó và hàm sẽ được thực thi bởi máy khách khi nhận được phản hồi từ cross-origin domain.

JSONP có thể không an toàn và có một số tác động bảo mật. Vì JSONP thực sự là JavaScript, nó có thể làm mọi thứ khác mà JavaScript có thể làm, vì vậy bạn cần tin tưởng nhà cung cấp dữ liệu JSONP.

Ngày nay, CORS là phương pháp được khuyến nghị và JSONP được coi là một cuộc tấn công (hack).

Constructor Design Pattern trong Javascript như thế nào?


Trong các ngôn ngữ lập trình hướng đối tượng, constructor là một phương thức đặt biệt được sử dụng để khởi tạo đối tượng mới cùng với các thuộc tính khởi tạo ban đầu.

Trong JavaScript, hầu như mọi thứ đều là một object và chúng ta thường quan tâm đến việc khởi tạo đối tượng với Object Constructor.

// Sử dụng {}
const person  = {};

// Sử dụng Object()
const person = new Object();
Ngoài ra, bạn cũng có thể khởi tạo 1 constructor bằng function

function Person() {}

const personA = new Person();

personA.name = "Codestus.com";

Sự khác biệt giữa Map và WeakMap trong ES6 là gì?


Cả hai đều hoạt động khác nhau khi một đối tượng được tham chiếu bởi các keys/values của chúng bị xóa. Hãy lấy ví dụ dưới đây:

var map = new Map();
var weakmap = new WeakMap();

(function () {
  var a = {
    x: 12,
  };
  var b = {
    y: 12,
  };

  map.set(a, 1);
  weakmap.set(b, 2);
})();

Khi IIFE ở trên được thực thi, chúng ta không có cách nào có thể tham chiếu đến {x: 12}{y: 12} nữa. Trình thu gom rác sẽ hành động và xóa con trỏ khóa b khỏi “WeakMap” và cũng xóa {y: 12} khỏi bộ nhớ. Nhưng trong trường hợp Map, trình thu gom rác không xóa một con trỏ khỏi Map và cũng không xóa {x: 12} khỏi bộ nhớ.

WeakMap cho phép bộ thu gom rác thực hiện nhiệm vụ của nó nhưng Map thì không. Với các maps được viết tay, mảng các keys sẽ giữ các tham chiếu đến các đối tượng chính, ngăn chúng bị bộ thu gom rác thu thập. Trong WeakMaps, các tham chiếu đến các đối tượng chính được giữ "một cách yếu ớt", có nghĩa là chúng không ngăn chặn việc bị bộ thu gom rác thu thập trong trường hợp không có tham chiếu nào khác đến đối tượng.

Sự khác biệt giữa từ khóa await và từ khóa yield là gì?


yield có thể được coi là cơ sở xây dựng của await. yield nhận giá trị mà nó được cho và chuyển nó cho caller. Caller sau đó có thể làm bất cứ điều gì nó muốn với giá trị đó (1). Sau đó, caller có thể trả lại một giá trị cho generator (thông qua generate.next()), giá trị này sẽ trở thành kết quả của biểu thức yield (2) hoặc một lỗi sẽ được thảy ra bởi biểu thức yield (3).

async - await có thể được coi là sử dụng yield. Tại (1), caller (tức là trình điều khiển async - await - tương tự như chức năng bạn đã đăng) sẽ bọc giá trị trong một promise bằng cách sử dụng một thuật toán tương tự như new Promise(r => r(value))` (lưu ý, không phải là Promise.resolve, nhưng đó không phải là một vấn đề lớn). Sau đó nó sẽ đợi promise để resolve. Nếu nó hoàn thành, nó sẽ chuyển giá trị đã hoàn thành trở lại (2). Nếu nó reject, nó sẽ thảy ra lý do reject để làm lỗi ở (3).

Vì vậy, tiện ích của async - await là sử dụng yield để lấy giá trị thu được như một promise và truyền lại giá trị đã resolve của nó, lặp lại cho đến khi hàm trả về giá trị cuối cùng của nó.

Làm sao để deep-freeze một đối tượng trong JavaScript?


Nếu bạn muốn đảm bảo đối tượng được deep-freeze (đóng băng sâu), bạn phải tạo một hàm đệ quy để freeze từng thuộc tính thuộc loại object:

without deep-freeze:

let person = {
  name: "Leonardo",
  profession: {
    name: "developer",
  },
};
Object.freeze(person); // make object immutable

person.profession.name = "doctor";
console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }

with deep-freeze:

function deepFreeze(object) {
  let propNames = Object.getOwnPropertyNames(object);
  for (let name of propNames) {
    let value = object[name];
    object[name] =
      value && typeof value === "object" ? deepFreeze(value) : value;
  }
  return Object.freeze(object);
}
let person = {
  name: "Leonardo",
  profession: {
    name: "developer",
  },
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object

Kết quả đoạn code sau là gì?

const user = {
  email: "my@email.com",
  updateEmail: (email) => {
    this.email = email;
  },
};

user.updateEmail("new@email.com"); console.log(user.email);

  • A: my@email.com
  • B: new@email.com
  • C: undefined
  • D: ReferenceError

Đáp án: A

Hàm updateEmail là một cú pháp arrow function và nó không gắn với user object. Điều này cho thấy từ khoá this không trỏ tới user object mà trỏ tới global scope. Giá trị của email trong user object không thay đổi. Khi ta in ra giá trị của user.email, nó trả về giá trị ban đầu của my@email.com.

Kết quả đoạn code sau là gì?

const promise1 = Promise.resolve("First");
const promise2 = Promise.resolve("Second");
const promise3 = Promise.reject("Third");
const promise4 = Promise.resolve("Fourth");

const runPromises = async () => {
  const res1 = await Promise.all([promise1, promise2]);
  const res2 = await Promise.all([promise3, promise4]);
  return [res1, res2];
};

runPromises()
  .then((res) => console.log(res))
  .catch((err) => console.log(err));
  • A: [['First', 'Second'], ['Fourth']]
  • B: [['First', 'Second'], ['Third', 'Fourth']]
  • C: [['First', 'Second']]
  • D: 'Third'

Đáp án: D

Hàm Promise.all trả về những promise truyền vào song song nhau. Nếu một promise thất bại, hàm Promise.all rejects với giá trị của promise đó. Trong trường hợp này, promise3 bị reject với giá trị "Third". Ta đang kiểm tra giá trị bị reject trong chuỗi hàm catch khi goi hàm runPromises để tìm ra lỗi trong hàm runPromises. Chỉ có "Third" được trả về vì promise3 reject giá trị này.

Giá trị nào của `method` sẽ được trả về với log `{ 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

Đáp án: C

Hàm fromEntries trả về một mảng 2d trong một object. Phần tử đầu tiên trong từng mảng con sẽ là từ khoá và phần tử thứ hai trong từng mảng con sẽ là giá trị. Trong trường hợp này, ta tiến hành map qua mảng keys, nó sẽ trả về một mảng mà phần tử đầu tiên của mảng đó là phần tử trên thứ tự hiện tại của mảng key, và phần tử thứ hai của mảng đó là phần tử trên thứ tự hiện tại của mảng values.

Theo như trên thì ta tạo ra một mảng gồm những mảng con chứa đựng những từ khoá và giá trị đúng, và nó trả về { name: "Lydia", age: 22 }.

Kết quả đoạn code sau là gì?

const createMember = ({ email, address = {} }) => {
  const validEmail = /.+\@.+\..+/.test(email);
  if (!validEmail) throw new Error("Valid email pls");

  return {
    email,
    address: address ? address : null,
  };
};

const member = createMember({ email: "my@email.com" });
console.log(member);
  • A: { email: "my@email.com", address: null }
  • B: { email: "my@email.com" }
  • C: { email: "my@email.com", address: {} }
  • D: { email: "my@email.com", address: undefined }

Đáp án: C

Giá trị mặc định của address là một object rỗng {}. Khi ta cho biến member bằng với object được trả về bởi hàm createMember, ta đã không truyền vào một giá trị của address, nghĩa là giá trị của address là object rỗng {} được mặc định. Object rỗng mang giá trị truthy, tức là điều kiện address ? address : null trả về true. Giá trị của address là một object rỗng {}.

Kết quả đoạn code sau là gì?

let randomValue = { name: "Lydia" };
randomValue = 23;

if (!typeof randomValue === "string") {
  console.log("It's not a string!");
} else {
  console.log("Yay it's a string!");
}
  • A: It's not a string!
  • B: Yay it's a string!
  • C: TypeError
  • D: undefined

Đáp án: B

Điều kiện trong mệnh đề if kiểm tra xem giá trị của !typeof randomValue bằng với "string" hay không. Phép toán ! chuyển giá trị đó thành giá trị boolean. Nếu giá trị là truthy, giá trị trả về sẽ là false, nếu giá trị là falsy, giá trị trả về sẽ là true. Trong trường hợp này, giá trị trả về của typeof randomValue là giá trị truthy "number", nghĩa là giá trị của !typeof randomValue là một giá trị boolean false.

!typeof randomValue === "string" luôn trả về false, vì ta thực sự đang kiểm tra false === "string". Vì điều kiện đã trả về false, code của mệnh đề else sẽ chạy và Yay it's a string! được in ra.

Kết quả đoạn code sau 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: 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.

Kết quả đoạn code sau 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: 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 }.

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

JSON.parse();
  • A: Parse JSON thành một giá trị JavaScript
  • B: Parse một JavaScript object thành JSON
  • C: Parse giá trị JavaScript bất kì thành JSON
  • D: Parse JSON thành một JavaScript object

Đá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' }
Avatar Techmely Team
VIẾT BỞI

Techmely Team