Namespace

使用 namespace 可避免發生命名衝突,並降低 global variable 的需求量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 只用一個 global object
var MYAPP = {};
// constructors
MYAPP.Parent = function () {};
MYAPP.Child = function () {};
// a variable
MYAPP.some_var = 1;
// an object container
MYAPP.modules = {};
// nested objects
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;
// strip redundant leading global
if (parts[0] === "MYAPP") {
parts = parts.slice(1);
}
for (i = 0; i < parts.length; i += 1) {
// create a property if it doesn't exist
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; // true

這類使用 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() {
// private member
var height = 10,
width = 5,
area = height * width;
// public function
this.getArea = function () {
return area;
};
}
var shape1 = new Rectangle();
console.log(shape1.area); // undefined (private)
console.log(shape1.getArea()); // 50 (public)

要特別注意的一點是,當我們從 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 () {
// private member
var name = "test";
// public function
return {
getName: function () {
return name;
}
};
}());
obj.getName(); // "test"

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 () {
// private member
var type = "Rectangle";
// public prototype member
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(); // "still alive"

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 () {
// dependencies
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,
// private properties
array_string = "[object Array]",
ops = Object.prototype.toString;
// private methods
// ...
// end var
// optionally one-time init procedures
// ...
// public API
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;
}
// ... more methods and properties
};
}());

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
// constructor
var Circle = function () {};
// a static method
Circle.isShape = function () {
return true;
};
// a normal method added to the prototype
Shape.prototype.setColor = function (color) {
this.color = color;
};
// calling a static method
Circle.isShape(); // true
// creating an instance and calling a method
var moon = new Circle();
moon.setColor("yellow");
typeof Circle.setColor; // "undefined"
typeof moon.isShape; // "undefined"

比較兩種 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
// constructor
var Circle = (function () {
// static variable/property
var counter = 0,
NewCircle;
// new constructor implementation
NewCircle = function () {
counter += 1;
};
// a privileged method
NewCircle.prototype.getLastId = function () {
return counter;
};
// overwrite the constructor
return NewCircle;
}()); // execute immediately
var shape1 = new Circle();
shape1.getLastId(); // 1
var shape2 = new Circle();
shape2.getLastId(); // 2

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(); // 8
  • 優點:code 較精簡、各 method 有各別專屬功能,提升可維護性
  • 缺點:debug 難度增加