Top 137 câu hỏi phỏng vấn Javascript

01

Sự khác nhau giữa nullundefined trong Javascript?

Trong JavaScript, nullundefined là hai giá trị đặc biệt đại diện cho sự vắng mặt của giá trị.

Undefined có nghĩa là không xác định. Trong javascript, khi bạn khai báo một biến nhưng chưa gán giá trị cho nó, giá trị của biến đó sẽ là undefined.

Ví dụ:

js
let x;
console.log(x); // undefined
let obj = {a: 1};
console.log(obj.b); // undefined

Bất cứ biến nào cũng có thể bị làm rỗng bằng cách thiết lập giá trị về không xác định (undefined).

js
var test = undefined;
alert(test); //undefined

Null có nghĩa là giá trị rỗng hoặc giá trị không tồn tại, nó có thể được sử dụng để gán cho một biến như là một đại diện không có giá trị.

js
let y = null;
console.log(y); // null

Ngoài ra thì còn một chú ý nữa đó là undefine có kiểu giá trị là undefined nhưng null lại là 1 object

js
  typeof undefined; // undefined
  typeof null; // object

Vì vậy, khi muốn kiểm tra xem một biến đã được khởi tạo hay chưa, bạn nên sử dụng undefined, trong khi khi muốn chỉ định rõ ràng rằng một giá trị không có ý nghĩa thì nên sử dụng null.

02

So sánh ===== trong Javascript?

Trong JavaScript, việc hiểu rõ sự khác biệt giữa toán tử so sánh == (so sánh trừu tượng) và === (so sánh nghiêm ngặt) là rất quan trọng, vì nó ảnh hưởng đến cách giá trị được so sánh trong code của bạn.

So sánh trừu tượng (==)

Toán tử == so sánh hai giá trị sau khi thực hiện chuyển đổi kiểu dữ liệu (nếu cần), để chúng có thể được so sánh một cách công bằng. Nếu hai giá trị không cùng kiểu, JavaScript sẽ cố gắng chuyển đổi chúng sang một kiểu chung trước khi so sánh. Ví dụ, khi so sánh một số với một chuỗi, chuỗi sẽ được chuyển đổi thành một số trước khi thực hiện so sánh

So sánh nghiêm ngặt (===)

Ngược lại, toán tử === thực hiện so sánh nghiêm ngặt, nghĩa là nó sẽ không thực hiện chuyển đổi kiểu dữ liệu giữa hai giá trị được so sánh. Điều này có nghĩa là nếu hai giá trị có kiểu dữ liệu khác nhau, toán tử === sẽ ngay lập tức trả về false mà không cần xem xét giá trị cụ thể của chúng

Ví dụ

Sử dụng ==:

javascript
console.log(3 == "3"); // true, vì "3" được chuyển đổi thành số trước khi so sánh
console.log(true == '1'); // true, vì '1' được chuyển đổi thành true

Sử dụng ===:

javascript
console.log(3 === "3"); // false, vì một bên là số và một bên là chuỗi
console.log(true === '1'); // false, vì một bên là boolean và một bên là chuỗi

Khi nào sử dụng?

Nên sử dụng === (so sánh nghiêm ngặt) thay vì == (so sánh trừu tượng) trong hầu hết các trường hợp để tránh những kết quả không mong muốn do chuyển đổi kiểu dữ liệu tự động. Sử dụng === giúp code của bạn trở nên dễ đọc và dễ hiểu hơn, đồng thời giảm thiểu nguy cơ gặp phải lỗi logic do chuyển đổi kiểu dữ liệu không rõ ràng

Tóm lại, ===== đều là toán tử so sánh trong JavaScript nhưng hoạt động theo hai cách khác nhau. == sẽ chuyển đổi kiểu dữ liệu trước khi so sánh, trong khi === yêu cầu cả kiểu dữ liệu và giá trị phải giống nhau. Để viết code chính xác và dễ bảo trì, bạn nên sử dụng === trừ khi có một lý do chính đáng để sử dụng ==.

03

Sự khác biệt của biến dùng var, letconst trong javascript là gì?

Trong JavaScript, việc khai báo biến có thể sử dụng ba từ khóa là var, let, và const, mỗi từ khóa có những đặc điểm và cách sử dụng riêng biệt. Dưới đây là sự khác biệt chính giữa chúng:

1. Phạm vi (Scope)

  • var: Phạm vi của var là function scope nếu nó được khai báo bên trong một hàm. Nếu khai báo ngoài hàm, var sẽ có phạm vi toàn cục (global scope)
  • letconst: Cả hai đều có block scope, nghĩa là chúng chỉ có thể truy cập được trong block (được định nghĩa bởi dấu ngoặc nhọn {}) mà chúng được khai báo

2. Hoisting

  • var: Biến được khai báo bằng var được hoisted (nâng lên đầu phạm vi của chúng) và khởi tạo với giá trị undefined trước khi mã được thực thi
  • letconst: Cũng được hoisted nhưng không được khởi tạo giá trị, dẫn đến lỗi ReferenceError nếu truy cập trước khi khai báo

3. Khai báo lại (Re-declaration) và Cập nhật (Update)

  • var: Có thể khai báo lại và cập nhật giá trị trong cùng một phạm vi
  • let: Có thể cập nhật nhưng không thể khai báo lại trong cùng một phạm vi. Tuy nhiên, có thể khai báo lại trong phạm vi khác nhau (ví dụ, bên trong một block khác)
  • const: Không thể cập nhật giá trị cũng như khai báo lại. Khi khai báo, const yêu cầu phải được khởi tạo giá trị

4. Sử dụng

  • var: Do những hạn chế về phạm vi và hoisting, var ít được khuyến khích sử dụng trong các dự án JavaScript hiện đại
  • let: Thích hợp cho các biến có giá trị thay đổi, ví dụ như trong vòng lặp hoặc các điều kiện logic
  • const: Thích hợp cho các biến có giá trị không thay đổi sau khi được khai báo, giúp tăng tính bảo mật và dễ đọc của mã

Tóm lại, letconst được giới thiệu trong ES6 như là những cải tiến so với var, với mục đích giải quyết các vấn đề liên quan đến phạm vi và hoisting của var. Sử dụng letconst giúp làm cho mã JavaScript trở nên dễ đọc và dễ bảo trì hơn.

04

Javascript có các kiểu dữ liệu nào? Bạn biết gì về chúng

Trong JavaScript, có tổng cộng 8 kiểu dữ liệu cơ bản, bao gồm cả kiểu dữ liệu nguyên thủy (primitive types) và kiểu dữ liệu phức tạp (complex type). Dưới đây là danh sách và mô tả ngắn gọn về từng kiểu:

Kiểu Dữ Liệu Nguyên Thủy

  1. String: Dùng để biểu diễn văn bản. Ví dụ: "Hello, world!".
  2. Number: Dùng cho cả số nguyên và số thực. JavaScript không phân biệt kiểu số nguyên và số thực. Ví dụ: 42, 3.14.
  3. Boolean: Dùng để biểu diễn giá trị đúng (true) hoặc sai (false).
  4. Null: Một kiểu đặc biệt chỉ có một giá trị null, dùng để biểu diễn "không có giá trị".
  5. Undefined: Một biến được khai báo nhưng chưa được gán giá trị sẽ có giá trị là undefined.
  6. Symbol: Dùng để tạo ra các giá trị duy nhất, không trùng lặp, thường dùng làm key cho object

Kiểu Dữ Liệu Phức Tạp

  1. Object: Dùng để lưu trữ tập hợp dữ liệu hoặc các phương thức phức tạp. Object bao gồm các cặp key-value, trong đó value có thể là dữ liệu kiểu nguyên thủy hoặc kiểu phức tạp khác

Kiểu Dữ Liệu Mới (ES6+)

  1. BigInt: Dùng để biểu diễn các số nguyên lớn hơn giới hạn của kiểu Number

Mỗi kiểu dữ liệu trong JavaScript có những đặc điểm và cách sử dụng riêng, phù hợp với các tình huống cụ thể trong lập trình. Điều quan trọng là lựa chọn đúng kiểu dữ liệu để biểu diễn dữ liệu một cách chính xác và hiệu quả nhất.

05

Hàm anonymous là gì và khi nào nên sử dụng?

Một Anonymous Function là một hàm không có tên (hay còn gọi là hàm ẩn danh), là một hàm được sinh ra đúng vào thời điểm chạy của chương trình.

Thông thường khi bạn khai báo một hàm thì trình biên dịch sẽ lưu lại trong bộ nhớ nên bạn có thể gọi ở trên hay dưới vị trí khai báo hàm đều được, nhưng với anonymous functions thì nó sẽ được sinh ra khi trình biên dịch xử lý tới vị trí của nó. Ví dụ:

js
// gọi trước hàm
showDomain(); // hoạt động

function showDomain() {
  alert("Học Javascript tại example.com");
}

// gọi sau hàm
showDomain(); // hoạt động

Trong ví dụ này cho dù bạn gọi hàm ở phía trên hay dưới đều hoạt động tốt là vì chương trình đã lưu hàm đó vào bộ nhớ. Nhưng nếu ta sử dụng anonymous function như ví dụ dưới đây sẽ bị lỗi ngay.

js
// gọi trước hàm
showDomain(); // Lỗi vì hàm này chưa tồn tại

var showDomain = function () {
  alert("Học Javascript tại example.com");
};

// gọi sau hàm
showDomain(); // hoạt động vì hàm đã tồn tại

Khi nào thì cần dùng Anonymous Function?

Nếu hàm cần được truyền ở nhiều nơi:

  • Định nghĩa 1 hàm thông thường.
  • Truyền hàm đó vào 1 hàm.

Nếu hàm chỉ truyền 1 nơi?

  • Bất tiện khi tạo ra 1 hàm mới (các chi phí như đặt tên hàm).
  • Giúp tăng tính ràng buộc cho việc chỉ được phép truyền 1 lần.
  • Bên cạnh đó hỗ trợ được thêm khả năng chỉ gọi 1 lần.
06

Trong Javascript, đây có phải là một pure function không?

javascript
  function sum(a, b) {
    return a + b;
  }
  • A: Yes
  • B: No

Đá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.

07

Strict mode trong JavaScript là gì?

Strict mode trong JavaScript là một tính năng cho phép bạn đặt một chương trình hoặc một hàm vào một "chế độ nghiêm ngặt" (strict operating context). Khi được kích hoạt, nó giúp phát hiện ra các lỗi tiềm ẩn, ngăn chặn hoặc báo lỗi cho một số hành vi không an toàn của JavaScript, và cải thiện hiệu suất thực thi mã. Để sử dụng strict mode, bạn chỉ cần thêm chuỗi "use strict"; vào đầu file JavaScript hoặc đầu một hàm

Tính năng và Lợi ích

  • Ngăn chặn sử dụng biến không được khai báo: Trong strict mode, việc sử dụng một biến mà không khai báo trước sẽ dẫn đến lỗi, giúp tránh được các vấn đề về phạm vi biến và hoisting không mong muốn.
  • Báo lỗi khi sử dụng delete: Không cho phép sử dụng delete với một biến, một hàm, hoặc một đối số hàm
  • Không thể sử dụng with: Cấm sử dụng câu lệnh with, giúp cải thiện hiệu suất mã và ngăn chặn các vấn đề về phạm vi biến
  • Giá trị this trong hàm không được tự động gán cho global object: Trong strict mode, giá trị this trong các hàm không được gọi trong một context đối tượng sẽ là undefined, giúp ngăn chặn việc thay đổi không mong muốn đối với global object.
  • Các lỗi silent được chuyển thành throwing errors: Nhiều lỗi mà trong "non-strict mode" sẽ bị im lặng và không báo lỗi, trong strict mode sẽ được báo lỗi, giúp dễ dàng phát hiện và sửa chữa.
  • Cấm một số từ khóa làm tên biến: Các từ khóa như eval, arguments không thể được sử dụng làm tên biến hoặc tên hàm.

Kích hoạt Strict Mode

Để kích hoạt strict mode, bạn thêm chuỗi "use strict"; vào đầu file JavaScript hoặc đầu một hàm. Điều này áp dụng strict mode cho toàn bộ script hoặc chỉ cho hàm đó

javascript
"use strict";
function myFunction() {
  // Code ở đây sẽ chạy trong strict mode
}

Hoặc:

javascript
function myFunction() {
  "use strict";
  // Chỉ code trong hàm này chạy trong strict mode
}

Kết luận

Strict mode là một công cụ hữu ích trong JavaScript, giúp làm cho mã của bạn trở nên an toàn và dễ bảo trì hơn bằng cách cung cấp một lớp kiểm tra và bảo vệ thêm. Nó khuyến khích các thực hành lập trình tốt và giúp phát hiện lỗi sớm trong quá trình phát triển.

08

Array trong Javascript là gì?

Trong JavaScript, một Array (mảng) là một kiểu dữ liệu tham chiếu (reference type) được sử dụng để lưu trữ nhiều giá trị trong một biến duy nhất. Mỗi giá trị trong mảng được gọi là một phần tử (element), và mỗi phần tử có một chỉ số (index) bắt đầu từ 0.

Khai báo Array

Có hai cách chính để khai báo một mảng trong JavaScript:

  1. Sử dụng Array literals, được biểu diễn bằng dấu ngoặc vuông []:

    javascript
    var foo = [];
    var numbers = [1, 2, 3, 4, 5];
  2. Sử dụng Array constructor, được khởi tạo bằng từ khóa new:

    javascript
    var foo = new Array();
    var numbers = new Array(1, 2, 3, 4, 5);

Thuộc tính và Phương thức của Array

  • length: Thuộc tính trả về độ dài của mảng
  • push(): Phương thức thêm một hoặc nhiều phần tử vào cuối mảng và trả về độ dài mới của mảng
  • pop(): Phương thức xóa phần tử cuối cùng của mảng và trả về phần tử đã xóa
  • shift(): Phương thức xóa phần tử đầu tiên của mảng và trả về phần tử đó
  • unshift(): Phương thức thêm một hoặc nhiều phần tử vào đầu mảng và trả về độ dài mới của mảng
  • map(), filter(), find(), reduce(): Các phương thức mạnh mẽ để tìm kiếm, biến đổi và xử lý dữ liệu trong mảng
  • includes(): Phương thức kiểm tra sự tồn tại của một phần tử trong mảng và trả về true hoặc false
  • indexOf(): Phương thức trả về chỉ mục đầu tiên tìm thấy của phần tử trong mảng, hoặc -1 nếu không tìm thấy
  • join(): Phương thức nối các phần tử của mảng thành một chuỗi, với một ký tự nối (nếu được cung cấp)

Lưu ý

  • Mảng trong JavaScript có thể chứa các phần tử với kiểu dữ liệu khác nhau, bao gồm số, chuỗi, đối tượng, hàm, và thậm chí là mảng khác (mảng đa chiều)
  • Kiểu dữ liệu của mảng khi kiểm tra bằng typeof sẽ trả về object
  • Mảng có thể được sử dụng với các vòng lặp như for, forEach, và for...of để duyệt qua các phần tử

Mảng là một công cụ linh hoạt và mạnh mẽ trong JavaScript, cho phép lập trình viên lưu trữ và quản lý tập hợp dữ liệu một cách hiệu quả.

09

Kết quả đoạn result của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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 đó.

10

Kết quả của hàm sau là gì? Hãy giải thích tại sao?

javascript
  function sayHi() {
    console.log(name);
    console.log(age);
    var name = "Lydia";
    let age = 21;
  }

  sayHi();
  • A: Lydiaundefined
  • B: LydiaReferenceError
  • C: ReferenceError21
  • D: undefinedReferenceError

Đáp án: D

Trong hàm chúng ta đã khai báo biến name với var. Điều đó có nghĩa là biến này sẽ được hoisted (một vùng nhớ sẽ được set up khi biến được khởi tạo) với giá trị mặc định là undefined, cho tới khi chúng ta thực sự định nghĩa biến đó. Trong hàm này, chúng ta chưa hề định nghĩa biến name tại dòng mà ta log ra, vậy nên giá trị mặc định của nó vẫn là undefined.

Các biến được khai báo với keyword let (và const) cũng được hoisted nhưng không giống như var, chúng không được khởi tạo. Chúng ta sẽ không thể truy cập chúng cho tới khi chúng ta khai báo (khởi tạo) chúng. Người ta gọi đó là "temporal dead zone". Khi ta truy cập đến một giá trị trước khi chúng được khai báo, JavaScript sẽ throws một ReferenceError.

11

Kết quả đoạn code javascript sau là true hay false? Hãy giải thích tại sao?

javascript
  console.log("❤️" === "❤️");
  • A: true
  • B: false

Đá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.

12

Ép kiểu ngầm (implicit type coercion) trong JavaScript là gì?

Ép kiểu ngầm (implicit type coercion) trong JavaScript là quá trình tự động hoặc ngầm định chuyển đổi giá trị từ một kiểu dữ liệu này sang kiểu dữ liệu khác, như từ chuỗi sang số. Điều này thường xảy ra khi bạn áp dụng các toán tử cho các giá trị có kiểu khác nhau hoặc khi giá trị được sử dụng trong một ngữ cảnh cụ thể mà ở đó kiểu dữ liệu cần phải được chuyển đổi

Ví dụ về ép kiểu ngầm:

javascript
const value1 = "5";
const value2 = 9;
let sum = value1 + value2;
console.log(sum); // Kết quả là "59"

Trong ví dụ trên, JavaScript đã tự động chuyển đổi số 9 thành một chuỗi và sau đó nối hai giá trị lại với nhau, kết quả là một chuỗi "59". JavaScript đã có thể chọn chuyển đổi chuỗi "5" thành một số và trả về tổng là 14, nhưng nó không làm vậy. Để có kết quả này, bạn cần phải chuyển đổi rõ ràng "5" thành một số bằng cách sử dụng phương thức Number()

Có ba loại chuyển đổi trong JavaScript:

  • Chuyển đổi sang chuỗi (to string)
  • Chuyển đổi sang boolean (to boolean)
  • Chuyển đổi sang số (to number)

Chuyển đổi cho các giá trị nguyên thủy và đối tượng hoạt động khác nhau. Ví dụ, chuyển đổi một đối tượng thành một giá trị nguyên thủy sẽ diễn ra thông qua phương thức nội bộ [[ToPrimitive]] của đối tượng đó

Một số quy tắc chuyển đổi ngầm định:

  • Khi sử dụng toán tử + với một số và một chuỗi, số sẽ được chuyển đổi thành chuỗi và sau đó thực hiện nối chuỗi
  • Trong một ngữ cảnh logic, hoặc khi sử dụng các toán tử logic (||, &&, !), giá trị sẽ được chuyển đổi ngầm định sang boolean
  • Toán tử bằng lỏng lẻo == thực hiện cả so sánh và ép kiểu ngầm định nếu cần thiết, trong khi toán tử bằng chặt === không thực hiện ép kiểu và chỉ trả về true nếu cả kiểu và giá trị của hai biến là giống nhau

Ép kiểu ngầm có thể hữu ích nhưng cũng có thể gây ra những lỗi không mong muốn, đặc biệt là khi so sánh các giá trị với toán tử bằng lỏng lẻo. Do đó, việc sử dụng toán tử bằng chặt được khuyến nghị để tránh những hành vi không mong muốn này

13

Kết quả đoạn code sau là gì? Và hãy giải thích?

javascript
  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ụ:

javascript
[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à:

javascript
[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.

14

Bạn biết gì về AMD (Asynchronous Module Definition) và CommonJS trong Javascript?

AMD (Asynchronous Module Definition)

AMD là một quy chuẩn cho ngôn ngữ lập trình JavaScript, định nghĩa một API cho việc định nghĩa các module mã và các phụ thuộc của chúng, và tải chúng một cách bất đồng bộ nếu muốn. Các triển khai AMD mang lại nhiều lợi ích như cải thiện hiệu suất trang web, giảm lỗi trang do cho phép các nhà phát triển định nghĩa các phụ thuộc cần tải trước khi một module được thực thi. Ngoài ra, AMD cũng cung cấp một số khả năng tương thích với CommonJS, cho phép sử dụng một giao diện exportsrequire() tương tự trong mã, mặc dù giao diện define() của riêng nó được ưu tiên hơn

CommonJS

CommonJS là cách đóng gói mã JavaScript ban đầu cho Node.js. Node.js cũng hỗ trợ tiêu chuẩn ECMAScript modules được sử dụng bởi trình duyệt và các môi trường JavaScript khác. Trong Node.js, mỗi file được xem như một module riêng biệt. Ví dụ, một file tên là foo.js có thể tải module circle.js nằm trong cùng thư mục với foo.js. CommonJS chủ yếu được sử dụng trong các ứng dụng JS phía server với Node, và mỗi module được xuất thông qua việc gán giá trị mới cho thuộc tính module.exports

Sự Khác Biệt và Tương Thích

  • AMD được thiết kế chủ yếu cho trình duyệt, cho phép tải các module và phụ thuộc của chúng một cách bất đồng bộ. Điều này giúp giảm thời gian tải trang và cải thiện trải nghiệm người dùng trên các ứng dụng web phức tạp
  • CommonJS, mặt khác, được thiết kế với tâm trí hướng đến môi trường server, nơi tất cả các file và tài nguyên cần thiết đều có sẵn trên server. Điều này dẫn đến việc tải module một cách đồng bộ, phù hợp với môi trường Node.js

Cả hai hệ thống module này đều có ưu và nhược điểm riêng, tùy thuộc vào môi trường và yêu cầu cụ thể của ứng dụng. Trong khi AMD tối ưu cho việc tải bất đồng bộ và phù hợp với các ứng dụng web lớn, CommonJS lại phù hợp với các ứng dụng Node.js phía server với việc tải đồng bộ các module.

Mặc dù có sự khác biệt, cả hai hệ thống này đều hướng tới mục tiêu chung là cung cấp một cách tổ chức và quản lý mã JavaScript một cách hiệu quả, giúp phát triển các ứng dụng lớn và phức tạp trở nên dễ dàng hơn.

15

Kết quả set của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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}.

16

Trong Javascript, Anonymous Function thường dùng cho trường hợp nào?

Hàm anonymous, hay còn gọi là hàm ẩn danh, là một loại hàm không có tên trong JavaScript. Hàm này có thể được sử dụng như một đối số cho các hàm khác, hoặc được gán vào một biến để sử dụng. Hàm anonymous thường được tạo ra và sử dụng ngay tại thời điểm chạy của chương trình

Đặc điểm

  • Không có tên: Điểm đặc biệt nhất của hàm anonymous là nó không có tên. Khi khai báo, bạn chỉ cần sử dụng từ khóa function mà không cần đặt tên cho hàm
  • Linh hoạt: Hàm anonymous có thể được gán vào một biến, hoặc được sử dụng trực tiếp như một đối số cho hàm khác
  • Sử dụng một lần: Thường được sử dụng trong các trường hợp cần một hàm thực thi một tác vụ cụ thể mà không cần tái sử dụng sau này

Khi nào nên sử dụng?

  • Callback: Khi một hàm cần một hàm khác làm đối số, hàm anonymous là lựa chọn phù hợp vì nó giúp code trở nên gọn gàng và trực quan hơn
  • Event handling: Trong việc xử lý sự kiện, hàm anonymous thường được sử dụng để xử lý các hành động người dùng mà không cần định nghĩa một hàm riêng biệt
  • IIFE (Immediately Invoked Function Expression): Hàm anonymous có thể được sử dụng để tạo ra một IIFE, giúp đóng gói và bảo vệ phạm vi của các biến

Ví dụ

javascript
// Gán hàm anonymous vào biến
var showMsg = function() {
    console.log("Hello, this is an anonymous function!");
};
showMsg();

// Sử dụng hàm anonymous làm callback
setTimeout(function() {
    console.log("This message is shown after 1 second.");
}, 1000);

// IIFE
(function() {
    console.log("This function runs immediately upon definition.");
})();

Lưu ý

  • Hàm anonymous không thể được gọi trực tiếp bằng tên vì nó không có tên. Để sử dụng, bạn cần gán nó vào một biến hoặc sử dụng trực tiếp như một đối số
  • Trong một số trường hợp, việc sử dụng quá nhiều hàm anonymous có thể khiến code trở nên khó đọc và khó bảo trì

Hàm anonymous là một công cụ linh hoạt và mạnh mẽ trong JavaScript, giúp lập trình viên có thể viết code một cách gọn gàng và hiệu quả, đặc biệt trong việc xử lý các tác vụ đơn lẻ hoặc tạo ra các IIFE.

17

Ưu điểm và nhược điểm của việc sử dụng use strict là gì?

use strict là một câu lệnh được sử dụng để bật strict mode cho toàn bộ tập lệnh hoặc các chức năng riêng lẻ. Strict mode là một cách để chọn tham gia một biến thể bị hạn chế (hạn chế một số tính năng) của JavaScript.

Ưu điểm:

  • Làm cho nó không thể vô tình tạo ra các biến toàn cục.
  • Đẩy ra các exception nếu xóa các thuộc tính không thể bị xóa (trước đó việc xóa đó chỉ đơn giản là không có tác dụng và cũng không có thông báo gì).
  • Bắt buộc tên tham số hàm là duy nhất.
  • thisundefined trong global context.
  • Nó bắt một số blooper mã hóa phổ biến, thảy ra các exceptions.
  • Nó vô hiệu hóa các tính năng khó hiểu hoặc không hiệu quả.

Nhược điểm:

  • Nhiều tính năng bị thiếu mà một số nhà phát triển có thể đã quen dùng.
  • Không còn quyền truy cập vào function.caller và function.arguments.
  • Việc kết hợp các tập lệnh được viết ở các strict mode khác nhau có thể gây ra sự cố.

Nhìn chung, tôi nghĩ rằng "use strict" có lợi ích nhiều hơn bất lợi.

18

Lợi ích của việc sử dụng spread so với rest như thế nào trong Javascript?

Trong JavaScript ES6, cả spread và rest sử dụng cú pháp ba dấu chấm (...), nhưng chúng phục vụ cho các mục đích khác nhau và mang lại những lợi ích riêng biệt.

Spread Operator

  • Tạo bản sao của mảng hoặc đối tượng: Spread operator cho phép tạo bản sao của mảng hoặc đối tượng mà không cần dùng đến các phương thức như Object.assign() hoặc Array.slice(), giúp code ngắn gọn và dễ đọc hơn
  • Kết hợp mảng: Spread operator có thể dùng để kết hợp nhiều mảng lại với nhau một cách dễ dàng và linh hoạt hơn so với phương thức concat()
  • Truyền đối số vào hàm: Khi cần truyền một mảng các giá trị vào hàm dưới dạng đối số riêng lẻ, spread operator giúp giải nén mảng đó thành các đối số, thay thế cho việc sử dụng apply()
  • Thêm phần tử vào mảng: Spread operator cho phép chèn các phần tử từ một mảng vào một mảng khác tại bất kỳ vị trí nào
  • Chuyển đổi các đối tượng có thể lặp (iterable) thành mảng: Spread operator có thể chuyển đổi các đối tượng như Set hoặc Map thành mảng

Rest Parameters

  • Nhóm các đối số thành mảng: Rest parameters cho phép hàm nhận một số lượng không xác định các đối số và nhóm chúng vào một mảng, giúp việc xử lý các đối số trở nên linh hoạt và dễ dàng hơn
  • Rõ ràng và dễ đọc: Rest parameters cung cấp một cách rõ ràng để chỉ định các đối số mà không cần dùng đến đối tượng arguments kiểu Array-like, giúp code dễ đọc và dễ hiểu hơn

Tóm lại, spread operator thường được sử dụng để "trải" các giá trị ra, trong khi rest parameters được sử dụng để "nhóm" các giá trị lại. Spread operator mang lại lợi ích trong việc tạo bản sao, kết hợp mảng, và truyền đối số, còn rest parameters giúp xử lý số lượng đối số không xác định trong hàm. Cả hai đều là những cải tiến quan trọng trong ES6, giúp lập trình viên viết code ngắn gọn, hiệu quả và dễ bảo trì hơn.

19

Lập trình bất đồng bộ trong Javascript là gì?

Lập trình bất đồng bộ trong JavaScript là một mô hình lập trình cho phép việc xử lý các tác vụ mà không cần chờ đợi kết quả của tác vụ trước đó hoàn thành. Điều này giúp cải thiện hiệu suất và thời gian phản hồi của ứng dụng, đặc biệt là khi thực hiện các tác vụ nặng như truy cập mạng hoặc I/O

JavaScript là một ngôn ngữ đơn luồng (single-threaded), nghĩa là nó chỉ có thể xử lý một câu lệnh tại một thời điểm. Để khắc phục vấn đề blocking khi thực hiện các tác vụ nặng, JavaScript sử dụng các WebAPIs do trình duyệt cung cấp để thực thi các tác vụ này một cách bất đồng bộ

Các phương thức xử lý bất đồng bộ phổ biến trong JavaScript bao gồm:

  1. Callback: Là hàm được truyền vào như một tham số cho hàm khác và được gọi lại (callback) sau khi một tác vụ bất đồng bộ hoàn thành
  2. Promise: Đại diện cho một giá trị có thể không được biết ngay lập tức nhưng sẽ được biết sau khi tác vụ bất đồng bộ hoàn thành. Promise có thể ở một trong ba trạng thái: pending, fulfilled, hoặc rejected
  3. Async/Await: Là một cú pháp mới hơn giúp viết mã bất đồng bộ một cách rõ ràng và dễ đọc hơn. async được khai báo trước một hàm để biểu thị rằng hàm đó sẽ trả về một Promise, và await được sử dụng để chờ đợi một Promise hoàn thành

Bất đồng bộ giúp giải quyết vấn đề blocking của đồng bộ, được gọi là non-blocking, nghĩa là khi chạy một tác vụ nặng, những lệnh tiếp theo được phép chạy ngay mà không cần chờ tác vụ kia hoàn thành. Tuy nhiên, lập trình bất đồng bộ cũng đặt ra thách thức trong việc xử lý lỗi và quản lý tài nguyên dùng chung

Trong thực tế, việc sử dụng lập trình bất đồng bộ trong JavaScript là rất phổ biến, đặc biệt là trong các ứng dụng web hiện đại, nơi mà việc tối ưu hiệu suất và trải nghiệm người dùng là rất quan trọng

20

Javascript là ngôn ngữ pass-by-reference hay pass-by-value không?

Trong JavaScript, việc truyền tham số vào hàm tuân theo nguyên tắc "pass-by-value" (truyền theo giá trị). Tuy nhiên, cách thức hoạt động của nguyên tắc này phụ thuộc vào loại dữ liệu của biến được truyền vào hàm: kiểu dữ liệu nguyên thủy (primitive types) hay kiểu dữ liệu tham chiếu (reference types).

Kiểu Dữ Liệu Nguyên Thủy (Primitive Types)

Các kiểu dữ liệu nguyên thủy bao gồm số (number), chuỗi (string), boolean, null, undefined, symbol (ES6), và bigint (ES2020). Khi một biến kiểu nguyên thủy được truyền vào hàm, JavaScript sẽ tạo một bản sao của giá trị đó và truyền bản sao vào hàm. Bất kỳ thay đổi nào đối với tham số trong hàm sẽ không ảnh hưởng đến biến bên ngoài hàm, vì thực chất giá trị được truyền vào hàm là một bản sao

Kiểu Dữ Liệu Tham Chiếu (Reference Types)

Các kiểu dữ liệu tham chiếu bao gồm các đối tượng (objects), mảng (arrays), và hàm (functions). Khi một biến kiểu tham chiếu được truyền vào hàm, JavaScript sẽ truyền địa chỉ tham chiếu của biến đó vào hàm, chứ không phải là một bản sao của giá trị. Do đó, bất kỳ thay đổi nào đối với thuộc tính hoặc phần tử của đối tượng hoặc mảng trong hàm sẽ ảnh hưởng trực tiếp đến biến bên ngoài hàm. Tuy nhiên, nếu bạn thay đổi tham chiếu của biến trong hàm (ví dụ, gán biến đó vào một đối tượng mới), thì tham chiếu bên ngoài hàm sẽ không bị thay đổi

Điều quan trọng cần lưu ý là mặc dù các kiểu dữ liệu tham chiếu được truyền theo địa chỉ tham chiếu, nhưng JavaScript vẫn tuân theo nguyên tắc "pass-by-value" vì địa chỉ tham chiếu đó được truyền dưới dạng một giá trị. Điều này có nghĩa là JavaScript luôn truyền tham số dưới dạng giá trị, nhưng đối với kiểu dữ liệu tham chiếu, giá trị đó là địa chỉ tham chiếu của đối tượng

Tóm lại, JavaScript là một ngôn ngữ "pass-by-value", nhưng cách thức hoạt động của nguyên tắc này khác nhau giữa kiểu dữ liệu nguyên thủy và kiểu dữ liệu tham chiếu.

21

Kết quả đoạn code javascript sau là gì và giải thích?

javascript
  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: 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".

22

Sự khác biệt giữa các Host objectsNative objects trong Javascript là gì?

Sự khác biệt giữa các Host objects và Native objects trong JavaScript chủ yếu nằm ở nguồn gốc và mục đích sử dụng của chúng.

Native Objects

Native objects (Đối tượng nguyên thuỷ) là các đối tượng được định nghĩa bởi đặc tả ECMAScript của ngôn ngữ JavaScript. Chúng là một phần không thể thiếu của ngôn ngữ và được cung cấp sẵn bởi môi trường thực thi JavaScript. Native objects bao gồm các đối tượng như String, Math, RegExp, Object, Function, và nhiều đối tượng khác. Chúng được gọi là built-in objects, pre-defined objects, hoặc global objects và có sẵn cho tất cả người dùng, bất kể máy tính hay môi trường thực thi. Các chức năng của native objects không thay đổi khi thay đổi máy tính hoặc môi trường và có thể được sử dụng như cả constructors (ví dụ: String(), Array(), Object()) và giá trị nguyên thủy (ví dụ: true, 999)

Host Objects

Host objects (Đối tượng máy chủ) là các đối tượng được cung cấp bởi môi trường thời gian chạy, chẳng hạn như trình duyệt web hoặc Node.js. Chúng không được định nghĩa bởi đặc tả ECMAScript mà được cung cấp bởi môi trường thời gian chạy và có thể khác nhau giữa các môi trường. Ví dụ, trong môi trường trình duyệt, các host objects có thể bao gồm window, XMLHttpRequest, và các đối tượng DOM như NodeList. Trong khi đó, một số đối tượng như console được cung cấp bởi cả trình duyệt và máy chủ Node.js. Host objects có thể khác nhau giữa các môi trường và là đặc thù cho từng host (môi trường)

Sự Khác Biệt Chính

  • Native Objects: Là các đối tượng tiêu chuẩn toàn cầu, giống nhau và được cung cấp bởi chính JavaScript.
  • Host Objects: Là các đối tượng đặc thù cho môi trường (host), khác nhau giữa các môi trường và không được định nghĩa bởi đặc tả ECMAScript.

Sự hiểu biết về sự khác biệt giữa native objects và host objects giúp lập trình viên JavaScript hiểu rõ hơn về cách các đối tượng hoạt động trong các môi trường khác nhau và cách tận dụng chúng một cách hiệu quả trong các ứng dụng của mình

23

Kết quả đoạn code sau là gì? Và giải thích?

javascript
  // counter.js
  let counter = 10;
  export default counter;
javascript
// index.js
import myCounter from "./counter";

myCounter += 1;

console.log(myCounter);
  • A: 10
  • B: 11
  • C: Error
  • D: NaN

Đá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.

24

Hãy cho biết kết quả đoạn code sau?

javascript
  const shape = {
    radius: 10,
    diameter() {
      return this.radius * 2;
    },
    perimeter: () => 2 * Math.PI * this.radius,
  };
  shape.diameter();
  shape.perimeter();
  • A: 20 and 62.83185307179586
  • B: 20 and NaN
  • C: 20 and 63
  • D: NaN and 63

Đáp án: B

Chú ý rằng giá trị diameter là một hàm thông thường, còn perimeter là một arrow function.

Không giống như hàm thông thường, với arrow function, biếnthis sẽ trỏ tới surrounding scope! Có nghĩa là khi chúng ta gọi perimeter, nó sẽ không được gọi bởi shape object, mà nó được gọi bởi object nào đó tại surrounding scope (ví dụ window chẳng hạn).

Khi không có giá trị radius tại object đó, nó sẽ trả về undefined.

25

Callback hell trong Javascript là gì?

Callback hell trong JavaScript, còn được gọi là "pyramid of doom" hoặc "hadouken", là một tình huống phổ biến trong lập trình bất đồng bộ với JavaScript, nơi mà các hàm callback được lồng vào nhau sâu và rộng, dẫn đến mã nguồn khó đọc, khó bảo trì và dễ gây lỗi. Điều này thường xảy ra khi bạn có nhiều thao tác bất đồng bộ cần phải được thực hiện theo một trình tự nhất định và kết quả của mỗi thao tác phụ thuộc vào thao tác trước đó.

Ví dụ về callback hell:

javascript
fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      // ... thêm nhiều lớp lồng nhau khác ...
    });
  }
});

Trong ví dụ trên, bạn có thể thấy rằng các hàm callback được lồng vào nhau nhiều lần, tạo ra một "hình kim tự tháp" của mã nguồn, và nhiều dấu }) ở cuối, làm cho mã nguồn trở nên rối rắm và khó theo dõi.

Để tránh callback hell, có một số phương pháp và kỹ thuật được khuyến nghị:

  1. Chia Nhỏ Code: Tách các hàm callback ra thành các hàm độc lập và đặt tên cho chúng, giúp làm cho mã nguồn dễ đọc và dễ bảo trì hơn
  2. Sử Dụng Promises: Promises cung cấp một cách tiếp cận chuỗi hơn và cho phép bạn xử lý các thao tác bất đồng bộ một cách rõ ràng và dễ đọc hơn
  3. Async/Await: Tính năng này được giới thiệu trong ES2017 (ES8) và cho phép viết mã bất đồng bộ một cách giống như đồng bộ, làm cho mã nguồn trở nên sạch sẽ và dễ hiểu hơn
  4. Thiết Kế Ứng Dụng Theo Dạng Module: Tách biệt logic thành các module nhỏ và tái sử dụng, giúp giảm sự phức tạp của mã nguồn và tránh lặp code
  5. Sử Dụng Các Thư Viện Hỗ Trợ: Có các thư viện như async.js giúp quản lý và kiểm soát luồng thực thi của các hàm bất đồng bộ

Tránh callback hell không chỉ giúp cải thiện chất lượng mã nguồn mà còn giúp đảm bảo rằng ứng dụng của bạn có thể mở rộng và bảo trì dễ dàng hơn.

26

Làm thế nào để so sánh hai object trong JavaScript?

So sánh hai đối tượng trong JavaScript không giống như so sánh các kiểu dữ liệu nguyên thủy (primitive types) như số hoặc chuỗi. Đối với các đối tượng, việc so sánh chúng dựa trên tham chiếu (reference) chứ không phải giá trị. Điều này có nghĩa là ngay cả khi hai đối tượng có cùng cấu trúc và giá trị, chúng vẫn không bằng nhau nếu chúng không tham chiếu đến cùng một vị trí trong bộ nhớ

So sánh bằng tham chiếu

Khi sử dụng toán tử === để so sánh hai đối tượng, JavaScript sẽ kiểm tra xem cả hai có trỏ đến cùng một đối tượng trong bộ nhớ hay không. Nếu hai biến tham chiếu đến cùng một đối tượng, kết quả sẽ là true; nếu không, kết quả sẽ là false

So sánh bằng giá trị

Để so sánh hai đối tượng dựa trên giá trị của chúng, bạn cần sử dụng các phương pháp khác. Dưới đây là một số cách phổ biến:

  1. Sử dụng JSON.stringify(): Phương pháp này chuyển đổi đối tượng thành chuỗi JSON và sau đó so sánh hai chuỗi này. Tuy nhiên, phương pháp này có hạn chế là thứ tự của các thuộc tính trong đối tượng phải giống nhau và không xử lý được các đối tượng có tham chiếu tuần hoàn

    javascript
    let a = { name: 'Dionysia', age: 29 };
    let b = { name: 'Dionysia', age: 29 };
    console.log(JSON.stringify(a) === JSON.stringify(b)); // true hoặc false tùy thuộc vào thứ tự của thuộc tính
  2. Sử dụng thư viện Lodash: Phương thức _.isEqual() của Lodash thực hiện so sánh sâu (deep comparison) giữa hai đối tượng, bao gồm cả các đối tượng lồng nhau và không quan tâm đến thứ tự của các thuộc tính

    javascript
    let a = { age: 29, name: 'Dionysia' };
    let b = { name: 'Dionysia', age: 29 };
    console.log(_.isEqual(a, b)); // true
  3. So sánh sâu tự viết: Bạn có thể tự viết hàm để so sánh sâu giữa hai đối tượng, bằng cách đệ quy kiểm tra từng thuộc tính và giá trị của chúng

    javascript
    function isDeepEqual(object1, object2) {
      const keys1 = Object.keys(object1);
      const keys2 = Object.keys(object2);
      if (keys1.length !== keys2.length) {
        return false;
      }
      for (let key of keys1) {
        const val1 = object1[key];
        const val2 = object2[key];
        const areObjects = isObject(val1) && isObject(val2);
        if (
          (areObjects && !isDeepEqual(val1, val2)) ||
          (!areObjects && val1 !== val2)
        ) {
          return false;
        }
      }
      return true;
    }

Kết luận

Khi so sánh hai đối tượng trong JavaScript, bạn không thể sử dụng toán tử == hoặc === vì chúng chỉ so sánh tham chiếu chứ không phải giá trị. Để so sánh giá trị của hai đối tượng, bạn có thể sử dụng JSON.stringify() cho các trường hợp đơn giản hoặc _.isEqual() từ thư viện Lodash cho một giải pháp mạnh mẽ và đáng tin cậy hơn. Nếu bạn cần một giải pháp tùy chỉnh, việc viết một hàm so sánh sâu tự viết có thể là lựa chọn phù hợp

27

So sánh sự khác nhau của forEach()map() trong Javascript?

forEach()map() là hai phương thức phổ biến trong JavaScript được sử dụng để lặp qua các phần tử của mảng, nhưng chúng có những sự khác biệt quan trọng về cách thức hoạt động và mục đích sử dụng.

Sự Khác Biệt Chính

  1. Giá Trị Trả Về:

    • forEach(): Không trả về giá trị nào (undefined). Phương thức này thường được sử dụng để thực hiện một hành động nào đó cho mỗi phần tử của mảng mà không cần tạo ra một mảng mới
    • map(): Trả về một mảng mới, trong đó mỗi phần tử là kết quả của việc áp dụng một hàm đã cho lên từng phần tử của mảng ban đầu. map() thích hợp khi bạn muốn "biến đổi" các phần tử của mảng và tạo ra một mảng mới mà không thay đổi mảng gốc
  2. Khả Năng Chaining (Liên Kết Các Phương Thức):

    • forEach(): Không thể chain (liên kết) với các phương thức khác vì nó trả về undefined
    • map(): Có thể được chain với các phương thức khác như reduce(), sort(), filter() vì nó trả về một mảng mới, cho phép thực hiện các thao tác tiếp theo trên mảng đó
  3. Tác Động Đến Mảng Ban Đầu:

    • forEach(): Có thể thay đổi mảng ban đầu thông qua các hành động được thực hiện trong callback. Tuy nhiên, việc này không phải là mục đích chính của forEach() và thường được tránh để duy trì tính bất biến của dữ liệu
    • map(): Không thay đổi mảng ban đầu. Mọi thay đổi đều được phản ánh trong mảng mới được trả về, giữ nguyên tính bất biến của mảng gốc

Khi Nào Sử Dụng

  • Sử dụng forEach() khi bạn cần thực hiện một hành động cho mỗi phần tử của mảng mà không cần tạo ra một mảng mới hoặc khi bạn không quan tâm đến kết quả trả về từ việc thực hiện hành động đó
  • Sử dụng map() khi bạn muốn tạo ra một mảng mới từ việc biến đổi mỗi phần tử của mảng ban đầu, ví dụ như tăng giá trị của mỗi phần tử lên 1, hoặc chuyển đổi dữ liệu từ dạng này sang dạng khác

Tóm lại, lựa chọn giữa forEach()map() phụ thuộc vào mục đích sử dụng: nếu bạn cần một mảng mới từ việc biến đổi các phần tử, hãy sử dụng map(). Nếu bạn chỉ muốn thực hiện một hành động nào đó mà không cần mảng mới, forEach() sẽ là lựa chọn phù hợp.

28

Hãy viết một hàm có tên lucky_sevens nhận một mảng các số nguyên và trả về giá trị true nếu ba phần tử liên tiếp bất kỳ có tổng bằng 7?

Để viết một hàm có tên lucky_sevens nhận một mảng các số nguyên và trả về giá trị true nếu ba phần tử liên tiếp bất kỳ có tổng bằng 7, bạn có thể thực hiện như sau:

javascript
function lucky_sevens(arr) {
    // Kiểm tra nếu mảng có ít hơn 3 phần tử thì không thể có 3 phần tử liên tiếp có tổng bằng 7
    if (arr.length < 3) return false;

    // Duyệt qua mảng, bắt đầu từ chỉ số 0 đến chỉ số length - 3
    for (let i = 0; i < arr.length - 2; i++) {
        // Tính tổng của ba phần tử liên tiếp
        let sum = arr[i] + arr[i + 1] + arr[i + 2];
        // Nếu tổng bằng 7, trả về true
        if (sum === 7) return true;
    }

    // Nếu không tìm thấy ba phần tử liên tiếp nào có tổng bằng 7, trả về false
    return false;
}

Giải thích:

  • Hàm lucky_sevens nhận vào một mảng arr.
  • Đầu tiên, kiểm tra nếu mảng có ít hơn 3 phần tử thì không thể có ba phần tử liên tiếp có tổng bằng 7, do đó trả về false.
  • Sử dụng một vòng lặp for để duyệt qua mảng từ phần tử đầu tiên đến phần tử áp chót thứ hai (vì ta cần ba phần tử để tính tổng).
  • Trong mỗi lần lặp, tính tổng của ba phần tử liên tiếp bắt đầu từ phần tử thứ i.
  • Nếu tổng bằng 7, trả về true.
  • Nếu vòng lặp kết thúc mà không tìm thấy ba phần tử liên tiếp nào có tổng bằng 7, trả về false.

Lưu ý: Mã giả định rằng mảng đầu vào chỉ chứa các số nguyên.

29

Trong Javascript, sự khác biệt giữa throw Error('msg') so với throw new Error('msg') là gì?

Trong JavaScript, không có sự khác biệt đáng kể giữa việc sử dụng throw Error('msg')throw new Error('msg'). Cả hai cách này đều tạo ra một instance mới của đối tượng Error và ném (throw) nó như một ngoại lệ. Điều này được xác nhận bởi việc gọi hàm Error với hoặc không có từ khóa new đều tạo ra một instance mới của Error

Khi bạn sử dụng throw để ném một ngoại lệ, bạn có thể ném bất kỳ loại giá trị nào, không chỉ là các đối tượng Error. Tuy nhiên, thực hành tốt nhất là ném một instance của Error hoặc một lớp kế thừa từ Error, vì điều này cung cấp thông tin lỗi chi tiết hơn và cho phép xử lý lỗi một cách hiệu quả hơn

Sử dụng Error hoặc new Error để tạo một ngoại lệ cung cấp cho bạn một số lợi ích như:

  • Có thể chỉ định thông điệp lỗi (message) để giải thích nguyên nhân của lỗi.
  • Có thể sử dụng các thuộc tính khác của đối tượng Error, như stack, để có được thông tin chi tiết về ngăn xếp gọi tại thời điểm lỗi xảy ra, giúp việc debug trở nên dễ dàng hơn.
  • Khi ném một đối tượng Error, bạn có thể bắt (catch) nó trong một khối catch và xử lý lỗi một cách linh hoạt.

Ví dụ về việc ném và bắt một lỗi:

javascript
try {
  throw new Error('Đây là một lỗi!');
} catch (e) {
  console.log(e.message); // In ra: Đây là một lỗi!
}

Tóm lại, việc sử dụng throw Error('msg')throw new Error('msg') trong JavaScript đều tạo ra một ngoại lệ mới với thông điệp lỗi được chỉ định. Không có sự khác biệt đáng kể nào giữa hai cách này về mặt kỹ thuật hoặc hành vi

30

Kết quả data đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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.

31

Kết quả đoạn code sau là gì? Hãy giải thích chi tiết tại sao?

javascript
  const one = false || {} || null;
  const two = null || false || "";
  const three = [] || 0 || true;
  console.log(one, two, three);
  • A: false null []
  • B: null "" true
  • C: {} "" []
  • D: null null true

Đáp án: C

Với phép toán ||, ta sẽ trả về giá trị truethy đầu tiên. Nếu tất cả đều là falsy, giá trị cuối cùng sẽ được trả về.

(false || {} || null): object rỗng {} là một giá trị truthy. Nó là giá trị truethy đầu tiên và duy nhất nên sẽ được trả về. Do đó one sẽ là {}.

(null || false || ""): Tất cả toán hạng đều là falsy. Có nghĩa là toán hạng cuối cùng "" sẽ được trả về. Do đó two sẽ là "".

([] || 0 || ""): mảng rỗng [] là một giá trị truthy. Nó là giá trị truthy đầu tiên nên sẽ được trả về. Do đó three sẽ là [].

32

Kết quả đoạn code javascript sau là gì? Hãy giải thích tại sao?

javascript
  console.log(String.raw`Hello
world`);
  • A: Hello world!
  • B: Hello
         world
  • C: Hello world
  • D: `Hello

Đáp án: C

String.raw trả về chuỗi nguyên bản, các ký 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:DocumentsProjects able.html`

Sẽ cho ta chuỗi là:

"C:DocumentsProjects able.html"

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

C:DocumentsProjects able.html

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

33

Bạn biết gì về load event trong Javascript?

Sự kiện load trong JavaScript là một sự kiện quan trọng, được kích hoạt khi một trang web hoàn toàn tải xong tất cả nội dung bao gồm hình ảnh, tệp JavaScript, tệp CSS, và các phụ thuộc khác. Sự kiện này thường được sử dụng trong thẻ <body> để thực thi một đoạn script ngay sau khi trang web tải xong hoàn toàn

Đặc điểm của sự kiện load:

  • Sự kiện load xảy ra khi đối tượng đã được tải hoàn toàn.
  • Sự kiện này không thể hủy bỏ (non-cancelable) và không lan truyền (non-bubbling).
  • Thường được sử dụng để kiểm tra loại trình duyệt và phiên bản trình duyệt của người dùng, và tải phiên bản phù hợp của trang web dựa trên thông tin đó
  • Có thể được sử dụng để xử lý cookies
  • Sự kiện load khác với sự kiện DOMContentLoaded, sự kiện này được kích hoạt ngay khi DOM của trang đã tải xong, không cần chờ đợi các tài nguyên khác hoàn tất việc tải

Cách sử dụng sự kiện load:

Bạn có thể sử dụng sự kiện load bằng cách thêm thuộc tính onload vào một phần tử HTML hoặc gán nó cho một đối tượng trong JavaScript. Dưới đây là một số cách để đăng ký sự kiện load:

Trong HTML:

html
<body onload="myFunction()">

Trong JavaScript:

javascript
window.onload = function() {
  // Mã lệnh được thực thi sau khi trang tải xong
};

Hoặc sử dụng addEventListener để thêm một trình xử lý sự kiện:

javascript
window.addEventListener("load", function() {
  // Mã lệnh được thực thi sau khi trang tải xong
});

Lưu ý khi sử dụng sự kiện load:

  • Nếu bạn cần thực thi mã ngay khi DOM sẵn sàng mà không cần chờ đợi các tài nguyên khác, bạn nên sử dụng sự kiện DOMContentLoaded thay vì load
  • Để đảm bảo rằng các tài nguyên như CSS được tải trước khi JavaScript thực thi, bạn nên đặt các tệp CSS trong thẻ <head> và các tệp JavaScript ở cuối thẻ <body>

Sự kiện load là một công cụ hữu ích để đảm bảo rằng tất cả các tài nguyên cần thiết cho trang web đã sẵn sàng trước khi thực hiện các thao tác liên quan đến DOM hoặc các tài nguyên khác.

34

Kết quả shape của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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.

35

Kết quả gen.next().value của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  function* generator(i) {
    yield i;
    yield i * 2;
  }
  const gen = generator(10);
  console.log(gen.next().value);
  console.log(gen.next().value);
  • A: [0, 10], [10, 20]
  • B: 20, 20
  • C: 10, 20
  • D: 0, 10 and 10, 20

Đáp án: C

Một hàm bình thường không thể bị dừng giữa chừng khi được gọi. Tuy nhiên một generator thì khác, nó có thể "dừng lại" được, và sau đó nó sẽ tiếp tục từ vị trí nó dừng lại. Mỗi khi một generator gặp một từ khóa yield, nó sẽ sinh ra giá trị ngay phía sau nó. Chú ý là generator không trả về giá trị, nó sinh ra giá trị.

Đầu tiên, chúng ta khởi tạo generator với giá trị i10. Generator được gọi bằng cách sử dụng phương thức next(). Khi lần đầu gọi thì i vẫn là 10. Khi nó bắt gặp từ khóa yield: nó sẽ sinh ra giá trị i. Generator sẽ được "tạm dừng" tại đây, và ghi ra giá trị 10.

Sau đó chung ta tiếp tục gọi generator bằng cách sử dụng tiếp phương thức next(). Nó sẽ bắt đầu từ vị trí nó tạm dừng lúc trước, khi i vẫn đang là 10. Và khi nó bắt gặp từ khóa yield, nó sẽ sinh ra giá trị i * 2. i10, nên nó sẽ sinh ra 10 * 2, tức 20. Vậy kết quả cuối cùng là 10, 20.

36

Kết quả list của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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.

37

Giải thích về bubbling event trong Javascript và làm sao để ngăn chặn nó?

Bubbling event là một khái niệm trong quá trình lan truyền sự kiện (event propagation) trong DOM của JavaScript. Khi một sự kiện xảy ra trên một phần tử, nó không chỉ được xử lý tại phần tử đó mà còn được "bong bóng" lên qua các phần tử cha của nó trong cấu trúc DOM, từ phần tử mục tiêu (target element) cho đến đối tượng cửa sổ toàn cầu (global window object)

Cách Thức Hoạt Động của Bubbling Event

Khi một sự kiện được kích hoạt, sau khi được xử lý tại phần tử mục tiêu, nó sẽ bắt đầu "bong bóng" lên qua các phần tử cha. Sự kiện sẽ đi qua tất cả các phần tử cha theo thứ tự lồng nhau của chúng trong giai đoạn "bubbling". Trong quá trình này, bất kỳ trình xử lý sự kiện (event handler) nào được đăng ký trên các phần tử cha cũng sẽ được thực thi

Ngăn Chặn Bubbling Event

Để ngăn chặn sự kiện tiếp tục "bong bóng" lên các phần tử cha, bạn có thể sử dụng phương thức stopPropagation() trên đối tượng sự kiện. Phương thức này ngăn chặn sự lan truyền của sự kiện hiện tại trong các giai đoạn capturing và bubbling. Tuy nhiên, nó không ngăn chặn các hành vi mặc định từ việc xảy ra; ví dụ, các click trên liên kết vẫn được xử lý. Nếu bạn muốn ngăn chặn cả hành vi mặc định, bạn cần sử dụng phương thức preventDefault()

Ví dụ

Dưới đây là một ví dụ về cách ngăn chặn bubbling event:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Event Bubbling Example</title>
</head>
<body>
    <div id="parent">
        <button id="child">Click me</button>
    </div>

    <script>
        // Trình xử lý sự kiện cho phần tử cha
        document.getElementById("parent").addEventListener("click", function() {
            alert("Parent element clicked!");
        });

        // Trình xử lý sự kiện cho phần tử con và ngăn chặn bubbling
        document.getElementById("child").addEventListener("click", function(event) {
            alert("Child element clicked!");
            event.stopPropagation(); // Ngăn chặn sự kiện "bong bóng" lên phần tử cha
        });
    </script>
</body>
</html>

Trong ví dụ trên, khi bạn click vào nút có id là "child", chỉ có thông báo "Child element clicked!" được hiển thị và sự kiện không "bong bóng" lên phần tử cha, do đó thông báo "Parent element clicked!" sẽ không được hiển thị.

Tóm lại, bubbling event là một phần quan trọng của việc xử lý sự kiện trong JavaScript, cho phép các sự kiện được xử lý theo cấp độ phân cấp. Tuy nhiên, trong một số trường hợp, bạn có thể muốn ngăn chặn sự kiện này để tránh kích hoạt các trình xử lý sự kiện không mong muốn trên các phần tử cha, và bạn có thể làm điều này bằng cách sử dụng phương thức stopPropagation()

38

Đoạn code sau kết quả là gì? Hãy giải thích tại sao?

javascript
  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();
  • A: I have resolved!, secondI have resolved!, second
  • B: second, I have resolved!second, I have resolved!
  • C: I have resolved!, secondsecond, I have resolved!
  • D: second, I have resolved!I have resolved!, second

Đá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.

39

Đoạn code javascript sau output là gì? Hãy giải thích tại sao?

javascript
  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.

40

Object trong Javascript là gì?

Trong JavaScript, một object (đối tượng) là một cấu trúc dữ liệu cho phép lưu trữ và quản lý dữ liệu dưới dạng cặp khóa (key) và giá trị (value). Object trong JavaScript có thể được so sánh với một thực thể độc lập, có thuộc tính và kiểu, giống như các đối tượng thực tế trong cuộc sống hàng ngày. Mỗi cặp khóa và giá trị trong một object được gọi là một property, trong đó khóa thường là một chuỗi hoặc symbol, và giá trị có thể là bất kỳ kiểu dữ liệu nào, bao gồm cả một object khác

Khai báo và Truy cập Object

Object trong JavaScript có thể được khai báo sử dụng cú pháp object literal, bằng cách sử dụng dấu ngoặc nhọn {} và liệt kê các property bên trong. Mỗi property được phân tách bởi dấu phẩy

javascript
const person = {
  name: "John Doe",
  age: 30
};

Có hai cách để truy cập vào các property của object:

  • Dot notation: objectName.propertyName
  • Bracket notation: objectName["propertyName"]

Thuộc tính và Phương thức

Trong một object, giá trị của property có thể là một hàm, khi đó property đó được gọi là một phương thức (method) của object. Phương thức là các hàm được gắn với object và thường được sử dụng để xác định hành vi của object

javascript
const person = {
  name: "John Doe",
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

person.greet(); // Output: Hello, John Doe

Nested Objects

Object trong JavaScript có thể chứa các object khác, tạo thành cấu trúc dữ liệu phức tạp với nhiều cấp độ lồng nhau

javascript
const person = {
  name: "John Doe",
  address: {
    street: "123 Main St",
    city: "Anytown"
  }
};

console.log(person.address.city); // Output: Anytown

Tính linh hoạt

Object trong JavaScript rất linh hoạt và cho phép thêm hoặc xóa các property sau khi object đã được tạo. Điều này giúp JavaScript trở thành một ngôn ngữ lập trình mạnh mẽ và linh hoạt, phù hợp với nhiều loại ứng dụng khác nhau

Kết luận

Object trong JavaScript là một khái niệm cốt lõi, giúp lập trình viên có thể lưu trữ và quản lý dữ liệu một cách hiệu quả. Sự linh hoạt và khả năng lưu trữ dữ liệu phức tạp làm cho object trở thành một công cụ không thể thiếu trong việc phát triển ứng dụng web.

41

Coercion trong JavaScript là gì?

Coercion trong JavaScript là quá trình chuyển đổi tự động hoặc ngầm định giá trị từ một kiểu dữ liệu này sang kiểu dữ liệu khác, như từ chuỗi sang số. Coercion có thể xảy ra khi bạn áp dụng các toán tử cho các giá trị có kiểu khác nhau hoặc khi giá trị được sử dụng trong một ngữ cảnh cụ thể mà ở đó kiểu dữ liệu cần phải được chuyển đổi

Coercion có thể là rõ ràng (explicit) hoặc ngầm định (implicit). Coercion rõ ràng xảy ra khi lập trình viên biểu thị ý định chuyển đổi giữa các kiểu bằng cách viết mã phù hợp, như sử dụng hàm Number(value) để chuyển một giá trị thành số. Trong khi đó, coercion ngầm định xảy ra khi JavaScript tự động chuyển đổi giữa các kiểu khác nhau mà không cần sự can thiệp của lập trình viên

Ví dụ về coercion ngầm định:

javascript
const value1 = "5";
const value2 = 9;
let sum = value1 + value2;
console.log(sum); // Kết quả là "59"

Trong ví dụ trên, JavaScript đã tự động chuyển đổi số 9 thành một chuỗi và sau đó nối hai giá trị lại với nhau, kết quả là một chuỗi "59". Để có kết quả là tổng số, bạn cần phải chuyển đổi rõ ràng "5" thành một số bằng cách sử dụng phương thức Number()

Coercion ngầm định có thể hữu ích nhưng cũng có thể gây ra những lỗi không mong muốn, đặc biệt là khi so sánh các giá trị với toán tử bằng lỏng lẻo ==. Do đó, việc sử dụng toán tử bằng chặt === được khuyến nghị để tránh những hành vi không mong muốn này

42

Kết quả đoạn code javascript sau là gì? Hãy giải thích?

javascript
  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: 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".

43

Giải thích về phương thức call(), aplly()bind() trong Javascript?

Trong JavaScript, call(), apply(), và bind() là ba phương thức quan trọng của Function, giúp chúng ta làm việc với từ khóa this và các đối số của hàm một cách linh hoạt. Mỗi phương thức có cách sử dụng và mục đích riêng.

call()

Phương thức call() cho phép bạn gọi một hàm với một giá trị this được chỉ định và các đối số được truyền vào một cách rõ ràng. Cú pháp của call()func.call(thisArg, arg1, arg2, ...), trong đó thisArg là giá trị this muốn gán, và arg1, arg2, ... là các đối số truyền vào hàm

apply()

Phương thức apply() rất giống với call(), nhưng thay vì truyền các đối số một cách rõ ràng, apply() cho phép bạn truyền một mảng các đối số. Cú pháp của apply()func.apply(thisArg, [argsArray]), với thisArg là giá trị this muốn gán, và [argsArray] là mảng chứa các đối số

bind()

Phương thức bind() tạo ra một hàm mới, có giá trị this được ràng buộc với giá trị được cung cấp. Khác với call()apply(), bind() không tự động gọi hàm mà trả về một hàm mới với context this đã được set. Cú pháp của bind()func.bind(thisArg[, arg1[, arg2[, ...]]]), trong đó thisArg là giá trị this muốn gán, và arg1, arg2, ... là các đối số mặc định cho hàm mới

Khi nào sử dụng?

  • call()apply() thường được sử dụng khi bạn muốn gọi một hàm với một giá trị this cụ thể hoặc khi bạn muốn mượn một hàm từ một đối tượng khác mà không tạo ra một phương thức mới cho đối tượng đó. Sự khác biệt chính giữa hai phương thức này là cách thức truyền đối số: call() nhận đối số dưới dạng danh sách, trong khi apply() nhận đối số dưới dạng mảng
  • bind() thường được sử dụng khi bạn cần ràng buộc this cho một hàm để sử dụng sau này, ví dụ như trong các sự kiện hoặc khi bạn cần đảm bảo rằng this trong một hàm luôn trỏ đến đối tượng mong muốn, bất kể nó được gọi như thế nào

Ví dụ

javascript
let person = {
    fullName: function() {
        return this.firstName + " " + this.lastName;
    }
};

let person1 = {
    firstName: "John",
    lastName: "Doe"
};

// Sử dụng call()
console.log(person.fullName.call(person1));  // John Doe

// Sử dụng apply()
console.log(person.fullName.apply(person1)); // John Doe

// Sử dụng bind()
let getPersonName = person.fullName.bind(person1);
console.log(getPersonName());  // John Doe

Tóm lại, call(), apply(), và bind() là các phương thức mạnh mẽ giúp bạn kiểm soát giá trị của this trong JavaScript, mỗi phương thức có những ứng dụng và cách sử dụng riêng biệt phù hợp với nhu cầu cụ thể trong lập trình

44

Sử dụng method nào được trả về với log '{ name: "Lydia", age: 22 }'?

javascript
  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 }.

45

Output đoạn code javascript sau là gì? Hãy giải thích tại sao?

javascript
  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: 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.

46

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

Để "deep-freeze" một đối tượng trong JavaScript, bạn cần sử dụng phương thức Object.freeze() kết hợp với đệ quy. Phương thức Object.freeze() ngăn chặn việc thêm mới, xóa bỏ, hoặc thay đổi các thuộc tính của đối tượng, cũng như ngăn chặn việc thay đổi prototype của đối tượng. Tuy nhiên, Object.freeze() chỉ áp dụng một "shallow freeze", nghĩa là nó chỉ đóng băng các thuộc tính trực tiếp của đối tượng và không áp dụng cho các đối tượng lồng nhau. Để đạt được một "deep freeze", bạn cần áp dụng đệ quy để đóng băng mọi đối tượng lồng nhau

Dưới đây là một ví dụ về cách thực hiện "deep freeze" một đối tượng:

javascript
function deepFreeze(object) {
    // Lấy tất cả tên thuộc tính của đối tượng
    const propNames = Reflect.ownKeys(object);

    // Đóng băng các thuộc tính trước khi đóng băng chính đối tượng
    for (const name of propNames) {
        const value = object[name];
        if ((value && typeof value === "object") || typeof value === "function") {
            deepFreeze(value);
        }
    }

    return Object.freeze(object);
}

const obj2 = {
    internal: {
        a: null,
    },
};

deepFreeze(obj2);
obj2.internal.a = "anotherValue"; // Thay đổi này sẽ không có hiệu lực

Trong ví dụ trên, hàm deepFreeze sẽ đệ quy qua tất cả các thuộc tính của đối tượng. Nếu một thuộc tính là một đối tượng hoặc một hàm, hàm deepFreeze sẽ được gọi đệ quy cho thuộc tính đó trước khi đóng băng chính đối tượng. Cuối cùng, Object.freeze() được gọi để đóng băng đối tượng. Kết quả là, bạn không thể thay đổi, thêm mới, hoặc xóa bỏ bất kỳ thuộc tính nào của đối tượng, kể cả trong các đối tượng lồng nhau

Lưu ý rằng việc sử dụng "deep freeze" có thể ảnh hưởng đến hiệu suất nếu đối tượng có cấu trúc phức tạp và lồng nhau sâu. Do đó, hãy cân nhắc kỹ trước khi áp dụng cho toàn bộ đối tượng lớn hoặc phức tạp.

47

Con trỏ this trong javascript là gì?

Con trỏ this trong JavaScript được dùng để tham chiếu đến đối tượng mà nó thuộc về, hay nói cách khác, this đại diện cho ngữ cảnh mà đoạn mã đang được thực thi. Trong JavaScript, giá trị của this không cố định mà phụ thuộc vào cách mà hàm hoặc phương thức được gọi

Cách hoạt động của this

  • Khi một hàm được gọi như là một phương thức của một đối tượng, this tham chiếu đến đối tượng đó
  • Khi một hàm được gọi mà không phải là một phương thức của đối tượng, this tham chiếu đến đối tượng global, tức là window trong trình duyệt và global trong Node.js. Tuy nhiên, trong strict mode, this sẽ là undefined
  • Trong các hàm mũi tên (arrow functions), this được "bắt" từ ngữ cảnh bên ngoài hàm mũi tên, tức là nó không có this riêng mà sử dụng this từ ngữ cảnh bên ngoài của nó

Ví dụ về this

javascript
const person = {
  name: "Alice",
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

person.greet(); // "Hello, Alice" - `this` tham chiếu đến đối tượng `person`

Trong trường hợp hàm được gọi không thông qua một đối tượng:

javascript
function showName() {
  console.log(this.name);
}

const user = { name: "Bob" };
window.name = "GlobalName";

showName(); // "GlobalName" - `this` tham chiếu đến `window` trong trình duyệt

Sử dụng this trong các trường hợp đặc biệt

  • Khi sử dụng this trong một hàm callback hoặc một hàm được truyền như một tham số, giá trị của this có thể không phải là như mong đợi. Trong trường hợp này, các phương thức như bind, call, và apply có thể được sử dụng để rõ ràng xác định giá trị của this

Tóm lại

this là một từ khóa quan trọng trong JavaScript, giúp xác định ngữ cảnh thực thi của mã. Hiểu rõ cách this hoạt động là cần thiết để viết mã chính xác và tránh các lỗi không mong muốn liên quan đến ngữ cảnh thực thi.

48

Sự khác biệt giữa shimpolyfill trong Javascript là gì?

Trong JavaScript, shimpolyfill là hai khái niệm thường được sử dụng để giúp code JavaScript hoạt động trên các trình duyệt khác nhau, đặc biệt là các trình duyệt cũ không hỗ trợ các tính năng mới. Dù có mục đích tương tự nhau, chúng có những đặc điểm và cách sử dụng khác nhau.

Shim

  • Định nghĩa: Shim là một thư viện trong ngôn ngữ JavaScript có chức năng chặn các lời gọi API (Application Programming Interface) và có thể sửa đổi các tham số được truyền, xử lý chính hoạt động đó hoặc đôi khi chuyển hướng hoạt động đến một nơi khác. Shim có thể được sử dụng để thực thi các chương trình trên nhiều nền tảng phần mềm khác nhau mà chúng không được phát triển cho
  • Chức năng: Shim cung cấp một API mới cho một môi trường nền tảng cũ, nhưng không thể thêm vào những thực thi bị thiếu trong một lời gọi API

Polyfill

  • Định nghĩa: Polyfill, hay còn gọi là polyfilling, là phiên bản tiếp theo hoặc phiên bản cao cấp hơn của shim. Polyfill là khái niệm về việc thực thi các tính năng bị mất trong một lời gọi API, nơi mà shim không thể thực thi các tính năng bị mất đó. Polyfill là một đoạn mã hoặc plugin cung cấp công nghệ mà bạn mong đợi trình duyệt web cung cấp một cách tự nhiên. Polyfill thêm vào các trình duyệt cũ với thực thi HTML5/CSS3 hiện đại
  • Chức năng: Polyfill cài đặt thực thi của riêng nó nếu các tính năng bị thiếu, không chỉ sửa chữa hành vi của mã đã tồn tại mà còn thực thi một tính năng trên các trình duyệt web không hỗ trợ tính năng đó, dù nó có tồn tại hay không

Khi nào sử dụng?

  • Shim thường được sử dụng khi cần mang một API mới đến một môi trường nền tảng cũ, nhưng không cần thêm vào những thực thi bị thiếu.
  • Polyfill được sử dụng khi cần thực thi các tính năng bị thiếu trong trình duyệt, đặc biệt là để đảm bảo tính tương thích giữa các trình duyệt cũ và các tính năng mới của HTML5/CSS3.

Tóm lại, cả shim và polyfill đều là những công cụ quan trọng trong việc viết mã JavaScript tương thích với nhiều trình duyệt, nhưng chúng khác nhau về mục đích và cách thức hoạt động. Shim cung cấp một API mới cho môi trường cũ mà không thêm vào thực thi bị thiếu, trong khi polyfill thêm vào thực thi cho các tính năng bị thiếu để đảm bảo tính tương thích trên các trình duyệt

49

Nêu một số trường hợp không nên sử dụng arrow functions trong Javascript?

Trong JavaScript ES6, Arrow Functions mang lại cú pháp ngắn gọn và tiện lợi cho việc viết hàm, nhưng không phải lúc nào chúng cũng là lựa chọn tốt nhất. Dưới đây là một số trường hợp bạn không nên sử dụng Arrow Functions:

  1. Khi cần sử dụng this trong ngữ cảnh động: Arrow Functions không có this riêng của chúng. this trong Arrow Functions được xác định bởi ngữ cảnh bên ngoài (lexical this) khi hàm được khai báo, không phải khi hàm được gọi. Điều này có thể gây ra vấn đề khi bạn cần this để tham chiếu đến đối tượng chứa hàm đó, như trong các phương thức của đối tượng hoặc khi xử lý sự kiện
  2. Khi định nghĩa phương thức trong đối tượng: Nếu bạn sử dụng Arrow Functions để định nghĩa phương thức trong một đối tượng, this sẽ không trỏ đến đối tượng đó mà trỏ đến ngữ cảnh bên ngoài, thường là window hoặc undefined trong strict mode. Điều này làm cho Arrow Functions không phù hợp để định nghĩa phương thức trong đối tượng
  3. Khi sử dụng làm hàm khởi tạo (constructor): Arrow Functions không thể sử dụng làm hàm khởi tạo và sẽ báo lỗi nếu bạn cố gắng sử dụng new với Arrow Function. Điều này là do Arrow Functions không có this riêng và không có thuộc tính prototype
  4. Khi cần sử dụng arguments object: Arrow Functions không cung cấp đối tượng arguments giống như các hàm thông thường. Nếu bạn cần truy cập vào các đối số được truyền vào hàm mà không được định nghĩa trước trong danh sách tham số, Arrow Functions sẽ không phù hợp
  5. Khi cần định nghĩa các phương thức trong class: Khi sử dụng Arrow Functions để định nghĩa phương thức trong class, bạn có thể gặp phải vấn đề với this giống như khi định nghĩa phương thức trong đối tượng. this trong Arrow Functions sẽ không trỏ đến instance của class

Tóm lại, mặc dù Arrow Functions mang lại nhiều lợi ích về cú pháp ngắn gọn và tiện lợi, nhưng chúng không phù hợp cho mọi tình huống, đặc biệt là khi cần sử dụng this trong ngữ cảnh động, định nghĩa phương thức trong đối tượng hoặc class, sử dụng làm hàm khởi tạo, hoặc khi cần truy cập vào đối tượng arguments.

50

Từ khóa new trong JavaScript để làm gì?

Từ khóa new trong JavaScript được sử dụng để tạo một thể hiện mới của một đối tượng từ một hàm tạo (constructor function) hoặc từ một lớp (class). Khi một hàm được gọi với từ khóa new, hàm đó sẽ được sử dụng như một hàm tạo, và new sẽ thực hiện các bước sau

  1. Tạo một đối tượng trống, đơn giản.
  2. Gán đối tượng mới này (newInstance) vào thuộc tính [[Prototype]] của hàm tạo, nếu prototype là một đối tượng. Nếu không, newInstance sẽ là một đối tượng đơn giản với Object.prototype làm [[Prototype]] của nó.
  3. Thực thi hàm tạo với các đối số đã cho, liên kết newInstance như là this trong hàm tạo.
  4. Trả về đối tượng mới tạo, trừ khi hàm tạo trả về một giá trị không phải nguyên thủy (đối tượng JavaScript tùy chỉnh).

Một hàm có thể biết liệu nó có được gọi với new hay không bằng cách kiểm tra new.target. new.target chỉ không xác định khi hàm được gọi mà không có new

Ví dụ về việc sử dụng new:

javascript
function Car(color) {
  if (!new.target) {
    // Được gọi như một hàm.
    return `${color} car`;
  }
  // Được gọi với new.
  this.color = color;
}

const a = Car("red"); // a là "red car"
const b = new Car("red"); // b là `Car { color: "red" }`

Nếu không sử dụng new, hàm tạo có thể được gọi như một hàm thông thường (tức là không có new), và trong trường hợp này không có đối tượng mới nào được tạo và giá trị của this cũng khác

Tóm lại, từ khóa new cho phép các nhà phát triển tạo ra một thể hiện của một kiểu đối tượng do người dùng định nghĩa hoặc một trong những kiểu đối tượng tích hợp sẵn có trong JavaScript

51

DOM là gì?

DOM trong ngữ cảnh của phát triển web là viết tắt của Document Object Model. Đây là một giao diện lập trình ứng dụng (API) đa nền tảng và độc lập với ngôn ngữ lập trình, cho phép các tài liệu HTML hoặc XML được xem như một cấu trúc cây, trong đó mỗi nút của cây đại diện cho một phần của tài liệu

Đặc Điểm của DOM:

  • Cấu trúc cây: DOM mô tả tài liệu dưới dạng một cấu trúc cây, với mỗi phần tử, thuộc tính, và nội dung văn bản được biểu diễn như là một nút (node) trong cây
  • Tương tác: DOM cho phép các ngôn ngữ lập trình như JavaScript tương tác với tài liệu, cho phép thay đổi cấu trúc, kiểu dáng và nội dung của tài liệu một cách động
  • Độc lập với ngôn ngữ: Mặc dù DOM thường được sử dụng với JavaScript, nó được thiết kế để độc lập với bất kỳ ngôn ngữ lập trình cụ thể nào và có thể được triển khai cho bất kỳ ngôn ngữ nào

DOM không phải là một phần của ngôn ngữ JavaScript; nó là một Web API được sử dụng để xây dựng các trang web. JavaScript thường được sử dụng để tương tác với DOM và thực hiện các thao tác như thêm, xóa hoặc sửa đổi các phần tử HTML, lắng nghe và xử lý sự kiện, và thay đổi CSS của các phần tử

52

Ưu điểm của promise so với callback là gì?

Ưu điểm của Promise so với Callback trong lập trình JavaScript bao gồm:

  1. Cải thiện Đọc và Bảo trì Mã: Promises cung cấp cú pháp sạch sẽ và dễ đọc hơn so với callbacks. Cấu trúc mã trở nên tuyến tính và dễ theo dõi hơn, giúp dễ dàng hơn cho việc hiểu và bảo trì mã.
  2. Loại bỏ Callback Hell: Promises giúp tránh được "Callback Hell" hoặc "Pyramid of Doom", nơi các callback lồng nhau dẫn đến mã khó đọc và hiểu. Promises cho phép bạn xử lý các hoạt động bất đồng bộ một cách duyên dáng hơn, cải thiện cấu trúc tổng thể của mã.
  3. Xử lý Lỗi Tốt hơn: Promises có cơ chế xử lý lỗi tích hợp, giúp dễ dàng bắt và xử lý lỗi có thể xảy ra trong quá trình hoạt động bất đồng bộ. Điều này dẫn đến mã mạnh mẽ và đáng tin cậy hơn, vì quản lý lỗi là một phần không thể tách rời của cấu trúc promise.
  4. Chuỗi và Tuần tự hóa: Promises tạo điều kiện cho việc chuỗi các hoạt động bất đồng bộ, làm cho việc tuần tự hóa nhiều tác vụ trở nên đơn giản. Khả năng chuỗi này tăng cường sự rõ ràng của mã, cung cấp một luồng logic hơn khi xử lý các tác vụ bất đồng bộ phụ thuộc.
  5. Đơn giản hóa Thiết kế: Promises đơn giản hóa thiết kế mã của bạn. Chúng tóm gọn một số phức tạp liên quan đến callbacks, cho phép bạn tập trung vào logic của ứng dụng hơn là quản lý luồng hoạt động bất đồng bộ.
  6. Đường Dẫn Thực thi Đơn lẻ: Promises đảm bảo một đường dẫn thực thi đơn lẻ, làm cho việc hiểu thứ tự hoạt động trở nên dễ dàng hơn. Sự dự đoán này góp phần vào độ tin cậy của mã và giảm thiểu khả năng xảy ra hành vi không mong muốn.
  7. Xử lý Lỗi Toàn cầu: Promises cung cấp một cơ chế tập trung cho việc xử lý lỗi. Bạn có thể đính kèm một khối .catch() duy nhất ở cuối chuỗi promise để bắt bất kỳ lỗi nào có thể đã xảy ra ở bất kỳ điểm nào trong chuỗi. Điều này đơn giản hóa việc theo dõi và gỡ lỗi.
  8. Thúc đẩy Luồng Mã Tự nhiên hơn: Promises góp phần vào một luồng mã tự nhiên và dễ hiểu hơn. Logic trở nên giống với mã đồng bộ hơn, cho phép các nhà phát triển biểu đạt trình tự hoạt động mong muốn mà không bị gián đoạn bởi các phức tạp liên quan đến callback.

Tóm lại, việc chọn Promises thay vì callbacks không chỉ cải thiện các khía cạnh kỹ thuật của mã mà còn cải thiện trải nghiệm tổng thể của việc viết và bảo trì mã. Các lợi ích bao gồm cải thiện khả năng đọc, loại bỏ callback hell, xử lý lỗi tốt hơn, khả năng chuỗi, đơn giản hóa thiết kế, một đường dẫn thực thi đơn lẻ và xử lý lỗi toàn cầu.

53

Sự khác biệt giữa null, undefined hoặc undeclared trong Javascript là gì?

Trong JavaScript, null, undefinedundeclared đều đại diện cho các trạng thái khác nhau của biến, và việc hiểu rõ sự khác biệt giữa chúng là quan trọng để viết mã nguồn chính xác và tránh lỗi.

null

  • Định nghĩa: null là một giá trị nguyên thủy trong JavaScript, được sử dụng để biểu thị một biến có giá trị "không có gì" hoặc "trống rỗng". Nó thể hiện sự vắng mặt ý định của một giá trị đối tượng
  • Kiểu dữ liệu: Khi kiểm tra kiểu dữ liệu của null bằng toán tử typeof, kết quả trả về là "object"
  • Sử dụng: null thường được gán một cách chủ động vào biến để biểu thị rằng biến đó không trỏ đến bất kỳ đối tượng nào

undefined

  • Định nghĩa: undefined biểu thị rằng một biến đã được khai báo nhưng chưa được gán giá trị, hoặc một thuộc tính không tồn tại trong đối tượng
  • Kiểu dữ liệu: Khi kiểm tra kiểu dữ liệu của undefined bằng toán tử typeof, kết quả trả về là "undefined"
  • Sử dụng: undefined thường xuất hiện tự nhiên khi một biến được khai báo mà không có giá trị khởi tạo, hoặc khi truy cập vào một thuộc tính không tồn tại của đối tượng

undeclared

  • Định nghĩa: Một biến undeclared là biến chưa được khai báo trong phạm vi hiện tại, tức là không sử dụng var, let, hoặc const để khai báo
  • Kiểu dữ liệu: Không áp dụng vì biến undeclared không tồn tại trong phạm vi hiện tại.
  • Sử dụng: Khi một biến được sử dụng mà không được khai báo, JavaScript sẽ báo lỗi ReferenceError

So sánh và Kiểm Tra

  • So sánh nullundefined: Trong so sánh trừu tượng (sử dụng ==), nullundefined được coi là bằng nhau (null == undefined trả về true). Tuy nhiên, trong so sánh nghiêm ngặt (sử dụng ===), chúng không bằng nhau (null === undefined trả về false)
  • Kiểm tra undeclared: Khi truy cập một biến undeclared, JavaScript sẽ báo lỗi ReferenceError vì biến đó không tồn tại trong phạm vi hiện tại

Kết luận

nullundefined đều là giá trị nguyên thủy trong JavaScript nhưng biểu thị hai trạng thái khác nhau: null là một giá trị rỗng được gán một cách chủ động, trong khi undefined là giá trị mặc định cho biến chưa được khởi tạo. undeclared là trạng thái của một biến không được khai báo và sẽ gây ra lỗi nếu cố gắng sử dụng nó

54

Đoạn code kết quả sau là gì và hãy giải thích tại sao?

javascript
  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.

55

IIFEs (Immediately Invoked Function Expressions) trong Javascript là gì?

IIFEs, viết tắt của Immediately Invoked Function Expressions, là một kỹ thuật trong JavaScript để thực thi một hàm ngay lập tức sau khi nó được định nghĩa. IIFEs thường được sử dụng để tạo ra một phạm vi lexical (phạm vi từ vựng) bằng cách sử dụng phạm vi hàm, giúp tránh việc các biến bị nâng lên (hoisting) từ bên trong các khối lệnh, bảo vệ khỏi việc làm bẩn không gian toàn cục (global namespace) và đồng thời cho phép truy cập công khai đến các phương thức trong khi vẫn giữ được tính riêng tư

Cấu trúc cơ bản của một IIFE bao gồm một hàm ẩn danh được bao quanh bởi toán tử nhóm () để ngăn chặn việc truy cập vào các biến bên trong IIFE cũng như ngăn chặn việc làm bẩn không gian toàn cục. Phần thứ hai tạo ra biểu thức hàm được gọi ngay lập tức () thông qua đó trình biên dịch JavaScript sẽ trực tiếp diễn giải hàm

IIFEs có thể được viết theo nhiều cách khác nhau, nhưng một quy ước phổ biến là đặt biểu thức hàm - và tùy chọn toán tử gọi hàm của nó - trong cặp dấu ngoặc đơn, để rõ ràng báo cho trình phân tích cú pháp biết rằng đây là một biểu thức

Ví dụ về một IIFE đơn giản:

javascript
(function () {
    // Mã lệnh của bạn ở đây
})();

Hoặc sử dụng biến thể hàm mũi tên (arrow function):

javascript
(() => {
    // Mã lệnh của bạn ở đây
})();

IIFEs có thể được sử dụng để tránh làm bẩn không gian toàn cục, thực hiện các hàm bất đồng bộ như setTimeout(), hoặc tạo ra các biến riêng tư mà bạn muốn ngăn chặn sự thay đổi hoặc chỉnh sửa không mong muốn

Một ví dụ về việc sử dụng IIFE để tạo ra các biến riêng tư:

javascript
const counter = (function () {
    let i = 0;
    return {
        get: function () { return i; },
        set: function (val) { i = val; },
        increment: function () { return ++i; }
    };
})();

Trong ví dụ trên, biến i là riêng tư và không thể truy cập từ bên ngoài IIFE. Các phương thức get, set, và increment là công khai và có thể được gọi từ bên ngoài IIFE, nhưng chúng vẫn có thể truy cập biến i riêng tư bên trong IIFE

IIFEs cũng có thể được đặt tên, mặc dù việc đặt tên không làm thay đổi thực tế là hàm không "rò rỉ" ra không gian toàn cục và không thể được gọi lại sau khi thực thi

Ngoài ra, IIFEs có thể được sử dụng để tạo ra các biến và phương thức riêng tư và công khai trong mô hình module

Tóm lại, IIFEs là một công cụ hữu ích trong JavaScript để tạo ra phạm vi riêng tư, tránh làm bẩn không gian toàn cục và thực thi mã ngay lập tức.

56

Kết quả đoạn javascript sau là gì? Hãy giải thích tại sao?

javascript
  const set = new Set();
  set.add(1);
  set.add("Lydia");
  set.add({ name: "Lydia" });
  for (let item of set) {
    console.log(item + 2);
  }
  • A: 3, NaN, NaN
  • B: 3, 7, NaN
  • C: 3, Lydia2, [object Object]2
  • D: "12", Lydia2, [object Object]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".

57

Kết quả của đoạn code javascript sau là gì? Hãy giải thích chi tiết tại sao?

javascript
let number = 0;
console.log(number++);
console.log(++number);
console.log(number);
  • A: 1 1 2
  • B: 1 2 2
  • C: 0 2 2
  • D: 0 1 2

Đáp án: C

Khi phép toán ++ nằm ở đằng sau (postfix):

  1. Trả về giá trị (trả về 0)
  2. Tăng giá trị lên (number giờ là 1)

Khi phép toán ++ nằm ở đằng trước (prefix):

  1. Tăng giá trị lên (number giờ là 2)
  2. Trả về giá trị (trả về 2)

Vậy kết quả là 0 2 2.

58

Hãy giải thích về ScopeScope Chain trong Javascript?

Scope trong JavaScript

Scope trong JavaScript xác định khả năng truy cập của biến và hàm ở các phần khác nhau trong một đoạn code. Có hai loại scope chính: global scope và local scope

Global Scope

Biến được khai báo ngoài tất cả các hàm là biến toàn cục (global) và có thể được truy cập từ bất kỳ đâu trong mã JavaScript. Ví dụ:

javascript
var name = 'GlobalName'; // Biến toàn cục

function showName() {
  console.log(name); // Truy cập biến toàn cục từ bên trong hàm
}

showName(); // In ra: GlobalName

Local Scope

Biến được khai báo bên trong một hàm là biến cục bộ (local) và chỉ có thể được truy cập từ bên trong hàm đó. Mỗi hàm định nghĩa một scope riêng, và biến cục bộ không thể được truy cập từ bên ngoài hàm đó:

javascript
function showName() {
  var localName = 'LocalName'; // Biến cục bộ
  console.log(localName); // Truy cập biến cục bộ từ bên trong hàm
}

showName(); // In ra: LocalName
console.log(localName); // Uncaught ReferenceError: localName is not defined

Scope Chain

Scope chain là cơ chế mà JavaScript sử dụng để tìm kiếm biến và hàm. Khi một biến không được tìm thấy trong scope hiện tại, JavaScript sẽ tìm kiếm trong scope cha, và tiếp tục như vậy cho đến khi tìm thấy biến hoặc đến khi đạt đến global scope

Mỗi function định nghĩa một scope riêng, và mỗi function được định nghĩa bên trong một function khác sẽ tạo ra một nested scope. Biến được định nghĩa trong một scope có thể được truy cập từ bất kỳ nested scope nào bên trong nó:

javascript
var globalName = 'GlobalName'; // Biến toàn cục

function outerFunction() {
  var outerName = 'OuterName'; // Biến cục bộ của outerFunction

  function innerFunction() {
    var innerName = 'InnerName'; // Biến cục bộ của innerFunction
    console.log(innerName); // InnerName
    console.log(outerName); // OuterName
    console.log(globalName); // GlobalName
  }

  innerFunction();
}

outerFunction();

Trong ví dụ trên, innerFunction có thể truy cập cả innerName, outerName, và globalName thông qua scope chain. Tuy nhiên, outerFunction không thể truy cập innerName vì nó nằm ngoài scope của outerFunction.

Scope chain cũng liên quan đến cơ chế hoisting, nơi mà các khai báo biến và hàm được di chuyển lên đầu scope trước khi code được thực thi. Điều này có nghĩa là bạn có thể tham chiếu đến một biến trước cả khi nó được khai báo trong cùng một scope:

javascript
function showName() {
  console.log(name); // undefined do hoisting
  var name = 'LocalName';
  console.log(name); // LocalName
}

showName();

Trong trường hợp trên, console.log(name) đầu tiên in ra undefined vì biến name đã được hoisted, nhưng chưa được gán giá trị.

Kết luận

Hiểu rõ về scope và scope chain là chìa khóa để viết code chống lỗi và trở thành developer tốt hơn. Bạn sẽ hiểu được ở đâu biến và hàm có thể được truy cập, có thể thay đổi scope của ngữ cảnh trong code của bạn, và viết code nhanh hơn, dễ bảo trì hơn, cũng như debug dễ dàng hơn

59

Kết quả data của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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!"

60

Đoạn code sau có xảy ra lỗi không?

javascript
  const colorConfig = {
    red: true,
    blue: false,
    green: true,
    black: true,
    yellow: false,
  };
  const colors = ["pink", "red", "blue"];
  console.log(colorConfig.colors[1]);
  • A: true
  • B: false
  • C: undefined
  • D: TypeError

Đá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 . 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], nó sẽ trả về giá trị của thuộc tính red trong object colorConfig.

61

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)

JSONP là một kỹ thuật được sử dụng để vượt qua hạn chế về chính sách cùng nguồn (same-origin policy) trong các ứng dụng web. Chính sách này ngăn chặn các trang web từ việc yêu cầu tài nguyên từ một miền khác, gây khó khăn trong việc truy cập dữ liệu từ các API nằm trên các miền khác nhau. JSONP giải quyết vấn đề này bằng cách sử dụng thẻ <script> để thực hiện yêu cầu, vì thẻ <script> không bị hạn chế bởi chính sách cùng nguồn

Cách hoạt động của JSONP bao gồm việc gửi một yêu cầu đến máy chủ với một tham số callback trong URL. Máy chủ sau đó trả về dữ liệu dưới dạng một hàm JavaScript, với dữ liệu được bao quanh bởi hàm callback đã được chỉ định. Khi dữ liệu được trả về, hàm callback sẽ được thực thi tự động, cho phép dữ liệu được xử lý bởi trang web

AJAX (Asynchronous JavaScript and XML)

AJAX, viết tắt của Asynchronous JavaScript and XML, là một kỹ thuật cho phép trang web gửi và nhận dữ liệu từ máy chủ một cách không đồng bộ, mà không cần phải tải lại trang web. Điều này giúp cải thiện trải nghiệm người dùng bằng cách làm cho các ứng dụng web trở nên nhanh chóng và đáp ứng hơn

AJAX hoạt động bằng cách sử dụng đối tượng XMLHttpRequest để gửi yêu cầu đến máy chủ và nhận dữ liệu trả về. Dữ liệu trả về có thể ở nhiều định dạng khác nhau, bao gồm XML, JSON, HTML, hoặc văn bản thuần túy. Sau khi nhận được dữ liệu, JavaScript được sử dụng để cập nhật trang web mà không cần tải lại trang

Sự Khác Biệt Chính

  • Chính sách cùng nguồn: AJAX tuân thủ chính sách cùng nguồn, nghĩa là nó chỉ có thể gửi yêu cầu đến cùng một miền như trang web đang gửi yêu cầu. Trong khi đó, JSONP cho phép vượt qua hạn chế này bằng cách sử dụng thẻ <script>
  • Phương thức yêu cầu: JSONP chỉ hỗ trợ phương thức GET do nó sử dụng thẻ <script> để gửi yêu cầu. AJAX hỗ trợ cả GET và POST, cho phép gửi dữ liệu phức tạp hơn đến máy chủ
  • Bảo mật: JSONP có rủi ro bảo mật cao hơn do nó thực thi mã JavaScript từ một miền khác, có thể dẫn đến các cuộc tấn công XSS. AJAX được coi là an toàn hơn khi sử dụng trong cùng một miền

Kết Luận

Mặc dù JSONP và AJAX đều là các kỹ thuật cho phép truy cập dữ liệu không đồng bộ trong các ứng dụng web, chúng hoạt động dựa trên các nguyên tắc khác nhau và có những ưu nhược điểm riêng. JSONP là một giải pháp để vượt qua hạn chế về chính sách cùng nguồn nhưng có rủi ro bảo mật cao hơn, trong khi AJAX cung cấp một cách an toàn và linh hoạt hơn để tương tác với máy chủ, nhưng bị hạn chế bởi chính sách cùng nguồn

62

Có thể reset một generator trong Javascript về state ban đầu của nó không?

Không thể reset một generator trong JavaScript để trở về trạng thái ban đầu mà không gọi lại hàm generator. Một khi generator đã đạt trạng thái "hoàn thành" (khi thuộc tính done trở thành true), nó sẽ ở trạng thái đó vĩnh viễn và không thể được reset. Điều này có nghĩa là bất kỳ trạng thái thực thi nào liên quan đến generator có thể được loại bỏ. Nếu bạn muốn sử dụng lại generator, bạn cần phải khởi tạo một thể hiện mới của nó

Tuy nhiên, có một số cách để "reset" giá trị mà generator đã yield về giá trị ban đầu hoặc cập nhật nó bằng cách truyền một đối số vào phương thức next(). Điều này chỉ có thể thực hiện trước khi generator hoàn thành. Ví dụ, bạn có thể truyền một giá trị vào phương thức next() để cập nhật biến trạng thái bên trong generator, nhưng điều này không thực sự "reset" generator về trạng thái ban đầu của nó mà chỉ thay đổi giá trị mà nó sẽ yield tiếp theo

Nếu bạn muốn sử dụng lại một generator sau khi nó đã hoàn thành, giải pháp duy nhất là khởi tạo lại nó bằng cách gọi lại hàm generator. Điều này tạo ra một thể hiện mới của generator, cho phép bạn bắt đầu quá trình lặp lại từ đầu

63

Hạn chế của phương thức private trong JavaScript là gì?

Trong JavaScript, việc sử dụng các phương thức và thuộc tính private được giới thiệu như một phần của cú pháp lớp (class syntax) trong ECMAScript 2015 (ES6) và được mở rộng với các tính năng bổ sung trong các phiên bản sau. Các phương thức và thuộc tính private trong một lớp chỉ có thể được truy cập từ bên trong lớp đó. Tuy nhiên, việc sử dụng các phương thức và thuộc tính private trong JavaScript có một số hạn chế:

  1. Khả năng tương thích: Hỗ trợ cho các thuộc tính và phương thức private trong JavaScript không phải là toàn cầu. Mặc dù các trình duyệt hiện đại và môi trường như Node.js đã bắt đầu hỗ trợ tính năng này, nhưng vẫn có một số môi trường cũ hơn không hỗ trợ hoặc chỉ hỗ trợ một phần. Điều này có thể gây ra vấn đề khi cố gắng viết mã tương thích với nhiều môi trường khác nhau.
  2. Gỡ lỗi và Kiểm tra: Việc gỡ lỗi và kiểm tra mã có thể trở nên phức tạp hơn khi sử dụng các thuộc tính và phương thức private. Các công cụ gỡ lỗi không thể truy cập trực tiếp vào các thành phần private từ bên ngoài lớp, điều này có thể làm cho việc kiểm tra và gỡ lỗi mã trở nên khó khăn hơn.
  3. Phản xạ và Động: Trong JavaScript, việc sử dụng phản xạ (reflection) và các kỹ thuật lập trình động để truy cập hoặc thay đổi mã tại thời gian chạy là khá phổ biến. Tuy nhiên, các thuộc tính và phương thức private không thể được truy cập hoặc thay đổi thông qua phản xạ hoặc các phương pháp động khác từ bên ngoài lớp, giới hạn khả năng linh hoạt của mã.
  4. Sử dụng và Hiểu biết: Cú pháp và cách sử dụng các thuộc tính và phương thức private có thể không quen thuộc với tất cả các nhà phát triển JavaScript, đặc biệt là những người mới làm quen với ngôn ngữ hoặc chưa cập nhật với các tính năng mới nhất. Điều này có thể làm tăng độ phức tạp của mã và làm giảm khả năng đọc và bảo trì mã.
  5. Thiết kế và Kiến trúc: Việc sử dụng quá mức các thuộc tính và phương thức private có thể dẫn đến việc thiết kế lớp quá phức tạp và khó mở rộng. Trong một số trường hợp, việc sử dụng các mẫu thiết kế và kỹ thuật khác có thể cung cấp một giải pháp linh hoạt và dễ bảo trì hơn.

Tóm lại, mặc dù các thuộc tính và phương thức private trong JavaScript mang lại nhiều lợi ích về bảo mật và đóng gói, nhưng cũng đi kèm với một số hạn chế liên quan đến khả năng tương thích, gỡ lỗi, phản xạ, và thiết kế. Các nhà phát triển cần cân nhắc kỹ lưỡng khi quyết định sử dụng tính năng này trong các dự án của mình.

64

Kết quả members của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  let person = { name: "Lydia" };
  const members = [person];
  person = null;
  console.log(members);
  • A: null
  • B: [null]
  • C: [{}]
  • D: [{ name: "Lydia" }]

Đáp án: D

Đầu tiên, chúng ta khai báo một biến person là một object có thuộc tính name.

Sau đó chúng ta khai báo một biến members. Ta set giá trị đầu tiên của mảng là giá trị của biến person. Khi sử dụng gán bằng, object sẽ được tham chiếu tới object mà nó được gán. Khi ta gán tham chiếu từ một biến sang biến khác, ta tạo ra một bản sao của tham chiếu đó. (nên nhớ rằng đó vẫn là 2 tham chiếu hoàn toàn khác nhau!)

Sau đó ta set giá trị của person bằng null.

Chúng ta chỉ đơn thuần là thay đổi giá trị của biến person mà thôi, chứ không phải giá trị của phần tử đầu tiên ở trong mảng, vì chúng ta có một tham chiếu khác đến object đó. Phần tử đầu tiên của mảng members vẫn giữ tham chiêu đến object gốc. Do vậy, khi chúng ta in ra mảng members, phần tử đầu tiên sẽ vẫn in ra giá trị của objet gốc.

65

Trong Javascript, hàm setInterval trả về cái gì?

javascript
setInterval(() => console.log("Hi"), 1000);
  • A: một id duy nhất
  • B: số lượng milliseconds
  • C: function truyền vào
  • D: undefined

Đáp án: A

Nó trả về một id duy nhất. Id này dùng để clear interval sau này với hàm clearInterval().

66

Đoạn code javascript sau có xảy ra lỗi không và giải thích tại sao?

javascript
  let c = { greeting: "Hey!" };
  let d;

  d = c;
  c.greeting = "Hello";
  console.log(d.greeting);
  • A: Hello
  • B: Hey
  • C: undefined
  • D: ReferenceError
  • E: TypeError

Đáp án: A

Trong JavaScript, tất cả các object sẽ được tham chiếu khi chúng được gán _bằng_wwwww một giá trị khác.

Đầu tiên, giá trị c có giá trị là một object. Sau đó, chúng ta gán d tham chiếu tới object mà c trỏ tới.

Khi ta thay đổi giá trị của object, tất cả các biến tham chiếu cũng đều thay đổi giá trị theo.

67

Kết quả đoạn code javascript sau là gì? Hãy giải thích chi tiết?

javascript
  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.

68

Kết quả output của đoạn code sau là gì? Hãy giải thích tại sao?

js
  var x = 1;
  var output = (function () {
    delete x;
    return x;
  })();
  console.log(output);

Đoạn mã trên có output là 1. Toán tử delete được sử dụng để xóa thuộc tính khỏi object. x ở đây không phải là một object mà là biến toàn cục (global variable) có kiểu là number.

69

Kết quả của đoạn code sau là gì? Hãy giải thích?

javascript
  let a = 3;
  let b = new Number(3);
  let c = 3;

  console.log(a == b);
  console.log(a === b);
  console.log(b === c);
  • A: true false true
  • B: false false true
  • C: true false false
  • D: false true true

Đáp án: C

new Number() là một hàm built-in constructor. Mặc dù nó trông có vẻ giống như là một số, nhưng không phải: nó thực sự là một object với hàng tá những thông số khác nữa.

Khi ta sử dụng phép so sánh ==, nó đơn thuần chỉ kiểm tra xem 2 biến có giá trị giống nhau. Chúng đều có giá trị là 3, vậy nên phép toán đầu trả về true.

Tuy nhiên khi sử dụng phép so sánh ===, cả giá trịkiểu đều phải giống nhau. Rõ ràng: new Number() không phải là một số, nó là một object. Cả 2 phép toán sau đều trả về false.

70

3 giai đoạn của event propagation trong Javascript là gì?

  • A: Target > Capturing > Bubbling
  • B: Bubbling > Target > Capturing
  • C: Target > Bubbling > Capturing
  • D: Capturing > Target > Bubbling

Đáp án: D

Trong capturing phase, event được truyền từ các phần tử cha cho tới phần tử target. Sau khi tới được phần tử target thì bubbling sẽ bắt đầu.

71

Kết quả member là gì? Hãy giải thích tại sao?

javascript
  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 {}.

72

Khi nào cần sử dụng asyncdefer trong javascript?

Khi sử dụng Javascript trong HTML, chúng ta có thể sử dụng các thuộc tính asyncdefer để tối ưu hóa quá trình tải trang web và cải thiện trải nghiệm người dùng.

Thuộc tính async cho phép các script được tải và thực thi song song với quá trình phân tích HTML, điều này thích hợp với các script độc lập và không phụ thuộc vào nhau. Trong trường hợp này, việc sử dụng thuộc tính async sẽ giúp cải thiện tốc độ tải trang.

Thuộc tính defer cũng cho phép tải và thực thi script song song với quá trình phân tích HTML, nhưng việc thực thi sẽ diễn ra sau khi trình duyệt đã phân tích xong toàn bộ HTML. Điều này thích hợp cho các script phụ thuộc vào nhau hoặc được sử dụng lại. Trong trường hợp này, việc sử dụng thuộc tính defer sẽ giúp đảm bảo thứ tự thực thi của các script.

Tuy nhiên, nếu script của bạn rất nhỏ (chỉ vài dòng code), tốt nhất là sử dụng inline script, tức là đặt code script trực tiếp vào trong thẻ script, để tránh tải thêm một file JavaScript độc lập.

Tóm lại, việc hiểu rõ tính chất, nguyên lý của các thuộc tính asyncdefer sẽ giúp bạn sử dụng chúng đúng cách và tối ưu hóa tốc độ tải trang của website.

Ví dụ sử dụng async

html
<!DOCTYPE html>
<html>
  <head>
    <title>Async Example</title>
  </head>
  <body>
    <h1>Async Example</h1>
    <script async src="script.js"></script>
    <p>This paragraph may appear before the script is loaded and executed.</p>
  </body>
</html>

Trong ví dụ trên, script.js là một tập tin script external, và được đặt trong thẻ script với thuộc tính async. Điều này cho phép trang web tiếp tục render các phần khác của HTML trong khi trang web đang tải script.js. Khi script.js được tải và chạy xong, nó sẽ thực thi.

Ví dụ sử dụng defer

html
<!DOCTYPE html>
<html>
  <head>
    <title>Defer Example</title>
    <script defer src="script.js"></script>
  </head>
  <body>
    <h1>Defer Example</h1>
    <p>This paragraph may appear before the script is loaded, but it won't execute until the entire HTML document is parsed.</p>
  </body>
</html>

Trong ví dụ trên, script.js được đặt trong thẻ script với thuộc tính defer. Script này sẽ được tải trong quá trình phân tích HTML, nhưng nó sẽ không thực thi cho đến khi HTML được phân tích hoàn tất. Điều này giúp tăng tốc độ hiển thị trang web của bạn, vì nó cho phép trình duyệt render phần còn lại của HTML trước khi script.js được thực thi.

73

Kết quả của đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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.

74

Kết quả hàm sau là gì? Hãy giải thích tại sao?

js
  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: 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.

75

Giải thích sự khác biệt giữa undefinednot defined trong JavaScript?

Trong JavaScript nếu bạn sử dụng một biến không tồn tại và chưa được khai báo, thì JavaScript sẽ thảy ra một lỗi var name is not defined và sau đó script sẽ bị ngừng thực thi. Nhưng nếu bạn sử dụng typeof undeclared_variable thì nó sẽ trả về undefined.

Trước khi bắt đầu thảo luận thêm, hãy hiểu sự khác biệt giữa khai báo và định nghĩa.

var x là một khai báo vì bạn chưa xác định nó giữ giá trị nào, bạn chỉ đang khai báo sự tồn tại của nó và nhu cầu cấp phát bộ nhớ.

js
var x; // declaring x
console.log(x); //output: undefined

var x = 1 vừa là khai báo vừa là định nghĩa (cũng có thể nói là chúng ta đang khởi tạo), ở đây việc khai báo và gán giá trị xảy ra nội tuyến cho biến x, trong JavaScript mọi khai báo biến và khai báo hàm đều đưa lên đầu phạm vi (scope) hiện tại của nó, nó được khai báo sau đó việc gán diễn ra theo thứ tự, thuật ngữ này được gọi là hoisting.

Một biến được khai báo nhưng không được định nghĩa và khi chúng ta cố gắng truy cập vào nó, nó sẽ trả về kết quả là undefined.

js
var x; // Declaration
if(typeof x === 'undefined') // Will return true

Một biến không được khai báo và cũng không được định nghĩa thì khi chúng ta cố gắng tham chiếu đến biến đó, kết quả sẽ là not defined.

js
console.log(y); // Output: ReferenceError: y is not defined
76

Đoạn code sau kết quả là gì? Giải thích tại sao?

javascript
  (() => {
    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: A

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

javascript
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".

77

Kết quả của vòng lặp sau là gì? Hãy giải thích tại sao?

javascript
  for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1);
  }

  for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1);
  }
  • A: 0 1 2 and 0 1 2
  • B: 0 1 2 and 3 3 3
  • C: 3 3 3 and 0 1 2

Đáp án: C

Bởi vì event queue trong JavaScript, hàm setTimeout callback sẽ được gọi sau khi vòng lặp được thực hiện. Bời vì biến i trong vòng lặp đầu tiên được khai báo với từ khóa var, nên nó sẽ là một biến global. Trong suốt vòng lặp, mỗi lần chúng ta tăng giá trị của i lên 1, sử dụng phép toán ++. Cho tới khi callback setTimeout được gọi, giá trị của i đã trở thành 3 rồi.

Trong vòng lặp thứ 2, biến i được khai báo với từ khóa let, có nghĩa nó là một biến block-scoped (block là những gì được viết bên trong cặp ngoặc { }). Tại mỗi vòng lặp, i sẽ là một biến mới có một giá trị mới, và giá trị đó có scope là bên trong vòng lặp mà thôi.

78

Kết quả đoạn code sau là gì? Hãy giải thích?

javascript
  +true;
  !"Lydia";
  • A: 1 and false
  • B: false and NaN
  • C: false and false

Đáp án: A

Phép toán cộng + sẽ convert một toán hạng sang dạng number. true1, và false is 0.

Chuỗi 'Lydia' là một truthy value. Điều chúng ta thật sự đang hỏi chính là "có phải một giá trị truthy là falsy?". Rõ ràng câu trả lời là false rồi.

79

Sự khác nhau giữa bind, callapply trong Javascript là gì?

Trong JavaScript, bind, callapply là ba phương thức của đối tượng Function và chúng đều được sử dụng để thiết lập giá trị của this khi gọi một hàm. Dưới đây là sự khác biệt chính giữa chúng:

call()

  • Gọi hàm ngay lập tức: call() gọi hàm ngay lập tức với giá trị this được chỉ định.
  • Truyền tham số: Các tham số được truyền vào hàm một cách rõ ràng, phân cách bởi dấu phẩy

apply()

  • Gọi hàm ngay lập tức: Giống như call(), apply() cũng gọi hàm ngay tức thì.
  • Truyền tham số: Thay vì truyền từng tham số riêng lẻ, apply() nhận một mảng các tham số

bind()

  • Không gọi hàm ngay lập tức: bind() không gọi hàm ngay khi nó được sử dụng, mà trả về một hàm mới với giá trị this được ràng buộc sẵn.
  • Truyền tham số: Các tham số được truyền vào giống như call(), nhưng hàm mới này có thể được gọi sau đó với giá trị this đã được thiết lập

Khi nào sử dụng từng phương thức

  • call(): Khi bạn muốn gọi một hàm ngay lập tức và bạn biết số lượng tham số cụ thể.
  • apply(): Khi bạn muốn gọi một hàm ngay lập tức nhưng tham số được lưu trữ trong một mảng.
  • bind(): Khi bạn muốn tạo ra một hàm mới với this được ràng buộc sẵn để sử dụng sau này, hoặc khi bạn muốn tránh việc this bị thay đổi trong các callback hoặc event handler.

Ví dụ

javascript
let person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

// Sử dụng call()
person.greet.call({ name: 'Bob' }); // "Hello, my name is Bob"

// Sử dụng apply()
person.greet.apply({ name: 'Charlie' }); // "Hello, my name is Charlie"

// Sử dụng bind()
let boundGreet = person.greet.bind({ name: 'David' });
boundGreet(); // "Hello, my name is David"

Trong ví dụ trên, call()apply() được sử dụng để gọi hàm greet ngay lập tức với this được thiết lập là một đối tượng mới. bind() tạo ra một hàm mới boundGreet mà khi gọi sau này sẽ sử dụng this đã được ràng buộc từ trước.

80

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

Trong JavaScript, awaityield là hai từ khóa được sử dụng trong các ngữ cảnh khác nhau, với mục đích và cách hoạt động riêng biệt. Dưới đây là sự khác biệt chính giữa chúng:

await

  • Ngữ cảnh sử dụng: await được sử dụng trong một hàm async để tạm dừng việc thực thi hàm cho đến khi Promise được giải quyết (resolved) hoặc bị từ chối (rejected).
  • Mục đích: Mục đích của await là đơn giản hóa việc sử dụng các hàm không đồng bộ, giúp mã nguồn trở nên dễ đọc và viết giống như mã đồng bộ, mặc dù nó thực sự là không đồng bộ.
  • Cách hoạt động: Khi await được sử dụng trước một Promise, việc thực thi của hàm async sẽ tạm dừng cho đến khi Promise đó hoàn thành. Sau đó, hàm tiếp tục thực thi với giá trị được giải quyết từ Promise.

yield

  • Ngữ cảnh sử dụng: yield được sử dụng trong một hàm generator (được định nghĩa bằng cách thêm * sau từ khóa function) để tạm dừng và tiếp tục việc thực thi của hàm.
  • Mục đích: yield cho phép một hàm generator tạm dừng việc thực thi và trả về một giá trị (hoặc undefined). Hàm generator có thể được tiếp tục lại từ điểm tạm dừng, cho phép tạo ra một chuỗi các giá trị theo yêu cầu thay vì tính toán tất cả cùng một lúc.
  • Cách hoạt động: Khi yield được gọi, nó trả về một đối tượng có hai thuộc tính: value (giá trị được trả về bởi yield) và done (một boolean chỉ ra liệu hàm generator đã hoàn thành hay chưa). Việc thực thi của hàm generator tạm dừng tại yield và có thể được tiếp tục lại bằng cách gọi phương thức next().

Kết luận

  • await được sử dụng trong hàm async để làm cho việc xử lý các Promise trở nên dễ dàng hơn, giúp mã nguồn không đồng bộ trông giống như đồng bộ.
  • yield được sử dụng trong hàm generator để tạo ra một chuỗi các giá trị theo yêu cầu, cho phép tạm dừng và tiếp tục việc thực thi của hàm.

Cả hai từ khóa đều mang lại lợi ích trong việc xử lý các tác vụ không đồng bộ và tạo ra dữ liệu theo yêu cầu, nhưng chúng được sử dụng trong các ngữ cảnh và mục đích khác nhau.

81

Ouput đoạn code sau là gì và giải thích tại sao?

javascript
function getPersonInfo(one, two, three) {
  console.log(one);
  console.log(two);
  console.log(three);
}

const person = "Lydia";
const age = 21;

getPersonInfo`${person} is ${age} years old`;
  • A: "Lydia" 21 ["", " is ", " years old"]
  • B: ["", " is ", " years old"] "Lydia" 21
  • C: "Lydia" ["", " is ", " years old"] 21

Đáp án: B

Nếu bạn dùng tagged template literals, giá trị của đối số đầu tiên luôn luôn là một mảng các string. Những đối số còn lại sẽ lấy giá trị từ biểu thức đưa vào!

82

Đoạn code dưới đây những chỗ nào không hợp lệ?

javascript
  const bird = {
    size: "small",
  };

  const mouse = {
    name: "Mickey",
    small: true,
  };
  • A: mouse.bird.size không hợp lệ
  • B: mouse[bird.size] không hợp lệ
  • C: mouse[bird["size"]] không hợp lệ
  • D: Tất cả đều hợp lệ

Đáp án: A

Trong JavaScript thì tất cả keys của các object đều là string (ngoại trừ khi nó là một Symbol). Dù chúng ta không viết chúng như một string, về cơ bản chúng sẽ luôn được chuyển sang dạng string.

JavaScript thông dịch (hay unboxes) từng câu lệnh. Khi chúng ta sử dụng cặp dấu ngoặc [], nó sẽ tìm kiếm dấu mở ngoặc đầu tiên [, và sẽ tiếp tục tìm kiếm cho tới khi gặp dấu đóng ngoặc ]. Chỉ khi đó thì câu lệnh mới được thực thi.

mouse[bird.size]: Giá trị đầu tiên bird.size"small". mouse["small"] sẽ trả về true

Tuy nhiên, khi chúng ta sử dụng dấu chấm ., điều trên không còn đúng nữa. mouse không hề có key nào tên là bird, có nghĩa mouse.bird sẽ là undefined. Sau đó chúng ta gọi size sử dụng chấm .: mouse.bird.size. Vì mouse.birdundefined, lời gọi sẽ trở thành undefined.size. Đây là một lời gọi không hợp lệ, nó sẽ throw ra một lỗi kiểu như Cannot read property "size" of undefined.

83

Sự khác biệt giữa MapWeakMap trong Javascript là gì?

Trong ECMAScript 6 (ES6), MapWeakMap là hai cấu trúc dữ liệu mới được giới thiệu để lưu trữ cặp khóa-giá trị. Mặc dù cả hai đều lưu trữ cặp khóa-giá trị, chúng có một số sự khác biệt quan trọng:

Map

  1. Khóa mạnh: Trong một Map, khóa có thể là bất kỳ kiểu dữ liệu nào, bao gồm cả đối tượng, chuỗi, số, và các kiểu dữ liệu khác.
  2. Duyệt được: Map là duyệt được, có nghĩa là bạn có thể lặp qua các phần tử của nó theo thứ tự chèn vào.
  3. Kích thước: Bạn có thể dễ dàng lấy được kích thước của một Map thông qua thuộc tính size.
  4. Hiệu suất: Map có hiệu suất tốt trong việc thêm và xóa các phần tử.
  5. Bộ nhớ: Map giữ các khóa với một tham chiếu mạnh, điều này có nghĩa là miễn là một khóa tồn tại trong Map, nó sẽ không được thu gom rác (garbage collected).

WeakMap

  1. Khóa yếu: Trong một WeakMap, khóa phải là một đối tượng. Không thể sử dụng các kiểu dữ liệu nguyên thủy như chuỗi hoặc số làm khóa.
  2. Không duyệt được: WeakMap không phải là duyệt được, và không có phương thức hoặc thuộc tính nào để lấy hoặc kiểm tra toàn bộ nội dung của nó.
  3. Không có kích thước: WeakMap không có thuộc tính size và không có cách nào để xác định số lượng phần tử mà nó chứa.
  4. Bộ nhớ: WeakMap giữ các khóa với một tham chiếu yếu, có nghĩa là nếu không còn tham chiếu nào khác đến một đối tượng được sử dụng làm khóa, đối tượng đó có thể được thu gom rác. Điều này giúp WeakMap trở thành một lựa chọn tốt cho việc lưu trữ dữ liệu riêng tư hoặc dữ liệu tạm thời mà không ngăn chặn việc thu gom rác của các đối tượng khóa.

Kết luận

MapWeakMap đều hữu ích cho các trường hợp sử dụng khác nhau. Map thích hợp cho việc lưu trữ cặp khóa-giá trị mà bạn muốn duyệt qua hoặc khi bạn cần biết số lượng phần tử. WeakMap thích hợp cho các trường hợp mà bạn muốn tránh ngăn chặn việc thu gom rác của các đối tượng khóa, hoặc khi bạn không cần duyệt qua các phần tử và không quan tâm đến số lượng phần tử trong cấu trúc dữ liệu.

84

Kết quả đoạn code javascript sau là gì và giải thích tại sao?

javascript
  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);
  • A: Not the same!
  • B: They are the same!
  • C: ReferenceError
  • D: SyntaxError

Đá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.

85

Kết quả đoạn js code sau là gì? Hãy giải thích tại sao?

javascript
  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: 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.

86

Generator trong Javascript là gì?

Generator trong JavaScript là một loại hàm đặc biệt có thể tạm dừng và tiếp tục việc thực thi của nó. Các hàm generator được định nghĩa bằng cách thêm một dấu sao (*) ngay sau từ khóa function. Khi được gọi, hàm generator không thực thi ngay lập tức mà thay vào đó, nó trả về một đối tượng Generator mà qua đó việc thực thi có thể được kiểm soát.

Đặc điểm của Generator

  • Tạm dừng và tiếp tục: Generator có khả năng tạm dừng việc thực thi tại từ khóa yield và sau đó có thể tiếp tục từ điểm tạm dừng đó.
  • Trả về nhiều giá trị: Generator có thể trả về (yield) một chuỗi các giá trị, mỗi lần một giá trị, thông qua nhiều lần gọi.
  • Lười biếng (Lazy evaluation): Các giá trị được tạo ra bởi generator không cần phải tính toán trước, chúng chỉ được tính toán khi cần thiết, giúp tiết kiệm tài nguyên.

Cách sử dụng Generator

Để sử dụng một hàm generator, bạn cần:

  1. Định nghĩa hàm generator bằng cách thêm dấu sao (*) sau từ khóa function.
  2. Sử dụng từ khóa yield bên trong hàm để trả về giá trị.
  3. Gọi hàm generator để nhận được đối tượng Generator.
  4. Sử dụng phương thức next() trên đối tượng Generator để tiếp tục việc thực thi và nhận giá trị tiếp theo.

Ví dụ

javascript
function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numberGenerator(); // Tạo đối tượng Generator

console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Trong ví dụ trên, mỗi lần gọi phương thức next() trên đối tượng Generator sẽ nhận được giá trị tiếp theo từ hàm generator.

Kết luận

Generator là một công cụ mạnh mẽ trong JavaScript cho phép viết mã có khả năng tạm dừng và tiếp tục, tạo ra dữ liệu theo yêu cầu và thực hiện các tác vụ không đồng bộ một cách hiệu quả. Các hàm generator thường được sử dụng trong các tình huống cần xử lý các chuỗi dữ liệu lớn hoặc phức tạp, hoặc khi cần kiểm soát chặt chẽ quá trình thực thi của hàm.

87

Làm thế nào có thể ghi ra giá trị giống như trong comment khi console.log?

javascript
  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: 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.

88

Thuộc tính NaN trong JavaScript là gì?

Thuộc tính NaN trong JavaScript đại diện cho "Not-a-Number", tức là một giá trị không phải là một số hợp lệ. Dưới đây là một số điểm quan trọng về NaN:

  • NaN là một giá trị đặc biệt trong JavaScript, biểu thị một giá trị không xác định hoặc không thể biểu diễn được dưới dạng số
  • NaN là kết quả của một số phép toán không hợp lệ, chẳng hạn như phép chia 0 cho 0, lấy căn bậc hai của một số âm, hoặc cố gắng chuyển đổi một chuỗi không phải là số thành số
  • Mặc dù NaN có kiểu là number, nó không bằng bất kỳ giá trị số nào khác, kể cả chính nó. Điều này có nghĩa là NaN !== NaN trả về true
  • Để kiểm tra một giá trị có phải là NaN hay không, bạn không thể sử dụng toán tử so sánh bình thường. Thay vào đó, bạn nên sử dụng hàm Number.isNaN() hoặc hàm isNaN(). Number.isNaN() trả về true nếu giá trị đó là NaN và kiểu dữ liệu là Number, trong khi isNaN() chuyển đổi giá trị thành số trước khi kiểm tra
  • Trong JavaScript hiện đại, NaN là một thuộc tính không ghi được, không liệt kê được và không cấu hình được của đối tượng Number

Ví dụ, để kiểm tra một giá trị có phải là NaN:

javascript
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(123)); // false

NaN và cách xử lý nó đóng một vai trò quan trọng trong việc xác định và xử lý các tình huống lỗi hoặc không hợp lệ trong các phép toán số học trong JavaScript

89

Kết của hàm sum sau là gì? Hãy giải thích tại sao?

javascript
function sum(a, b) {
  return a + b;
}

sum(1, "2");
  • A: NaN
  • B: TypeError
  • C: "12"
  • D: 3

Đáp án: C

JavaScript là một ngôn ngữ dynamically typed: chúng ta không khai báo kiểu dữ liệu khi khai báo biến. Giá trị có thể bị tự động convert sang một kiểu dữ liệu khác mà ta không hề hay biết, điều này được gọi là implicit type coercion. Coercion có nghĩa là convert từ kiểu này sang kiểu khác.

Trong ví dụ này, JavaScript sẽ convert số 1 sang dạng string. Mỗi khi ta cộng một số (1) với một string ('2'), số sẽ luôn được xem như là một string. Kết quả sẽ là một phép nối chuỗi giống như "Hello" + "World", vậy nên "1" + "2" sẽ trả về là "12".

90

Symbol trong ES6 là gì?

Symbol là một loại đối tượng mới, đặc biệt, có thể được sử dụng như một tên thuộc tính duy nhất trong các đối tượng.

Sử dụng Symbol thay vì string cho phép các modules khác nhau tạo ra các thuộc tính không xung đột với nhau. Các symbols cũng có thể được đặt ở chế độ riêng tư, để các thuộc tính của chúng không thể bị truy cập bởi những ai chưa có quyền truy cập trực tiếp vào Symbol.

Symbol là một loại primitive mới. Cũng giống như các primitive khác như number, string và boolean, Symbol có một hàm được sử dụng để tạo chúng. Không giống như các primitive khác, Symbols không có literal syntax (Literal là một giá trị mà nó thể hiện chính nó, ví dụ: số 12 -- thể hiện là một giá trị kiểu number, hay “Javascript” -- đại diện cho một giá trị kiểu string) - cách duy nhất để tạo chúng là với hàm tạo Symbol theo cách sau:

js
let symbol = Symbol();

Trên thực tế, các Symbol chỉ là một cách hơi khác để gắn các thuộc tính vào một đối tượng - bạn có thể dễ dàng cung cấp các Symbol thường dùng dưới dạng các phương thức chuẩn, giống như Object.prototype.hasOwnProperty xuất hiện trong mọi thứ kế thừa từ Object.

91

Bạn biết những cách nào để lặp quả các phần tử của mảng trong Javascript?

Trong JavaScript, có nhiều cách để lặp qua các phần tử của một mảng. Dưới đây là một số phương pháp phổ biến:

Vòng lặp for

Đây là cách truyền thống nhất để lặp qua mảng, sử dụng chỉ số để truy cập từng phần tử:

javascript
const items = ['item1', 'item2', 'item3'];
for (let i = 0; i < items.length; i++) {
  console.log(items[i]);
}

Vòng lặp for...of

ES6 giới thiệu vòng lặp for...of cho phép lặp qua các phần tử của mảng một cách trực tiếp mà không cần sử dụng chỉ số:

javascript
const items = ['item1', 'item2', 'item3'];
for (const item of items) {
  console.log(item);
}

Phương thức forEach

Phương thức forEach cho phép bạn thực hiện một hàm lên mỗi phần tử của mảng:

javascript
const items = ['item1', 'item2', 'item3'];
items.forEach(item => {
  console.log(item);
});

Phương thức map

Phương thức map tạo ra một mảng mới từ việc áp dụng một hàm lên mỗi phần tử của mảng ban đầu:

javascript
const items = ['item1', 'item2', 'item3'];
const copy = items.map(item => item);

Phương thức filter

Phương thức filter tạo ra một mảng mới chứa các phần tử thỏa mãn điều kiện được định nghĩa trong hàm callback:

javascript
const numbers = [65, 44, 12, 4];
const filteredNumbers = numbers.filter(number => number > 20);

Phương thức reduce

Phương thức reduce thực hiện một hàm lên từng phần tử của mảng, kết quả được tích lũy lại và trả về một giá trị duy nhất:

javascript
const numbers = [65, 44, 12, 4];
const sum = numbers.reduce((total, number) => total + number, 0);

Vòng lặp whiledo...while

Các vòng lặp whiledo...while cũng có thể được sử dụng để lặp qua mảng, nhưng chúng ít được sử dụng hơn do cú pháp không tiện lợi bằng các phương pháp trên và thường không cung cấp cùng mức độ rõ ràng về ý định lặp qua mảng

92

JavaScript là ngôn ngữ kiểu tĩnh hay kiểu động? Hãy giải thích

JavaScript là một ngôn ngữ lập trình có kiểu động (dynamically typed). Điều này có nghĩa là kiểu dữ liệu của biến được xác định tại thời điểm thực thi chương trình dựa trên giá trị của biến tại thời điểm đó, không cần phải khai báo kiểu dữ liệu một cách rõ ràng khi viết mã

Trong JavaScript, bạn có thể gán bất kỳ loại giá trị nào cho một biến mà không cần xác định kiểu dữ liệu trước. Ví dụ, bạn có thể gán một số cho một biến, sau đó gán một chuỗi cho cùng một biến mà không gặp phải lỗi kiểu dữ liệu

Điều này khác biệt với các ngôn ngữ có kiểu tĩnh (statically typed) như Java hoặc C++, nơi bạn cần khai báo kiểu dữ liệu của biến ngay từ đầu và không thể thay đổi sau khi đã được xác định. Kiểu dữ liệu của biến trong các ngôn ngữ này được kiểm tra tại thời điểm biên dịch (compile time), trước khi chương trình được thực thi

93

Sử dụng Promise trong JavaScript như thế nào?

Trước promise, callback được dùng cho các thao tác bất đồng bộ. Nhưng callback có giới hạn của nó, nếu sử dụng quá nhiều callback code sẽ trở nên khó quản lý.

Đối tượng promise có 4 trạng thái:

  • Pending: trạng thái bắt đầu, biểu diễn promise không phải là fulfilled, cũng không phải là rejected mà đang ở trạng thái pending.
  • Fulfilled: trạng thái này có nghĩa là thao tác bất đồng bộ đã hoàn tất.
  • Rejected: trạng này này có nghĩa là thao tác đã thất bại vì một vài lý do nào đó.
  • Settked: trạng thái này thể hiện promise đã rejected hoặc fulfilled.

Một promise được tạo bằng cách sử dụng phương thức khởi tạo Promise, hàm này nhận một hàm callback với hai tham số, resolvereject tương ứng.

  • resolve hàm được gọi, khi thao tác bất đồng bộ thực hiện thành công.
  • reject hàm được gọi, khi thao tác thất bại bởi một vài lỗi nào đó.

Ví dụ:

Promise được dùng cho các thao tác bất đồng bộ như yêu cầu của server, để dễ hiểu ta lấy ví dụ với một phép toán để tính tổng của ba phần tử.

js
function sumOfThreeElements(...elements){
  return new Promise((resolve,reject)=>{
    if(elements.length > 3 ){
      reject("Only three elements or less are allowed");
    }
    else{
      let sum = 0;
      let i = 0;
      while(i < elements.length){
        sum += elements[i];
        i++;
      }
      resolve("Sum has been calculated: "+sum);
    }
  })
}

Trong đoạn code trên, ta đang tính tổng của ba phần tử, nếu độ dài của mảng phần tử lớn hơn 3, thì promise sẽ bị rejected, ngược lại thì promise sẽ được resolved và tổng được trả về.

Chúng ta có thể sử dụng bất kỳ promise nào bằng cách gắn các phương thức then()catch() vào đối tượng sử dụng.

  • then() phương này được truy cập khi kết quả của promise là fulfilled.
  • catch() phương này được truy cập khi kết quả của promise là rejected.

Ví dụ:

js
sumOfThreeElements(4, 5, 6)
.then(result=> console.log(result))
.catch(error=> console.log(error));
// In the code above, the promise is fulfilled so the then() method gets executed

sumOfThreeElements(7, 0, 33, 41)
.then(result => console.log(result))
.catch(error=> console.log(error));
// In the code above, the promise is rejected hence the catch() method gets executed
94

So sánh sự khác nhau giữa Object.freeze()const trong Javascript là gì?

Trong JavaScript, Object.freeze()const đều được sử dụng để hạn chế sự thay đổi của dữ liệu, nhưng chúng hoạt động theo những cách khác nhau và phục vụ cho các mục đích khác nhau.

Object.freeze()

  • Mục đích: Object.freeze() được sử dụng để làm cho một đối tượng trở nên bất biến. Khi một đối tượng được "đóng băng" bằng Object.freeze(), bạn không thể thêm, xóa, hoặc thay đổi bất kỳ thuộc tính nào của đối tượng đó
  • Độ sâu: Object.freeze() chỉ áp dụng một "shallow freeze", nghĩa là nó chỉ đóng băng các thuộc tính ở cấp độ ngoài cùng của đối tượng. Đối với các đối tượng lồng nhau, bạn cần áp dụng Object.freeze() một cách đệ quy để đóng băng toàn bộ cấu trúc đối tượng

const

  • Mục đích: const được sử dụng để khai báo một biến với giá trị không thể được gán lại sau khi đã được khởi tạo. Tuy nhiên, nếu giá trị đó là một đối tượng, các thuộc tính bên trong đối tượng vẫn có thể được thay đổi
  • Phạm vi ứng dụng: const áp dụng cho việc gán lại biến chứ không phải tính bất biến của giá trị mà biến đó tham chiếu. Điều này có nghĩa là const ngăn chặn việc gán một đối tượng hoặc giá trị mới cho biến, nhưng không ngăn chặn việc thay đổi nội dung của một đối tượng

Khi nào sử dụng

  • Sử dụng Object.freeze() khi bạn muốn đảm bảo rằng một đối tượng không thể bị thay đổi, bao gồm cả việc thêm, xóa, hoặc chỉnh sửa các thuộc tính của nó. Điều này hữu ích trong việc tạo ra các đối tượng bất biến trong ứng dụng của bạn
  • Sử dụng const khi bạn muốn khai báo một biến mà giá trị của nó không được phép thay đổi qua việc gán lại. Tuy nhiên, nếu biến đó tham chiếu đến một đối tượng, hãy nhớ rằng các thuộc tính bên trong đối tượng vẫn có thể thay đổi trừ khi bạn cũng sử dụng Object.freeze() để đóng băng đối tượng

Tóm lại, Object.freeze()const đều có vai trò trong việc tạo ra và bảo vệ tính bất biến trong JavaScript, nhưng chúng hoạt động ở các cấp độ khác nhau và không thể thay thế cho nhau. Object.freeze() ngăn chặn sự thay đổi của đối tượng, trong khi const ngăn chặn việc gán lại biến.

95

Ouput đoạn code javascript sau là gì và hãy giải thích tại sao?

javascript
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const lydia = new Person("Lydia", "Hallie");
const sarah = Person("Sarah", "Smith");

console.log(lydia);
console.log(sarah);
  • A: Person {firstName: "Lydia", lastName: "Hallie"}undefined
  • B: Person {firstName: "Lydia", lastName: "Hallie"}Person {firstName: "Sarah", lastName: "Smith"}
  • C: Person {firstName: "Lydia", lastName: "Hallie"}{}
  • D:Person {firstName: "Lydia", lastName: "Hallie"}ReferenceError

Đáp án: A

Với sarah, chúng ta khai báo mà không có từ khóa new. Khi sử dụng new, nó sẽ trỏ đến một object mới mà ta vừa tạo ra. Tuy nhiên nếu ta không dùng new thì nó sẽ trỏ tới global object!

Chúng ta cho rằng this.firstName"Sarah"this.lastName"Smith". Tuy nhiên sự thực là chúng ta đã định nghĩa global.firstName = 'Sarah'global.lastName = 'Smith'. Bản thân biến sarah vẫn là undefined.

96

Làm sao để sao chép (clone) một mảng trong Javascript?

Trong JavaScript, có nhiều cách để clone một mảng, mỗi cách có những ưu và nhược điểm riêng. Dưới đây là một số phương pháp phổ biến:

1. Sử dụng Spread Operator (...)

Spread Operator cho phép bạn sao chép tất cả các phần tử của một mảng vào một mảng mới.

javascript
const originalArray = [1, 2, 3];
const clonedArray = [...originalArray];

2. Sử dụng Phương thức slice()

Phương thức slice() trả về một bản sao của một phần của mảng vào một mảng mới. Khi không truyền tham số, slice() sẽ sao chép toàn bộ mảng.

javascript
const originalArray = [1, 2, 3];
const clonedArray = originalArray.slice();

3. Sử dụng Phương thức Array.from()

Array.from() tạo một mảng mới từ một đối tượng có thể lặp lại hoặc giống mảng.

javascript
const originalArray = [1, 2, 3];
const clonedArray = Array.from(originalArray);

4. Sử dụng Phương thức concat()

Phương thức concat() được sử dụng để hợp nhất hai hoặc nhiều mảng. Khi sử dụng concat() với một mảng rỗng, nó sẽ tạo ra một bản sao của mảng.

javascript
const originalArray = [1, 2, 3];
const clonedArray = [].concat(originalArray);

5. Sử dụng Phương thức map()

Phương thức map() tạo một mảng mới với kết quả của việc gọi một hàm cho mỗi phần tử mảng. Khi sử dụng map() chỉ để trả về chính phần tử đó, bạn sẽ nhận được một bản sao của mảng.

javascript
const originalArray = [1, 2, 3];
const clonedArray = originalArray.map(item => item);

6. Sử dụng Phương thức reduce()

Phương thức reduce() thực hiện một hàm reducer trên mỗi phần tử của mảng, kết quả là một giá trị duy nhất. Để clone mảng, bạn có thể sử dụng reduce() để thêm từng phần tử vào một mảng mới.

javascript
const originalArray = [1, 2, 3];
const clonedArray = originalArray.reduce((acc, item) => {
  acc.push(item);
  return acc;
}, []);

7. Sử dụng Phương thức forEach() kết hợp push()

Bạn cũng có thể sử dụng vòng lặp forEach() để duyệt qua mỗi phần tử của mảng gốc và push() chúng vào một mảng mới.

javascript
const originalArray = [1, 2, 3];
let clonedArray = [];
originalArray.forEach(item => clonedArray.push(item));

Mỗi phương pháp trên có thể được sử dụng tùy thuộc vào tình huống cụ thể và yêu cầu về hiệu suất hoặc độ phức tạp của code

97

Đoạn code sau có xảy ra lỗi gì không? Hãy giải thích tại sao?

javascript
  function bark() {
    console.log("Woof!");
  }

  bark.animal = "dog";
  • A: Hoàn toàn không có vấn đề gì!
  • B: SyntaxError. Bạn không thể thêm thuộc tính theo cách này.
  • C: undefined
  • D: ReferenceError

Đáp án: A

Điều này là có thể với Javascript, bởi vì function cũng chỉ là object mà thôi! (Mọi primitive types đều là object)

Function là một object đặc biệt. Phần code mà bạn viết không phải là function thực tế đâu. Function ở đây chính là một object với các thuộc tính. Và các thuộc tính này có thể gọi được.

98

Sự khác nhau giữa anonymous functionnamed function trong Javascript là gì?

Trong JavaScript, hàm (function) là một khối mã có thể được gọi nhiều lần. Hàm có thể được phân loại thành anonymous function (hàm ẩn danh) và named function (hàm có tên) dựa trên việc chúng có được đặt tên hay không.

anonymous function

  • Định nghĩa: Một anonymous function là một hàm không có tên. Hàm này thường được sử dụng trong các biểu thức hoặc khi được truyền như một đối số cho hàm khác.
  • Cách khai báo:
    javascript
    let myFunction = function() {
      // Mã hàm ở đây
    };
  • Đặc điểm:
    • Không có tên, không thể gọi lại chính nó một cách dễ dàng từ bên trong nó mà không sử dụng một biến tham chiếu.
    • Thường được sử dụng cho các hàm callback hoặc khi định nghĩa một hàm mà không cần tái sử dụng.

named function

  • Định nghĩa: Một named function là một hàm có tên rõ ràng, có thể được sử dụng để gọi lại chính nó hoặc từ các phần khác của mã.
  • Cách khai báo:
    javascript
    function myFunction() {
      // Mã hàm ở đây
    }
  • Đặc điểm:
    • Có tên, có thể gọi lại chính nó và dễ dàng được xác định trong stack trace, giúp việc debug trở nên dễ dàng hơn.
    • Thường được sử dụng khi cần định nghĩa một hàm có thể tái sử dụng ở nhiều nơi trong mã.

Sự khác biệt chính

  • Tính tái sử dụng: named function dễ dàng tái sử dụng và gọi lại, trong khi anonymous function thường được sử dụng một lần hoặc được gán cho một biến.
  • Stack trace: Khi xảy ra lỗi, named function sẽ hiển thị tên trong stack trace, giúp việc debug trở nên dễ dàng hơn. anonymous function thường không có tên trong stack trace, có thể gây khó khăn khi debug.
  • Khả năng tự gọi: named function có thể tự gọi lại mình mà không cần tham chiếu đến một biến, trong khi anonymous function cần một biến tham chiếu để gọi lại chính nó.

Kết luận

anonymous functionnamed function đều có vai trò quan trọng trong JavaScript, tùy thuộc vào ngữ cảnh sử dụng và yêu cầu cụ thể của mã. anonymous function thích hợp cho các hàm callback và các trường hợp không cần tái sử dụng, trong khi named function tốt cho việc tái sử dụng và debug.

99

Kết quả của đoạn code javascript sau là gì và giải thích tại sao?

javascript
function getAge() {
  "use strict";
  age = 21;
  console.log(age);
}

getAge();
  • A: 21
  • B: undefined
  • C: ReferenceError
  • D: TypeError

Đáp án: C

Với "use strict", chúng ta sẽ đảm bảo được rằng ta sẽ không bao giờ khai báo biến global một cách vô ý. Tại đây chúng ta chưa khai báo biến age, và khi dùng "use strict", nó sẽ throw ra một reference error. Nếu như không dùng "use strict", nó sẽ vẫn hoạt động, vì thuộc tính age sẽ được thêm vào global object.

100

Giải thích chính sách same-origin trong JavaScript?

Same-origin policy (SOP) là một trong những chính sách bảo mật quan trọng nhất trên trình duyệt hiện đại, nhằm ngăn chặn JavaScript code có thể tạo ra những request đến những nguồn khác với nguồn mà nó được trả về. Ba tiêu chí chính để so sánh request bao gồm:

  • Domain (tên miền)
  • Protocol (giao thức)
  • Port (cổng kết nối)

Nói đơn giản thì request sẽ được coi là hợp lệ chỉ khi nó thỏa mãn 3 tiêu chí ở trên (cùng domain,cùng protocol và cùng port)

Giải thích chính sách same-origin trong JavaScript

Ví dụ: khi chúng ta đang mở 2 tab, 1 tab là facebook, tab kia là 1 trang web nào đó có chứa mã độc. Sẽ rất nguy hiểm nếu như các đoạn script ở bên tab chứa mã độc có thể tự do thao tác lên tab facebook phía bên kia, và SOP sinh ra với nhiệm vụ ngăn chặn các hành động này.

Dưới đây là vd về list các pages vi phạm SOP của site origin( http://www.example.com) :

101

Bạn biết những cách nào để lặp qua các thuộc tính đối tượng trong Javascript?

Trong JavaScript, có ba cách chính để lặp qua các thuộc tính của một đối tượng:

  1. Vòng lặp for...in: Lặp qua tất cả các thuộc tính có thể liệt kê của một đối tượng, bao gồm cả các thuộc tính được kế thừa từ prototype

    javascript
    for (let key in object) {
        if (object.hasOwnProperty(key)) {
            console.log(key, object[key]);
        }
    }
  2. Phương thức Object.keys(obj): Trả về một mảng chứa tất cả các khóa (keys) của các thuộc tính có thể liệt kê của một đối tượng (không bao gồm các thuộc tính từ prototype). Bạn có thể kết hợp với forEach để lặp qua các khóa này

    javascript
    Object.keys(object).forEach(function(key) {
        console.log(key, object[key]);
    });
  3. Phương thức Object.getOwnPropertyNames(obj): Tương tự như Object.keys nhưng trả về tất cả các thuộc tính của đối tượng, kể cả những thuộc tính không thể liệt kê.

    javascript
    Object.getOwnPropertyNames(object).forEach(function(key) {
        console.log(key, object[key]);
    });

Ngoài ra, từ ES2017 trở đi, bạn cũng có thể sử dụng:

  1. Phương thức Object.entries(obj): Trả về một mảng chứa các cặp khóa-giá trị của các thuộc tính có thể liệt kê của một đối tượng. Mỗi cặp khóa-giá trị là một mảng với hai phần tử: khóa và giá trị.

    javascript
    Object.entries(object).forEach(([key, value]) => {
        console.log(key, value);
    });
  2. Phương thức Object.values(obj): Trả về một mảng chứa giá trị của tất cả các thuộc tính có thể liệt kê của một đối tượng

    javascript
    Object.values(object).forEach(function(value) {
        console.log(value);
    });

Lưu ý rằng khi sử dụng for...in, bạn nên kiểm tra xem thuộc tính có phải là thuộc tính riêng của đối tượng (không phải từ prototype) bằng cách sử dụng hasOwnProperty để tránh lặp qua các thuộc tính không mong muốn.

102

Đoạn code javascript sau có xảy ra lỗi không? Hãy giải thích tại sao?

javascript
  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: 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'.

103

Kết quả của đoạn code javascript sau là gì? Và hãy giải thích tại sao?

javascript
  class Chameleon {
    static colorChange(newColor) {
      this.newColor = newColor;
      return this.newColor;
    }

    constructor({ newColor = "green" } = {}) {
      this.newColor = newColor;
    }
  }

  const freddie = new Chameleon({ newColor: "purple" });
  freddie.colorChange("orange");
  • A: orange
  • B: purple
  • C: green
  • D: TypeError

Đáp án: D

Hàm colorChange là một hàm static (hàm tĩnh). Hàm static được thiết kế để chỉ để tồn tại ở mức class, và không thể truyền cho bất cứ instance con nào. Vì freddie là một instance con, hàm static này sẽ không được truyền xuống, và do đó không thể gọi được tại freddie instance: nó sẽ throw ra một TypeError.

104

Kết quả đoạn code sau là gì? Và hãy giải thích tại sao?

javascript
function checkAge(data) {
  if (data === { age: 18 }) {
    console.log("You are an adult!");
  } else if (data == { age: 18 }) {
    console.log("You are still an adult.");
  } else {
    console.log(`Hmm.. You don't have an age I guess`);
  }
}

checkAge({ age: 18 });
  • A: You are an adult!
  • B: You are still an adult.
  • C: Hmm.. You don't have an age I guess

Đáp án: C

Khi test sự bằng nhau, các kiểu dữ liệu cơ bản sẽ so sánh giá trị của chúng, còn object thì so sánh tham chiếu. JavaScript sẽ kiểm tra xem các object đó có trỏ đến những vùng nhớ giống nhau hay không.

Hai object chúng ta đang so sánh không có được điều đó: object đối số tham chiếu đến một vùng nhớ khác với object chúng ta dùng để kiểm tra sự bằng nhau.

Đó là lý do tại sao cả { age: 18 } === { age: 18 }{ age: 18 } == { age: 18 } đều trả về false.

105

Kết quả đoạn code sau là gì? Hãy giải thích tại sao?

javascript
  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 }.

106

Bạn có thể so sánh việc sử dụng Module Pattern với Constructor/Prototype Pattern không?

Mô hình Module và Mô hình Constructor/Prototype là hai cách phổ biến để cấu trúc mã JavaScript, mỗi mô hình có những ưu và nhược điểm riêng biệt tùy thuộc vào nhu cầu cụ thể của dự án.

Mô hình Module

Mô hình Module sử dụng các hàm tự gọi (IIFE - Immediately Invoked Function Expression) để tạo ra một không gian tên riêng biệt, giúp che giấu các biến và hàm riêng tư khỏi phạm vi toàn cục. Điều này giúp tránh xung đột tên biến và tạo ra một cấu trúc mã rõ ràng, dễ bảo trì.

Ưu điểm:

  • Che giấu thông tin: Cung cấp khả năng che giấu dữ liệu và hàm, giúp tạo ra các biến và hàm riêng tư.
  • Giảm thiểu xung đột tên biến: Bằng cách giới hạn phạm vi của biến và hàm trong một hàm IIFE, giảm thiểu nguy cơ xung đột tên biến.

Nhược điểm:

  • Hiệu suất: Mỗi lần sử dụng mô hình module để tạo một đối tượng, tất cả các hàm đều được tạo lại, có thể ảnh hưởng đến hiệu suất nếu tạo nhiều thực thể.

Mô hình Constructor/Prototype

Mô hình Constructor/Prototype sử dụng các hàm constructor và prototype để tạo và kế thừa các đối tượng. Điều này cho phép tái sử dụng mã và tối ưu hóa bộ nhớ bằng cách chia sẻ các hàm giữa các thực thể của cùng một lớp thông qua prototype.

Ưu điểm:

  • Tái sử dụng mã: Các hàm được định nghĩa một lần trên prototype và được tái sử dụng cho mọi thực thể của lớp, giúp tối ưu hóa bộ nhớ.
  • Kế thừa: Hỗ trợ kế thừa, cho phép tạo các lớp con từ lớp cha và tái sử dụng mã.

Nhược điểm:

  • Phức tạp khi quản lý kế thừa: Việc quản lý kế thừa và chuỗi prototype có thể trở nên phức tạp, đặc biệt là trong các hệ thống lớn.
  • Khó hiểu cho người mới: Cú pháp và cách hoạt động của prototype có thể khó hiểu cho những người mới học JavaScript.

Tóm lại, việc lựa chọn giữa Mô hình Module và Mô hình Constructor/Prototype phụ thuộc vào yêu cầu cụ thể của dự án và sở thích cá nhân. Mô hình Module phù hợp cho việc tạo các module độc lập với khả năng che giấu thông tin, trong khi Mô hình Constructor/Prototype phù hợp cho việc tạo các đối tượng có cấu trúc kế thừa và tái sử dụng mã.

107

Giải thích sự khác biệt giữa function Person(){}, var person = Person(), và var person = new Person() là gì?

Sự khác biệt giữa function Person(){}, var person = Person(), và var person = new Person() là như sau:

  1. function Person(){}: Đây là một khai báo hàm bình thường. Cách viết này tạo ra một hàm có tên là Person. Theo quy ước, việc sử dụng PascalCase (viết hoa chữ cái đầu tiên của mỗi từ) cho tên hàm như vậy ám chỉ rằng hàm này được thiết kế để sử dụng như một hàm tạo (constructor function). Hàm tạo là một hàm đặc biệt được sử dụng để tạo ra các đối tượng mới.
  2. var person = Person(): Câu lệnh này gọi hàm Person như một hàm bình thường và gán giá trị trả về của hàm cho biến person. Nếu hàm Person được thiết kế để sử dụng như một hàm tạo nhưng lại được gọi mà không sử dụng từ khóa new, thì đây là một sai lầm phổ biến. Trong trường hợp này, thay vì tạo ra một đối tượng mới, hàm sẽ trả về undefined và giá trị này sẽ được gán cho biến person. Điều này dẫn đến việc không thể truy cập các thuộc tính của đối tượng như person.nameperson sẽ không phải là một đối tượng.
  3. var person = new Person(): Câu lệnh này sử dụng từ khóa new để gọi hàm Person như một hàm tạo. Khi sử dụng new, JavaScript sẽ tạo một đối tượng mới, sau đó gọi hàm Person với this được thiết lập để tham chiếu đến đối tượng mới đó. Hàm Person có thể gán các thuộc tính cho đối tượng thông qua this. Đối tượng mới sau đó sẽ được trả về và gán cho biến person. Điều này cho phép tạo ra các đối tượng có các thuộc tính và phương thức được định nghĩa trong hàm tạo Person.

Như vậy, sự khác biệt chính giữa ba cách sử dụng này là: function Person(){} tạo ra một hàm tạo, var person = Person() gọi hàm đó như một hàm bình thường và có thể dẫn đến việc không tạo ra đối tượng mới như mong đợi, trong khi var person = new Person() sử dụng hàm tạo đúng cách để tạo ra một đối tượng mới với các thuộc tính và phương thức được định nghĩa trong hàm tạo.

108

Polyfill trong JavaScript là gì?

Polyfill trong JavaScript là một đoạn mã (thường là JavaScript trên Web) được sử dụng để cung cấp các chức năng hiện đại trên các trình duyệt cũ không hỗ trợ natively các chức năng đó. Ví dụ, một polyfill có thể được sử dụng để mô phỏng chức năng của text-shadow trong IE7 bằng cách sử dụng các bộ lọc độc quyền của IE, hoặc mô phỏng các đơn vị rem hoặc media queries bằng cách sử dụng JavaScript để điều chỉnh động cách trình bày phù hợp, hoặc bất kỳ nhu cầu nào khác mà bạn yêu cầu.

Polyfill không được sử dụng độc quyền vì chức năng tốt hơn và hiệu suất tốt hơn. Các triển khai native của API có thể làm nhiều hơn và nhanh hơn so với polyfills. Ví dụ, polyfill của Object.create chỉ chứa các chức năng có thể thực hiện trong một triển khai không native của Object.create.

Polyfill cũng được sử dụng để giải quyết các vấn đề nơi các trình duyệt thực hiện cùng một tính năng theo các cách khác nhau. Polyfill sử dụng các tính năng không chuẩn trong một trình duyệt cụ thể để cung cấp cho JavaScript một cách tiếp cận tuân thủ chuẩn để truy cập vào tính năng đó.

Polyfill có thể được coi như một vật liệu lấp đầy các khoảng trống và nứt để làm mịn bất kỳ khuyết điểm nào. Nó cung cấp các chức năng và tính năng mà nhà phát triển mong đợi trình duyệt cung cấp theo mặc định.

Polyfill và các tính năng của nó: Polyfill không giới hạn chỉ được thực hiện bằng JavaScript, và "fill" sẽ lấp đầy khoảng trống trong trình duyệt nơi công nghệ cần thiết.

109

Tại sao, việc để nguyên phạm vi toàn cục của một trang web và không can thiệp vào nó lại là một ý tưởng tốt?

Việc giữ nguyên phạm vi toàn cục của một trang web và không can thiệp vào nó được coi là một ý tưởng tốt vì nhiều lý do:

  1. Tránh xung đột tên biến: Khi sử dụng biến toàn cục, rất dễ xảy ra tình trạng xung đột tên biến, nơi hai biến khác nhau cùng sử dụng một tên. Điều này có thể dẫn đến việc ghi đè dữ liệu không mong muốn và gây ra lỗi trong chương trình.
  2. Khó đọc và lý giải mã nguồn: Khi biến xuất hiện một cách bất ngờ từ phạm vi toàn cục, việc đọc và lý giải mã nguồn trở nên khó khăn hơn. Điều này làm giảm khả năng bảo trì và cập nhật mã nguồn.
  3. Rủi ro về bảo mật: Biến toàn cục có thể được truy cập và thay đổi từ bất kỳ đâu trong chương trình, điều này tạo ra rủi ro về bảo mật khi dữ liệu nhạy cảm có thể bị thay đổi hoặc lộ ra ngoài.
  4. Khó kiểm soát thay đổi: Khi một biến toàn cục có thể được thay đổi từ bất kỳ nơi nào, việc theo dõi và kiểm soát các thay đổi trở nên khó khăn. Điều này có thể dẫn đến các lỗi khó tìm và sửa.
  5. Giảm hiệu suất: Việc sử dụng biến toàn cục có thể làm giảm hiệu suất của ứng dụng do trình duyệt phải tìm kiếm biến trong một phạm vi lớn hơn.
  6. Làm giảm tính tái sử dụng mã: Các hàm phụ thuộc vào biến toàn cục trở nên khó tái sử dụng trong các dự án khác hoặc trong các phần khác của cùng một dự án do chúng gắn liền với trạng thái toàn cục cụ thể.
  7. Gây khó khăn trong việc kiểm thử và gỡ lỗi: Khi hành vi và kết quả của các hàm phụ thuộc vào giá trị của biến toàn cục, việc kiểm thử và gỡ lỗi trở nên khó khăn hơn do giá trị của biến toàn cục có thể thay đổi.

Tóm lại, việc tránh can thiệp vào phạm vi toàn cục của một trang web giúp tránh xung đột tên biến, làm cho mã nguồn dễ đọc và lý giải hơn, giảm rủi ro về bảo mật, kiểm soát thay đổi dễ dàng hơn, cải thiện hiệu suất, tăng tính tái sử dụng mã và làm cho việc kiểm thử và gỡ lỗi trở nên dễ dàng hơn.

110

Giải thích sự khác biệt về cách sử dụng foo giữa function foo() {}var foo = function() {} là gì?

Sự khác biệt chính giữa function foo() {} (Function Declaration) và var foo = function() {} (Function Expression) liên quan đến hoisting và cách chúng được tải trong JavaScript.

  1. Hoisting: Function Declaration (function foo() {}) được hoisted, nghĩa là nó được đưa lên đầu phạm vi (scope) trước khi bất kỳ mã nào được thực thi. Điều này cho phép bạn gọi hàm trước khi nó được định nghĩa trong mã nguồn. Ngược lại, Function Expression (var foo = function() {}) không được hoisted. Bạn không thể gọi hàm này trước khi nó được định nghĩa vì biến foo chỉ được hoisted như một biến chưa được khởi tạo, không phải là hàm.
  2. Thời điểm đánh giá: Với Function Declaration, hàm được đánh giá và có sẵn để sử dụng ngay khi chương trình bắt đầu chạy, do hoisting. Trong khi đó, Function Expression chỉ được đánh giá khi dòng mã chứa nó được thực thi. Điều này có nghĩa là bạn chỉ có thể sử dụng Function Expression sau khi nó được định nghĩa trong mã nguồn.
  3. Phạm vi sử dụng: Function Declaration tạo ra một hàm có thể được gọi trong phạm vi mà nó được định nghĩa. Function Expression, khi được gán cho một biến, cho phép bạn sử dụng hàm như một giá trị, có thể được truyền như một đối số cho hàm khác, được gán cho một thuộc tính của đối tượng, hoặc thậm chí được trả về từ một hàm khác.

Tóm lại, sự khác biệt giữa hai cách khai báo hàm này chủ yếu liên quan đến hoisting và thời điểm đánh giá của chúng trong quá trình thực thi mã JavaScript.

111

So sánh việc sử dụng Async/AwaitGenerators để đạt được cùng một chức năng trong javascript?

Trong JavaScript, cả Async/AwaitGenerators đều là các công cụ giúp xử lý các tác vụ không đồng bộ, nhưng chúng có cách tiếp cận và cú pháp khác nhau.

  1. Async/Await:

    • Được giới thiệu trong ECMAScript 2017 (ES8), Async/Await là một cách tiếp cận mới và đơn giản hơn để xử lý các tác vụ không đồng bộ, dựa trên Promises.
    • Một hàm được khai báo với từ khóa async sẽ luôn trả về một Promise. Khi bạn gọi một hàm async, bạn có thể sử dụng await bên trong hàm đó để "chờ" một Promise được giải quyết hoặc bị từ chối, trước khi tiếp tục thực thi các dòng code tiếp theo.
    • Async/Await làm cho code bất đồng bộ trông giống như đồng bộ và dễ đọc, dễ bảo trì hơn.
  2. Generators:

    • Được giới thiệu trong ECMAScript 2015 (ES6), Generators là các hàm có thể tạm dừng và tiếp tục lại, cho phép chúng ta tạo ra các chuỗi giá trị (hoặc các tác vụ không đồng bộ) theo cách linh hoạt hơn.
    • Một Generator được định nghĩa bằng cách thêm dấu * sau từ khóa function và sử dụng từ khóa yield bên trong hàm để tạm dừng việc thực thi.
    • Để xử lý các tác vụ không đồng bộ với Generators, chúng ta thường kết hợp chúng với Promises hoặc thư viện bổ trợ như co, tạo ra một cơ chế tương tự như Async/Await nhưng phức tạp hơn về cú pháp và cách tiếp cận.

So sánh:

  • Async/Await cung cấp một cách tiếp cận đơn giản và trực quan hơn để xử lý các tác vụ không đồng bộ, làm cho code dễ đọc và dễ bảo trì hơn.
  • Generators cung cấp sự linh hoạt cao hơn trong việc kiểm soát quá trình thực thi của hàm, nhưng cách tiếp cận này đòi hỏi phải hiểu rõ về cách thức hoạt động của GeneratorsPromises, cũng như cần thêm bước kết hợp với các thư viện bổ trợ để xử lý các tác vụ không đồng bộ một cách hiệu quả.

Tóm lại, Async/AwaitGenerators đều có thể được sử dụng để đạt được cùng một chức năng trong việc xử lý các tác vụ không đồng bộ trong JavaScript, nhưng Async/Await thường được ưa chuộng hơn vì tính đơn giản và dễ đọc của nó

112

Giải thích về phép gán qua giá trị và phép gán qua tham chiếu trong Javascript?

Trong JavaScript, kiểu dữ liệu nguyên thuỷ được gán với giá trị, còn kiểu đối tượng được gán bằng tham chiếu.

Trước tiên, ta cần hiểu về điều gì xảy ra khi ta tạo một biến và gán giá trị cho nó.

js
var x = 2;

Trong ví dụ trên, ta tạo một biến x và gán nó giá trị là "2". Phép "=" chỉ định một vài không gian trong bộ nhớ, để lưu trữ giá trị là "2" và trả về vị trí được chỉ định trong bộ nhớ. Do đó, biến x ở trên trỏ đến vị trí trong bộ nhớ thay vì trỏ trực tiếp đến giá trị 2.

Phép gán thực hiện hành vi khác nhau khi làm việc với kiểu nguyên thuỷ và kiểu đối tượng.

Phép gán với kiểu nguyên thuỷ

Phép gán với kiểu nguyên thuỷ
js
var y = 234;
var z = y;

Ở ví dụ này, dòng đầu phép gán giá trị cho y là kiểu nguyên thuỷ, sau đó ở dòng thứ hai, giá trị của y được gán cho z. Phép gán chỉ định một vùng không gian mới trong bộ nhớ và trả về địa chỉ của nó. Do đó, biến z không chỉ đến vị trí của biến y thay vào đó nó chỉ đến vùng không gian mới trong bộ nhớ.

js
var y = #8454; // y pointing to address of the value 234

var z = y;

var z = #5411; // z pointing to a completely new address of the value 234

// Changing the value of y
y = 23;
console.log(z);  // Returns 234, since z points to a new address in the memory so changes in y will not effect z

Từ ví dụ trên, ta có thể thấy rằng các kiểu dữ liệu nguyên thủy khi được truyền cho một biến khác sẽ được truyền theo giá trị. Thay vì chỉ gán cùng một địa chỉ cho một biến khác, giá trị sẽ được gán và không gian bộ nhớ mới được tạo ra.

Phép gán với kiểu đối tượng

Phép gán với kiểu đối tượng
js
var obj = { name: "Vivek", surname: "Bisht" };

var obj2 = obj;

Trong ví dụ trên, phép gán truyền trực tiếp vị trí của biến obj đến biến obj2. Nói cách khác, tham chiếu của biến obj được chuyển cho biến obj2.

js
var obj = #8711;  // obj pointing to address of { name: "Vivek", surname: "Bisht" }

var obj2 = obj;

var obj2 = #8711; // obj2 pointing to the same address

// changing the value of obj1

obj1.name = "Akki";

console.log(obj2);

// Returns {name:"Akki", surname:"Bisht"} since both the variables are pointing to the same address.

Từ ví dụ trên, ta có thể thấy rằng trong khi truyền các kiểu dữ liệu đối tượng, phép gán trực tiếp truyền địa chỉ (tham chiếu).

Do đó, các kiểu dữ liệu đối tượng luôn được truyền bằng tham chiếu.

113

Giải thích Function.prototype.bind trong javascript?

Function.prototype.bind trong JavaScript là một phương thức cho phép bạn tạo ra một hàm mới từ một hàm hiện có, với một số điều chỉnh về ngữ cảnh this và các tham số truyền vào hàm. Khi bạn gọi phương thức bind trên một hàm, bạn có thể xác định giá trị của this cho hàm mới đó, cũng như truyền trước một số tham số mà hàm sẽ nhận khi nó được gọi.

Cú pháp của Function.prototype.bind là:

javascript
var boundFunction = originalFunction.bind(thisArg, arg1, arg2, ...);

Trong đó:

  • originalFunction là hàm bạn muốn "bind".
  • thisArg là giá trị bạn muốn thiết lập cho this khi hàm được gọi.
  • arg1, arg2, ... là các tham số bạn muốn truyền trước cho hàm.

Hàm mới được tạo ra bởi bind sẽ có cùng cơ thể với hàm gốc, nhưng this bên trong hàm sẽ tham chiếu đến thisArg đã được cung cấp, và các tham số arg1, arg2, ... sẽ được đặt trước các tham số được truyền vào khi hàm mới được gọi.

Ví dụ:

javascript
var obj = {
  x: 10,
  getX: function() {
    return this.x;
  }
};

var unboundGetX = obj.getX;
console.log(unboundGetX()); // undefined hoặc giá trị của x trong global object, tùy thuộc vào strict mode

var boundGetX = unboundGetX.bind(obj);
console.log(boundGetX()); // 10

Trong ví dụ trên, unboundGetX là một tham chiếu đến hàm getX nhưng không giữ ngữ cảnh this của obj. Khi gọi unboundGetX(), this không tham chiếu đến obj nữa, do đó kết quả là undefined. Tuy nhiên, khi sử dụng bind để tạo boundGetX, this trong boundGetX được thiết lập để tham chiếu đến obj, và kết quả là 10 khi gọi hàm.

Function.prototype.bind là một công cụ hữu ích trong JavaScript, đặc biệt khi làm việc với các hàm callback và event handler, nơi mà việc kiểm soát ngữ cảnh this là rất quan trọng.

114

Toán tử typeof trong Javascript để làm gì?

Toán tử typeof trong JavaScript là một toán tử được sử dụng để kiểm tra kiểu dữ liệu của một giá trị hoặc biến. Khi áp dụng, typeof trả về một chuỗi mô tả kiểu dữ liệu của giá trị đó. Cú pháp của typeof có thể được viết theo hai cách: typeof operand hoặc typeof(operand), trong đó operand là giá trị hoặc biến cần kiểm tra kiểu dữ liệu

Cách Sử Dụng và Ví dụ

Toán tử typeof có thể được sử dụng để kiểm tra kiểu dữ liệu của các giá trị nguyên thủy như số, chuỗi, và boolean, cũng như các kiểu dữ liệu phức tạp như object và function. Dưới đây là một số ví dụ về cách sử dụng typeof:

javascript
console.log(typeof true); // 'boolean'
console.log(typeof 3000); // 'number'
console.log(typeof 'foobar'); // 'string'
console.log(typeof [1, 2, 'foobar']); // 'object'
console.log(typeof {a: 'foobar'}); // 'object'
console.log(typeof function(){}); // 'function'

Đặc Điểm và Lưu Ý

  • typeof trả về "object" cho cả mảng và object, làm cho việc phân biệt giữa mảng và object trở nên khó khăn
  • typeof null trả về "object", một điểm gây nhầm lẫn cho nhiều người vì null thực sự không phải là một object. Điều này là do một lỗi lịch sử trong JavaScript mà không được sửa chữa để đảm bảo tính tương thích ngược
  • typeof trả về "function" cho các hàm, bao gồm cả các constructors Class trong JavaScript
  • typeof có thể được sử dụng để kiểm tra sự tồn tại của một biến hoặc một đối tượng trước khi sử dụng nó, giúp tránh lỗi khi tham chiếu đến một giá trị không tồn tại

Kết Luận

Toán tử typeof là một công cụ hữu ích trong JavaScript để kiểm tra kiểu dữ liệu của các giá trị và biến. Tuy nhiên, do một số hạn chế và điểm gây nhầm lẫn, lập trình viên cần lưu ý khi sử dụng nó, đặc biệt là khi làm việc với null và phân biệt giữa mảng và object.

115

Sự khác biệt giữa .call.apply là gì?

Trả lời câu hỏi: Sự khác biệt chính giữa .call.apply trong JavaScript nằm ở cách chúng truyền đối số vào hàm. Cả hai phương thức này đều cho phép bạn gọi một hàm với một giá trị this cụ thể và một danh sách các đối số.

  • Phương thức .call gọi một hàm với một giá trị this được chỉ định và các đối số được truyền vào một cách riêng lẻ. Các đối số được liệt kê theo dạng phân cách bằng dấu phẩy. Đối số đầu tiên là giá trị this, nó đề cập đến đối tượng hiện tại hay còn gọi là đối tượng gọi hàm.
  • Phương thức .apply cũng gọi một hàm với một giá trị this được chỉ định nhưng khác biệt là nó nhận các đối số dưới dạng một mảng duy nhất. Điều này hữu ích khi bạn muốn sử dụng một mảng thay vì một danh sách đối số.

Ví dụ, nếu bạn có một hàm myFunction và bạn muốn gọi nó với giá trị thissomeObject và các đối số là arg1, arg2, arg3, bạn sẽ sử dụng .call.apply như sau:

javascript
myFunction.call(someObject, arg1, arg2, arg3);
myFunction.apply(someObject, [arg1, arg2, arg3]);

Trong cả hai trường hợp, someObject sẽ được sử dụng như là giá trị this bên trong hàm myFunction. Tuy nhiên, với .call, các đối số được liệt kê riêng lẻ, trong khi với .apply, các đối số được truyền dưới dạng một mảng.

Tóm lại, sự khác biệt giữa .call.apply là cách chúng truyền đối số vào hàm: .call nhận đối số dưới dạng danh sách, còn .apply nhận đối số dưới dạng một mảng.

116

Sự khác biệt giữa sự kiện tải trang document load event và sự kiện DOMContentLoaded event là gì?

Sự kiện DOMContentLoaded được kích hoạt khi tài liệu HTML đã được tải và phân tích cú pháp hoàn toàn, không chờ đợi các tài nguyên khác như stylesheets, hình ảnh và subframes hoàn thành việc tải. Điều này có nghĩa là sự kiện DOMContentLoaded xảy ra ngay sau khi cấu trúc DOM của trang web đã sẵn sàng, cho phép các mã JavaScript thao tác với DOM ngay lập tức mà không cần đợi tất cả các tài nguyên khác được tải.

Trong khi đó, sự kiện load được kích hoạt khi toàn bộ trang đã tải xong, bao gồm tất cả các tài nguyên phụ thuộc như stylesheets, scripts, iframes và hình ảnh. Sự kiện load xảy ra sau khi tất cả nội dung của trang và tài nguyên liên quan đã được tải và hiển thị hoàn chỉnh, thích hợp cho các tác vụ cần truy cập đến các tài nguyên này, chẳng hạn như thực hiện các thao tác liên quan đến kích thước hình ảnh hoặc khởi tạo các hoạt ảnh.

Tóm lại, sự kiện DOMContentLoaded thích hợp cho việc thực thi các mã JavaScript cần tương tác với DOM ngay khi nó sẵn sàng, trong khi sự kiện load thích hợp cho các tác vụ cần đợi cho đến khi toàn bộ trang và tất cả các tài nguyên liên quan đã được tải hoàn toàn.

117

Một số ưu/nhược điểm của việc viết code JavaScript bằng ngôn ngữ biên dịch sang JavaScript là gì?

Việc sử dụng các ngôn ngữ lập trình biên dịch sang JavaScript như TypeScript, CoffeeScript, Elm, ClojureScript, và PureScript mang lại một số ưu và nhược điểm như sau:

Ưu điểm:

  1. Tăng cường tính bảo mật: Các framework như React và Angular cung cấp các tính năng bảo mật tích hợp giúp bảo vệ dữ liệu người dùng và thông tin nhạy cảm khỏi các cuộc tấn công độc hại.
  2. Giảm bớt gánh nặng: JavaScript là một ngôn ngữ nhẹ và không yêu cầu cài đặt phần mềm nặng nề hoặc cơ sở hạ tầng phần cứng để chạy. Điều này làm cho nó trở thành lựa chọn tốt cho các ứng dụng web và ứng dụng di động cần hiệu suất nhanh và hiệu quả.
  3. Tốc độ cao: JavaScript là một ngôn ngữ nhanh chóng, có thể được sử dụng để xây dựng các ứng dụng hiệu suất cao có thể xử lý các phép tính và hoạt động phức tạp.
  4. Dễ học và triển khai: JavaScript được coi là dễ học và triển khai, với sự hỗ trợ rộng rãi trên các trình duyệt.

Nhược điểm:

  1. Rủi ro về bảo mật: Mã JavaScript chạy ở phía client có thể bị khai thác bởi người dùng không có trách nhiệm, gây ra rủi ro bảo mật.
  2. Khó khăn trong việc gỡ lỗi: Mặc dù một số trình soạn thảo HTML hỗ trợ gỡ lỗi, nhưng chúng không hiệu quả bằng các trình soạn thảo khác. Do trình duyệt không hiển thị cảnh báo cho lỗi, việc tìm ra vấn đề có thể trở nên khó khăn.
  3. Khả năng tương thích trình duyệt: Các trình duyệt web khác nhau có thể giải thích mã JavaScript một cách khác nhau, gây ra sự không nhất quán. Do đó, cần phải kiểm tra và chạy mã trên các trình duyệt khác nhau.
  4. Vấn đề về hiệu suất: Ứng dụng JavaScript có thể sử dụng nhiều tài nguyên hệ thống trong khi chạy, dẫn đến thời gian tải trang chậm và trải nghiệm người dùng kém.
    a
    Như vậy, việc sử dụng các ngôn ngữ biên dịch sang JavaScript mang lại cả ưu điểm về bảo mật, giảm bớt gánh nặng, tốc độ cao, và dễ học cũng như nhược điểm về rủi ro bảo mật, khó khăn trong việc gỡ lỗi, khả năng tương thích trình duyệt, và vấn đề về hiệu suất.
118

Cách đơn giản để loại bỏ các phần tử trùng lặp từ một mảng sử dụng ES6 là gì?

Một cách đơn giản để loại bỏ các phần tử trùng lặp từ một mảng trong ES6 là sử dụng cấu trúc dữ liệu Set kết hợp với spread operator. Set là một cấu trúc dữ liệu trong ES6 cho phép lưu trữ các giá trị duy nhất, không cho phép các giá trị trùng lặp. Bạn có thể tạo một Set mới từ mảng ban đầu, sau đó sử dụng spread operator để chuyển đổi Set trở lại thành một mảng mới chỉ chứa các giá trị duy nhất. Dưới đây là cách thực hiện:

javascript
const duplicates = [1, 2, 3, 4, 4, 1];
const uniques = [...new Set(duplicates)];
console.log(uniques); // [1, 2, 3, 4]

Trong đoạn mã trên, new Set(duplicates) tạo ra một Set từ mảng duplicates, loại bỏ các giá trị trùng lặp. Sau đó, ... (spread operator) được sử dụng để chuyển đổi Set thành một mảng mới chứa các giá trị duy nhất từ Set.

119

Bạn có thể đưa ra một ví dụ về hàm curry và giải thích tại sao cú pháp này lại có ích?

Ví dụ về một hàm curry trong JavaScript có thể như sau:

javascript
function sum(a) {
  return (b) => {
    return (c) => {
      return a + b + c;
    };
  };
}

const addOne = sum(1);
const addOneAndTwo = addOne(2);
const result = addOneAndTwo(3); // Kết quả là 6

Trong ví dụ trên, hàm sum được curry hóa để nhận từng đối số một lần. Đầu tiên, nó nhận đối số a, sau đó trả về một hàm mới nhận đối số b, và cuối cùng là hàm nhận đối số c. Khi tất cả các đối số đã được cung cấp, nó sẽ thực hiện phép cộng và trả về kết quả.

Lợi ích của cú pháp này là:

  1. Ứng dụng một phần (Partial Application): Curry hóa cho phép bạn tạo ra các hàm mới từ một hàm hiện có bằng cách cố định một số đối số. Điều này giúp tái sử dụng mã nguồn và tạo ra các hàm với mục đích cụ thể từ một hàm chung chung hơn.
  2. Tăng cường khả năng tái sử dụng mã nguồn: Bằng cách chia nhỏ hàm thành các hàm nhỏ hơn với ít đối số hơn, bạn có thể tái sử dụng các hàm này trong nhiều ngữ cảnh khác nhau, giúp mã nguồn trở nên gọn gàng và dễ bảo trì hơn.
  3. Tạo ra các hàm cấp cao (Higher-order functions): Curry hóa giúp dễ dàng tạo ra các hàm cấp cao, từ đó tăng cường khả năng composable của mã nguồn và tạo ra các trừu tượng mạnh mẽ hơn.
  4. Cải thiện tính rõ ràng và đọc hiểu mã nguồn: Khi sử dụng hàm curry, mã nguồn thường trở nên rõ ràng và dễ đọc hơn, vì mỗi hàm chỉ làm một việc cụ thể và nhận một đối số cụ thể.

Tuy nhiên, cũng cần lưu ý rằng curry hóa có thể làm tăng độ phức tạp của mã nguồn và sử dụng nhiều bộ nhớ hơn, đặc biệt nếu không được sử dụng một cách cẩn thận.

120

Prototype trong Javascript là gì?

Trong JavaScript, prototype là một cơ chế mà thông qua đó các đối tượng JavaScript kế thừa các tính năng từ một đối tượng khác. JavaScript là một ngôn ngữ lập trình dựa trên prototype, không dựa trên lớp (class-based), nghĩa là thay vì định nghĩa các lớp và tạo ra các thể hiện của lớp, JavaScript sử dụng các đối tượng prototype để tạo ra các đối tượng mới bằng cách kế thừa các tính năng từ các đối tượng khác.

Cách hoạt động của Prototype

Mỗi đối tượng JavaScript có một thuộc tính nội bộ gọi là [[Prototype]], có thể được tham chiếu thông qua thuộc tính __proto__ (trong môi trường hiện đại, việc sử dụng Object.getPrototypeOf() được khuyến khích hơn là trực tiếp sử dụng __proto__). Thuộc tính này là một tham chiếu đến một đối tượng khác, từ đó đối tượng hiện tại kế thừa các thuộc tính và phương thức.

Khi bạn cố gắng truy cập một thuộc tính hoặc phương thức của một đối tượng, JavaScript sẽ trước tiên tìm kiếm thuộc tính hoặc phương thức đó trên chính đối tượng đó. Nếu không tìm thấy, JavaScript sẽ tiếp tục tìm kiếm trên prototype của đối tượng, và quá trình này sẽ tiếp tục cho đến khi tìm thấy thuộc tính/phương thức hoặc cho đến khi đạt đến cuối chuỗi prototype mà không tìm thấy (đối tượng prototype cuối cùng thường là Object.prototype, mà từ đó không còn prototype nào khác để tìm kiếm).

Ví dụ về Prototype

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

Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.name);
};

const alice = new Person("Alice");
alice.greet(); // In ra: "Hello, my name is Alice"

Trong ví dụ trên, Person.prototype là đối tượng mà tất cả các thể hiện của Person sẽ kế thừa các thuộc tính và phương thức từ. Khi alice.greet() được gọi, JavaScript tìm thấy phương thức greet trên Person.prototype và thực thi nó.

Tại sao lại sử dụng Prototype?

Sử dụng prototype trong JavaScript mang lại một số lợi ích:

  • Tái sử dụng mã: Các phương thức được định nghĩa trên prototype có thể được tái sử dụng bởi tất cả các thể hiện của constructor function hoặc lớp, giúp giảm bớt lượng mã cần viết và bảo trì.
  • Tiết kiệm bộ nhớ: Vì các phương thức được lưu trữ trên prototype chứ không phải trên mỗi thể hiện của đối tượng, nên chúng không chiếm thêm bộ nhớ với mỗi đối tượng mới được tạo.

Tóm lại, prototype là một phần cốt lõi của JavaScript, cho phép kế thừa và tái sử dụng mã một cách hiệu quả.

121

Triển khai Prototype Design Pattern trong Javascript như thế nào?

Prototype Design Pattern trong JavaScript là một kỹ thuật lập trình cho phép tạo ra các đối tượng mới dựa trên một đối tượng mẫu (prototype) đã tồn tại. Mẫu này là một đối tượng cụ thể, và các đối tượng mới được tạo ra sẽ kế thừa các thuộc tính và phương thức từ nó. Điều này giúp tiết kiệm bộ nhớ và tài nguyên bởi vì không cần phải tạo ra một bản sao hoàn chỉnh của mẫu cho mỗi đối tượng mới.

Cách hoạt động của Prototype Pattern

Trong JavaScript, mọi đối tượng đều có một thuộc tính nội bộ gọi là [[Prototype]] (hoặc __proto__ trong một số trình duyệt). Thuộc tính này là một tham chiếu đến một đối tượng khác, và nó được sử dụng để kế thừa các thuộc tính và phương thức. Khi bạn cố gắng truy cập một thuộc tính không tồn tại trên đối tượng hiện tại, JavaScript sẽ tự động tìm kiếm thuộc tính đó trên [[Prototype]].

Ví dụ sử dụng Prototype Pattern

javascript
// Định nghĩa một đối tượng mẫu
const carPrototype = {
  start() {
    console.log(`The ${this.make} ${this.model} car has started.`);
  }
};

// Hàm tạo đối tượng Car mới
function Car(make, model) {
  const car = Object.create(carPrototype); // Tạo đối tượng mới từ prototype
  car.make = make;
  car.model = model;
  return car;
}

// Tạo một đối tượng Car mới
const myCar = Car('Toyota', 'Corolla');
myCar.start(); // Output: The Toyota Corolla car has started.

Trong ví dụ trên, carPrototype là một đối tượng mẫu có phương thức start(). Hàm Car tạo ra một đối tượng mới dựa trên carPrototype và gán giá trị cho các thuộc tính makemodel. Khi gọi myCar.start(), phương thức start() được kế thừa từ carPrototype.

Lợi ích của Prototype Pattern

  • Hiệu suất: Kế thừa thông qua prototype giúp tiết kiệm bộ nhớ vì các phương thức được chia sẻ giữa tất cả các thể hiện của một lớp, thay vì tạo ra mỗi lần cho mỗi đối tượng.
  • Tính linh hoạt: Có thể thêm hoặc thay đổi các thuộc tính và phương thức của prototype mà không cần sửa đổi định nghĩa của hàm tạo hoặc các đối tượng hiện có.
  • Đơn giản hóa mã nguồn: Prototype Pattern giúp giảm sự phức tạp của mã nguồn khi làm việc với kế thừa và tái sử dụng mã.

Kết luận

Prototype Design Pattern là một phần cốt lõi của JavaScript, cho phép kế thừa và tái sử dụng mã một cách hiệu quả. Nó cung cấp một cách để tạo ra các đối tượng mới mà không cần định nghĩa lại các phương thức và thuộc tính, giúp tiết kiệm tài nguyên và tối ưu hóa hiệu suất ứng dụng.

122

Triển khai Command Pattern trong Javascript như thế nào?

Command Pattern là một mẫu thiết kế hành vi (behavioral design pattern) trong lập trình hướng đối tượng, nó cho phép bạn đóng gói các yêu cầu dưới dạng một đối tượng, từ đó cho phép người dùng tham chiếu đến các yêu cầu này, xếp hàng hoặc ghi nhật ký chúng, và hỗ trợ các hoạt động hoàn tác.

Cách triển khai Command Pattern

Để triển khai Command Pattern trong JavaScript, bạn cần tạo ra các đối tượng command với một phương thức execute chính và một hoặc nhiều đối tượng receiver mà command sẽ thao tác trên đó. Bạn cũng có thể cần một Invoker để quản lý và gọi các command.

Bước 1: Tạo Command Interface

Đầu tiên, bạn cần một interface cho các command với ít nhất một phương thức execute.

javascript
class Command {
  execute() {
    throw new Error('This method must be overwritten!');
  }
}

Bước 2: Tạo Concrete Commands

Sau đó, tạo các lớp concrete command thực thi interface command và triển khai phương thức execute.

javascript
class TurnOnCommand extends Command {
  constructor(receiver) {
    super();
    this.receiver = receiver;
  }

  execute() {
    this.receiver.turnOn();
  }
}

class TurnOffCommand extends Command {
  constructor(receiver) {
    super();
    this.receiver = receiver;
  }

  execute() {
    this.receiver.turnOff();
  }
}

Bước 3: Tạo Receiver

Tạo một hoặc nhiều receiver chứa logic thực sự để thực hiện các hành động.

javascript
class Light {
  turnOn() {
    console.log('The light is on');
  }

  turnOff() {
    console.log('The light is off');
  }
}

Bước 4: Tạo Invoker

Invoker sẽ gọi command mà không cần biết chi tiết về cách thức thực hiện.

javascript
class RemoteControl {
  submit(command) {
    command.execute();
  }
}

Bước 5: Sử dụng Command Pattern

Cuối cùng, bạn có thể sử dụng Command Pattern để thực thi các hành động.

javascript
const light = new Light();
const turnOnLight = new TurnOnCommand(light);
const turnOffLight = new TurnOffCommand(light);

const remote = new RemoteControl();
remote.submit(turnOnLight); // Output: The light is on
remote.submit(turnOffLight); // Output: The light is off

Kết luận

Command Pattern trong JavaScript giúp tách biệt phần logic yêu cầu thực thi từ phần logic thực sự thực hiện hành động. Điều này không chỉ giúp mã nguồn dễ bảo trì và mở rộng hơn mà còn hỗ trợ thêm các tính năng như hoàn tác và ghi nhật ký một cách dễ dàng.

123

Closure trong javascript là gì, cho ví dụ?

Closure trong JavaScript là một khái niệm quan trọng, cho phép một hàm truy cập các biến từ phạm vi bên ngoài của nó, ngay cả sau khi phạm vi đó đã kết thúc. Cụ thể, closure là một hàm có khả năng "nhớ" và truy cập các biến và tham số của hàm bên ngoài mà nó được tạo ra, ngay cả khi hàm bên ngoài đó đã thực thi xong

Đặc điểm của Closure

  • Truy cập biến từ phạm vi bên ngoài: Closure có thể truy cập các biến từ phạm vi bên ngoài của nó, bao gồm cả biến toàn cục, biến từ hàm bên ngoài (outer function), và biến cục bộ của chính nó
  • Bảo vệ dữ liệu: Closure giúp bảo vệ dữ liệu bằng cách giữ cho các biến không thể truy cập trực tiếp từ bên ngoài, tạo ra các biến "riêng tư"
  • Duy trì trạng thái: Closure cho phép duy trì trạng thái trong các hàm con mà không cần tạo ra các biến toàn cục, giúp giảm thiểu rủi ro xung đột biến và làm bẩn không gian tên

Ví dụ về Closure

javascript
function createCounter() {
    let count = 0;
    return function() {
        count++;
        console.log(count);
    };
}

let counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

Trong ví dụ trên, hàm createCounter trả về một hàm con, là một closure, có thể truy cập và thay đổi biến count của hàm createCounter. Mỗi lần gọi counter(), biến count sẽ tăng lên một, và giá trị mới của count được in ra màn hình. Điều này cho thấy closure đã "nhớ" và duy trì trạng thái của biến count sau mỗi lần gọi

Ứng dụng của Closure

  • Bảo mật dữ liệu: Sử dụng closure để tạo ra các biến "riêng tư", giúp bảo vệ dữ liệu khỏi sự truy cập không mong muốn từ bên ngoài
  • Module pattern: Tạo ra các module độc lập, giúp cấu trúc mã nguồn gọn gàng và dễ bảo trì hơn
  • Callback và bất đồng bộ: Closure thường được sử dụng trong việc xử lý các hàm callback và lập trình bất đồng bộ, cho phép truy cập vào các biến từ phạm vi bên ngoài mà không lo bị mất trạng thái

Closure là một công cụ mạnh mẽ trong JavaScript, giúp lập trình viên quản lý và bảo vệ dữ liệu hiệu quả, đồng thời tạo ra các ứng dụng web động và phức tạp. Hiểu và sử dụng thành thạo closure sẽ mở ra nhiều khả năng mới trong lập trình JavaScript.

124

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

Constructor Design Pattern trong JavaScript là một kỹ thuật lập trình được sử dụng để tạo ra các đối tượng với các thuộc tính và phương thức cụ thể. Mẫu thiết kế này sử dụng hàm tạo (constructor function) hoặc lớp (class) để khởi tạo đối tượng.

Cách triển khai Constructor Pattern

Để triển khai Constructor Pattern, bạn cần tạo một hàm tạo hoặc một lớp với các thuộc tính và phương thức mà bạn muốn đối tượng của mình có.

Bước 1: Tạo hàm tạo (Constructor Function)

Trong JavaScript cổ điển (ES5 và trước đó), bạn sẽ sử dụng hàm tạo:

javascript
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;

  this.start = function() {
    console.log(this.make + ' ' + this.model + ' engine started.');
  };
}

// Tạo một đối tượng Car mới
var myCar = new Car('Toyota', 'Corolla', 2020);
myCar.start(); // Output: Toyota Corolla engine started.

Bước 2: Sử dụng từ khóa class (ES6+)

Trong JavaScript hiện đại (ES6 trở lên), bạn có thể sử dụng từ khóa class để tạo một lớp:

javascript
class Car {
  constructor(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
  }

  start() {
    console.log(`${this.make} ${this.model} engine started.`);
  }
}

// Tạo một đối tượng Car mới
const myCar = new Car('Toyota', 'Corolla', 2020);
myCar.start(); // Output: Toyota Corolla engine started.

Lợi ích của Constructor Pattern

  • Tổ chức mã: Constructor Pattern giúp tổ chức mã nguồn của bạn bằng cách đóng gói thông tin và hành vi liên quan đến một đối tượng vào một định nghĩa duy nhất.
  • Tái sử dụng mã: Khi bạn cần tạo nhiều đối tượng có cùng cấu trúc, hàm tạo hoặc lớp giúp tái sử dụng mã nguồn một cách dễ dàng.
  • Khởi tạo đối tượng: Constructor Pattern cung cấp một cách rõ ràng và logic để khởi tạo đối tượng với các thuộc tính và phương thức cụ thể.

Kết luận

Constructor Design Pattern là một phương pháp cơ bản và mạnh mẽ trong JavaScript để tạo ra các đối tượng. Nó cho phép bạn định nghĩa cách một đối tượng được tạo và cấu trúc của nó, giúp mã nguồn trở nên dễ đọc, dễ bảo trì và tái sử dụng.

125

Triển khai Singleton Design Pattern trong Javascript như thế nào?

Singleton Design Pattern là một mẫu thiết kế phần mềm được sử dụng để đảm bảo rằng một lớp chỉ có một thể hiện duy nhất và cung cấp một điểm truy cập toàn cục đến thể hiện đó. Trong JavaScript, Singleton Pattern thường được sử dụng để quản lý các tài nguyên chia sẻ, như kết nối cơ sở dữ liệu hoặc cấu hình ứng dụng.

Cách triển khai Singleton Pattern

Để triển khai Singleton Pattern trong JavaScript, bạn có thể sử dụng một số cách khác nhau. Dưới đây là một ví dụ sử dụng một hàm tự gọi (IIFE - Immediately Invoked Function Expression) để tạo ra một Singleton:

javascript
var Singleton = (function() {
  var instance;

  function createInstance() {
    var object = new Object("I am the instance");
    return object;
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

// Sử dụng Singleton
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // Output: true

Trong ví dụ trên, Singleton là một IIFE trả về một đối tượng với một phương thức getInstance. Phương thức này kiểm tra xem một thể hiện của lớp đã được tạo hay chưa. Nếu chưa, nó sẽ tạo một thể hiện mới; nếu có, nó sẽ trả về thể hiện hiện tại. Điều này đảm bảo rằng chỉ có một thể hiện duy nhất của lớp được tạo ra.

Lợi ích của Singleton Pattern

  • Kiểm soát truy cập: Singleton Pattern cung cấp một điểm truy cập toàn cục đến thể hiện duy nhất của lớp, giúp dễ dàng kiểm soát và quản lý truy cập.
  • Tiết kiệm tài nguyên: Bằng cách đảm bảo chỉ có một thể hiện duy nhất, Singleton Pattern giúp tiết kiệm tài nguyên hệ thống, đặc biệt là khi việc tạo thể hiện mới là tốn kém.
  • Dễ dàng quản lý trạng thái: Singleton Pattern thích hợp cho việc quản lý trạng thái ứng dụng hoặc tài nguyên chia sẻ, vì tất cả các phần của ứng dụng đều truy cập vào cùng một thể hiện.

Kết luận

Singleton Design Pattern là một công cụ hữu ích trong JavaScript để đảm bảo rằng chỉ có một thể hiện duy nhất của một lớp được tạo ra và cung cấp một điểm truy cập toàn cục đến thể hiện đó. Mẫu thiết kế này giúp quản lý tài nguyên và trạng thái ứng dụng một cách hiệu quả.

126

Những lợi ích của WeakMap trong Javascript là gì?

WeakMap là một cấu trúc dữ liệu trong ECMAScript 6 (ES6) giống như Map, nhưng với một số điểm khác biệt quan trọng giúp nó có những công dụng đặc biệt trong một số trường hợp sử dụng. Dưới đây là những công dụng chính của WeakMap:

1. Quản lý Bộ Nhớ Hiệu Quả

WeakMap giữ các key dưới dạng yếu, nghĩa là nếu không còn tham chiếu nào khác đến một key, thì key đó cùng với giá trị của nó có thể được thu hồi bộ nhớ tự động. Điều này giúp quản lý bộ nhớ hiệu quả, đặc biệt trong các ứng dụng lớn và phức tạp, giảm thiểu rủi ro rò rỉ bộ nhớ.

2. Lưu Trữ Dữ Liệu Riêng Tư

WeakMap thường được sử dụng để lưu trữ dữ liệu riêng tư của đối tượng mà không làm ô nhiễm không gian tên toàn cục hoặc đối tượng mà bạn đang làm việc với. Bởi vì các key trong WeakMap không thể được liệt kê, điều này giúp giữ cho dữ liệu trở nên "yếu" và không thể truy cập từ bên ngoài, tăng cường tính bảo mật và đóng gói.

3. Làm Điều Kiện Gác cho Các Đối Tượng

WeakMap có thể được sử dụng như một cơ chế "điều kiện gác" (guard) cho các đối tượng, cho phép bạn kiểm tra xem một đối tượng có đáp ứng một số điều kiện hoặc có trong một tập hợp nhất định mà không cần giữ một tham chiếu mạnh đến đối tượng đó. Điều này hữu ích trong việc quản lý các tài nguyên hoặc thực thể mà bạn không muốn ngăn chặn việc thu hồi bộ nhớ.

4. Lưu Trữ Dữ Liệu Liên Kết với Đối Tượng

WeakMap cho phép bạn lưu trữ dữ liệu liên kết với một đối tượng mà không cần phải sửa đổi đối tượng đó. Điều này rất hữu ích khi bạn cần thêm dữ liệu hoặc trạng thái vào các đối tượng mà bạn không kiểm soát, chẳng hạn như các đối tượng từ thư viện bên thứ ba.

5. Tối Ưu Hóa Hiệu Suất

Trong một số trường hợp, việc sử dụng WeakMap có thể giúp tối ưu hóa hiệu suất bằng cách giảm thiểu việc giữ các tham chiếu không cần thiết đến các đối tượng, từ đó giúp bộ thu gom rác (garbage collector) hoạt động hiệu quả hơn.

Kết luận

WeakMap trong ES6 mang lại một số lợi ích quan trọng liên quan đến quản lý bộ nhớ, bảo mật, và kiến trúc ứng dụng. Sự khác biệt chính của nó so với Map là khả năng giữ các key dưới dạng yếu, giúp giảm thiểu rủi ro rò rỉ bộ nhớ và tạo điều kiện cho việc lưu trữ dữ liệu riêng tư hoặc liên kết với đối tượng một cách hiệu quả.

127

Trong JavaScript hàm map có thể lặp qua các thuộc tính của một đối tượng không?

Trong JavaScript, không có hàm map được tích hợp sẵn để trực tiếp lặp qua các thuộc tính của một đối tượng như khi bạn lặp qua các phần tử của một mảng. Tuy nhiên, bạn có thể đạt được chức năng tương tự bằng cách sử dụng một sự kết hợp của các phương thức khác và kỹ thuật xử lý đối tượng.

Có hai cách chính để lặp qua các thuộc tính của một đối tượng và áp dụng một hàm lên chúng, tạo ra một kết quả tương tự như khi sử dụng map với mảng:

  1. Sử dụng Object.entries() kết hợp với map(): Bạn có thể chuyển đổi đối tượng thành một mảng các cặp [key, value] bằng cách sử dụng Object.entries(), sau đó áp dụng map() lên mảng đó. Cuối cùng, bạn có thể chuyển mảng kết quả trở lại thành một đối tượng mới nếu cần.

    Ví dụ:

    javascript
    const obj = { key1: 'value1', key2: 'value2', key3: 'value3' };
    const result = Object.fromEntries(
      Object.entries(obj).map(([key, value]) => [key, value.toUpperCase()])
    );
    console.log(result); // { key1: 'VALUE1', key2: 'VALUE2', key3: 'VALUE3' }
  2. Sử dụng vòng lặp for...in hoặc các phương thức Object.keys()/Object.values() kết hợp với forEach(): Bạn cũng có thể lặp qua các khóa (keys) hoặc giá trị (values) của đối tượng và áp dụng một hàm lên chúng. Điều này không trực tiếp tạo ra một mảng mới như map(), nhưng bạn có thể tự tạo một đối tượng mới từ kết quả.

    Ví dụ sử dụng Object.keys()forEach():

    javascript
    const obj = { key1: 'value1', key2: 'value2', key3: 'value3' };
    const result = {};
    Object.keys(obj).forEach(key => {
      result[key] = obj[key].toUpperCase();
    });
    console.log(result); // { key1: 'VALUE1', key2: 'VALUE2', key3: 'VALUE3' }

Mặc dù JavaScript không cung cấp một hàm map dành riêng cho đối tượng, nhưng bằng cách sử dụng các phương thức và kỹ thuật trên, bạn có thể dễ dàng lặp qua và biến đổi các thuộc tính của đối tượng một cách linh hoạt.

128

Temporal Dead Zone trong Javascript là gì?

Temporal Dead Zone (TDZ) là một khái niệm trong JavaScript, đặc biệt là trong phiên bản ES6, liên quan đến việc sử dụng các từ khóa letconst để khai báo biến. TDZ mô tả trạng thái mà trong đó các biến không thể truy cập được, mặc dù chúng đã nằm trong phạm vi (scope) nhưng chưa được khai báo hoàn chỉnh

Đặc điểm của TDZ

  • Không thể truy cập trước khi khai báo: Biến được khai báo bằng letconst tồn tại trong TDZ từ đầu phạm vi chứa chúng cho đến khi chúng được khai báo hoàn toàn. Trong thời gian này, mọi cố gắng truy cập biến sẽ dẫn đến lỗi ReferenceError
  • Bắt đầu và kết thúc của TDZ: TDZ bắt đầu từ khi bắt đầu khối lệnh chứa khai báo biến và kết thúc khi biến được khởi tạo giá trị. Điều này có nghĩa là, TDZ không chỉ liên quan đến vị trí trong mã nguồn mà còn liên quan đến thời điểm thực thi mã
  • Mục đích của TDZ: TDZ giúp phát hiện và ngăn chặn các lỗi do truy cập biến trước khi chúng được khởi tạo, từ đó giúp cải thiện tính rõ ràng và dễ bảo trì của mã nguồn

Ví dụ về TDZ

javascript
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 5;

Trong ví dụ trên, biến a đang nằm trong TDZ tại thời điểm console.log(a) được thực thi, do đó dẫn đến ReferenceError.

So sánh với var

Biến được khai báo bằng var không nằm trong TDZ. Thay vào đó, chúng được "hoisted" (nâng lên đầu phạm vi chứa chúng) và khởi tạo với giá trị undefined, cho phép truy cập trước khi khai báo mà không gây ra lỗi

Kết luận

TDZ là một phần quan trọng của cơ chế hoạt động của letconst trong JavaScript ES6, giúp ngăn chặn các lỗi tiềm ẩn do truy cập biến trước khi chúng được khởi tạo. Hiểu rõ về TDZ giúp lập trình viên viết mã nguồn chính xác và dễ bảo trì hơn.

129

Prototype Inheritance trong JavaScript là gì?

Prototype Inheritance trong JavaScript là một cơ chế cho phép một đối tượng kế thừa các thuộc tính và phương thức từ một đối tượng khác. Trong JavaScript, mọi đối tượng đều có một thuộc tính nội bộ gọi là [[Prototype]], thường được truy cập thông qua __proto__ hoặc các phương thức Object.getPrototypeOf()Object.setPrototypeOf().

Cách hoạt động của Prototype Inheritance

Khi bạn truy cập một thuộc tính hoặc phương thức của một đối tượng, JavaScript sẽ tìm kiếm thuộc tính hoặc phương thức đó trên chính đối tượng đó. Nếu không tìm thấy, JavaScript sẽ tìm kiếm trên prototype của đối tượng, và quá trình này sẽ tiếp tục lên chuỗi prototype cho đến khi tìm thấy hoặc đến cuối chuỗi.

Ví dụ về Prototype Inheritance

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

Person.prototype.greet = function() {
  console.log('Hello, my name is ' + this.name);
};

var alice = new Person('Alice');
alice.greet(); // "Hello, my name is Alice"

Trong ví dụ trên, alice là một thể hiện của Person. greet không phải là một phương thức trực tiếp của alice, nhưng vì nó được định nghĩa trên Person.prototype, alice có thể truy cập nó thông qua chuỗi prototype.

Lợi ích của Prototype Inheritance

  • Tái sử dụng mã: Các phương thức được định nghĩa trên prototype có thể được tái sử dụng bởi tất cả các thể hiện của constructor function, giúp giảm bớt lặp lại mã.
  • Tiết kiệm bộ nhớ: Các phương thức được lưu trữ trên prototype chứ không phải trên mỗi thể hiện của đối tượng, giúp tiết kiệm bộ nhớ.

Hạn chế của Prototype Inheritance

  • Quản lý phức tạp: Việc quản lý chuỗi prototype có thể trở nên phức tạp, đặc biệt là khi có nhiều cấp độ kế thừa.
  • Hiểu biết: Cần phải hiểu rõ về cách hoạt động của prototype để tránh gặp phải các vấn đề không mong muốn trong quá trình kế thừa và truy cập thuộc tính.

Tóm lại, Prototype Inheritance là một phần cốt lõi của JavaScript, cung cấp khả năng kế thừa và tái sử dụng mã một cách hiệu quả. Hiểu rõ về prototype là quan trọng để viết mã JavaScript hiệu quả và bảo trì dễ dàng.

130

Giải thích về Hoisting trong Javascript?

Hoisting là một hành vi mặc định trong Javascript, nó sẽ chuyển tất cả khai báo biến và hàm lên trên cùng.

Hoisting trong Javascript

Điều này có nghĩa là bất kể hàm và biến được khai báo ở đâu, chúng cũng sẽ đuọc chuyển lên đầu scope. Scope có thể là toàn cục hoặc cục bộ.

Ví dụ 1:

js
hoistedVariable = 3;
console.log(hoistedVariable);
// output là 3 vì biến được khởi tạo trước khi khai báo.
var hoistedVariable;

Ví dụ 2:

js
hoistedFunction();
// Outputs " Hello world! " kể cả khi hàm được khai báo sau khi gọi.

function hoistedFunction() {
  console.log(" Hello world! ");
}

Ví dụ 3:

js
// Hoisting takes place in the local scope as well
function doSomething() {
  x = 33;
  console.log(x);
  var x;
}

Lưu ý: Khai báo biến được hoisting chứ phép gán biến thì không.

js
var x;
console.log(x); // Output sẽ trả về "undefined" vì phép gán không được hoisting
x = 23;

Lưu ý: Để tránh hoisting bạn có thể dùng "use strict"

js
"use strict";
x = 23; // Báo lỗi x  chưa được khai báo
var x;
131

Currying trong Javascript là gì?

Currying trong JavaScript là một kỹ thuật chuyển đổi một hàm có nhiều đối số thành một chuỗi các hàm, mỗi hàm chỉ nhận một đối số. Kỹ thuật này được sử dụng để tạo ra một phiên bản mới của hàm ban đầu, cho phép một số đối số của hàm được cố định trước khi hàm được gọi.

Cách hoạt động

Currying hoạt động bằng cách tạo ra một chuỗi các hàm, mỗi hàm nhận một đối số và trả về một hàm mới cho đến khi tất cả các đối số đã được cung cấp. Cuối cùng, kết quả của hàm gốc sẽ được tính toán với tất cả các đối số đã được cung cấp.

Ví dụ

javascript
function add(a) {
  return function(b) {
    return a + b;
  };
}

const add5 = add(5);
console.log(add5(3)); // 8

Trong ví dụ trên, hàm add được "curried" để tạo ra một hàm mới add5, hàm này đã cố định giá trị của đối số đầu tiên là 5. Khi gọi add5 với một đối số, nó sẽ thêm đối số đó vào 5.

Lợi ích

  • Tái sử dụng mã: Currying cho phép tái sử dụng một phần logic của hàm với một số đối số cố định, giúp giảm bớt lặp lại mã.
  • Hỗ trợ lập trình hàm: Currying là một kỹ thuật phổ biến trong lập trình hàm, giúp việc xử lý hàm trở nên linh hoạt hơn.
  • Tạo hàm với cấu hình trước: Cho phép tạo ra các phiên bản của một hàm với một hoặc nhiều đối số đã được "cài đặt" trước, giúp dễ dàng tạo ra các hàm mới từ hàm gốc.

Hạn chế

  • Độ phức tạp: Sử dụng currying có thể làm tăng độ phức tạp của mã, đặc biệt là với những người mới làm quen với kỹ thuật này.
  • Hiệu suất: Trong một số trường hợp, việc sử dụng currying có thể ảnh hưởng đến hiệu suất do tạo ra nhiều hàm trung gian.

Tóm lại, currying là một kỹ thuật mạnh mẽ trong JavaScript, giúp tạo ra các hàm linh hoạt và tái sử dụng mã. Tuy nhiên, cần cân nhắc khi sử dụng để tránh làm tăng độ phức tạp không cần thiết.

132

Khi nào bạn sẽ sử dụng import * as X from 'X'?

Bạn sẽ sử dụng cú pháp import * as X from 'X' trong các trường hợp sau:

  1. Khi Bạn Muốn Nhập Tất Cả Các Xuất Khẩu (Exports) Từ Một Module: Cú pháp này cho phép bạn nhập tất cả các thành phần được xuất khẩu từ module 'X' và gom chúng vào một đối tượng có tên là 'X'. Điều này giúp bạn có thể truy cập các thành phần này thông qua đối tượng 'X', giữ cho không gian tên (namespace) của bạn gọn gàng và tránh xung đột tên.
  2. Khi Bạn Muốn Giữ Các Xuất Khẩu Trong Một Namespace Riêng: Sử dụng import * as X giúp bạn duy trì một namespace rõ ràng cho tất cả các thành phần được nhập từ module 'X', giúp bạn dễ dàng xác định nguồn gốc của chúng khi đọc hoặc gỡ lỗi mã nguồn.
  3. Khi Bạn Làm Việc Với Các Module Lớn: Đối với các module lớn có nhiều thành phần, việc sử dụng import * as X có thể giúp bạn dễ dàng quản lý và sử dụng các thành phần này mà không cần phải nhập từng thành phần một cách riêng lẻ.
  4. Khi Bạn Muốn Tránh Việc Nhập Các Thành Phần Không Sử Dụng: Mặc dù việc nhập tất cả các thành phần có thể dẫn đến việc nhập những thứ không cần thiết, nhưng các công cụ hiện đại như webpack có thể loại bỏ các thành phần không được sử dụng khi tạo gói ứng dụng, giúp giảm kích thước của gói cuối cùng.

Tuy nhiên, cần lưu ý rằng việc sử dụng import * as X có thể làm cho mã nguồn trở nên khó đọc hơn nếu bạn nhập quá nhiều thành phần và không sử dụng chúng. Ngoài ra, việc sử dụng cú pháp này cũng có thể làm tăng khả năng xung đột tên nếu các module khác nhau có các thành phần với cùng tên.

133

Thuật ngữ transpiling trong Javascript là gì?

Transpiling trong JavaScript là quá trình chuyển đổi mã từ một ngôn ngữ hoặc phiên bản ngôn ngữ này sang một ngôn ngữ hoặc phiên bản khác mà vẫn giữ nguyên hoặc tương đương về chức năng. Trong bối cảnh của JavaScript, transpiling thường được sử dụng để chuyển đổi mã JavaScript được viết bằng các phiên bản mới hơn của ngôn ngữ (ví dụ: ES6/ES2015 trở lên) sang một phiên bản tương thích với các trình duyệt hoặc môi trường thực thi cũ hơn.

Mục đích của Transpiling

  • Tương thích ngược: Cho phép các nhà phát triển sử dụng các tính năng mới của JavaScript mà không phải lo lắng về việc liệu trình duyệt hoặc môi trường thực thi của người dùng cuối có hỗ trợ các tính năng đó hay không.
  • Cải thiện khả năng bảo trì và đọc mã: Các tính năng mới của JavaScript thường làm cho mã nguồn dễ đọc và bảo trì hơn. Transpiling cho phép sử dụng các tính năng này ngay cả khi mục tiêu thực thi không hỗ trợ trực tiếp chúng.
  • Sử dụng các ngôn ngữ khác: Transpiling cũng cho phép chuyển đổi mã từ các ngôn ngữ khác sang JavaScript, cho phép sử dụng các ngôn ngữ có cú pháp hoặc tính năng ưu việt trong phát triển web, như TypeScript hoặc CoffeeScript.

Cách hoạt động

Transpilers, như Babel hoặc TypeScript, đọc mã nguồn được viết bằng một phiên bản hoặc ngôn ngữ nhất định và chuyển đổi nó thành mã nguồn tương đương hoặc tương tự trong một phiên bản hoặc ngôn ngữ khác. Quá trình này thường bao gồm việc chuyển đổi cú pháp mới thành cú pháp cũ hơn mà các trình duyệt hoặc môi trường thực thi hiện tại có thể hiểu và thực thi.

Ví dụ

Một ví dụ điển hình của transpiling là sử dụng Babel để chuyển đổi mã JavaScript ES6+ thành mã ES5, giúp mã có thể chạy trên các trình duyệt cũ không hỗ trợ ES6+:

javascript
// Mã ES6+
const greet = (name) => console.log(`Hello, ${name}!`);

// Sau khi transpiling, mã trở thành ES5
var greet = function(name) {
  return console.log("Hello, " + name + "!");
};

Tóm lại, transpiling là một công cụ quan trọng trong phát triển web hiện đại, giúp tận dụng các tính năng mới của JavaScript và các ngôn ngữ khác mà vẫn đảm bảo mã có thể chạy trên một phạm vi rộng lớn các môi trường thực thi.

134

Mô tả module design pattern trong JavaScript?

module design pattern trong JavaScript là một mẫu thiết kế được sử dụng để cải thiện khả năng bảo trì và tái sử dụng mã bằng cách tạo ra quyền truy cập công cộng và riêng tư. Mẫu thiết kế này giúp bảo vệ giá trị bên trong một module khỏi bị truy cập từ các phạm vi khác, được gọi là đóng gói (encapsulation).

Module trong JavaScript giúp chia nhỏ một file lớn thành nhiều phần nhỏ hơn, có thể tái sử dụng. Các giá trị bên trong module mặc định được giữ kín, không thể bị sửa đổi từ bên ngoài, trừ khi chúng được xuất khẩu rõ ràng bằng từ khóa export. Điều này giúp bảo vệ các giá trị khỏi bị rò rỉ vào phạm vi toàn cục hoặc xảy ra xung đột tên.

Mẫu thiết kế module cung cấp một cách để có cả phần tử công cộng và riêng tư với từ khóa export, giúp mã nguồn dễ bảo trì và tái sử dụng hơn. Mẫu thiết kế module tương tự như một biểu thức hàm được gọi ngay lập tức (IIFE - immediately invoked functional expression), nhưng một module luôn trả về một đối tượng thay vì một hàm.

Một ví dụ về mẫu thiết kế module là việc tạo ra một module với các biến và hàm riêng tư, sau đó trả về một đối tượng công cộng mà chỉ bao gồm các biến và phương thức mà chúng ta muốn tiết lộ. Các biến hoặc phương thức không được trả về trong đối tượng này sẽ không thể truy cập từ bên ngoài module, giúp giữ các chi tiết triển khai ẩn đi và chỉ cung cấp một API công cộng cho các phần khác của ứng dụng sử dụng.

Mẫu thiết kế module cũng giúp giảm thiểu khả năng xung đột tên hàm với các hàm khác được định nghĩa trong các script khác trên trang, do các hàm và biến được bảo vệ trong phạm vi của module.

Tóm lại, mẫu thiết kế module trong JavaScript giúp tổ chức và cấu trúc mã nguồn một cách tốt hơn, tạo ra các đơn vị mã độc lập giúp dễ dàng bảo trì và mở rộng, đồng thời giữ cho mã nguồn sạch sẽ và dễ quản lý.

135

Higher-Order Function trong Javascript là gì?

Higher-Order Function trong JavaScript là một hàm có khả năng nhận một hoặc nhiều hàm khác làm đối số hoặc trả về một hàm như là kết quả của nó. Điều này cho phép các chương trình có thể tạo ra các hàm tùy chỉnh dựa trên các hàm khác, tăng cường khả năng tái sử dụng và modular hóa mã.

Đặc điểm của Higher-Order Function

  • Nhận hàm làm đối số: Một higher-order function có thể nhận một hoặc nhiều hàm khác làm đối số. Điều này cho phép nó thực hiện các thao tác hoặc tính toán dựa trên hàm được truyền vào.
  • Trả về hàm: Ngoài việc nhận hàm làm đối số, một higher-order function cũng có thể tạo và trả về một hàm mới. Hàm mới này có thể sử dụng hoặc mở rộng logic của các hàm đầu vào.

Ví dụ về Higher-Order Function

Nhận hàm làm đối số

javascript
function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    action(i);
  }
}

repeat(3, console.log); // In ra 0, 1, 2

Trong ví dụ trên, repeat là một higher-order function vì nó nhận console.log (một hàm) làm đối số.

Trả về hàm

javascript
function greaterThan(n) {
  return m => m > n;
}

let greaterThan10 = greaterThan(10);
console.log(greaterThan10(11)); // true

Ở đây, greaterThan là một higher-order function vì nó trả về một hàm mới, hàm này kiểm tra giá trị đầu vào có lớn hơn n hay không.

Lợi ích của Higher-Order Function

  • Tái sử dụng mã: Cho phép tái sử dụng logic chung trong nhiều hàm khác nhau, giảm bớt sự trùng lặp mã.
  • Tạo ra các hàm động: Có thể tạo ra các hàm mới dựa trên các đối số hoặc điều kiện đầu vào, làm cho mã linh hoạt và mạnh mẽ hơn.
  • Hỗ trợ lập trình hàm: Là một khái niệm cốt lõi trong lập trình hàm, giúp xây dựng các chương trình dễ đọc, dễ bảo trì và ít lỗi hơn.

Tóm lại, higher-order functions là một công cụ mạnh mẽ trong JavaScript, giúp tạo ra các chương trình linh hoạt, tái sử dụng mã và hỗ trợ lập trình hàm.

136

Làm thế nào để ngăn chặn Callback Hell mà không sử dụng promises, async hoặc generators?

Để ngăn chặn Callback Hell mà không sử dụng promises, async hoặc generators, bạn có thể áp dụng một số kỹ thuật sau:

  1. Chia nhỏ các hàm: Thay vì viết một hàm lớn với nhiều callback lồng nhau, hãy chia nhỏ chúng thành các hàm nhỏ hơn và tái sử dụng được. Mỗi hàm sẽ thực hiện một nhiệm vụ cụ thể và gọi callback tiếp theo.
  2. Sử dụng các hàm đặt tên: Thay vì sử dụng các hàm vô danh làm callback, hãy đặt tên cho các hàm callback của bạn. Điều này giúp mã nguồn dễ đọc và dễ debug hơn, vì tên hàm sẽ xuất hiện trong stack trace.
  3. Tránh lồng ghép sâu: Nếu bạn thấy mình đang lồng ghép nhiều cấp độ callback, hãy xem xét việc tái cấu trúc mã nguồn của bạn. Bạn có thể sử dụng các hàm trung gian để giảm độ sâu của lồng ghép.
  4. Xử lý lỗi một cách nhất quán: Khi sử dụng callback, hãy đảm bảo rằng bạn xử lý lỗi một cách nhất quán. Điều này giúp dễ dàng xác định và sửa chữa các vấn đề trong mã nguồn của bạn.
  5. Sử dụng các module như async: Mặc dù không phải là promises, async hoặc generators, nhưng module async cung cấp các hàm giúp quản lý luồng xử lý bất đồng bộ mà không cần lồng ghép callback. Ví dụ, async.waterfall hoặc async.series cho phép bạn thực hiện các tác vụ tuần tự mà không cần lồng ghép.
  6. Sử dụng sự kiện: Bạn cũng có thể sử dụng mô hình sự kiện (event-driven model) để quản lý các tác vụ bất đồng bộ. Thay vì sử dụng callback, bạn có thể phát ra và lắng nghe các sự kiện để xử lý các tác vụ.
  7. Modularization: Phân chia mã nguồn thành các module nhỏ và rõ ràng, mỗi module thực hiện một chức năng cụ thể và có thể được tái sử dụng ở nhiều nơi trong ứng dụng của bạn.

Những kỹ thuật này giúp bạn quản lý mã nguồn bất đồng bộ một cách hiệu quả hơn, giảm thiểu sự phức tạp và tăng cường khả năng bảo trì và mở rộng mã nguồn.

137

JavaScript truyền tham số theo kiểu tham chiếu (pass by references) hay theo kiểu tham trị (pass by values)?

JavaScript truyền tham số theo kiểu tham trị (pass by value). Tuy nhiên, khi chúng ta truyền đối tượng (bao gồm cả mảng và hàm) vào một hàm, thực chất chúng ta truyền địa chỉ tham chiếu của đối tượng đó như một giá trị. Điều này có nghĩa là bất kỳ thay đổi nào đối với thuộc tính của đối tượng trong hàm sẽ ảnh hưởng đến đối tượng gốc, vì cả hai đều tham chiếu đến cùng một vị trí trong bộ nhớ.

Các kiểu dữ liệu nguyên thủy như số (number), chuỗi (string), boolean, null, undefined và symbol được truyền theo giá trị. Khi bạn gán một biến có kiểu dữ liệu nguyên thủy cho một biến khác, JavaScript lưu trữ giá trị trực tiếp và bất kỳ thay đổi nào đối với một biến không ảnh hưởng đến biến kia.

Mặt khác, các kiểu dữ liệu tham chiếu như đối tượng (object), mảng (array) và hàm (function) khi được gán từ một biến này sang biến khác, JavaScript không lưu trữ giá trị thực sự mà là một tham chiếu đến giá trị đó. Bất kỳ thay đổi nào thông qua tham chiếu này sẽ ảnh hưởng đến giá trị gốc vì chúng cùng tham chiếu đến cùng một nơi trong bộ nhớ.

Ví dụ, khi bạn truyền một biến kiểu nguyên thủy vào một hàm, một bản sao của giá trị được tạo ra và sử dụng trong hàm. Bất kỳ thay đổi nào đối với giá trị này trong hàm không ảnh hưởng đến biến gốc. Tuy nhiên, khi bạn truyền một đối tượng vào một hàm, tham chiếu đến đối tượng được truyền đi. Bất kỳ thay đổi nào đối với đối tượng trong hàm sẽ phản ánh trên đối tượng gốc.