+11

JavaScript Nâng Cao - Kỳ 4

Có một câu nói 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é.

Series này có thể sẽ khá dài mình không biết sẽ có bao nhiêu Kỳ tuy nhiên để tiện cho các bạn nào không đọc các bài trước đó của mình về JS thì trong loạt bài này mình sẽ giải thích lại toàn bộ. Các lý thuyết trong loạt bài này mình cũng có thể sẽ giải thích lại nhiều lần (tùy hứng) để các bạn có thể năm rõ nó hơn 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ép tăng trong JavaScript

Chắc hẳn các bạn đã từng thấy và sử dụng toán tử ++ trong JavaScript (hoặc các ngôn ngữ lập trình khác). Đây là một toán tử thú vị và phổ biến, nhưng đôi khi lại gây ra một chút nhầm lẫn cho người mới học. Hãy cùng mình tìm hiểu vấn đề qua ví dụ sau:

let number = 0;
console.log(number++);
console.log(++number);
console.log(number);

Output của đoạn code trên sẽ là:

  • A: 1 1 2
  • B: 1 2 2
  • C: 0 2 2
  • D: 0 1 2
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: C

Để dễ nhớ, mình xin phép so sánh phép toán này với việc các bạn đi mua bánh mì. Hãy tưởng tượng khi bạn đến 1 quầy hàng, trước khi mua bạn muốn biết giá của nó là bao nhiêu, sau khi biết rồi bạn mới quyết định mua hay không mua. Hoặc ngược lại, bạn không cần biết giá mà mua ngay và sau đó mới hỏi giá. Đó chính là sự khác biệt giữa việc toán tử ++ nằm ở đằng trước hay đằng sau biến.

1.1 Phép tăng postfix (number++)

Khi bạn gặp number++, hãy tưởng tượng bạn đang hỏi giá bánh mì trước:

  1. Trả về giá trị gốc của number (trong trường hợp này là 0).
  2. Tăng giá trị của number lên 1.

Do đó, lệnh console.log(number++) sẽ xuất ra 0, nhưng sau đó, giá trị của number đã tăng lên và trở thành 1.

1.2. Phép tăng prefix (++number)

Còn khi gặp ++number, đây giống như việc bạn mua bánh mì trước mà không cần biết giá:

  1. Tăng giá trị của number lên trước (từ 1 lên 2).
  2. Trả về giá trị mới của number (trở thành 2).

Nên lệnh console.log(++number) sẽ in ra màn hình giá trị 2.

1.3. Kết quả cuối cùng

Kết thúc đoạn code, number đã được tăng lên 2 lần và có giá trị cuối cùng là 2, nên lệnh console.log(number) sẽ in ra màn hình số 2.

Vậy nên, kết quả đúng của đoạn code trên sẽ là:

0
2
2

Giống như đáp án C mà bạn đã đưa ra. Rất đơn giản và dễ hiểu phải không nào? Mình hy vọng với ví dụ mua bánh mì này, các bạn sẽ dễ dàng nhớ và áp dụng phép tăng này hơn.

2. Tagged Template Literals

Chúng ta có đoạn code JavaScript như sau:

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

Khi chạy đoạn code trên, output là gì?

  • A: "Lydia" 21 ["", " is ", " years old"]
  • B: ["", " is ", " years old"] "Lydia" 21
  • C: "Lydia" ["", " is ", " years old"] 21
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: B

2.2. Cơ bản về Template Literals

Trước khi mình giải thích vấn đề trên, các bạn cần phải hiểu về template literals trong JavaScript. Template literals (được bắt đầu và kết thúc bằng dấu ` ` ) cho phép bạn nhúng biến hoặc biểu thức vào chuỗi một cách dễ dàng:

let ten = "Tony";
console.log(`Chào ${ten}!`); // Chào Tony!

2.2. Tagged Template Literals: Là gì?

Tagged template literals là một cách nâng cao để sử dụng template literals. Chúng cho phép bạn sử dụng một hàm để xử lý một template literal.

Vậy, getPersonInfo trong đoạn code của chúng ta là một "tag" cho template literal.

2.3. Làm thế nào Tagged Template Literals hoạt động?

Khi bạn sử dụng một tag cho một template literal, các chuỗi và biểu thức trong template literal đó sẽ được "phân rã" và truyền vào hàm như các tham số.

Thứ tự của các tham số là điều quan trọng cần chú ý:

  • Đối số đầu tiên: Một mảng các chuỗi
  • Các đối số tiếp theo: Giá trị của biểu thức trong template literal

2.4. Áp dụng vào vấn đề của chúng ta

Xem xét đoạn code của mình:

getPersonInfo`${person} is ${age} years old`;
  • one (đối số đầu tiên) sẽ nhận một mảng các chuỗi: ["", " is ", " years old"]
  • two (đối số thứ hai) sẽ nhận giá trị từ biểu thức đầu tiên: "Lydia"
  • three (đối số thứ ba) sẽ nhận giá trị từ biểu thức thứ hai: 21

Như vậy, output sẽ là:

["", " is ", " years old"]
"Lydia"
21

2.5. Ví dụ khác**

Để hiểu rõ hơn, chúng ta thử một ví dụ khác:

function testTag(one, two, three, four) {
  console.log(one);
  console.log(two);
  console.log(three);
  console.log(four);
}

let fruit = "táo";
let price = 10000;
testTag`Một kg ${fruit} giá ${price} yen.`;

Output sẽ là:

["Một kg ", " giá ", " yen."]
"táo"
10000
undefined

=> Tagged Template Literals là một công cụ mạnh mẽ, cho phép bạn tùy chỉnh cách template literals hoạt động.

3. So sánh trong 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 });

Khi chạy đoạn code trên, output là gì?

  • A: You are an adult!
  • B: You are still an adult.
  • C: Hmm.. You don't have an age I guess
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: C

Câu hỏi trên yêu cầu xác định output sau khi chạy hàm checkAge({ age: 18 });. Điều này liên quan đến cách mà JavaScript so sánh giá trị và tham chiếu, đặc biệt là trong trường hợp của object.

3.1. Bên trong đoạn code

Trước hết, hãy xem xét hàm checkAge(data):

  • Hàm này kiểm tra nếu data bằng với { age: 18 } (sử dụng toán tử so sánh triệt để ===), nó sẽ in ra You are an adult!.
  • Nếu data bằng với { age: 18 } (sử dụng toán tử so sánh đơn ==), nó sẽ in ra You are still an adult..
  • Trong trường hợp còn lại, nó sẽ in ra Hmm.. You don't have an age I guess.

3.2. So sánh trong JavaScript

Trong JavaScript, chúng ta có hai loại so sánh:

3.2.1. So sánh triệt để (===)

Để giá trị và kiểu dữ liệu cả hai bên đều bằng nhau.

console.log(3 === 3); // true
console.log('3' === 3); // false vì kiểu dữ liệu không giống nhau

3.2.2. So sánh đơn (==)

Chỉ kiểm tra giá trị, không quan tâm đến kiểu dữ liệu.

console.log('3' == 3); // true
console.log(true == 1); // true

3.3. So sánh object

Giờ mình sẽ đến phần hay ho nhất, khi so sánh các object.

3.3.1. So sánh tham chiếu

Chúng ta đã biết rằng, khi so sánh các object trong JS, ta không phải so sánh nội dung bên trong chúng, mà lại so sánh địa chỉ tham chiếu của chúng.

Hãy xem ví dụ sau:

const person1 = { name: "Man" };
const person2 = { name: "Man" };
console.log(person1 === person2); // false

Dù cả hai object person1person2 đều có nội dung giống hệt nhau, kết quả lại trả về false. Đó là vì mỗi object thực chất là một "cái hộp" riêng biệt, được lưu trữ ở một địa chỉ khác nhau trong bộ nhớ.

3.4. Áp dụng vào đoạn code ban đầu ta có

Với đoạn code được đưa ra, khi gọi checkAge({ age: 18 });, ta đang tạo một object mới { age: 18 } và truyền nó vào hàm.

Cả hai lệnh so sánh:

  • data === { age: 18 }
  • data == { age: 18 }

đều trả về false vì mỗi lần chúng ta tạo một object { age: 18 }, JS đều tạo ra một đối tượng mới ở một vùng nhớ riêng biệt.

Vì vậy, output cuối cùng của hàm checkAge({ age: 18 }); sẽ là Hmm.. You don't have an age I guess.

Đó là lý do vì sao đáp án là C: Hmm.. You don't have an age I guess

4. Spread Operator và Typeof

function getAge(...args) {
  console.log(typeof args);
}

getAge(21);

Khi chạy đoạn code trên, output là gì?

  • A: "number"
  • B: "array"
  • C: "object"
  • D: "NaN"
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: C

4.1. Spread Operator (...) - Một công cụ mạnh mẽ

Trước hết, mình muốn chắc chắn rằng các bạn đã nghe qua về Spread Operator, đúng không? Dấu ba chấm ... trước một biến không phải là dấu lặp đi lặp lại, mà đó chính là Spread Operator trong JavaScript. Nhưng vậy Spread Operator dùng làm gì?

4.1.1. Đặc điểm cơ bản của Spread Operator

Spread Operator giúp chúng ta "trải" các phần tử của mảng hoặc các thuộc tính của đối tượng ra. Ví dụ cơ bản về việc sử dụng nó với mảng:

let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5]; // Kết quả: [1, 2, 3, 4, 5]

Ở đây, mình đã "trải" toàn bộ phần tử trong arr1 vào arr2.

4.2. Làm quen với typeof

typeof là một toán tử trong JavaScript dùng để kiểm tra kiểu dữ liệu của một biến. Rất đơn giản, nhưng rất mạnh!

Ví dụ:

let age = 21;
console.log(typeof age); // Kết quả: "number"

Nhưng khi đến mảng hoặc đối tượng, kết quả lại khác một chút:

let ages = [20, 30];
console.log(typeof ages); // Kết quả: "object"

Bạn thấy đấy, dù ages là một mảng, nhưng typeof lại trả về "object". Điều này làm cho nhiều người mới học JavaScript bị bối rối. Trong javaScript có 8 kiểu dữ liệu cơ bản, trong đó, có 7 kiểu dữ liệu nguyên thủy (boolean, null, undefined, number, BigInt, string, symbol) và 1 kiểu dữ liệu dạng tham chiếu (object). Có nghĩa là ngoài 7 kiểu dữ liệu nguyên thủy ra thì còn lại hầu hết đều là object.

4.3. Kết hợp Spread Operator và typeof

Giờ, quay lại với câu hỏi ban đầu. Mình đã giải thích Spread Operator, và mình cũng đã nói về typeof. Bây giờ, mình kết hợp chúng:

function getAge(...args) {
  console.log(typeof args);
}

getAge(21);

Ở đây, ...args dùng Spread Operator để lấy tất cả các đối số được truyền vào hàm. Mặc dù chúng ta chỉ truyền vào một số (21), nhưng args vẫn là một mảng. Và như đã giải thích ở phần trước, typeof một mảng sẽ trả về "object".

Vì vậy, khi bạn chạy đoạn mã trên, console.log sẽ in ra "object". Đáp án chính xác cho câu hỏi này là C.

Hy vọng rằng sau phần giải thích này, các bạn đã hiểu rõ hơn về Spread Operator và typeof trong JavaScript.

5. Giải đáp về "use strict" và lỗi ReferenceError

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

getAge();

Khi chạy đoạn code trên, output là gì?

  • A: 21
  • B: undefined
  • C: ReferenceError
  • D: TypeError
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: C

Chắc các bạn đôi khi đã từng thấy hoặc nghe đến từ khóa "use strict" trong JavaScript và tự hỏi nó là gì và dùng làm gì, đúng không? Trong phần này, mình sẽ giải thích cho các bạn về "use strict" thông qua một ví dụ cụ thể.

5.1. Một chút về "use strict"

Chắc hẳn mọi người đã nghe tới "use strict" (chế độ nghiêm ngặt) trong Javascript. Để giúp code của mình trở nên chặt chẽ hơn, JavaScript đã giới thiệu một tính năng mang tên "use strict". Khi bạn thêm câu lệnh này vào đầu file hoặc đầu một hàm, nó sẽ giúp bạn xác định một số lỗi mà trước đây JavaScript đã bỏ qua.

5.2. Phân tích lỗi trong đoạn code ban đầu

5.2.1. Khai báo biến trong JavaScript

Trong JavaScript, khi bạn muốn sử dụng một biến, bạn cần khai báo nó trước, thường sử dụng các từ khóa như var, let, const. Nếu không, biến sẽ trở thành biến global và nằm trong global object (là window nếu chạy trong trình duyệt).

5.2.2. Lỗi gặp phải khi dùng "use strict"

Trong đoạn code trên, mình chưa khai báo biến age mà đã gán giá trị cho nó. Khi thêm "use strict" vào đầu hàm, nó sẽ không cho phép chúng ta làm như vậy và sẽ ném ra lỗi ReferenceError.

Còn nếu bạn không sử dụng "use strict", đoạn code sẽ vẫn chạy mà không có lỗi, và biến age sẽ tự động trở thành biến global.

Như vậy, từ ví dụ trên, các bạn đã thấy được tầm quan trọng của "use strict". Nó giúp anh em javascript dev tránh được nhiều sai sót mà có thể gây ra lỗi trong tương lai.

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.