JavaScript设计模式笔记 - 单例模式 链式调用

单例模式:

适用场合

单例模式是Javascript中最常用的模式之一;

用作命名空间:将自己的所有代码组织在一个全局变量名下,方便日后维护,示例程序:

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
/* DataParser singleton, converts character delimited strings into arrays. */
/* Now using true private methods. */

GiantCorp.DataParser = (function() {
// Private attributes.
var whitespaceRegex = /\\s+/;

// Private methods.
function stripWhitespace(str) {
return str.replace(whitespaceRegex, '');
}
function stringSplit(str, delimiter) {
return str.split(delimiter);
}

// Everything returned in the object literal is public, but can access the
// members in the closure created above.
return {
// Public method.
stringToArray: function(str, delimiter, stripWS) {
if(stripWS) {
str = stripWhitespace(str);
}
var outputArray = stringSplit(str, delimiter); // 这里不再使用 this. 或 GiantCorp.DataParser. 这些前缀只用于访问单例对象的公用属性
return outputArray;
}
};
})();
// Invoke the function and assign the returned object literal to
// GiantCorp.DataParser.

惰性加载:在大型或复杂的项目中,起到了优化的作用:那些开销较大却很少用到的组件可以被包装到惰性加载单例中,示例程序:

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
/* Singleton with Private Members, step 3. */

MyNamespace.Singleton = (function() {
// Private members.
var privateAttribute1 = false;
var privateAttribute2 = \[1, 2, 3\];

function privateMethod1() {
...
}
function privateMethod2(args) {
...
}

return { // Public members.
publicAttribute1: true,
publicAttribute2: 10,

publicMethod1: function() {
...
},
publicMethod2: function(args) {
...
}
};
})();
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
/* General skeleton for a lazy loading singleton, step 1. */

MyNamespace.Singleton = (function() {

function constructor() { // All of the normal singleton code goes here.
// Private members.
var privateAttribute1 = false;
var privateAttribute2 = \[1, 2, 3\];

function privateMethod1() {
...
}
function privateMethod2(args) {
...
}

return { // Public members.
publicAttribute1: true,
publicAttribute2: 10,

publicMethod1: function() {
...
},
publicMethod2: function(args) {
...
}
}
}

})();

/* General skeleton for a lazy loading singleton, step 2. */

MyNamespace.Singleton = (function() {

function constructor() { // All of the normal singleton code goes here.
...
}

return {
getInstance: function() {
// Control code goes here.
}
}
})();

/* General skeleton for a lazy loading singleton, step 3. */

MyNamespace.Singleton = (function() {

var uniqueInstance; // Private attribute that holds the single instance.

function constructor() { // All of the normal singleton code goes here.
...
}

return {
getInstance: function() {
if(!uniqueInstance) { // Instantiate only if the instance doesn't exist.
uniqueInstance = constructor();
}
return uniqueInstance;
}
}
})();

使用分支单例:针对特定环境的代码可以被包装到分支型单例中,示例程序:

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
/* SimpleXhrFactory singleton, step 1. */

var SimpleXhrFactory = (function() {

// The three branches.
var standard = {
createXhrObject: function() {
return new XMLHttpRequest();
}
};
var activeXNew = {
createXhrObject: function() {
return new ActiveXObject('Msxml2.XMLHTTP');
}
};
var activeXOld = {
createXhrObject: function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
};

})();

/* SimpleXhrFactory singleton, step 2. */

var SimpleXhrFactory = (function() {

// The three branches.
var standard = {
createXhrObject: function() {
return new XMLHttpRequest();
}
};
var activeXNew = {
createXhrObject: function() {
return new ActiveXObject('Msxml2.XMLHTTP');
}
};
var activeXOld = {
createXhrObject: function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
};

// To assign the branch, try each method; return whatever doesn't fail.
var testObject;
try {
testObject = standard.createXhrObject();
return standard; // Return this if no error was thrown.
}
catch(e) {
try {
testObject = activeXNew.createXhrObject();
return activeXNew; // Return this if no error was thrown.
}
catch(e) {
try {
testObject = activeXOld.createXhrObject();
return activeXOld; // Return this if no error was thrown.
}
catch(e) {
throw new Error('No XHR object found in this environment.');
}
}
}

})();

优点:

使用单例模式可以用来创建命名空间以减少全局变量的数目,而全局变量也很容易被改写,不安全。描述性的命名空间也可以增强代码的说明性,便于理解;

把相关方法和属性组织到一个不会被多次实例化的单例中,更好的维护和调试代码;

惰性加载使对象被使用的时候才创建,减少不需要使用时消耗了不必要的内存或带宽;

分支技术可创建高效的方法,根据运行时的条件确定赋予单例变量的对象字面量,可以创建出为特定环境量身定制的方法,这种方法不会再每次调用时都再次浪费时间去检查运行环境。

缺点:

提供的是一种单点访问,导致模块间的强耦合,不利于单元测试。单例最好还是留给定义命名空间和实现分支方法这些用途。

方法的链式调用:

Javascript中的对象是作为引用被传递的,所以可以让每个方法都传回对象的引用,即返回this值;

这种编程风格可以简化代码的编写工作,让代码更简洁易读,示例程序:

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
function $() {
var elements = \[\];
for (var i = 0, len = arguments.length; i < len; ++i) {
var element = arguments\[i\];
if (typeof element == 'string') {
element = document.getElementById(element);
}
if (arguments.length == 1) {
return element;
}
elements.push(element);
}
return elements;
}

(function() {
// Use a private class.
function _$(els) {
this.elements = \[\];
for (var i = 0, len = els.length; i < len; ++i) {
var element = els\[i\];
if (typeof element == 'string') {
element = document.getElementById(element);
}
this.elements.push(element);
}
}
// The public interface remains the same.
window.$ = function() {
return new _$(arguments);
};
})();

(function() {
function _$(els) {
// ...
}
_$.prototype = {
each: function(fn) {
for ( var i = 0, len = this.elements.length; i < len; ++i ) {
fn.call(this, this.elements\[i\]);
}
return this;
},
setStyle: function(prop, val) {
this.each(function(el) {
el.style\[prop\] = val;
});
return this;
},
show: function() {
var that = this;
this.each(function(el) {
that.setStyle('display', 'block');
});
return this;
},
addEvent: function(type, fn) {
var add = function(el) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
}
else if (window.attachEvent) {
el.attachEvent('on'+type, fn);
}
};
this.each(function(el) {
add(el);
});
return this;
}
};
window.$ = function() {
return new _$(arguments);
};
})();

/\* Usage. */

$(window).addEvent('load', function() {
$('test-1', 'test-2').show().
setStyle('color', 'red').
addEvent('click', function(e) {
$(this).setStyle('color', 'green');
});
});

为了解决命名冲突,使用指定的API,可以为框架提供一个安装器,示例程序:

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Include syntactic sugar to help the development of our interface.
Function.prototype.method = function(name, fn) {
this.prototype\[name\] = fn;
return this;
};
(function() {
function _$(els) {
// ...
}
/*
Events
\* addEvent
\* getEvent
*/
_$.method('addEvent', function(type, fn) {
// ...
}).method('getEvent', function(e) {
// ...
}).
/*
DOM
\* addClass
\* removeClass
\* replaceClass
\* hasClass
\* getStyle
\* setStyle
*/
method('addClass', function(className) {
// ...
}).method('removeClass', function(className) {
// ...
}).method('replaceClass', function(oldClass, newClass) {
// ...
}).method('hasClass', function(className) {
// ...
}).method('getStyle', function(prop) {
// ...
}).method('setStyle', function(prop, val) {
// ...
}).
/*
AJAX
\* load. Fetches an HTML fragment from a URL and inserts it into an element.
*/
method('load', function(uri, method) {
// ...
});
window.$ = function() {
return new _$(arguments);
});
})();

Function.prototype.method = function(name, fn) {
// ...
};
(function() {
function _$(els) {
// ...
}
_$.method('addEvent', function(type, fn) {
// ...
})
// ...

window.installHelper = function(scope, interface) {
scope\[interface\] = function() {
return new _$(arguments);
}
};
})();

/\* Usage. */

installHelper(window, '$');

$('example').show();

/\* Another usage example. */

// Define a namespace without overwriting it if it already exists.
window.com = window.com || {};
com.example = com.example || {};
com.example.util = com.example.util || {};

installHelper(com.example.util, 'get');

(function() {
var get = com.example.util.get;
get('example').addEvent('click', function(e) {
get(this).addClass('hello');
});
})();

如果想让getter和setter方法都支持链式调用,可以在getter方法中使用回调技术,示例程序:

// Accessor without function callbacks: returning requested data in accessors.
window.API = window.API || {};
API.prototype = function() {
var name = 'Hello world';
// Privileged mutator method.
setName: function(newName) {
name = newName;
return this;
},
// Privileged accessor method.
getName: function() {
return name;
}
}();

// Implementation code.
var o = new API;
console.log(o.getName()); // Displays 'Hello world'.
console.log(o.setName('Meow').getName()); // Displays 'Meow'.

// Accessor with function callbacks.
window.API2 = window.API2 || {};
API2.prototype = function() {
var name = 'Hello world';
// Privileged mutator method.
setName: function(newName) {
name = newName;
return this;
},
// Privileged accessor method.
getName: function(callback) {
callback.call(this, name);
return this;
}
}();

// Implementation code.
var o2 = new API2;
o2.getName(console.log).setName('Meow').getName(console.log);
// Displays 'Hello world' and then 'Meow'.
arthinking wechat
欢迎关注itzhai公众号