最近在看这本书《JavaScript模式》。记录精华部分至博客方便查阅。
基本技巧
声明变量的问题
我们知道js里仅存在函数作用域,var和let声明都会有变量提升,但是var声明后变量保持undefined,而let声明后保持uninitialized。
这个问题已经在TDZ里描述清楚了。
避免使用隐式类型转换
js在使用比较语句时会执行隐式类型转换,这也就是为什么执行 false==0
或 ""==0
,都会返回true。
推荐使用===减少阅读代码时的脑力开销。
字面量和构造函数
对象字面量
字面量即通过直接定义的方式,这样更富有表现力。
如var dog = {}; 由于js定义的对象是可变的,我们可以向dog中添加/删除属性和方法。
注意:即使{}对象也是具有从Object.prototype继承的属性和方法。这关系到js原型链,下面会介绍。
来自构造函数的对象
也可以通过一些构造函数来创建对象,如Object()、String()、Number()等。
而字面量创建的对象,强调了该对象仅是一个可变哈希映射,而不是从对象中提取的属性或方法。
自定义构造函数
我们可以通过自己的构造函数来定义对象。如:
1
2
3
4
5
6
7
| let Person = function(name) {
this.name = name;
this.say = function() {
return `I am ${this.name}`;
};
}
let jack = new Person('jack');
|
当我们new自定义构造函数时,函数内部发生以下情况:
- 创建一个空对象(继承自Object.prototype),同时继承了该函数的原型(通过代理,修改它的原型生成的实例也会变化),this指向该对象。
- 构造函数里的属性和方法加入到this指向的对象中。
- this指向新创建的对象,并且构造函数最后隐式地返回this(
前提没有显示地返回其它对象
)。
基本值类型包装器
js中有五个基本的值类型:number、string、boolean、null、undefined。
除了null和undefined以外,其他三个都具有基本包装对象。即可以通过Number()、String()、Boolean()来创建包装对象。
相比基本类型,包装对象提供了我们一些常用方法如:
1
2
| let a = 'hello world';
a.split(' ')//为了使用split方法,基本数据类型被转换为包装类型。
|
所以,基本值类型在调用包装对象方法时,会临时被转换成对象
。
而当没有使用new操作符是,包装构造函数将传递给它们的参数转换为一个基本类型值。
typeof Number(1);//number
函数
js中函数比较特殊:
- 函数是第一类对象。
- 它们可以提供作用域。
表现在函数可以动态创建、函数可以分配给变量、函数可以做参数传递、函数可以有自己的属性和方法。
js里{}定义的代码块不创建作用域,js中仅存在函数作用域。而()是分组操作符,并不提供作用域。
自定义函数(self-defining function)
具体表现:该函数内部通过新函数覆盖了自身。如下:
1
2
3
4
5
6
7
8
9
| let say = function() {
log('Boo')
say = function() {
log('Double Boo')
}
}
say()//Boo
say()//Double Boo
say()//Double Boo
|
- 优势:函数有仅需执行一次的初始化工作,使用该模式。
- 缺点:当重新定义自身时,已经添加到原始函数的任何属性都会丢失。(避免该函数使用不同的名称,即分配给不同的变量及对象使用,那么重定义将不会发生,并将将会执行原始函数体。)
即时函数(IIFE)
IIFE是一种可以支持在定义函数后立即执行该函数的语法。
1
2
3
| (function () {
log('l')
}())
|
注意js有函数作用域,我们可以在IIFE内部存储私有数据,返回特定内容。不用担心变量污染。
它常常使用在模块模板中。
Curry
函数柯里化
它是FP的特性,将一段代码分解成可配置、可复用的小函数。
具体实现:只传递函数的一部分参数去调用curry函数,让它返回一个函数去处理剩下的参数,可以参考Ramda。
Curry化的目标:make js function more readable and flexible
如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function currify(fn) {
let oldArgs = Array.from(arguments).slice(1);
return function() {
let newArgs = Array.from(arguments);
let args = oldArgs.concat(newArgs);
return fn.apply(null, args);
};
}
function add(...args) {
return args.reduce(function (res, each) {
res += each;
return res;
}, 0)
}
let res = currify(add, 2, 4)(1, 2, 3);
log(res)//12
|
对象创建模式
命名空间模式
为应用程序创建一个全局对象,让函数和变量变成它的属性。如jQuery里的$
我们也可以使用通用命名空间函数,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| let APP = {};
APP.namespace = function (nsStr) {
let nsAry = nsStr.split('.'), parent = APP;
if (nsAry[0] === 'APP') { nsAry = nsAry.slice(1) };
for (let i = 0; i < nsAry.length; i++) {
if (typeof parent[nsAry[i]] === 'undefined') {
parent[nsAry[i]] = {};
}
parent = parent[nsAry[i]];
}
return parent;
};
let arrayModule = APP.namespace('APP.utils.array');
log(arrayModule === APP.utils.array);//true
|
私有属性和方法
js中并没有语法来表示private、protect、public属性和方法。js对象中的成员都是公共的。
但是我们可以通过闭包来实现。如下:
1
2
3
4
5
6
| function Person() {
var name = 'Jack';
this.getName = function() {
return name;
};
}
|
沙箱模式
沙箱模式就是一个全局构造函数。可以使用构造函数创建对象并传递回调函数。
解决了命名空间模式中,没办法存在2个版本代码运行。注意,它也是创建对象的一种方式,通过事先添加好的模块 和 我们自定义的沙箱环境函数 来创建出一个对象
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 Sandbox() {
let args = Array.prototype.slice.call(arguments),
callback = args.pop(),
// modules can be passed as an array or as individual parameters
modules = (args[0] && typeof args[0] === "string") ? args : args[0],
i;
// make sure the function is called as a constructor
if (!(this instanceof Sandbox)) {
return new Sandbox(modules, callback);
}
// add properties to `this` as needed:
this.a = 1;
this.b = 2;
// now add modules to the core `this` object
// no modules or "*" both mean "use all modules"
if (!modules || modules === '*') {
modules = [];
for (i in Sandbox.modules) {
if (Sandbox.modules.hasOwnProperty(i)) {
modules.push(i);
}
}
}
// initialize the required modules
for (i = 0; i < modules.length; i += 1) {
Sandbox.modules[modules[i]](this);
}
// call the callback
callback(this);
}
// any prototype properties as needed
Sandbox.prototype = {
name: "Sandbox",
version: "1.0",
getName: function () {
return this.name;
}
};
Sandbox.modules = {};
Sandbox.modules.dom = function (box) {
box.module = 'dom';
};
let testDom = Sandbox('dom', function (box) {
//可以理解为:这个函数是我们独立的沙箱环境,并且生成的对象包含dom模块添加的属性
box.say = function() {
console.log('hello dom');
}
});
testDom.module//dom
testDom.getName()//Sandbox
|
对象常量
这里实现一个通用的常量对象,注意Object.prototype.hasOwnProperty的使用,可以参考。
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
| let Constant = (function() {
let constants = {},
prefix = 'CONST',
ownProp = Object.prototype.hasOwnProperty,
allowed = {
string: 1,
number: 1,
boolean: 1
};
return {
isDefined: function(name) {
return ownProp.call(constants, prefix+name);
},
set: function(name, value) {
if (this.isDefined(name)) return false;
if (!ownProp.call(allowed, typeof value)) return false;
constants[prefix+name] = value;
return true;
},
get: function(name) {
if (this.isDefined(name)) return constants[prefix+name];
else return null;
}
};
})();
log(Constant.isDefined('MAX_WIDTH'))//false
Constant.set('MAX_WIDTH', 100);
log(Constant.get('MAX_WIDTH'));//100
Constant.set('MAX_WIDTH', 500);
log(Constant.get('MAX_WIDTH'));//100
|
链模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| class App {
constructor(num = 0) {
this.num = num;
}
add(val) {
this.num += val;
return this;
}
minus(val) {
this.num -= val;
return this;
}
result() {
return this.num;
}
}
log(new App(1).add(10).minus(5).result());//6
|
代码复用模式
Js原型链作用:
Javascript只有一种结构:对象。每个对象都有一个内部链接到另一个对象,称为它的原型。
函数和对象的原型分别是prototype和__proto__。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,
依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾 null。
类式继承-默认模式
使用Parent()的构造函数创建一个对象,并将该对象赋值给Child()的原型。
注意:原型属性应该指向一个对象,而不是一个函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function Parent(name = 'parent') {
this.name = name;
}
Parent.prototype.say = function() {
log(this.name);
}
function Child(name = 'child') {
this.name = name;
}
Child.prototype = new Parent();
Child.prototype.hello = function() {
log(this.name);
}
let a = new Child();
a.say()//child
a.hello()//child
|
缺点:同时继承了2个对象的属性。每次创建Child时都会反复创建父对象,该机制效率低下。
类式继承-借用构造函数
由下代码看出,blog并不包含tags属性,当访问tags时,它通过原型链找到article的tags,所以本质上blog和article包含的是同一个tags。
而page通过借助Article构造函数,并将this指向了自身,从而产生了新的tags属性。
但是我们发现,通过构造函数StaticPage和Article之间不会再有链接,因为StaticPage.prototype指向的是个继承于Object的空对象。所以,StaticPage无法继承Article原型中的东西。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| function Article() {
this.tags = 'Article-tags';
}
Article.prototype.say = function() {
log('Article say hello');
}
function BlogPost() {}
let article = new Article();
BlogPost.prototype = article;
let blog = new BlogPost();
log(blog.tags, blog.hasOwnProperty('tags'));//Article-tags false
blog.say();//Article say hello
function StaticPage() {
Article.call(this);
}
let page = new StaticPage();
log(page.tags, page.hasOwnProperty('tags'));//Article-tags true
page.say();//throw TypeError
|
- 缺点:StaticPage无法继承Article原型中的东西。
- 优点:获取父对象成员变量的真实副本
类式继承-借用和设置原型
它是采用前两种模式的结合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function Parent(name = 'parent') {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
}
function Child(name = 'child') {
Parent.call(this, name);
}
Child.prototype = new Parent();
let kid = new Child();
log(kid.name);//child
log(kid.getName());//child
delete kid.name;
log(kid.getName());//parent
|
- 优点:1、获取父对象成员变量副本。2、获取父对象原型上的成员。
- 缺点:父构造函数被调用两次,自身属性也会被继承两次。这就是删除了kid.name,它还能从原型链上找到。
类式继承-共享原型
1
2
3
4
5
6
7
8
9
| function Parent(name = 'parent') {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
}
function Child() {
}
Child.prototype = Parent.prototype;
|
注意该模式的可复用成员只能放在原型中,因为所有对象实际上共享了同一个原型。
缺点:由于共享同一个原型,若继承链上某子孙修改了原型,会影响到所有父对象和祖先对象。
类式继承-临时构造函数
通过断开父对象与子对象的原型之间的直接链接关系,从而解决共享同一个原型带来的问题,而且继续受益原型链带来的好处。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| function Parent(name = 'parent') {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
}
function Child() {
}
let inherit = (function(){
let F = function() {};
return function(C, P) {
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
C.prototype.constructor = C;
};
}());
inherit(Child, Parent);
let kid = new Child();
log(kid.name);//undefined
log(kid.getName());//undefined
log(kid.constructor.uber);//Parent { getName: [Function] }
|
注意:这种模式也被称为: 使用代理函数模式,因为创建的临时函数F实际上是一个用于获取父对象原型的代理。
原型继承
本模式中并不涉及类,这里的对象都是继承自其他对象。
考虑方式:有一个想要复用的对象,现在创建一个对象从它那获取功能。
1
2
3
4
5
| let parent = {name:'Test'};
//第二个参数是obj,名字为属性名,值为属性描述符。与Object.defineProperties第二个参数一样。
let child = Object.create(parent, {age: {value: 1}});
log(child.name)//Test
log(child.age)//1
|
通过复制属性实现继承
这里实现一个deep extend版本
注意:for...in循环遍历对象自身的和继承的可枚举属性,所以这里用hasOwnProperty保证只复制自身属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function extendDeep(parent, child) {
let toStr = Object.prototype.toString;
let ARRAY_TYPE_STR = '[object Array]';
child = child || {};
for (let i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === 'object') {
child[i] = toStr.call(parent[i]) === ARRAY_TYPE_STR ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}
|
Mix-in
注意这里在for…in循环里也判断了hasOwnProperty防止来自继承的属性。
1
2
3
4
5
6
7
8
9
10
11
| function mixin() {
let res = {};
for (let i = 0; i < arguments.length; i++) {
for (let p in arguments[i]) {
if (arguments[i].hasOwnProperty(p)) {
res[p] = arguments[i][p];
}
}
}
return res;
}
|
借用方法
有时候只想使用对象中的某个方法,而不希望继承那些用不到的方法。可以通过借用方法实现,这得益于call和apply。
1
2
3
4
| function slice(ary, start, end) {
return Array.prototype.slice.call(ary, start, end);
}
log(slice([1,2,3,4,5,6,7], 0, 3));//[ 1, 2, 3 ]
|
小结
js里很少建立长而且复杂的继承链,而采用上面一些简洁的方式(借用方法/mix-in/extend等)。
代码重用才是最终目标,而继承只是实现这一目标的方法之一。
设计模式
单例模式(Singleton)
目的:每次获取的obj都是同一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| let Singleton = (function(){
let instance;
function init() {
//private
return {};
};
return {
getInstance: function() {
if(!instance) {
instance = init();
}
return instance;
}
};
}());
let s1 = Singleton.getInstance();
let s2 = Singleton.getInstance();
log(s1 === s2);//true
|
工厂模式(Factory)
目的:
- 创建相似对象时执行重复操作。
- 不知道创建类型的情况下,为工厂客户提供一种创建对象的接口。
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
| function CarMaker() { }
CarMaker.prototype.drive = function () {
console.log( "Vroom, I have " + this.doors + " doors");
};
CarMaker.factory = function(type) {
let constr = type, newcar;
if (typeof CarMaker[constr] !== "function") {
throw {
name: "Error",
message: constr + " doesn't exist"
};
}
if (typeof CarMaker[constr].prototype.drive !== "function") {
CarMaker[constr].prototype = new CarMaker();
}
newcar = new CarMaker[constr]();
return newcar;
};
CarMaker.Compact = function() {
this.doors = 4;
};
CarMaker.Convertible = function() {
this.doors = 2;
};
CarMaker.SUV = function() {
this.doors = 24;
};
CarMaker.factory('Compact').drive();//Vroom, I have 4 doors
CarMaker.factory('Convertible').drive();//Vroom, I have 2 doors
CarMaker.factory('SUV').drive();//Vroom, I have 24 doors
|
迭代器模式(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
| var iterator = (function() {
var index = 0,
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
length = data.length;
return {
next: function() {
var element;
if (!this.hasNext()) {
return null;
}
element = data[index];
index += 1;
return element;
},
hasNext: function() {
return index < length;
},
rewind: function() {
index = 0;
return data[index];
},
current: function() {
return data[index];
}
}
}());
|
装饰者模式(Decorator)
在运行时动态附加功能到对象中
特征:其预期行为可定制和可配置。即从普通对象开始,从装饰资源池中选择 需要增强普通对象的功能,并且按照顺序对其进行装饰。
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
| function Sale(price) {
this.price = price || 1;
this.decoratorsList = [];
}
Sale.decorators = {};
Sale.decorators.fedtax = {
getPrice: function(price) {
return price * 3;
}
};
Sale.decorators.quebec = {
getPrice: function(price) {
return price * 2;
}
};
Sale.prototype.decorate = function(decorator) {
this.decoratorsList.push(decorator);
}
Sale.prototype.getPrice = function() {
let price = this.price;
for (let i=0, max=this.decoratorsList.length; i < max; i++) {
price = Sale.decorators[this.decoratorsList[i]].getPrice(price);
}
return price;
}
let sale = new Sale(50);
sale.decorate('fedtax');
sale.decorate('quebec');
log(sale.getPrice());//300
|
策略模式(Strategy)
在运行时选择算法
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
| let Validator = function(config) {
this.config = config;
this.messages = [];
}
Validator.prototype.types = {
isNumber: {
validate: function(value) {
return !isNaN(value);
},
instructions: 'the value can noly be a valid number'
},
isNonEmpty: {
validate: function(value) {
return value !== '';
},
instructions: 'the value can not be empty'
}
};
Validator.prototype.validate = function(data) {
for (let i in data) {
if (data.hasOwnProperty(i)) {
let type = this.config[i], checker = this.types[type];
if (!type) continue;
if (!checker) throw `No handler to validate type : ${type}`;
if (!checker.validate(data[i])) {
this.messages.push(checker.instructions);
}
}
}
return this.messages.length === 0;
};
let test = new Validator({name: 'isNonEmpty', age: 'isNumber'});
log(test.validate({name: 'frank', age: 'f'}));//false
log(test.messages);//[ 'the value can noly be a valid number' ]
|
代理模式(Proxy)
一个对象充当另一个对象的接口。介于对象客户端和对象本身之间,对对象进行保护。
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
| var products = new Proxy([
{ name: 'Spam', type: 'canned' },
{ name: 'Carrots', type: 'vegetable' },
{ name: 'Rice', type: 'canned' },
{ name: 'Pork Chop', type: 'meat' }
],
{
get: function(obj, prop) {
if (prop in obj) return obj[prop];
if (prop === 'number') return obj.length;
var result, types = {};
for (var product of obj) {
if (product.name === prop) {
result = product;
}
if (types[product.type]) {
types[product.type].push(product);
} else {
types[product.type] = [product];
}
}
// Get the product name
if (result) {
return result;
}
// Get product
if (prop in types) {
return types[prop];
}
// Get product types in array
if (prop === 'types') {
return Object.keys(types);
}
// default
return undefined;
}
});
log(products[0]); // returns Object {name: "Spam", type: "canned"}
log(products['Spam']); // Object {name: "Spam", type: "canned"}
log(products['Chorizo']); // undefined not in object
log(products.canned); // {name: "Spam", type: "canned"} {name: "'Carrots'", type: "'vegetable' "}
log(products.types); // ["canned", "vegetable", "meat"]
log(products.number);// 4
|
问题:对象互相知道太多信息,并且直接通信。导致不良的紧耦合,不利于程序的扩展修改。
解决:Mediator为了促进松耦合。独立对象之间不直接通信,而是通过Mediator对象。
实施:当其中一个对象改变状态后,它会通知Mediator,而Mediator会把此变化通知到 其它应该知道此变化的对象。
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
| let Participant = function(name) {
this.name = name;
this.chatroom = null;
};
Participant.prototype = {
send: function(message, to) {
this.chatroom.send(message, this, to);
},
receive: function(message, from) {
log.add(from.name + " to " + this.name + ": " + message);
}
};
let Chatroom = function() {
let participants = {};
return {
register: function(participant) {
participants[participant.name] = participant;
participant.chatroom = this;
},
send: function(message, from, to) {
if (to) {
to.receive(message, from);
} else {
for (let key in participants) {
if (participants[key] !== from) {
participants[key].receive(message, from);
}
}
}
}
};
};
let log = (function() {
let logStr = "";
return {
add: function(msg) { logStr += msg + "\n"; },
show: function() { console.log(logStr); logStr = ""; }
}
})();
function run() {
let yoko = new Participant("Yoko");
let john = new Participant("John");
let paul = new Participant("Paul");
let ringo = new Participant("Ringo");
let chatroom = new Chatroom();
chatroom.register(yoko);
chatroom.register(john);
chatroom.register(paul);
chatroom.register(ringo);
yoko.send("All you need is love.");
yoko.send("I love you John.");
john.send("Hey, no need to broadcast", yoko);
paul.send("Ha, I heard that!");
ringo.send("Paul, what do you think?", paul);
log.show();
}
run();
// Yoko to John: All you need is love.
// Yoko to Paul: All you need is love.
// Yoko to Ringo: All you need is love.
// Yoko to John: I love you John.
// Yoko to Paul: I love you John.
// Yoko to Ringo: I love you John.
// John to Yoko: Hey, no need to broadcast
// Paul to Yoko: Ha, I heard that!
// Paul to John: Ha, I heard that!
// Paul to Ringo: Ha, I heard that!
// Ringo to Paul: Paul, what do you think?
|
观察者模式(Observer)
目的促进松耦合。实施:一个对象订阅另一个对象的特定活动,并在状态改变时获取通知。订阅者称作观察者,被观察的对象称作发布者。
注意:在中介者模式中,mediator对象必须知道所有其他对象,以便在变化时通知。而观察者模式中,主要依赖于订阅的某些事件。这导致更为松散的耦合(知道更少的对象)
实施中几个关键成员:
- subscribes数组
- subscribe()将订阅者添加到subscribes数组中
- unsubscribe()从订阅者subscribes数组中删除订阅者
- publish()循环遍历subscribes中元素,并调用他们注册时提供的方法
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
| var publisher = {
subscribers: {
any: [] // event type: subscribers
},
subscribe: function (fn, type) {
type = type || 'any';
if (typeof this.subscribers[type] === "undefined") {
this.subscribers[type] = [];
}
this.subscribers[type].push(fn);
},
unsubscribe: function (fn, type) {
this.visitSubscribers('unsubscribe', fn, type);
},
publish: function (publication, type) {
this.visitSubscribers('publish', publication, type);
},
visitSubscribers: function (action, arg, type) {
var pubtype = type || 'any',
subscribers = this.subscribers[pubtype],
i,
max = subscribers.length;
for (i = 0; i < max; i += 1) {
if (action === 'publish') {
subscribers[i](arg);
} else {
if (subscribers[i] === arg) {
subscribers.splice(i, 1);
}
}
}
}
};
function makePublisher(o) {
var i;
for (i in publisher) {
if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
o[i] = publisher[i];
}
}
o.subscribers = {any: []};
}
var paper = {
daily: function () {
this.publish("big news today");
},
monthly: function () {
this.publish("interesting analysis", "monthly");
}
};
var joe = {
drinkCoffee: function (paper) {
console.log('Just read ' + paper);
},
sundayPreNap: function (monthly) {
console.log('About to fall asleep reading this ' + monthly);
}
};
makePublisher(paper);
paper.subscribe(joe.drinkCoffee);
paper.subscribe(joe.sundayPreNap, 'monthly');
paper.daily();//Just read big news today
paper.monthly();//About to fall asleep reading this interesting analysis
paper.unsubscribe(joe.drinkCoffee);
paper.daily();//nothing
|
总结
- 单例模式:针对一个’类’只创建一个对象
- 工厂模式:根据字符串指定的类型在运行时创建对象的方法
- 迭代器模式:提供一个API来遍历和操作复杂的自定义数据结构
- 装饰者模式:通过从预定义装饰者对象中添加功能,从而在运行时修改对象
- 策略模式:在选择最佳策略处理任务时,仍保持相同的接口
- 外观模式:把常用方法包装到新方法中,提供一个更便利的API
- 代理模式:包装一个对象来控制它的访问,避免高额的操作开销
- 中介者模式:对象间不直接通信,而是通过中介者对象进行通信,保证松耦合
- 观察者模式:创建可观察对象,当监听事件发生时通知所有观察着,实现松耦合