Contents

JavaScript Patten Learning Thoughts

最近在看这本书《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自定义构造函数时,函数内部发生以下情况:

  1. 创建一个空对象(继承自Object.prototype),同时继承了该函数的原型(通过代理,修改它的原型生成的实例也会变化),this指向该对象。
  2. 构造函数里的属性和方法加入到this指向的对象中。
  3. 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中函数比较特殊:

  1. 函数是第一类对象。
  2. 它们可以提供作用域。

表现在函数可以动态创建、函数可以分配给变量、函数可以做参数传递、函数可以有自己的属性和方法。

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. 不知道创建类型的情况下,为工厂客户提供一种创建对象的接口。
 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,而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
  • 代理模式:包装一个对象来控制它的访问,避免高额的操作开销
  • 中介者模式:对象间不直接通信,而是通过中介者对象进行通信,保证松耦合
  • 观察者模式:创建可观察对象,当监听事件发生时通知所有观察着,实现松耦合