《JavaScript 模式》养分

《JavaScript 模式》作者: Stoyan Stefanov 译: 陈新

养分

1. 自调用构造函数: 实现不使用new关键字,构造出实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Waffle() {
if ((!this instanceof Waffle)) {
return new Waffle();
}

this.tastes = "yummy";
}
Waffle.prototype.wantAnother = true;

var first = new Waffle(),
section = Waffle();

console.log(first.tastes); // yummy
console.log(section.tastes); // yummy

2. 备忘模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var myFunc = function() {
var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),
result = {};

if (!myFunc.cache[cachekey]) {
result = {};

// 开销很大的操作
myFunc.cache[cachekey] = result;
}
return myFunc.cache[cachekey];
}

// 缓存存储
myFunc.cache = {};

3. 配置对象模式: 将参数列表封装到一个对象内。

1
2
3
4
5
6
7
8
9
10
11
12
function addPerson(first, last, dob, gender, address) {...}

addPerson("Bruce", "Wayne", new Date(), null, null, "batma");

// 配置
var conf = {
username : "batman",
first : "Bruce",
last : "Wayne"
};

addPerson(conf);

4. 通用命名空间函数: 减少全局变量,避免命名冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var MYAPP = MYAPP || {};
MYAPP.namespace = function(ns_string) {
var parts = ns_string.split("."),
parent = MYAPP;

if (parts[0] === "MYAPP") {
parts = parts.splice(1);
}

for (var i = 0, max = parts.length; i < max; i += 1 ) {
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return this;
};

5. 模块模式: 提供了结构化的思想并且有助于组织日益增长的代码。可以根据所编写的软件的需求(千变万化的需求)添加、替换或删除这些模块。

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
32
33
34
35
36
37
38
MYAPP.namespace("MYAPP.utilities.array").utilities.array = (function() {
// 依赖
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,

// 私有属性
array_string = "[object Array]",
ops = Object.prototype.toStirng;

// 私有方法
// ...

// var变量定义结束

// 可选的一次性初始化过程
// ...

// 公有 API
return {

inArray: function(needle, haystack) {
for (var i = 0, max = haystack.length; i < mas; i += 1) {
if (haystack[i] === needle) {

// 其它操作

return true;
}
}
},

isArray: function(a) {
return ops.call(a) === array_string;
}

// ... 更多方法和属性
};
}());

6. 创建构造函数的模块: 使用模块模式返回一个构造函数。

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
32
33
34
35
MYAPP.namespace("MYAPP.utilities.Array").utilities.Array = (function() {
// 依赖
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,

// 私有属性和方法
Constr;

// var变量定义结束

// 可选的一次性初始化过程
// ...

// 公有 API --- 构造函数
Constr = function(o) {
this.elements= this.toArray(o);
};

Constr.prototype = {
constructor: MYAPP.utilities.Array,
version: "2.0",
toArray: function(obj) {
for (var i = 0, a = []; len = obj.length; i < len; i += 1) {
a[i] = obj[i];
}
return a;
}
};

// 返回要分配给新命名空间的构造函数
return Constr;
}());

// usage
var arr = new MYAPP.utilities.Array(obj);

7. 将全局变量导入到模块中: 有助于加速即时函数中的全局符号解析的速度,因为这些导入的变量成为了该函数的局部变量。

1
2
3
4
MYAPP.utilities.module = (function(app, global) {

// 使用参数
}(MYAPP, this));

8. 沙箱模式: 提供了一个可用于模块运行的环境,且不会对其他模块和个人沙箱造成任何影响。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Sandbox 构造函数
function Sandbox() {

// 将参数转换成一个数组
var args = Array.prototype.slice.call(arguments),

// 最后一个参数是回调函数
callback = args.pop(),

// 模块可以作为一个数组传递,或作为单独的参数传递
modules = (args[0] && typeof args[0] === "string") ? args : args[0],
i;

// 确保该函数作为构造函数被调用
if (!(this instanceof Sandbox)) {
return new Sandbox(modules, callback);
}

// 需要向 `this` 添加的属性
this.a = 1;
this.b = 2;

// 现在向该核心 `this` 对象添加模块
// 不指定模块名称或指定“*”都表示“使用所有模块”
if (!modules || modules === "*") {
modules = [];
for (i in Sandbox.modules) {
if (Sandbox.modules.hasOwnProperty(i)) {
modules.push(i);
}
}
}

// 初始化所需的模块
for (i = 0; i < modules.length; i += 1) {
Sandbox.modules[modules[i]](this);
}

callback(this);
}

Sandbox.prototype = {
name : "My Application",
version : "1.0",
getName : function() {
return this.name;
}
};

Sandbox.prototype.constructor = Sandbox;

// 增加模块
Sandbox.modules = {};
Sandbox.modules.dom = function(box) {
box.getElement = function() {};
box.getStyle = function() {};
box.foo = "bar";
};

Sandbox.modules.event = function(box) {
// 如果需要, 就访问 Sandbox 原型,如:
// box.constructor.prototype.m = "mmm";
box.attachEvent = function() {};
box.dettachEvent = function() {};
};

Sandbox.modules.ajax = function(box) {
box.makeRequest = function() {};
box.makeResponse = function() {};
};

// usage

// 使用 ajax 和 event 模块
Sandbox(["ajax", "event"], function(box) {
//console.log(box);
});

// 使用所有可用模块
Sandbox("*", function(box) {
});
Sandbox(function(box) {
});

// 模块嵌套
Sandbox("dom", "event", function(box) {
Sandbox("ajax", function(box) {
});
});

9. 圣杯继承模式

1
2
3
4
5
6
7
8
9
var inherit = (function() {
var F = function() {};
return function(C, P) {
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
C.prototype.constructor = C;
}
});

10. 浅拷贝对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function extend(parent, child) {
var i;
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
return child;
}

// usage

var dad = {name: "Adam"};
var kid = extend(dad);

console.log(kid.name); // "Adam"

11. 深拷贝对象

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
32
function extendDeep(parent, child) {
var i,
toStr = Object.prototype.toString,
astr = "[object Array]";

child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === "object") {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}

// usage

var dad = {
counts: [1, 2, 3],
reads: {paper: true}
};
var kid = extendDeep(dad);

kid.counts.push(4);
console.log(kid.counts.toString()); // "1,2,3,4"
console.log(dad.counts.toString()); // "1,2,3"

console.log(dad.reads === kid.reads); // false

12. 混入模式(mix-in),就是将多个对象组合成一个新的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function mix() {
var arg,
prop,
child = {};

for (arg = 0; arg < arguments.length; arg += 1) {
for (prop in arguments[arg]) {
if (arguments[arg].hasOwnProperty(prop)) {
child[prop] = arguments[arg][prop];
}
}
}
return child;
}

// usage

var cake = mix({
{eggs: 2, large: true},
{butter: 1, salted: true}
});

13. 单例模式(Singleton),保证一个特定类仅有一个实例。当第二次使用这个特定类创建对象的时候,应该得到与第一次所创建对象完全相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Universe() {
var instance;

Universe = function Universe() {
return instance;
}
Universe.prototype = this;

instance = new Universe();
instance.constructor = Universe;

instance.start_time = 0;
instance.bang = "Big";

return instance;
}

14. 迭代器模式(Iterator),提供一个简单的接口,顺序遍历数据集合中的各个元素。

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
32
33
34
35
36
37
38
39
40
var agg = (function () {
var index = 0,
data = [1, 2, 3, 4, 5],
length = data.length;

return {
next: function () {
var element;
if (!this.hasNext()) {
return null;
}
element = data[index];
index = index + 2;
return element;
},

hasNext: function () {
return index < length;
},

// 重置指针到初始位置
rewind: function () {
index = 0;
},

// 返回当前元素
current: function () {
return data[index];
}

};
}());

// usage
while(agg.hasNext()) {
console.log(agg.next()); // 迭代的结果是:1,3,5
}

agg.rewind(); // 重置
console.log(agg.current()); // 1

15. 装饰者模式,可以在运行时动态添加附加功能到对象中。装饰者模式的一个比较方便的特征在于其预期行为的可定制和可配置特性。可以从仅具有一些基本功能的普通对象开始,然后从可用装饰资源池中选择需要用于增强普通对象的那些功能,并且按照顺序进行装饰,尤其是当装饰顺序很重要的时候。我感觉就是一个普通的材料通过了流水线的加工后产生了一个新的产品。也可说是一个对象进行了升级的过程,改变了自己的功能。

  • 方法一: 让每个装饰者成为一个对象,并且该对象包含了应该被重载的方法。每个装饰者实际上继承了目前已经被前一个装饰者进行增强后的对象。每个装饰方法在 uber (被继承的对象) 上调用了同样的方法并获取其值,此外它还继续执行了一些其它操作。
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function Sale(price) {
this.price = price || 100;
}

Sale.prototype.getPrice = function() {
return this.price;
};

Sale.decorators = {};

Sale.decorators.fedtax = {
getPrice: function() {
var price = this.uber.getPrice();
price += price * 5 / 100;
return price;
}
};

Sale.decorators.quebec = {
getPrice: function() {
var price = this.uber.getPrice();
price += price * 7.5 / 100;
return price;
}
};

Sale.decorators.money = {
getPrice: function() {
return "$" + this.uber.getPrice().toFixed(2);
}
};

Sale.decorators.cdn = {
getPrice: function() {
return "CDN$" + this.uber.getPrice().toFixed(2);
}
};

Sale.prototype.decorate = function() {
var F = function() {},
overrides = this.constructor.decorators[decorate],
i, newobj;

F.prototype = this;
newobj = new F();
newobj.uber = F.prototype;

for (i in overrides) {
if (overrides.hasOwnProperty(i)) {
newobj[i] = overrides[i];
}
}
return newobj;
};

// usage

var sale = new Sale(100); // 该价格为 100 美元
sale = sale.decorate('fedtax'); // 增加联邦税
sale = sale.decorate('quebec'); // 增加省级税
sale = sale.decorate('quebec'); // 格式化为美元货币形式
sale.getPrice(); // "$112.88"
  • 方法二: 使用列表实现,将装饰者放在一个列表中,可以很容易的支持反装饰或撤销装饰(就是对装饰者列表进行 push 和 pop 的过程)。
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function Sale(price) {
this.price = (price > 0) || 100;
this.decorators_list = [];
}

Sale.decorators = {};

Sale.decorators.fedtax = {
getPrice: function() {
return price + price * 5 / 100;
}
};

Sale.decorators.quebec = {
getPrice: function() {
return price + price * 7.5 / 100;
}
};

Sale.decorators.money = {
getPrice: function() {
return "$" + price.toFixed(2);
}
};

Sale.prototype.decorate = function(decorator) {
this.decorators_list.push(decorator);
};

Sale.prototype.getPrice = function() {
var price = this.price,
i,
max = this.decorators_list.length,
name;

for (i = 0; i < max; i += 1) {
name = this.decorators_list[i];
price = Sale.decorators[name].getPrice(price);
}

return price;
};

// usage
var sale = new Sale(100); // 该价格为 100 美元
sale.decorate('fedtax'); // 增加联邦税
sale.decorate('quebec'); // 增加省级税
sale.decorate('quebec'); // 格式化为美元货币形式
sale.getPrice(); // "$112.88"

16. 策略模式,策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。就是先抽象出相同的处理流程(算法调用的过程,或做的事情),然后根据在流程中获取到的算法策略不同,从而使流程内具体要做的事就不同。感觉像是框架(流程),数据(需要处理的数据),算法(算法策略)模式。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
var validator = {

// 所有可用的检查
types: {},

// 在当前验证会话中的错误信息
messages: [],

// 当前需要使用的验证类型
config: {},

// 暴露的公开验证方法
// 传入的参数是 key => value,js中的,object类型
validate: function (data) {

var i, msg, type, checker, result_ok;

// 清空所有的错误信息
this.messages = [];

for (i in data) {
if (data.hasOwnProperty(i)) {

type = this.config[i]; // 根据key查询是否有存在的验证规则
checker = this.types[type]; // 获取验证规则的验证类

if (!type) {
continue; // 如果验证规则不存在,则不处理
}
if (!checker) { // 如果验证规则类不存在,抛出异常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}

result_ok = checker.validate(data[i]); // 使用查到的单个验证类进行验证
if (!result_ok) {
msg = "Invalid value for *" + i + "*, " + checker.instructions;
this.messages.push(msg);
}
}
}
return this.hasErrors();
},

// helper
hasErrors: function () {
return this.messages.length !== 0;
}
};

使用:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// usage
// 定义types里存放的各种验证类

// 验证给定的值是否不为空
validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "传入的值不能为空"
};

// 验证给定的值是否是数字
validator.types.isNumber = {
validate: function (value) {
return !isNaN(value);
},
instructions: "传入的值只能是合法的数字,例如:1, 3.14 or 2010"
};

// 验证给定的值是否只是字母或数字
validator.types.isAlphaNum = {
validate: function (value) {
return !/[^a-z0-9]/i.test(value);
},
instructions: "传入的值只能保护字母和数字,不能包含特殊字符"
};

// 定义需要验证的数据集合,然后还需要定义每种数据需要验证的规则类型
var data = {
first_name: "Tom",
last_name: "Xu",
age: "unknown",
username: "TomXu"
};

validator.config = {
first_name: 'isNonEmpty',
age: 'isNumber',
username: 'isAlphaNum'
};


validator.validate(data);

if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}

17. 外观模式,为子系统的一组接口提供一个一致的界面(接口),此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。感觉就是对一些接口的组合(封装),形成一个高层接口(抽象接口),以备外界调用。

1
2
3
4
5
6
7
8
var myEvent = {
// ...
stop: function (e) {
e.preventDefault(); // 终止事件以避免其冒泡上升到父节点
e.stopPropagation(); // 阻止浏览器执行默认动作(例如, 阻止下面的链接或提交表单)
}
// ...
};