+12

JavaScript Nâng Cao - Kỳ 19

Có một câu nói vui là: Trên đời chỉ có thứ nhiều người chửi và thứ không ai thèm dùng.

Javascript là một ví dụ điển hình, nó có một số điểm thú vị nhưng cũng khiến chúng ta phải đau đầu. Lý thuyết thì dễ hiểu, nhưng khi thực hành là cả một vấn đề. Vậy nên, mình sẽ cùng các bạn đi sâu vào từng ví dụ cụ thể và phân tích, mổ xẻ nó để hiểu hơn về Javascript nhé.

Ok vào bài thôi nào... GÉT GÔ 🚀

Nếu có bất kỳ câu hỏi nào đừng ngại hãy bình luận dưới phần comment nhé. Hoặc chỉ cần để lại một comment chào mình là đã giúp mình có thêm động lực hoàn thành series này. Cảm ơn các bạn rất nhiều. 🤗

1. Phương thức push() và giá trị trả về

let newList = [1, 2, 3].push(4)

console.log(newList.push(5))
  • A: [1, 2, 3, 4, 5]
  • B: [1, 2, 3, 5]
  • C: [1, 2, 3, 4]
  • D: Error
Đáp án của câu hỏi này là 
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: D

Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️

1.1. Phương thức push() trong JavaScript

Trước hết, chúng ta cần hiểu rõ về phương thức push() trong JavaScript. Phương thức này được sử dụng để thêm một hoặc nhiều phần tử vào cuối mảng. Tuy nhiên, điều quan trọng cần lưu ý là push() không trả về mảng mới sau khi thêm phần tử, mà nó trả về độ dài mới của mảng.

1.2. Phân tích đoạn code

Hãy xem xét từng dòng code:

let newList = [1, 2, 3].push(4)

Ở đây, chúng ta đang gán giá trị trả về của [1, 2, 3].push(4) cho biến newList. Như đã nói, push() trả về độ dài mới của mảng. Vậy newList sẽ có giá trị là 4 (độ dài của mảng [1, 2, 3, 4]).

console.log(newList.push(5))

Ở dòng này, chúng ta đang cố gắng gọi phương thức push() trên newList. Tuy nhiên, newList không phải là một mảng, mà là một số (4). Số không có phương thức push(), vì vậy JavaScript sẽ throw ra một TypeError.

1.3. Kết luận

Đáp án đúng là D: Error. Cụ thể hơn, chúng ta sẽ nhận được một TypeError với thông báo kiểu như "newList.push is not a function".

1.4. Bài học rút ra

Đây là một ví dụ điển hình về việc cần phải hiểu rõ giá trị trả về của các phương thức trong JavaScript. Nhiều lập trình viên mới thường nhầm tưởng rằng push() trả về mảng mới, nhưng thực tế nó trả về độ dài mới của mảng.

Để tránh lỗi này, chúng ta có thể viết lại code như sau:

let arr = [1, 2, 3];
arr.push(4);
arr.push(5);
console.log(arr); // [1, 2, 3, 4, 5]

Hoặc nếu muốn tạo một mảng mới:

let newList = [...[1, 2, 3], 4];
newList = [...newList, 5];
console.log(newList); // [1, 2, 3, 4, 5]

2. Prototype trong JavaScript

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

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

console.log(giveLydiaPizza.prototype)
console.log(giveLydiaChocolate.prototype)
  • A: { constructor: ...} { constructor: ...}
  • B: {} { constructor: ...}
  • C: { constructor: ...} {}
  • D: { constructor: ...} undefined
Đáp án của câu hỏi này là 
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: D

Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️

2.1. Prototype trong JavaScript

Trong JavaScript, mọi function đều có một thuộc tính prototype mặc định. Tuy nhiên, điều này chỉ đúng với các function thông thường (regular functions), không áp dụng cho arrow functions.

2.2. Regular Function vs Arrow Function

  1. Regular Function (giveLydiaPizza):

    • Được khai báo bằng từ khóa function
    • Có thuộc tính prototype
    • prototype là một object chứa thuộc tính constructor và có thể được mở rộng
  2. Arrow Function (giveLydiaChocolate):

    • Được khai báo bằng cú pháp () =>
    • Không có thuộc tính prototype

2.3. Phân tích kết quả

  • console.log(giveLydiaPizza.prototype): Sẽ in ra một object có dạng { constructor: f }, trong đó f chính là function giveLydiaPizza.

  • console.log(giveLydiaChocolate.prototype): Sẽ in ra undefined vì arrow function không có thuộc tính prototype.

2.4. Tại sao lại như vậy?

Sự khác biệt này xuất phát từ cách JavaScript xử lý các loại function khác nhau:

  1. Regular functions được thiết kế để có thể được sử dụng như constructors (với từ khóa new), vì vậy chúng cần có prototype.

  2. Arrow functions không thể được sử dụng như constructors và không có this của riêng chúng, vì vậy chúng không cần prototype.

2.5. Ví dụ minh họa

function RegularFunc() {}
const ArrowFunc = () => {};

console.log(RegularFunc.prototype); // { constructor: f }
console.log(ArrowFunc.prototype); // undefined

// Có thể thêm methods vào prototype của regular function
RegularFunc.prototype.newMethod = function() {};

// Không thể làm điều tương tự với arrow function
// ArrowFunc.prototype.newMethod = function() {}; // Throws TypeError

2.6. Kết luận

Hiểu về prototype và sự khác biệt giữa regular functions và arrow functions là rất quan trọng trong JavaScript. Nó không chỉ giúp bạn hiểu rõ hơn về cách ngôn ngữ hoạt động, mà còn giúp bạn tránh được những lỗi không đáng có khi làm việc với OOP trong JavaScript.

3. Object.entries() và Destructuring

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

for (const [x, y] of Object.entries(person)) {
  console.log(x, y)
}
  • A: name Lydiaage 21
  • B: ["name", "Lydia"]["age", 21]
  • C: ["name", "age"]undefined
  • D: Error
Đáp án của câu hỏi này là 
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: A

Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️

3.1. Object.entries()

Object.entries() là một phương thức trong JavaScript được sử dụng để chuyển đổi một object thành một mảng các cặp [key, value]. Mỗi cặp [key, value] được biểu diễn dưới dạng một mảng con.

Ví dụ:

const obj = { a: 1, b: 2 };
console.log(Object.entries(obj)); // [['a', 1], ['b', 2]]

3.2. Destructuring trong vòng lặp for...of

Trong đoạn code của chúng ta, chúng ta sử dụng destructuring assignment trong vòng lặp for...of. Cú pháp const [x, y] cho phép chúng ta gán giá trị của mảng con (được trả về bởi Object.entries()) vào hai biến xy.

3.3. Phân tích từng bước

  1. Object.entries(person) trả về:

    [
      ['name', 'Lydia'],
      ['age', 21]
    ]
    
  2. Vòng lặp for...of sẽ lặp qua từng mảng con:

    • Trong lần lặp đầu tiên: [x, y] = ['name', 'Lydia']
    • Trong lần lặp thứ hai: [x, y] = ['age', 21]
  3. Trong mỗi lần lặp, console.log(x, y) sẽ in ra giá trị của x (key) và y (value).

3.4. Kết quả

Vì vậy, output sẽ là:

name Lydia
age 21

3.5. Ví dụ mở rộng

Để hiểu rõ hơn về cách Object.entries() và destructuring hoạt động, hãy xem xét ví dụ sau:

const car = {
  brand: 'Toyota',
  model: 'Camry',
  year: 2022
};

for (const [key, value] of Object.entries(car)) {
  console.log(`${key}: ${value}`);
}

Output:

brand: Toyota
model: Camry
year: 2022

3.6. Lưu ý

Destructuring là một tính năng mạnh mẽ trong JavaScript, cho phép chúng ta trích xuất dữ liệu từ arrays hoặc objects một cách dễ dàng. Kết hợp với Object.entries(), nó cung cấp một cách tiện lợi để duyệt qua các cặp key-value của một object.

4. Rest Parameters và Syntax Error

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

getItems(["banana", "apple"], "pear", "orange")
  • A: ["banana", "apple", "pear", "orange"]
  • B: [["banana", "apple"], "pear", "orange"]
  • C: ["banana", "apple", ["pear"], "orange"]
  • D: SyntaxError
Đáp án của câu hỏi này là 
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: D

Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️

4.1. Rest Parameters trong JavaScript

Rest parameters là một tính năng trong JavaScript cho phép một hàm nhận một số lượng đối số không xác định dưới dạng một mảng. Nó được biểu thị bằng ba dấu chấm (...) trước tên tham số.

4.2. Quy tắc sử dụng Rest Parameters

Có một quy tắc quan trọng khi sử dụng rest parameters: nó phải là tham số cuối cùng trong danh sách tham số của hàm. Nếu không tuân thủ quy tắc này, JavaScript sẽ throw ra một SyntaxError.

4.3. Phân tích đoạn code

Trong đoạn code của chúng ta:

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

Chúng ta đang vi phạm quy tắc sử dụng rest parameters. ...args không phải là tham số cuối cùng, mà là favoriteFruit.

4.4. Tại sao lại xảy ra lỗi?

JavaScript engine không thể xác định được đâu là phần thuộc về ...args và đâu là favoriteFruit. Nếu cho phép cú pháp này, sẽ dẫn đến sự mơ hồ trong cách truyền đối số vào hàm.

4.5. Cách sửa lỗi

Để sửa lỗi này, chúng ta cần đặt rest parameter ở cuối danh sách tham số:

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

console.log(getItems(["banana", "apple"], "pear", "orange"))
// Output: ["banana", "apple", "pear", "orange"]

4.6. Ví dụ minh họa khác

Để hiểu rõ hơn về cách sử dụng rest parameters, hãy xem xét một vài ví dụ sau:

// Ví dụ 1: Sử dụng rest parameter đúng cách
function sum(first, second, ...rest) {
  let result = first + second;
  for (let num of rest) {
    result += num;
  }
  return result;
}

console.log(sum(1, 2, 3, 4, 5)); // Output: 15

// Ví dụ 2: Sử dụng rest parameter với destructuring
function introduceTeam(teamName, ...members) {
  console.log(`Team ${teamName} has ${members.length} members:`);
  members.forEach(member => console.log(member));
}

introduceTeam("JavaScript", "Alice", "Bob", "Charlie");
// Output:
// Team JavaScript has 3 members:
// Alice
// Bob
// Charlie

4.7. Lưu ý quan trọng

  1. Rest parameter luôn tạo ra một mảng, ngay cả khi không có đối số nào được truyền vào.
  2. Chỉ có thể có một rest parameter trong một hàm.
  3. Rest parameter không thể được sử dụng trong object literal setters.
// Không hợp lệ
let obj = {
  set latest(...args) { // SyntaxError
    // code
  }
};

4.8. Kết luận

Hiểu và sử dụng đúng rest parameters là một kỹ năng quan trọng trong JavaScript hiện đại. Nó cho phép chúng ta viết các hàm linh hoạt hơn, có thể xử lý số lượng đối số không xác định. Tuy nhiên, cần nhớ rằng rest parameter phải luôn là tham số cuối cùng trong danh sách tham số của hàm để tránh SyntaxError.

5. Automatic Semicolon Insertion (ASI) trong JavaScript

function nums(a, b) {
  if
  (a > b)
  console.log('a is bigger')
  else 
  console.log('b is bigger')
  return 
  a + b
}

console.log(nums(4, 2))
console.log(nums(1, 2))
  • A: a is bigger, 6b is bigger, 3
  • B: a is bigger, undefinedb is bigger, undefined
  • C: undefinedundefined
  • D: SyntaxError
Đáp án của câu hỏi này là 
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: B

Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️

5.1. Automatic Semicolon Insertion (ASI)

ASI là một tính năng của JavaScript, nơi trình thông dịch tự động thêm dấu chấm phẩy (😉 vào cuối các câu lệnh khi nó nghĩ rằng cần thiết. Điều này cho phép lập trình viên bỏ qua dấu chấm phẩy trong nhiều trường hợp, nhưng đôi khi có thể dẫn đến những kết quả không mong muốn.

5.2. Phân tích đoạn code

Hãy xem xét hàm nums:

function nums(a, b) {
  if
  (a > b)
  console.log('a is bigger')
  else 
  console.log('b is bigger')
  return 
  a + b
}

Có hai điểm quan trọng cần lưu ý ở đây:

  1. Cú pháp của câu lệnh if...else là hợp lệ, mặc dù không được viết trên cùng một dòng.
  2. Câu lệnh return và biểu thức a + b được viết trên hai dòng khác nhau.

5.3. ASI và câu lệnh return

Khi return và giá trị trả về được viết trên hai dòng khác nhau, ASI sẽ tự động thêm dấu chấm phẩy sau return. Điều này có nghĩa là hàm sẽ trả về undefined ngay lập tức, và dòng code a + b sẽ không bao giờ được thực thi.

Thực tế, JavaScript sẽ hiểu đoạn code trên như sau:

function nums(a, b) {
  if (a > b)
    console.log('a is bigger');
  else 
    console.log('b is bigger');
  return; 
  a + b; // Dòng code này không bao giờ được thực thi
}

5.4. Kết quả thực thi

  • Khi gọi nums(4, 2):

    • In ra "a is bigger"
    • Trả về undefined
  • Khi gọi nums(1, 2):

    • In ra "b is bigger"
    • Trả về undefined

5.5. Cách sửa lỗi

Để hàm hoạt động như mong muốn, chúng ta nên viết return và biểu thức trả về trên cùng một dòng:

function nums(a, b) {
  if (a > b)
    console.log('a is bigger')
  else 
    console.log('b is bigger')
  return a + b
}

console.log(nums(4, 2)) // "a is bigger", 6
console.log(nums(1, 2)) // "b is bigger", 3

5.6. Kinh nghiệm

  1. Luôn cẩn thận khi sử dụng return trong JavaScript. Nếu bạn muốn trả về một giá trị, hãy đảm bảo rằng nó nằm trên cùng một dòng với từ khóa return hoặc bắt đầu ngay sau dấu mở ngoặc.

  2. Mặc dù ASI giúp code trở nên "sạch" hơn bằng cách cho phép bỏ qua dấu chấm phẩy, nhưng nó cũng có thể dẫn đến những lỗi khó phát hiện. Vì vậy, nhiều lập trình viên vẫn chọn cách viết dấu chấm phẩy một cách rõ ràng.

  3. Sử dụng các công cụ linting như ESLint có thể giúp phát hiện những vấn đề tiềm ẩn liên quan đến ASI.

5.7. Kết luận

ASI là một tính năng gây tranh cãi trong JavaScript. Mặc dù nó có thể giúp code trông "sạch" hơn, nhưng cũng có thể dẫn đến những lỗi không mong muốn. Hiểu rõ về cách ASI hoạt động sẽ giúp bạn tránh được những lỗi như trong ví dụ trên và viết code JavaScript an toàn và đáng tin cậy hơn. Tuy nhiên gần đây đa số dự án đều sử dụng TypeScript và ESLint để giúp kiểm tra lỗi trước khi chạy code nên gặp lỗi này trở nên hiếm hơn.


Đó là tất cả những gì chúng ta cần biết về 5 ví dụ trong JavaScript Nâng Cao - Kỳ 19. Hy vọng qua bài viết này, các bạn đã hiểu rõ hơn về cách hoạt động của push(), prototype, Object.entries(), rest parameters, và Automatic Semicolon Insertion trong JavaScript.

Hãy nhớ rằng, để trở thành một lập trình viên JavaScript giỏi, bạn không chỉ cần biết cú pháp, mà còn phải hiểu sâu về cách ngôn ngữ hoạt động. Tiếp tục thực hành và đừng ngại đặt câu hỏi nhé!

Hẹn gặp lại các bạn trong những bài viết tiếp theo của series JavaScript Nâng Cao. Chúc các bạn học tập tốt! 🚀🎉

Nếu có bất kỳ câu hỏi nào đừng ngại hãy bình luận dưới phần comment nhé. Hoặc chỉ cần để lại một comment chào mình là đã giúp mình có thêm động lực hoàn thành series này. Cảm ơn các bạn rất nhiều. 🤗


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí