Nội dung bài viết
Tiếp nối [Phần 1] Học nhanh các Javascript Design Pattern thì hôm nay chúng ta sẽ đi đến phần còn lại về các Javascript Design Pattern nhé.
ES6 Module
Trước khi ES6 xuất hiện thì Javascript không có tính năng module hóa, anh em dev phải dựa vào các thứ viện ngoài như Webpack hoặc tự tạo theo Module Pattern mà mình giới thiệu ở phần 1 để module hóa ứng dụng web.
Giờ đây thì ta có thể dùng ES6 module ở trong môi trường trình duyệt thông thường, ở trong Webpack hoặc NodeJs phiên bản 13.2.0 trở lên.
ES6 Module lưu trữ trên từng file, mỗi file là một module. Mọi thứ bên trong module mặc định là private. Hàm, biến và các class được “xuất” ra ngoài bằng từ khóa export
. Code bên trong module luôn luôn chạy ở chế độ strict mode.
Export một module
Có 2 cách để export một function hoặc 1 biến:
- Sử dụng từ khóa
export
ở trước khai báo function hoặc biến. Ví dụ:
utils.js
export const greeting = 'Hello World' export function sum(num1, num2) { console.log('Sum:', num1, num2) return num1 + num2 } export function subtract(num1, num2) { console.log('Subtract:', num1, num2) return num1 - num2 } // Đây là một private function function privateLog() { console.log('Private Function') }
Thêm từ khóa export
vào dưới khai báo biến hoặc function. Ví dụ:
function multiply(num1, num2) { console.log('Multiply:', num1, num2) return num1 * num2 } function divide(num1, num2) { console.log('Divide:', num1, num2) return num1 / num2 } // Đây là một private function function privateLog() { console.log('Private Function') } export { multiply, divide }
Import một module
Tương tự như export một module, có 2 cách để import một module bằng cách sử dụng từ khóa import
. Ví dụ:
- Import nhiều biến/hàm cùng một lần
main.js
// import nhiều phần tử import { sum, multiply } from './utils.js' console.log(sum(3, 7)) console.log(multiply(3, 7))
- Import tất cả ở trong module
main.js
// import tất cả phần tử trong module import * as utils from './utils.js' console.log(utils.sum(3, 7)) console.log(utils.multiply(3, 7))
Import và Export cũng có thể dùng bí danh (alias)
Nếu bạn muốn tránh việc xung đột tên, bạn có thể thay đổi tên export cũng như import. Ví dụ:
- Đổi tên một export
utils.js
function sum(num1, num2) { console.log('Sum:', num1, num2) return num1 + num2 } function multiply(num1, num2) { console.log('Multiply:', num1, num2) return num1 * num2 } export { sum as add, multiply }
- Đổi tên một import
main.js
import { add, multiply as mult } from './utils.js' console.log(add(3, 7)) console.log(mult(3, 7))
Trong file HTML các bạn nhớ link đến file main.js (file mà import các file khác) nhé
index.html
<script src="main.js" type="module"></script>
Factory pattern
Factory Pattern là pattern sử dụng phương thức factory để tạo object mà không cần chỉ định chính xác class hoặc constructor function.
Factory Pattern được sử dụng để tạo các object mà không “làm lộ” logic khởi tạo. Pattern này có thể được sử dụng khi chúng ta cần tạo một object khác dựa vào một điều kiện được chỉ rõ từ trước. Ví dụ:
class Car { constructor(options) { this.doors = options.doors || 4 this.state = options.state || 'brand new' this.color = options.color || 'white' } } class Truck { constructor(options) { this.doors = options.doors || 4 this.state = options.state || 'used' this.color = options.color || 'black' } } class VehicleFactory { createVehicle(options) { if (options.vehicleType === 'car') { return new Car(options) } else if (options.vehicleType === 'truck') { return new Truck(options) } } }
Ở đây mình tạo một class Car
và Truck
(với một số giá trị mặc định), 2 class này được sử dụng để tạo object car
và truck
. Và mình định nghĩa một class VehicleFactory
để tạo và return một object mới dựa trên vehicleType
nhận được từ object options
như một đối số.
const factory = new VehicleFactory() const car = factory.createVehicle({ vehicleType: 'car', doors: 4, color: 'silver', state: 'Brand New' }) const truck = factory.createVehicle({ vehicleType: 'truck', doors: 2, color: 'white', state: 'used' }) // Prints Car {doors: 4, state: "Brand New", color: "silver"} console.log(car) // Prints Truck {doors: 2, state: "used", color: "white"} console.log(truck)
Mình đã tạo một object mới là factory
từ VehicleFactory
class. Sau đó chúng ta có thể tạo một object Car
hoặc Truck
mới bằng cách gọi factory.createVehicle
và truyền một object options vào với thuộc tính vehicleType
là car
hoặc truck
.
Decorator pattern
Decorator pattern được sử dụng để mở rộng chức năng của một object mà không phải thay đổi class hoặc constructor function hiện có.
Pattern này có thể được sử dụng để thêm tính năng vào một object mà không thay đổi code sâu bên dưới chúng.
Một ví dụ đơn giản của pattern này là:
function Car(name) { this.name = name // Default values this.color = 'White' } // Tạo object mới const tesla = new Car('Tesla Model 3') // Thêm tính năng mới vào object mà không phải thay đổi constructor function tesla.setColor = function (color) { this.color = color } tesla.setPrice = function (price) { this.price = price } tesla.setColor('black') tesla.setPrice(49000) // prints black console.log(tesla.color)
Một ví dụ thực tế hơn của pattern này là:
Bình thường thì chi phí chi trả cho một chiếc xe sẽ khác nhau dựa vào tính năng mà nó có. Nếu không có decorator pattern, chúng ta sẽ tạo nhiều class khác nhau đại diện cho những loại xe mà tính năng của chúng khác nhau, mỗi cái lại có một phương thức tính giá tiền. Ví dụ:
class Car() { } class CarWithAC() { } class CarWithAutoTransmission { } class CarWithPowerLocks { } class CarWithACandPowerLocks { }
Nhưng với decorator pattern, chúng ta có thể tạo một class cơ bản là Car
và thêm phương thức tính toán dựa vào decorator function. Ví dụ:
class Car { constructor() { // Default Cost this.cost = function () { return 20000 } } } // Decorator function function carWithAC(car) { car.hasAC = true const prevCost = car.cost() car.cost = function () { return prevCost + 500 } } // Decorator function function carWithAutoTransmission(car) { car.hasAutoTransmission = true const prevCost = car.cost() car.cost = function () { return prevCost + 2000 } } // Decorator function function carWithPowerLocks(car) { car.hasPowerLocks = true const prevCost = car.cost() car.cost = function () { return prevCost + 500 } }
Đầu tiên, chúng ta tạo class cơ bản là Car cho việc tạo Car
object. Sau đó, chúng ta tạo các decorator function cho các tính năng mà chúng ta muốn thêm vào Car
object. Tiếp theo chúng ta override cost
function để return về giá tiền dựa vào từng đặc điểm của xe.
Quy trình sẽ thực hiện như thế này:
const car = new Car() console.log(car.cost()) carWithAC(car) carWithAutoTransmission(car) carWithPowerLocks(car)
Cuối cùng, chúng ta có thể tính toán giá tiền của chiếc xe như thế này:
// Tính toán chi phí của chiếc xe sau khi thêm n tính năng console.log(car.cost())
Nếu bạn nào đã từng code React thì sẽ biết High Order Component, nó chính là Decorator Pattern!
Đọc thêm:
- Cấu trúc React Folder tối ưu, dễ bảo trì, dễ nâng cấp
- So sánh connect vs useSelector & useDispatch: Redux Connect với Redux Hooks mới
- Sự thật về Virtual DOM, thực sự có nhanh hơn DOM thật hay không?
Observer pattern
Observer pattern là một design pattern nơi mà một object (thường được gọi là subject) có nhiệm vụ duy trì một danh sách các object dựa vào nó, tự động thông báo đến các object đó nếu có bất kì sự thay đổi nào về trạng thái.
Để dễ hình dung, Observer pattern nó giống như kiểu lắng nghe sự kiện bằng addEventListener
khi ta DOM bằng Javascript ý. Ta thêm lắng nghe "scroll"
thì khi scroll nó sẽ chạy function mà ta đưa vào.
document.body.addEventListener("scroll", event => { console.log(event) })
Bây giờ mình sẽ mô phỏng một observer pattern nhé.
class Subject { constructor() { this.observers = [] } // Dùng để đăng ký subscribe(func) { this.observers.push(func) } // Dùng để hủy đăng ký unsubscribe(func) { this.observers = this.observers.filter((subscriber) => subscriber !== func) } // Gửi 1 thông báo đến mọi fire(data) { this.observers.forEach((observer) => observer(data)) } }
Bạn đang thắc mắc cách sử dụng như thế nào phải không, xem tiếp nhé.
const $gun = new Subject() const func = (value) => { console.log(value) } $gun.subscribe(func) $gun.fire('boom')
Mình đăng ký lắng nghe sự kiện, khi mà chạy phương thức fire
thì những object đã đăng ký sẽ nhận được thông báo ngay lập tức. Tức là func
function sẽ được chạy.
Nếu muốn không nhận thông báo nữa chỉ cần unsubcribe
nó là được.
$gun.unsubscribe(func)
Angular dùng cực nhiều pattern này
Tóm lại
Qua 2 phần, mình và các bạn đã đi tìm hiểu top những design pattern phổ biến nhất trong Javascript, mình biết 2 bài vừa rồi không thể bao quát hết những design pattern nên nếu bạn hứng thú với các design pattern thì có thể đọc thêm ở đây của tác giả Addy Osmani.
Vậy đó 😛 , hy vọng bài viết của mình mang đến giá trị cho các bạn, nếu ủng hộ mình bạn có thể like fanpage hoặc để lại comment là được rồi
hay quá