JavaScript设计模式学习笔记1

  |  

前言

设计模式对于一门语言来说十分重要,它是已经被验证的解决方案,容易被复用且具有表达力


10



经典与现代设计模式的JavaScript实现

这一篇我们主要探讨:

  • Constructor(构造器)模式;
  • Module(模块)模式;
  • Revealing Module(揭示模块)模式;
  • Singleton(单例)模式;
  • Observer(观察者)模式;

Constructor(构造器)模式

我们知道JavaScript是不支持类的概念,但它支持与对象一起使用的特殊constructor(构造器)函数。通过在构造器前面使用new关键字,告诉JavaScript像使用构造器一样实例化一个新对象,并且对象成员由该函数定义。

所以我们可以使用构造器来模拟类的实现。

由于单纯使用构造器会导致每个实例都会重定义一遍方法,所以我们使用带有prototype(原型)的构造器,这样在调用构造器后,新对象都会具有构造器原型的所有属性。

Module(模块)模式

在JavaScript中,有几种用于实现模块的方法,包括:

  • 对象字面量表示法
  • Module模式
  • AMD模式
  • CommonJS模块
  • ECMAScript Harmony模块

这里我们先只介绍前两种

对象字面量:

对象字面量不需要使用new运算符进行实例化,但不能用在一个语句的开头,下面是一个简单的对象字面量表示法定义的模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
var myModule = {
myProperty: "someValue",
myConfig: {
useCachiong: true,
language: "en
},
mythod: function () {
console.log("Where in teh world is Paul Irish today?");
},
myMethod2: function () {
console.log("Caching is:" + (this.myConfig.useCahcing)? "enabled" : "disabled");
}
}

Module(模块)模式:

Module模式最初被定义为一种在传统软件工程中为类提供私有和公有封装的方法。

在JavaScript中,Module模式用于进一步模拟类的概念,通过这种方式,能够使一个单独的对象提供公有/私有方法和变量,从而屏蔽来自全局作用域的特殊部分,使函数名与页面给上其它脚本定义的函数冲突的可能性降低。

Module模式使用闭包封装“私有”状态和组织。它提供了一种包装混合公有/私有方法和变量的方式,防止其泄露至全局作用域,并于其它开发人员的接口发生冲突。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myNamespace = (function () {
//私有计数器变量
var myPrivateVar = 0;
//记录所有参数的私有函数
var myPrivateMethod = function (foo) {
console.log(foo);
}
return {
//公有变量
myPublicVar: "foo",
myPublicFUnction: function (bar) {
myPrivateVar++;
//传入bar调用私有方法
myPrivateMethod(bar);
}
}
})();

在这里面私有变量myPrivateVar完全与全局作用域隔离的,它的存在被局限于模块的闭包里,因此它表现得就像是一个私有变量。

他的实现原理是通过作用域函数来包裹私有变量和私有函数,然后调用并将公有函数和变量包裹在对象内来返回并存储在一个全局变量内。

这样的优点有:

  • 只有相应的模块才能享有拥有私有函数的自由
  • 鉴于函数往往已声明并命名,在试图找到有哪些函数抛出异常时,这将使得在调试器中显示调用堆栈变得更容易。
  • 根据环境,我们可以返回不同的函数。

Revealing Module(揭示模块)模式

原有的模块模式有几点不足:

1.当在一个方法中调用一个公有方法或访问公有变量时,必须要重复主对象的名称。

对于这句话我理解样子是这样,及在返回的公有函数中调用一个同样返回的公有函数需要通过this的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
var myNamespace = (function () {
var count = 0;
return {
myPublicFunction1: function () {
count++;
},
myPublicFunction: function () {
this.myPublicFunction1();
console.log(count);
}
}
})();
myNamespace.myPublicFunction();

2.使用Module模式时,必须要切换到对象字面量表示法来让某种方法变成公有方法。

所以我们采用一个更新的模式,及揭示模块模式,它能够在私有范围内简单定义所有的函数和变量,并返回一个匿名对象,它拥有指向私有函数的指针,该函数是它希望展示为公有的方法。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var myRevealingModule = function () {
var privateVar = "Ben Cherry";
var publicVar = "Hey there!";
function privateFunction() {
console.log("Name:" + privateVar);
}
function publicSetName(strName) {
privateVar = strName;
}
function publicGetName() {
privateFunction();
}

return {
setName: publicSetName,
greeting: publicVar,
getName: publicGetName
};
}();

优点:

揭示模块模式的有点在于可以使脚本语法更加一致,在模块代码底部,他也会更容易指出哪些函数和变量可以被公开访问,从而改善代码可读性。

缺点:

揭示模块模式如果有要给私有函数引用了一个公有函数,那么在修改公有函数时,虽然公有方法会被覆盖,但是私有函数仍将继续引用私有实现。这么说可能有点难理解,我们举个例子就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var nameSpace = (function() {
function getFullName() {
return 'LiMing';
}
function sayHello() {
return 'Hi ' + getFullName();
}
function firstMeet() {
console.log(sayHello() + ', I am XiaoHong');
}
return {
getFullName: getFullName,
firstMeet: firstMeet
}
})();
nameSpace.firstMeet();
//Hi LiMing, I am XiaoHong

在这个例子中,模块有两个公有方法,getFullName和firstMeet,公有方法firstMeet调用了私有方法sayHello,私有方法sayHello调用了公有方法getFullName。如果修改私有方法getFullName

1
2
3
4
5
nameSpace.getFullName = function() {
return "Doubler";
}
nameSpace.firstMeet();
//Hi LiMing, I am XiaoHong

我们调用firstMeet,还是原先的输出结果。这就是揭露模块模式的缺点,虽然我们覆盖了公有方法getFullName,但是私有方法sayHello还是保持对原实现的引用。

Singleton(单例)模式

Singleton(单例)模式被熟知的原因是因为它限制了类的实例化次数只能一次。从经典意义上来说,Singleton模式,在该实例不存在的情况下,可以通过一个方法创建一个类来实现创建类的新实例;如果实例已经存在,它会简单返回该对象的引用。在JavaScript中,Singleton充当共享资源命名空间,从全局命名空间中隔离出代码实现,从而为函数提供的那一访问点。我们可以像如下这样实现一个Singleton:

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
var mySingleton = (function () {
var instance;
function init(){
function privateMethod() {
console.log("I am private");
}
var privateVariable = "I'm also private";
var privateRandomNumber = Math.random();
return {
publicMethod: function() {
console.log("The public can see me!");
},
publicProperty: "I am also public",
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
getInstance: function() {
if (!instance) {
instance = init();
}
return instance;
}
};
})();

var myBadSingleton = (function() {
var instance;
function init() {
var privateRandomNumber = Math.random();
return {
getRandomNumber: function() {
return privateRandomNumber;
}
};
}
return {
getInstance: function() {
instance = init();
return instance;
}
};
})();

测试代码:

1
2
3
4
5
6
7
var singleA = mySingleton.getInstance();
var singleB = mySingleton.getInstance();
console.log(singleA.getRandomNumber() === singleB.getRandomNumber());

var badSingleA = myBadSingleton.getInstance();
var badSingleB = myBadSingleton.getInstance();
console.log(badSingleA.getRandomNumber() === badSingleB.getRandomNumber());

Observer(观察者)模式

Observer(观察者)模式是一种设计模式,其中,一个对象(称为subject)维持一系列依赖于它的对象(观察者),将有关状态的任何变更自动通知给它们。

当我们不再希望某个特定的观察者获得其注册目标发出的变更通知时,该目标可以将这个特定的观察者从它的观察者列表中删除。

下面我们通过一个完整的例子来看一下观察者模式是如何在具体代码中实现的。

首先我们定义观察者列表类,它包含一些增删改操作:

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
function ObserverList() {
this.observerList = [];
}

ObserverList.prototype.Add = function(obj) {
return this.observerList.push(obj);
};
ObserverList.prototype.Empty = function() {
this.observerList = [];
};
ObserverList.prototype.Count = function() {
return this.observerList.length;
};
ObserverList.prototype.Get = function(index) {
if(index > -1 && index < this.observerList.length) {
return this.observerList[index];
}
};
ObserverList.prototype.Insert = function(obj, index) {
var pointer = -1;
if(index === 0) {
this.observerList.unshift(obj);
pointer = index;
} else if (index === this.observerList.length) {
this.observerList.push(obj);
pointer = index;
}
return pointer;
};
ObserverList.prototype.IndexOf = function(obj, startIndex) {
var i = startIndex, pointer = -1;
while(i < this.observerList.length) {
if(this.observerList[i] === obj){
pointer = i;
}
i++;
}
return pointer;
}
ObserverList.prototype.RemoveIndexAt = function(index) {
if(index === 0) {
this.observerList.shift();
} else if(index === this.observerList.length - 1) {
this.observerList.pop();
}
};
function extend(obj, extension) {
for(var key in obj) {
extension[key] = obj[key];
}
}

接下来我们来定义目标类,它包含一些对自身观察者列表的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Subject() {
this.observers = new ObserverList();
}
Subject.prototype.AddObserver = function(observer) {
this.observers.Add(observer);
};
Subject.prototype.RemoveObserver = function(observer) {
this.observers.RemoveIndexAt(this.observers.IndexOf(observer, 0));
};
//这个就是目标通知观察者列表中各观察者的函数
Subject.prototype.Notify = function(context) {
var observerCount = this.observers.Count();
for(var i = 0; i < observerCount; i++) {
this.observers.Get(i).Update(context);
}
}

然后我们定义观察者类,他只有一个function操作,这个操作后面会被覆盖:

1
2
3
4
5
function Observer() {
this.Update = function() {
...
};
}

接下来,我们开始编写html标签,这次我们的要用观察者模式实现的功能很简单:

  • 存在一个用于添加新checkbox的容器
  • 存在一个向页面添加新可见checkbox的按钮
  • 控制一个checkbox,将它作为一个目标,其它checkbox作为观察者,通知其它checkbox需要进行检查

HTML代码如下:

1
2
3
4
5
6
7
<html>
<body>
<button id="addNewObserver">Add a new checkbox(Observer)</button>
<input id="mainCheckbox" type="checkbox" />
<div id="observersContainer"></div>
</body>
</html>

下面我们需要做的就是获得mainCheckbox的dom对象,并将其扩展为目标,然后为按钮编写响应函数,创建新的checkbox(也就是观察者)并把它增加到目标的观察者列表中

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
var controlCheckbox = document.getElementById("mainCheckbox"),
addBtn = document.getElementById("addNewObserver"),
container = document.getElementById("observersContainer");

//利用Subject(目标类)将原有的checkbox扩展为目标
extend(new Subject, controlCheckbox);
//将原有的checkbox的点击事件通知到其观察者列表中的观察者
controlCheckbox["onclick"] = function() {
controlCheckbox.Notify(controlCheckbox.checked);
};
//创建观察者的函数
function AddNewObserver() {
//创建一个新的checkbox
var check = document.createElement("input");
check.type = "checkbox";
//利用Observer类(观察者类)将新创建的checkbox扩展为观察者
extend(new Observer(), check);
//重写观察者的更新行为
check.Update = function(value) {
this.checked = value;
};
//将新创建的观察者添加到目标的观察者列表中,开始观察
controlCheckbox.AddObserver(check);
//将新创建的checkbox显示在容器上
container.appendChild(check);
}
//将创建观察这的函数作为按钮的响应函数
addBtn["onclick"] = AddNewObserver;
文章目录
  1. 1. 经典与现代设计模式的JavaScript实现
    1. 1.1. Constructor(构造器)模式
    2. 1.2. Module(模块)模式
    3. 1.3. Revealing Module(揭示模块)模式
    4. 1.4. Singleton(单例)模式
    5. 1.5. Observer(观察者)模式