Namespace
使用 namespace 可避免發生命名衝突,並降低 global variable 的需求量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var MYAPP = {}; MYAPP.Parent = function () {}; MYAPP.Child = function () {}; MYAPP.some_var = 1; MYAPP.modules = {}; MYAPP.modules.module1 = {}; MYAPP.modules.module1.data = {a: 1, b: 2}; MYAPP.modules.module2 = {};
|
為避免覆蓋掉已存在的 namespace 或 property,我們可以設計一個檢查用的 function,用來處理建立 namespace 與新增其 property 的細節。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var MYAPP = MYAPP || {}; MYAPP.namespace = function (ns_string) { var parts = ns_string.split('.'), parent = MYAPP, i; if (parts[0] === "MYAPP") { parts = parts.slice(1); } for (i = 0; i < parts.length; i += 1) { if (typeof parent[parts[i]] === "undefined") { parent[parts[i]] = {}; } parent = parent[parts[i]]; } return parent; }; var mod = MYAPP.namespace('MYAPP.modules.module2'); mod === MYAPP.modules.module2;
|
這類使用 namespace 且模組化的程式(尤其在完善的 library 中常見),使用時可在 function 頂端建立 local variable 並指向所需模組,如:var event = MYAPP.util.Event
。此 pattern 稱為 declaring dependency,這麼做有許多優點:
- 明確提醒其他會用到這份 code 的人有哪些特殊的 script 檔案必須 include
- 使用 local variable(如
event
)永遠比使用 global variable(如 MYAPP
)來得快,尤其比巢狀 property(如 MYAPP.util.Event
)效能更好
- minify 工具會 rename local variable,可得到較少的 code
Private Properties & Methods
JavaScript 所有 object member 皆是 public,雖然語言並沒有為 private member 提供特殊語法,但我們仍可用 closure 來實作出 private member。
Constructor Function
主要觀念即 function 的 scope 特性,只要在 function 中宣告即可成為 local variable,雖然並非真正的 property,但已可達成 private 的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function Rectangle() { var height = 10, width = 5, area = height * width; this.getArea = function () { return area; }; } var shape1 = new Rectangle(); console.log(shape1.area); console.log(shape1.getArea());
|
要特別注意的一點是,當我們從 privileged method(如上例的 getArea()
)回傳 private 變數時,該變數千萬不可以是 object 或 array,因為此時回傳的是其 reference,如此將會使得外部的 code 仍然可修改 private member。
如果要解決這個問題,可以讓 privileged method 直接回傳一個新的 object,或直接使用 extend()
或 extendDeep()
做 object 的複製。
Object Literal
使用 object literal 建立 object 一樣也能實作出 private member,可以用 immediate function 來建立 closure。
1 2 3 4 5 6 7 8 9 10 11 12
| var obj = (function () { var name = "test"; return { getName: function () { return name; } }; }()); obj.getName();
|
Prototype
如果每次使用 constructor function 建立的 object 都會重新建立相同的 private member,則可以將共同的 property 或 method 加到 prototype 中以節省 memory,這麼做能使共有的 member 在所有 instance 間共享。
1 2 3 4 5 6 7 8 9 10 11
| Rectangle.prototype = (function () { var type = "Rectangle"; return { getType: function () { return type; } }; }());
|
Revelation
此 pattern 可將 private method 揭露為 public,但原始的實作內容並不會如同 public 的狀態能夠真的被修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var test; (function () { function foo() { return "still alive"; } test = { fn1: foo, fn2: foo }; }()); test.fn1 = null; test.fn2();
|
Module
雖然 JavaScript 沒有 package 的語法,我們可以結合前面介紹過的 namespace、immediate function、private member、declaring dependency 等 patterns 來實現 module pattern。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| MYAPP.namespace('MYAPP.utilities.array'); MYAPP.utilities.array = (function () { var uobj = MYAPP.utilities.object, ulang = MYAPP.utilities.lang, array_string = "[object Array]", ops = Object.prototype.toString; return { inArray: function (needle, haystack) { for (var i = 0, max = haystack.length; i < max; i += 1) { if (haystack[i] === needle) { return true; } } }, isArray: function (a) { return ops.call(a) === array_string; } }; }());
|
Sandbox
此 pattern 解決 namespace 的缺點:隨時可被變更的 global variable、很長的巢狀 property 導致打字不便及解析效能下降。 (詳細使用方式筆記略)
Static Members
static member 特性:instance 間可共享、不需建立 instance 即可直接使用
Public Static Members
即直接新增 property 給 constructor function。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var Circle = function () {}; Circle.isShape = function () { return true; }; Shape.prototype.setColor = function (color) { this.color = color; }; Circle.isShape(); var moon = new Circle(); moon.setColor("yellow"); typeof Circle.setColor; typeof moon.isShape;
|
比較兩種 method,static 可直接透過 constructor 呼叫,一般的則需先有 instance 才可呼叫。當然也可以讓 static method 透過 instance 也能呼叫:Circle.prototype.isShape = Circle.isShape;
,但若其實作中有使用到 this
,須特別注意兩者 reference 有所不同。
Private Static Members
如同上方 private properties & methods 的做法,只需使用一 immediate function 來建立 closure 以容納 private member,並在 prototype 實作一 privileged method。(closure 內的 private static member 為所有 instance 間共享)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var Circle = (function () { var counter = 0, NewCircle; NewCircle = function () { counter += 1; }; NewCircle.prototype.getLastId = function () { return counter; }; return NewCircle; }()); var shape1 = new Circle(); shape1.getLastId(); var shape2 = new Circle(); shape2.getLastId();
|
Object Constants
一般會使用 naming convention 將所有字母改為全大寫來建立 constant,如 Math.PI
。
附註:如果真的想實作一個不可變動的值,可參考書上提供的 general-purpose constant
object。
Chaining
jQuery 的一大特色即使用此 pattern,允許讓 object 不用 assign 給變數或拆成多行,可以直接連續地呼叫多個 method。實作時讓 method 回傳 this
(即 instance 本身),即可達成此效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var calculate = { value: 0, plus: function (v) { this.value += v; return this; }, minus: function (v) { this.value -= v; return this; }, output: function () { console.log(this.value); } }; calculate.plus(7).minus(2).plus(3).output();
|
- 優點:code 較精簡、各 method 有各別專屬功能,提升可維護性
- 缺點:debug 難度增加