0%
这是一片思考的空间 -- arthinking
Spring 重构&代码整洁之道 软件设计 JVM 并发编程 数据结构与算法 分布式 存储 网络 微服务 设计模式
Java技术栈 - 涉及Java技术体系

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'.

欢迎关注我的其它发布渠道

订阅IT宅
内功修炼
Java技术栈
Java架构杂谈是IT宅精品文章公众号,欢迎订阅:
📄 网络基础知识:两万字长文50+张趣图带你领悟网络编程的内功心法 📄 HTTP发展史:三万长文50+趣图带你领悟web编程的内功心法 📄 HTTP/1.1:可扩展,可靠性,请求应答,无状态,明文传输 📄 HTTP/1.1报文详解:Method,URI,URL,消息头,消息体,状态行 📄 HTTP常用请求头大揭秘 📄 HTTPS:网络安全攻坚战 📄 HTTP/2:网络安全传输的快车道 📄 HTTP/3:让传输效率再一次起飞 📄 高性能网络编程:图解Socket核心内幕以及五大IO模型 📄 高性能网络编程:三分钟短文快速了解信号驱动式IO 📄 高性能网络编程:彻底弄懂IO复用 - IO处理杀手锏,带您深入了解select,poll,epoll 📄 高性能网络编程:异步IO:新时代的IO处理利器 📄 高性能网络编程:网络编程范式 - 高性能服务器就这么回事 📄 高性能网络编程:性能追击 - 万字长文30+图揭秘8大主流服务器程序线程模型
📄 Java内存模型:如果有人给你撕逼Java内存模型,就把这些问题甩给他 📄 一文带你彻底理解同步和锁的本质(干货) 📄 AQS与并发包中锁的通用实现 📄 ReentrantLock介绍与使用 📄 ReentrantReadWriteLock介绍与使用 📄 ReentrantLock的Condition原理解析 📄 如何优雅的中断线程 📄 如何优雅的挂起线程 📄 图解几个好玩的并发辅助工具类 📄 图解BlockingQueue阻塞队列
📄 消息队列那么多,为什么建议深入了解下RabbitMQ? 📄 高并发异步解耦利器:RocketMQ究竟强在哪里? 📄 Kafka必知必会18问:30+图带您看透Kafka
📄 洞悉MySQL底层架构:游走在缓冲与磁盘之间 📄 SQL运行内幕:从执行原理看调优的本质 📄 洞悉Redis技术内幕:缓存,数据结构,并发,集群与算法