Cách viết JavaScript tốt theo phong cách mới
Bài đăng này đã không được cập nhật trong 8 năm
Nguồn
Lời mở đầu
Gần đây, môi trường chạy JavaScript không chỉ bị gói gọn trong browser nữa(node.js, Web Workers)
Ngoài ra, phương thức load thông qua tag <script>
cũng đã trở nên lỗi thời, hiện nay việc thực hiện load module sử dụng require
(phong cách CommonJS) được đánh giá cao hơn nhiều.
Chính vì thế cần suy nghĩ lại về những điều sau:
- Ta có
var YourModule = {};
, cách viết để gọiYourModule.hoge();
từ bên ngoài - Nghĩ rằng
this === window
Lần này tôi sẽ giới thiệu với các bạn WebModulePattern của anh uupaa
, từ đó tìm ra cách viết module Javascript một cách tốt nhất.
(do bình thường tôi không hay viết Javascript lắm cho nên đây chỉ là memo của cá nhân mà thôi)
Cách viết
Đầu tiên, bọc toàn thể bằng (function(){...})();
, bằng cách này sẽ giúp global không bị làm bẩn bởi var
.
(function() {
// ...
})();
Sử dụng "use strict";
(Tham khảo về use strict)
(function() {
"use strict";
// ...
})();
Tiếp theo, lấy global object bằng cách viết có thể hoạt động được cả tại browser, Web Worker hay node.js.
(function(global) {
"use strict";
// ...
})((this || 0).self || global);
Với cách viết này có thể lấy được global khi tại môi trường browser, window, Web Workers hay thậm chí cả WorkerGlobalScope, node.
Hãy tham khảo cụ thể hơn với WebModuleIdiom.
Bổ sung:
Mặc dù global sẽ bị làm bẩn, tuy nhiên nếu không thực hiện cách viết global.XX = XX
sẽ không động tới global.
Nếu không bọc bởi (function(){})();
, chỉ cần có var XX = XX;
sẽ làm bẩn global ngay lập tức.
Nếu muốn chỉ một môi trường chạy nào đó trong lúc này, có thể sử dụng cách viết như sau:
var isBrowser = "document" in global;
var isWebWorkers = "WorkerLocation" in global;
var isNode = "process" in global;
Tiếp theo, để có thể support với require tại phong cách của CommonJS
, ta cần export module vào module.exports
.
Gần đây do không chỉ với node.js
mà tại browser cũng có thể thông qua browserify
hay webpack
để sử dụng được require
, ta cần thực hiện export kể cả với browser.
if ("process" in global) {
module.exports = YourModule;
}
Cuối cùng, để support việc load <script>
thông thường (không phải phong cách CommdodenJS
, chúng ta sẽ đưa Module vào global Object.
global["YourModule"] = YourModule;
Tổng thể:
(function(global) {
"use strict;"
// Your Module
function YourModule() {
// ...
};
// Exports
if ("process" in global) {
module["exports"] = YourModule;
}
global["YourModule"] = YourModule;
})((this || 0).self || global);
Chúng ta đã hoàn thành đoạn code export tốt nhất đảm bảo những điều cơ bản. Mặc dù nhìn qua có vẻ hơi dài, tuy nhiên do là thời kỳ quá độ nên đành để tạm thế. Vậy nên lưu tạm vào đâu theo dạng template thì tốt?
Phân tách header và implementation
Không có .h
ở JS.
Chính vì vậy, bên đọc source sẽ rất khổ nếu muốn biết trong đó có những method gì và như thế nào.
Nếu viết kiểu YourModule.prototype.someMethod = function() {...}
sẽ làm cho phần implementation và khởi tạo bị viết giống nhau cho nên cần thiết phải phân tách.
Bổ sung
Mặc dù hơi rắc rối, tuy nhiên nếu viết someMethod = function() {}
thì function này sẽ trở thành anonymous function, từ đó profiler sẽ rất khó để theo dấu.
Với implementation, thực hiện theo phong cách khởi tạo function bằng function method()
, cho header dưới dạng YourModule.prototype.method = method;
.
// Class
function YourModule() {
}
// Header
YourModule["prototype"]["constructor"] = YourModule;
YourModule["prototype"]["method"] = method;
// Implementation
function YourModule_method(someArg) {
// ...
}
Nếu cứ như thế này mà xem Header
sẽ không thể hiểu được argument là gì, chính vì thế ta thêm comment vào
YourModule["prototype"]["method"] = method;
bằng // YourModule#method(someArg:any):void
Phong cách comment thì tuỳ vào bạn thích thế nào cũng được. (đây cũng là phong cách Help.js
của anh uupaa
)
Tổng hợp
Tóm lại, viết module theo phong cách như dưới đây sẽ được coi là tốt:
(function(global) {
"use strict;"
// Class ------------------------------------------------
function YourModule() {
};
// Header -----------------------------------------------
YourModule["prototype"]["method"] = YourModule_method; // YourModule#method(someArg:any):void
// Implementation ---------------------------------------
function YourModule_method(someArg) {
// ...
}
// Exports ----------------------------------------------
if ("process" in global) {
module["exports"] = YourModule;
}
global["YourModule"] = YourModule;
})((this || 0).self || global);
All rights reserved