JavaScript Nâng Cao - Kỳ 18
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é
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ộtcomment 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. Truthy và Falsy trong JavaScript
Bằng cách nào chúng ta có thể set hasName
bằng true
, nếu chúng ta không đưa true
vào đối số?
function getName(name) {
const hasName = //
}
- A:
!!name
- B:
name
- C:
new Boolean(name)
- D:
name.length
Đáp án 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é ❓️
1.1. Truthy và Falsy là gì?
Trước khi đi vào giải thích đáp án, chúng ta cần hiểu về khái niệm "truthy" và "falsy" trong JavaScript.
- Truthy: Là những giá trị mà khi ép kiểu về boolean sẽ cho kết quả
true
. - Falsy: Là những giá trị mà khi ép kiểu về boolean sẽ cho kết quả
false
.
Trong JavaScript, có 6 giá trị falsy:
false
0
(số không)''
hoặc""
(chuỗi rỗng)null
undefined
NaN
Tất cả các giá trị khác đều là truthy.
1.2. Toán tử !! (Double NOT)
Toán tử !!
(Double NOT) là một cách ngắn gọn để chuyển đổi một giá trị sang kiểu boolean. Nó hoạt động như sau:
- Toán tử
!
đầu tiên chuyển đổi giá trị thành boolean và đảo ngược nó. - Toán tử
!
thứ hai lại đảo ngược kết quả một lần nữa.
Ví dụ:
console.log(!!0); // false
console.log(!!"hello"); // true
console.log(!!null); // false
console.log(!!1); // true
1.3. Giải thích đáp án
Quay lại câu hỏi ban đầu, chúng ta cần set hasName
thành true
nếu name
có giá trị (không phải falsy).
-
Đáp án A (
!!name
): Đây là cách chính xác.!!name
sẽ chuyển đổiname
thành boolean. Nếuname
có giá trị (truthy),!!name
sẽ trả vềtrue
. -
Đáp án B (
name
): Không chính xác vì nó chỉ gán giá trị củaname
chohasName
, không chuyển đổi thành boolean. -
Đáp án C (
new Boolean(name)
): Không chính xác vì nó tạo ra một object Boolean, không phải giá trị boolean nguyên thủy. -
Đáp án D (
name.length
): Không chính xác vì nó trả về độ dài của chuỗiname
, không phải giá trị boolean.
1.4. Ví dụ minh họa
Hãy xem xét ví dụ sau:
function getName(name) {
const hasName = !!name;
console.log(hasName);
}
getName("John"); // true
getName(""); // false
getName(null); // false
getName(undefined); // false
Trong ví dụ này, !!name
sẽ trả về true
cho bất kỳ giá trị truthy nào của name
, và false
cho các giá trị falsy.
1.5. Tóm lại
Sử dụng !!
là một cách ngắn gọn và hiệu quả để chuyển đổi một giá trị sang kiểu boolean trong JavaScript. Nó đặc biệt hữu ích khi bạn cần kiểm tra xem một biến có giá trị hay không mà không quan tâm đến giá trị cụ thể của nó.
2. Truy cập ký tự trong chuỗi
Output của đoạn code sau là gì?
console.log("I want pizza"[0])
- A:
"""
- B:
"I"
- C:
SyntaxError
- D:
undefined
Đá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é ❓️
2.1. Chuỗi trong JavaScript
Trong JavaScript, chuỗi (string) là một dãy các ký tự và được coi như một mảng các ký tự. Điều này có nghĩa là chúng ta có thể truy cập vào từng ký tự của chuỗi bằng cách sử dụng chỉ số (index), tương tự như cách chúng ta truy cập các phần tử trong mảng.
2.2. Truy cập ký tự trong chuỗi
Có hai cách chính để truy cập ký tự trong chuỗi:
- Sử dụng dấu ngoặc vuông
[]
và chỉ số. - Sử dụng phương thức
charAt()
.
Trong ví dụ của chúng ta, cách thứ nhất được sử dụng.
2.3. Giải thích đáp án
Trong đoạn code "I want pizza"
:
- Chuỗi "I want pizza" được coi như một mảng các ký tự.
- `` truy cập vào phần tử đầu tiên của "mảng" này.
- Phần tử đầu tiên (index 0) của chuỗi là ký tự "I".
Do đó, kết quả sẽ là "I"
, và đáp án đúng là B.
2.4. Các trường hợp khác
- Đáp án A (
"""
): Không đúng vì đây không phải là cách trả về một ký tự đơn. - Đáp án C (
SyntaxError
): Không đúng vì cú pháp này hoàn toàn hợp lệ trong JavaScript. - Đáp án D (
undefined
): Không đúng vì chỉ số 0 là hợp lệ và sẽ trả về ký tự đầu tiên, không phải undefined.
2.5. Ví dụ mở rộng
Hãy xem xét một vài ví dụ khác:
console.log("Hello"[1]); // "e"
console.log("World"[4]); // "d"
console.log("JavaScript"[10]); // "t"
console.log("Python"[6]); // undefined (vì chuỗi chỉ có 6 ký tự, index từ 0-5)
2.6. Lưu ý về tương thích
Như đã đề cập trong gợi ý, phương pháp sử dụng dấu ngoặc vuông []
không hoạt động trên Internet Explorer 7 trở xuống. Trong trường hợp cần hỗ trợ các trình duyệt cũ, bạn nên sử dụng phương thức charAt()
:
console.log("I want pizza".charAt(0)); // "I"
2.7. Tóm lại
Truy cập ký tự trong chuỗi bằng chỉ số là một tính năng mạnh mẽ của JavaScript, cho phép chúng ta dễ dàng thao tác với từng ký tự trong chuỗi. Tuy nhiên, cần lưu ý về vấn đề tương thích với các trình duyệt cũ và sử dụng phương thức thay thế khi cần thiết.
3. Giá trị mặc định cho tham số hàm
Output của đoạn code sau là gì?
function sum(num1, num2 = num1) {
console.log(num1 + num2)
}
sum(10)
- A:
NaN
- B:
20
- C:
ReferenceError
- D:
undefined
Đá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é ❓️
3.1. Giá trị mặc định cho tham số trong ES6
Từ ES6 (ECMAScript 2015) trở đi, JavaScript cho phép chúng ta định nghĩa giá trị mặc định cho các tham số của hàm. Điều này rất hữu ích khi chúng ta muốn đảm bảo rằng một tham số luôn có giá trị, ngay cả khi nó không được truyền vào khi gọi hàm.
Cú pháp cơ bản như sau:
function functionName(param1 = defaultValue1, param2 = defaultValue2) {
// function body
}
3.2. Phân tích đoạn code
Trong ví dụ của chúng ta:
function sum(num1, num2 = num1) {
console.log(num1 + num2)
}
sum(10)
num1
không có giá trị mặc định.num2
có giá trị mặc định lànum1
.
Khi chúng ta gọi sum(10)
:
num1
nhận giá trị 10.num2
không được truyền vào, nên nó sẽ lấy giá trị mặc định, chính là giá trị củanum1
(10).- Hàm thực hiện phép cộng: 10 + 10 = 20.
Do đó, kết quả in ra là 20.
3.3. Các trường hợp khác
- Nếu chúng ta gọi
sum(10, 5)
, kết quả sẽ là 15 vìnum2
được truyền giá trị 5, không sử dụng giá trị mặc định. - Nếu chúng ta gọi
sum()
, sẽ xảy ra lỗi vìnum1
không có giá trị mặc định và không được truyền giá trị.
3.4. Lưu ý quan trọng
Một điểm quan trọng cần nhớ là thứ tự của các tham số. Trong JavaScript, chúng ta chỉ có thể tham chiếu đến các tham số đã được khai báo trước đó khi đặt giá trị mặc định. Ví dụ:
// Hợp lệ
function foo(a, b = a) { ... }
// Không hợp lệ, sẽ gây ra lỗi
function bar(a = b, b) { ... }
3.5. Ví dụ mở rộng
Hãy xem xét một vài ví dụ phức tạp hơn:
function greet(name, greeting = "Hello", punctuation = "!") {
console.log(`${greeting}, ${name}${punctuation}`);
}
greet("John"); // "Hello, John!"
greet("Mary", "Hi"); // "Hi, Mary!"
greet("Tom", "Welcome", "..."); // "Welcome, Tom..."
3.6. Tóm lại
Giá trị mặc định cho tham số là một tính năng mạnh mẽ trong JavaScript hiện đại. Nó giúp code của chúng ta ngắn gọn hơn, dễ đọc hơn và giảm thiểu các lỗi có thể xảy ra khi tham số không được truyền vào. Tuy nhiên, cần lưu ý về thứ tự khai báo và sử dụng các tham số để tránh các lỗi không mong muốn.
4. Export và Import trong ES6 Modules
Output của đoạn code sau là gì?
// module.js
export default () => "Hello world"
export const name = "Lydia"
// index.js
import * as data from "./module"
console.log(data)
- A:
{ default: function default(), name: "Lydia" }
- B:
{ default: function default() }
- C:
{ default: "Hello world", name: "Lydia" }
- D: Global object of
module.js
Đáp án 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é ❓️
4.1. ES6 Modules
ES6 (ECMAScript 2015) giới thiệu hệ thống module mới cho JavaScript, cho phép chúng ta chia code thành các phần nhỏ, độc lập và có thể tái sử dụng. Hai khái niệm quan trọng trong ES6 modules là export
và import
.
4.2. Export trong ES6
Có hai loại export chính:
- Named exports: Cho phép export nhiều giá trị từ một module.
- Default export: Cho phép export một giá trị mặc định từ module.
Trong ví dụ của chúng ta:
// module.js
export default () => "Hello world"
export const name = "Lydia"
export default () => "Hello world"
là một default export.export const name = "Lydia"
là một named export.
4.3. Import trong ES6
Có nhiều cách để import trong ES6:
- Import named exports
- Import default export
- Import tất cả như một object
Trong ví dụ của chúng ta:
import * as data from "./module"
Đây là cách import tất cả các export (cả default và named) từ module như một object.
4.4. Giải thích đáp án
Khi chúng ta sử dụng import * as data
, JavaScript tạo ra một object chứa tất cả các export từ module:
- Default export được gán cho thuộc tính
default
của object. - Named exports được gán cho các thuộc tính có tên tương ứng.
Do đó, data
sẽ có dạng:
{
default: function default() { return "Hello world" },
name: "Lydia"
}
Đây chính xác là đáp án A.
4.5. Các đáp án khác
- Đáp án B không đúng vì nó bỏ qua named export
name
. - Đáp án C không đúng vì nó hiển thị giá trị trả về của hàm default thay vì chính hàm đó.
- Đáp án D không đúng vì
import * as data
không tạo ra global object.
4.6. Ví dụ mở rộng
Hãy xem xét một vài cách import khác:
// Chỉ import default export
import myFunction from "./module"
// Import named export
import { name } from "./module"
// Import cả default và named exports
import myFunction, { name } from "./module"
// Đổi tên khi import
import { name as myName } from "./module"
4.7. Lưu ý quan trọng
- Default export chỉ có thể có một trong mỗi module.
- Tên của default import có thể tùy chọn, không nhất thiết phải trùng với tên trong module gốc.
- Named exports phải được import với đúng tên (trừ khi sử dụng alias).
4.8. Tóm lại
ES6 Modules cung cấp một cách mạnh mẽ để tổ chức và chia sẻ code trong JavaScript. Hiểu rõ cách hoạt động của export
và import
sẽ giúp bạn xây dựng các ứng dụng JavaScript lớn và có cấu trúc tốt hơn. Việc sử dụng import * as
như trong ví dụ này cho phép bạn import tất cả các export từ một module dưới dạng một object, giúp quản lý các import dễ dàng hơn trong các module lớn.
5. Classes trong JavaScript
Output của đoạn code sau là gì?
class Person {
constructor(name) {
this.name = name
}
}
const member = new Person("John")
console.log(typeof member)
- A:
"class"
- B:
"function"
- C:
"object"
- D:
"string"
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: C
Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️
5.1. Classes trong JavaScript
Classes được giới thiệu trong ES6 như một cú pháp mới để định nghĩa các đối tượng và kế thừa dựa trên prototype. Tuy nhiên, điều quan trọng cần nhớ là classes trong JavaScript chỉ là "syntactic sugar" (cú pháp đường) cho cơ chế prototype-based inheritance đã tồn tại từ trước.
5.2. Constructor trong Classes
Constructor là một phương thức đặc biệt được sử dụng để khởi tạo các đối tượng mới được tạo ra từ class. Nó được gọi tự động khi chúng ta sử dụng từ khóa new
để tạo một instance mới của class.
5.3. Phân tích đoạn code
Trong ví dụ của chúng ta:
class Person {
constructor(name) {
this.name = name
}
}
const member = new Person("John")
console.log(typeof member)
- Chúng ta định nghĩa một class
Person
với một constructor nhận vào tham sốname
. - Constructor này gán giá trị của
name
cho thuộc tínhthis.name
của instance. - Chúng ta tạo một instance mới của
Person
vớinew Person("John")
và gán nó cho biếnmember
. - Cuối cùng, chúng ta kiểm tra kiểu của
member
bằngtypeof
.
5.4. Giải thích đáp án
Kết quả của typeof member
là "object"
. Đây là bởi vì:
- Khi chúng ta sử dụng
new
với một class, nó tạo ra một đối tượng mới. - Trong JavaScript, classes thực chất là các hàm đặc biệt, nhưng instances của chúng là các đối tượng.
typeof
trả về"object"
cho bất kỳ đối tượng nào không phải là một hàm.
5.5. Các đáp án khác
- Đáp án A (
"class"
) không đúng vìclass
không phải là một kiểu dữ liệu trong JavaScript. - Đáp án B (
"function"
) không đúng vì mặc dù class là một loại function đặc biệt, nhưng instance của nó là một object. - Đáp án D (
"string"
) không đúng vìmember
không phải là một chuỗi.
5.6. Classes vs Function Constructors
Để hiểu rõ hơn về cách classes hoạt động, hãy so sánh nó với cách viết tương đương sử dụng function constructor:
// Sử dụng Class
class Person {
constructor(name) {
this.name = name;
}
}
// Tương đương với Function Constructor
function Person(name) {
this.name = name;
}
// Cả hai cách đều cho kết quả giống nhau
const member1 = new Person("John");
const member2 = new Person("John");
console.log(typeof member1); // "object"
console.log(typeof member2); // "object"
5.7. Lưu ý quan trọng
- Classes trong JavaScript không tạo ra một kiểu dữ liệu mới. Chúng chỉ là một cách khác để định nghĩa và làm việc với objects.
instanceof
operator có thể được sử dụng để kiểm tra xem một object có phải là instance của một class cụ thể hay không.
5.8. Tóm lại
Classes trong JavaScript cung cấp một cú pháp rõ ràng và dễ hiểu hơn để làm việc với objects và kế thừa. Tuy nhiên, điều quan trọng là phải hiểu rằng chúng vẫn dựa trên cơ chế prototype-based của JavaScript. Khi chúng ta tạo một instance của class bằng từ khóa new
, kết quả là một object thông thường, và đó là lý do tại sao typeof
trả về "object"
.
Kết luận
Qua 5 ví dụ trên, chúng ta đã đi sâu vào một số khía cạnh thú vị của JavaScript:
- Cách sử dụng toán tử
!!
để chuyển đổi giá trị sang boolean. - Truy cập ký tự trong chuỗi bằng index.
- Sử dụng giá trị mặc định cho tham số hàm.
- Cách hoạt động của export và import trong ES6 modules.
- Bản chất của classes và instances trong JavaScript.
Những kiến thức này không chỉ giúp bạn hiểu rõ hơn về cách JavaScript hoạt động, mà còn có thể áp dụng vào thực tế để viết code hiệu quả và tránh các lỗi phổ biến.
Hãy nhớ rằng, JavaScript là một ngôn ngữ rất linh hoạt và có nhiều cách để thực hiện cùng một tác vụ. Việc hiểu rõ các khái niệm cơ bản và các tính năng trong ES6 sẽ giúp bạn chọn cách tiếp cận tốt nhất cho từng tình huống cụ thể.
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ộtcomment 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