logo

JavaScript设计模式(二)

Prototype(原型)模式

Prototype 模式是一种基于现有模板,通过克隆方式创建对象的模式。

javascript 中的继承就是基于原型模式。
带来性能提升,多个子类,子类实例都指向一个 prototype,节省资源。
通过Object.create方法,或指定prototype属性我们可以轻松实现原型继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let Person = {
getName() {˜
return this.name;
}
};
let Tom = Object.create(Person, {
name: {
value: "Tom"
}
});

Tom.getName();
/*
"Tom"
*/

Command(命令)模式

Command 模式下我们不会直接去调用对象的各个方法,而是需要进行的操作调用封装成一个个命令,只调用对象 run(command)execute(command)等执行方法进行,之对外暴露执行方法以及命令。相当于增加了一层代码逻辑处理层,即使核心方法移除,也只需要改变执行方法中对命令解析的逻辑即可。

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
var Person = {
firstName: "Hanks",
lastName: "Tom",
sayHi() {
console.log("Hi");
},
sayBye() {
console.log("Bye");
},
run(...arg) {
arg.forEach(item => {
switch (item) {
case "sayName":
console.log(this.lastName + " " + this.firstName);
break;
case "sayHi":
console.log(this.sayHi());
break;
case "sayBye":
console.log(this.sayBye());
break;
}
});
}
};
Person.run("sayHi", "sayName", "sayBye");
/*
Hi
Tom Hanks
Bye
*/

Facade(外观)模式

Facade 模式下提供一个方便的高层次结构,能够隐藏底层的真实复杂性(如不同浏览器之间的兼容),将简化的 API 提供给使用者,提高可用性。

以 jQuery 为例子,$()函数会根据传递参数不同执行不同的方法,如传入选择器字符串则使用兼容方法查找 DOM 元素并返回一个列表,如果传入函数则会在 DOMContentLoaded 或 load(IE fallback)时执行该函数,如果传入两个选择器字符串则会在第一个选择器查找结果下再使用第二个选择器过滤结果等等。
同时 jQuery 提供自定义伪类如visable来判断元素的的显示隐藏等等易用 API,jQuery 抹平了兼容性,隐藏底层复杂实现,返回统一强大接口,因此被广泛应用。

这种设计模式下包含隐藏的性能成本,但这是在所难免的,高层次接口最终也是调用底层接口实现,效率肯定会低效一些。这是一个便利性可维护性与性能权衡的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
let Person = (function() {
let info = {
name: "aaron",
age: "18",
weight: "40kg"
};
return {
getInfo() {
return `name:${info.name};age:${info.age};weight:${info.weight}`;
}
};
})();
Person.getInfo();

Factory(工厂)模式

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
function Fruits() {}
Fruits.prototype = {
fruitClass: Banana,
getFruits(type) {
if (type === "apple") {
this.fruitClass = Apple;
} else if (type === "banana") {
this.fruitClass = Banana;
}
return this.createFruits();
},
createFruits() {
return new this.fruitClass();
}
};
function Banana() {}
Banana.prototype = { color: "yellow" };
function Apple() {}
Apple.prototype = { color: "red" };

let fruitsFactory = new Fruits();
console.log(fruitsFactory.getFruits("apple") instanceof Apple);
console.log(fruitsFactory.getFruits("banana") instanceof Banana);
/*
true
true
*/

这种设计模是会带来不必要的复杂性,除非为创建对象提供一个接口是一个库或者框架的设计要求,否则还是显示创建对象,保持代码的简洁透明。

Mixin(混合)模式

Mixin 模式可以看做灵活扩展多个对象的方法属性的一种方式,提高代码的复用,如Vue.mixin可以混入自定义需求。

Mixin 模式有助于减少重复功能,增加函数复用。

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
const logMixin = {
log(text) {
console.log(new Date().toLocaleTimeString("en") + ":" + text);
}
};
function Person() {
this.name = "Superise";
this.age = 233;
}

Person.prototype = {
getName() {
this.log("get name!");
return this.name;
},
getAge() {
this.log("get age");
return this.age;
}
};

Object.assign(Person.prototype, logMixin);

let person = new Person();
person.getName();
person.getAge();
/*
8:19:26 PM:get name!
8:19:26 PM:get age
*/

Decorator(装饰者)模式

Decorator 模式也是一种拓展对象属性方法的模式, 但是这种模式更注重于动态为对象拓展属性方法, 拓展的属性方法往往不是必须的(否则就在构造函数上拓展  了), 针对不同对象可以拓展不同的方法属性。

Decorator 模式不依赖创建对象的方式,而关注扩展的属性方法,每一个装饰器函数为了灵活性,拓展的属性方法较为单一。
与 ES7 中的 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
31
32
33
34
35
36
37
38
39
function MacBook() {
this.cost = function() {
return 997;
};
this.screenSize = function() {
return 11.6;
};
}
/*Decorator1*/
function Memory(macbook) {
var v = macbook.cost();
macbook.cost = function() {
return v + 75;
};
}
/*Decorator2*/
function Engraving(macbook) {
var v = macbook.cost();
macbook.cost = function() {
return v + 200;
};
}
/*Decorator3*/
function Insurance(macbook) {
var v = macbook.cost();
macbook.cost = function() {
return v + 250;
};
}
var mb = new MacBook();
Memory(mb);
Engraving(mb);
Insurance(mb);
console.log(mb.cost());
console.log(mb.screenSize());
/*
1522
11.6
*/

Flyweight(享元)模式

Flyweight 模式是一种经典的结构型解决方案,用于优化重复、缓慢及数据共享效率低得代码。

在 Flyweight 模式中, 通常要处理大量  的对象,因此经常出现在 Factory 模式中。
Flyweight 内部  状态是用来共享的, 通过维护一个对象存储池 Flyweight Pool 来存放内部对象,可以提高程序的速度。

比如我们要生产 10000 台 iPhone6s,一半 16G,一半 32G。

1
2
3
4
5
6
7
8
9
10
11
function IPhone(model, screen, memory, SN) {
this.model = model;
this.screen = screen;
this.memory = memory;
this.SN = SN;
}
var phones = [];
for (var i = 0; i < 1000000; i++) {
var memory = i % 2 === 0 ? 16 : 32;
phones.push(new IPhone("iphone6s", 5.0, memory, i));
}

这样的话每一个 iPhone 都需要实例化占据一块内存。但是其实 iPhone 之间只有 SN 与内存不一样。
我们可以使用享元模式进行优化。

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
/*享元对象*/
function IPhoneFlyweight(model, screen, memory) {
this.model = model;
this.screen = screen;
this.memory = memory;
}
/*享元工厂*/
var flyweightFactory = (function() {
/*对象池*/
var phones = {};
return {
get: function(model, screen, memory) {
var key = model + screen + memroy;
if (!phones[key]) {
phones[key] = new IPhoneFlyweight(model, screen, memory);
}
return phones[key];
}
};
})();
function IPhone(model, screen, memory, SN) {
this.flyweight = flyweightFactory.get(model, screen, memory);
this.SN = SN;
}
var phones = [];
for (var i = 0; i < 1000000; i++) {
var memory = i % 2 === 0 ? 16 : 32;
phones.push(new IPhone("iPhones", 5.0, memory, i));
}

这样我们通过共享相同数据优化了占用内存。

参考链接

JavaScript 设计模式 - Addy Osmani