1.面向对象的javascript

1.1 动态类型语言和鸭子类型

静态类型:编译时已确定变量的类型
优点:编译时就能发现不匹配的错误,还可以针对这些信息对程序进行一些优化,提高程序执行速度
缺点:被迫使用强契约编写程序,而且还会让精力从思考业务逻辑上分散开来

动态类型:在运行时,变量被赋值后才知道是某种类型。
优点:代码少,简洁。
缺点:无法保证变量类型。吃牛肉辣条,只有吃到嘴里才知道是不是牛肉味的

鸭子类型:鸡叫声和鸭子一样,所以可以充当鸭子来使用。
所以就有了多态这东西···

1.2 多态

比较低级的实现多态就是用if...else
多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。

1.2.2 对象的多态–ES6之前实现多态(原型链)

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 makeSound = function( animal ){
animal.sound(); //不用if...else 而是用prototype来区分不同
};

var Duck = function(){};

Duck.prototype.sound = function(){
console.log( '嘎嘎嘎' );
};

var Chicken = function(){};

Chicken.prototype.sound = function(){
console.log( '咯咯咯' );
};

makeSound( new Duck() ); // 嘎嘎嘎
makeSound( new Chicken() ); // 咯咯咯

var Dog = function(){};

Dog.prototype.sound = function(){
console.log( '汪汪汪' );
};

makeSound( new Dog() ); // 汪汪汪

1.2.4 使用继承得到多态效果 –ES6实现

参考你不知道的javascript 这里是用java写的
关键字:class、extend、

1.2.5 javascript的多态–ES6之前实现(判断数据类型)

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
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};

var renderMap = function(){
googleMap.show();
};

renderMap(); // 输出:开始渲染谷歌地图

var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};

var baiduMap = {
show: function(){
console.log( '开始渲染百度地图' );
}
};

var renderMap = function( type ){ //低端多态用if-else实现,耦合度很高
if ( type === 'google' ){
googleMap.show();
}else if ( type === 'baidu' ){
baiduMap.show();
}
};

renderMap( 'google' ); // 输出:开始渲染谷歌地图
renderMap( 'baidu' ); // 输出:开始渲染百度地图

var renderMap = function( map ){ //高级多态 耦合度低
if ( map.show instanceof Function ){
map.show();
}
};

renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( baiduMap ); // 输出:开始渲染百度地图

var sosoMap = {
show: function(){
console.log( '开始渲染搜搜地图' );
}
};

renderMap( sosoMap ); // 输出:开始渲染搜搜地图

1.3 封装

封装数据、封装实现、封装类型、封装变化

1.3.1 封装数据

1
2
3
4
5
6
7
8
9
10
var myObject = (function(){
var __name = 'sven'; // ★私有(private)变量
return {
getName: function(){ // ★return 公开(public)方法
return __name;
}
}
})();

console.log( myObject.getName() ); // 输出:sven

1.3.2 封装实现

内部变化,但是实现功能即可

1.3.3 封装类型

封装类型通过抽象类和接口来进行的。
之前章节的animal就是封装类型
js不区分类型是一种失色,也是一种解脱

1.3.4 封装变化

把系统不变的部分和需要替换的部分隔离开来。这样可复用性会很好。

1.4 原型模式和基于原型继承的javascript对象系统

1.4.1 使用克隆(其实不是克隆只是引用)的原型模式

主要是Object.create方法

1
2
3
4
5
6
7
8
9
10
11
12
13
var Plane = function(){
this.blood = 100;
this.attackLevel = 1;
this.defenseLevel = 1;
};

var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;

var clonePlane = Object.create( plane );
console.log( clonePlane ); // 输出:Object {blood: 500, attackLevel: 10, defenseLevel: 7}

在不支持Object.create 方法的浏览器中,则可以使用以下代码:

1
2
3
4
5
Object.create = Object.create || function( obj ){
var F = function(){};
F.prototype = obj;
return new F();
}

理解:clonePlane.prototype == plane
new的时候

1.4.5 javascript中的原型继承

1.4.5.1 所有数据都是对象

1.4.5.2 得到对象不是通过实例化类,而是找到一个对象作为原型并克隆他

当使用 new 运算符来调用函数时,此时的函数就是一个构造器。 用
new 运算符来创建对象的过程,实际上也只是先克隆 Object.prototype 对象,再进行一些其他额外操作的过程。

1.4.5.3 对象会记住它的原型

a.__proto__=== Object.prototype

1.4.6 原型继承的未来

通过 Object.create( null ) 可以创建出没有原型的对象。

2. this、call和apply

2.1 this

2.1.1 this的指向

1. 作为对象的方法

1
2
3
4
5
6
7
8
9
var obj = {
a: 1,
getA: function(){
alert ( this === obj ); // 输出:true
alert ( this.a ); // 输出: 1
}
};

obj.getA();

2. 作为普通函数调用

1
2
3
4
5
6
window.name = 'globalName';

var getName = function(){
return this.name; //只想window
};
console.log( getName() ); // 输出:globalName

注意:ES5的strict模式下,this不会指向全局对象,而是undefined

1
2
3
4
5
6
function func(){
"use strict"
alert ( this ); // 输出:undefined
}

func();

3. 构造器调用

1
2
3
4
5
6
7
8
9
var MyClass = function(){
this.name = 'sven';
//return { // 如果有return的话 那最后结果就不是sven而是anne了
// name: 'anne'
// }
};

var obj = new MyClass();
alert ( obj.name ); // 输出:sven

4. Function.prototype.call和Function.prototype.apply

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
};

var obj2 = {
name: 'anne'
};

console.log( obj1.getName() ); // 输出: sven
console.log( obj1.getName.call( obj2 ) ); // 输出:anne ★修改this

2.1.2 丢失的this

用闭包来保持this

2.2 call和apply

call 是包装在 apply 上面的一颗语法糖

2.2.2 call和apply的用途

  1. 改变this指向

  2. bind方法

  3. 借用其他对象的方法

3.闭包和高阶函数

3.1 闭包

3.1.3 闭包的更多作用

  1. 封装变量
  2. 延续局部变量的寿命(bind)

3.1.4 闭包和对象设计

1. 函数式的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var extent = function(){
var value = 0;
return {
call: function(){
value++;
console.log( value );
}
}
};

var extent = extent();
extent.call(); // 输出:1
extent.call(); // 输出:2
extent.call(); // 输出:3

初步了解函数式编程可以参考下列文章:
柯里化、函数组合、Point Free这些都是很好的用法
JavaScript函数式编程
关于javascript函数式编程中compose的实现

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
var extent = {
value: 0,
call: function(){
this.value++;
console.log( this.value );
}
};

extent.call(); // 输出:1
extent.call(); // 输出:2
extent.call(); // 输出:3

//或者:

var Extent = function(){
this.value = 0;
};

Extent.prototype.call = function(){
this.value++;
console.log( this.value );
};

var extent = new Extent();

extent.call();
extent.call();
extent.call();

3.1.6 闭包与内存管理

把变量设置成null

3.2 高阶函数

3.2.1 函数作为参数传递

  1. 回调函数callback
  2. Array.prototype.sort(fn)

3.2.2 函数作为返回值输出

3.2.2.1 判断数据的类型

1
2
3
4
5
6
7
8
9
10
11
var isType = function( type ){
return function( obj ){ //如果下边没有return的话,就会直接返回一个函数了。return后边接运行的程序,不要写函数
return Object.prototype.toString.call( obj ) === '[object '+ type +']'; //如果没有这个return的话 这个只能返回这个函数,这个函数是不执行的
}
};

var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumber = isType( 'Number' );

console.log( isArray( [ 1, 2, 3 ] ) ); // 输出:true

3.2.2.2 getSingle

单例模式:设置变量,返回这个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var getSingle = function ( fn ) {
var ret;
return function () {
return ret || ( ret = fn.apply( this, arguments ) );
};
};

//这个高阶函数的例子,既把函数当作参数传递,又让函数执行后返回了另外一个函数。我们
//可以看看getSingle 函数的效果:

var getScript = getSingle(function(){
return document.createElement( 'script' );
});
var script1 = getScript();
var script2 = getScript();
alert ( script1 === script2 ); // 输出:true

3.2.3 高阶函数实现AOP

AOP(面向切面编程):把无关的抽离出来,当需要的时候通过动态织入的方式传入逻辑模块中

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
Function.prototype.before = function( beforefn ){
var __self = this; // 保存原函数的引用
return function(){ // 返回包含了原函数和新函数的"代理"函数
beforefn.apply( this, arguments ); // 执行新函数,修正this ★先执行引入的这个方法
return __self.apply( this, arguments ); // 执行原函数 ★再执行原来的函数,这样原来的函数就在上边那个函数之后了,重点就是执行顺序
}
};

Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
};

var func = function(){
console.log( 2 );
};

func = func.before(function(){
console.log( 1 );
}).after(function(){
console.log( 3 );
});

func(); // 1,2,3

3.2.4 高阶函数的其他应用

1.currying 柯里化
定义:分步传递参数。每次传递参数后,应用部分参数,并返回一个函数接受剩下的参数,可嵌套多层。
因此柯里化的过程是逐步传参,(传入一个参数就返回一个新函数,这样他的范围就减小了)逐步缩小函数的适用范围,逐步求解的过程。
高程定义:函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。
两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数
目的:缩小适用范围,创建一个针对性更强的函数
应用:可固定部分参数,接受剩余参数,称为部分计算函数

最简单的求和柯里化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var concat3Words = function (a, b, c) {
return a+b+c;
};

var concat3WordsCurrying = function(a) {
return function (b) {
return function (c) {
return a+b+c;
};
};
};
console.log(concat3Words("foo ","bar ","baza")); // foo bar baza
console.log(concat3WordsCurrying("foo ")); // [Function]
console.log(concat3WordsCurrying("foo ")("bar ")("baza")); // foo bar baza

高程上的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function curry(fn){
var args = Array.prototype.slice.call(arguments, 1); //5
return function(){
var innerArgs = Array.prototype.slice.call(arguments); //3
var finalArgs = args.concat(innerArgs); // [5,3]
return fn.apply(null, finalArgs); //执行add(5,3)
};
}

function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5); //传入curry()这里的参数
alert(curriedAdd(3)); //8 这个是传入return 后function()这个里边的参数

参考:http://www.cnblogs.com/zztt/p/4142891.html

2.uncurrying 反柯里化
定义:使本来特定对象所拥有的功能函数可以被其他对象使用,通过借用泛化、扩大了函数的使用范围(call/apply)
目的:扩大函数的适用范围
应用:对 Javascript 内置的其他方法的 借调 而不用自己都去实现一遍。例如push方法

例子:扩充push方法:

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
Function.prototype.uncurrying = function () {
var self = this; // self 此时是Array.prototype.push
return function () {
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
// 相当于Array.prototype.push.apply( obj, 2 )
};
};

var push = Array.prototype.push.uncurrying(); //本来push只能数组用,但是反柯里化后对象也可以用了
var obj = {
"length": 1,
"0": 1
};

push( obj, 2 );
console.log( obj ); // 输出:{0: 1, 1: 2, length: 2}

//相当于另一种方法
Function.prototype.uncurrying = function(){
var self = this;
return function(){
return Function.prototype.call.apply( self, arguments );
}
};

参考:http://www.cnblogs.com/zztt/p/4152147.html

3.函数节流
window.onresize事件
mousemove事件
上传进度

4.分时函数

5.惰性加载函数/延迟加载
在高性能里已经说过了

1
2
3
4
5
6
7
8
9
10
11
12
var addEvent = function( elem, type, handler ){
if ( window.addEventListener ){
addEvent = function( elem, type, handler ){
elem.addEventListener( type, handler, false );
}
}else if ( window.attachEvent ){
addEvent = function( elem, type, handler ){
elem.attachEvent( 'on' + type, handler );
}
}
addEvent( elem, type, handler );
};

4. 单例模式

核心:确保只有一个实例,并提供全局访问
用途:线程池、全局缓存、浏览器中的window对象、ajax重复获取
实现方式:命名空间、闭包
惰性单例:

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 createIframe= (function(){
var iframe;
return function(){
if ( !iframe){ //主要是这里和下边的return结合用
iframe= document.createElement( 'iframe' ); //这里要赋值才行
iframe.style.display = 'none';
document.body.appendChild( iframe);
}
return iframe; //当已经存在的时候,直接返回
}
})();
//核心思想
var obj;
if(!obj){
obj = xxx;
}
//通式
var getSingle = function( fn ){
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
};
//使用
function fetch(){
iframe= document.createElement( 'iframe' ); //这里要赋值才行
iframe.style.display = 'none';
document.body.appendChild( iframe);
}
getSingle(fetch)

5. 策略模式

核心:第一部分是一组策略类(封装了具体的算法)。第二部分环境类(接受客户请求,然后委托给策略类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 第一部分:策略类
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;

}
};
//第二部分:环境类
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
//调用
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

高阶用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 第一部分:这里是 函数 而不再是对象
var S = function( salary ){
return salary * 4;
};
var A = function( salary ){
return salary * 3;
};
var B = function( salary ){
return salary * 2;
};
// 第二部分:这里直接传入 函数
var calculateBonus = function( func, salary ){
return func( salary );
};
calculateBonus( S, 10000 ); // 输出:40000

6. 代理模式

小明送花的例子
图片预加载的例子
核心:可以让本体对象只关心自身所干的事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//一个本体对象
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
imgNode.src = src;
}
}
})();
//一个代理对象
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage.setSrc( this.src );
}
return {
setSrc: function( src ){
myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src; //当有了src才会触发img.onload事件
}
}
})();
proxyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

创建缓存代理的例子:

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
// 计算乘积 
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
// 计算加和
var plus = function(){
var a = 0;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a + arguments[i];
}
return a;
};
// 创建缓存代理的工厂
var createProxyFactory = function( fn ){
var cache = {}; //缓存作用,再访问Cache['1,2,3,4']的时候,就直接取出了
return function(){
var args = Array.prototype.join.call( arguments, ',' ); //(1,2,3,4)
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = fn.apply( this, arguments ); //Cache[("1,2,3,4")]
}
};

var proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10

7.迭代器模式

其实就是jquery的each方法
现在有了forEach等方法就已经足够了

8.发布–订阅模式 (观察者模式)

add+fork形式

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
//所以我们把发布—订阅的功能提取出来,放在一个单独的对象内:通式
var event = {
clientList: [],
listen: function( key, fn ){
if ( !this.clientList[ key ] ){
this.clientList[ key ] = [];
}
this.clientList[ key ].push( fn ); // 订阅的消息添加进缓存列表
},
trigger: function(){
var key = Array.prototype.shift.call( arguments ), // (1);
fns = this.clientList[ key ];
if ( !fns || fns.length === 0 ){ // 如果没有绑定对应的消息
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是trigger 时带上的参数
}
}
};
// 这个可以用来让所有新定义的对象都拥有上边event的属性和方法
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
// 例子:再来测试一番,我们给售楼处对象salesOffices 动态增加发布—订阅功能:
var salesOffices = {};
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', function( price ){ // 小明订阅消息
console.log( '价格= ' + price );
});
salesOffices.listen( 'squareMeter100', function( price ){ // 小红订阅消息
console.log( '价格= ' + price );
});
salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000
salesOffices.trigger( 'squareMeter100', 3000000 ); // 输出:3000000

9.命令模式

核心:其实就是回调函数的一个面向对象的替代品
很乱,感觉还不如直接用面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 要被执行的动作
var MenuBar = {
refresh: function(){
console.log( '刷新菜单界面' );
}
};
// 触发这个动作的命令
var RefreshMenuBarCommand = function( receiver ){
return {
execute: function(){
receiver.refresh();
}
}
};
// 通过什么方式来触发
var setCommand = function( button, command ){
button.onclick = function(){
command.execute();
}
};
// 创建一个命令
var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar );
// 执行
setCommand( button1, refreshMenuBarCommand );

10.组合模式(宏)

就是一次执行几个动作,也可以分布执行
1.打开空调
2.打开电视和音响
3.关门、开电脑、登录QQ

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
// 组合宏通式
var MacroCommand = function(){
return {
commandsList: [],
add: function( command ){
this.commandsList.push( command );
},
execute: function(){
for ( var i = 0, command; command = this.commandsList[ i++ ]; ){
command.execute();
}
}
}
};
var openAcCommand = {
execute: function(){ //必须用execute
console.log( '打开空调' );
}
};

/**********打开电视和打开音响的命令*********/
var openTvCommand = {
execute: function(){
console.log( '打开电视' );
}
};
var openSoundCommand = {
execute: function(){
console.log( '打开音响' );
}
};
var macroCommand1 = MacroCommand(); //这里没有new是因为用了()就是一个已经执行后的所产生的对象了,而每个对象都不是同一个对象,所以没有必要用new
macroCommand1.add( openTvCommand );
macroCommand1.add( openSoundCommand );
/*********关门、打开电脑和打登录QQ 的命令***********/
var closeDoorCommand = {
execute: function(){
console.log( '关门' );
}
};
var openPcCommand = {
execute: function(){
console.log( '开电脑' );
}
};
var openQQCommand = {
execute: function(){
console.log( '登录QQ' );
}
};
var macroCommand2 = MacroCommand();
macroCommand2.add( closeDoorCommand );
macroCommand2.add( openPcCommand );
macroCommand2.add( openQQCommand );
/*********现在把所有的命令组合成一个“超级命令”**********/
var macroCommand = MacroCommand();
macroCommand.add( openAcCommand );
macroCommand.add( macroCommand1 );
macroCommand.add( macroCommand2 );
/*********最后给遥控器绑定“超级命令”**********/
var setCommand = (function( command ){
document.getElementById( 'button' ).onclick = function(){
command.execute();
}
})( macroCommand );

11.模板方法模式

核心:可以理解为继承,创建一个父类,父类拥有init方法,子类进行继承,执行init方法
javascript没必要一定创建模板,利用高阶函数就可以很好的解决这个问题了
1.把水煮沸
2.用沸水冲泡饮料
3.把饮料倒进杯子
4.加调料

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
var Beverage = function( param ){
var boilWater = function(){
console.log( '把水煮沸' );
};
var brew = param.brew || function(){
throw new Error( '必须传递brew 方法' );
};
var pourInCup = param.pourInCup || function(){
throw new Error( '必须传递pourInCup 方法' );
};
var addCondiments = param.addCondiments || function(){
throw new Error( '必须传递addCondiments 方法' );
};
var F = function(){};
F.prototype.init = function(){
boilWater();
brew();
pourInCup();
addCondiments();
};
return F;
};
var Coffee = Beverage({
brew: function(){
console.log( '用沸水冲泡咖啡' );
},
pourInCup: function(){
console.log( '把咖啡倒进杯子' );
},
addCondiments: function(){
console.log( '加糖和牛奶' );
}
});

var Tea = Beverage({
brew: function(){
console.log( '用沸水浸泡茶叶' );
},
pourInCup: function(){
console.log( '把茶倒进杯子' );
},
addCondiments: function(){
console.log( '加柠檬' );
}
});
var coffee = new Coffee();
coffee.init();
var tea = new Tea();
tea.init();

12.享元模式

核心:尽量减少遍历共享对象的数量
服装模特试衣服的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Model = function( sex ){
this.sex = sex;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};

var maleModel = new Model( 'male' ), // 男人单提出来
femaleModel = new Model( 'female' ); // 女人单提出来

for ( var i = 1; i <= 50; i++ ){
maleModel.underwear = 'underwear' + i;
maleModel.takePhoto();
};

for ( var j = 1; j <= 50; j++ ){
femaleModel.underwear = 'underwear' + j;
femaleModel.takePhoto();
};

通用对象池:

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
// 通用对象池
var objectPoolFactory = function( createObjFn ){
var objectPool = []; //主要还是这里避免再次运算
return {
create: function(){
var obj = objectPool.length === 0 ?
createObjFn.apply( this, arguments ) : objectPool.shift(); // 善用shift
return obj;
},
recover: function( obj ){
objectPool.push( obj );

}
}
};

// 装在一些iframe的对象池
var iframeFactory = objectPoolFactory( function(){
var iframe = document.createElement( 'iframe' );
document.body.appendChild( iframe );
iframe.onload = function(){
iframe.onload = null; // 防止iframe 重复加载的bug
iframeFactory.recover( iframe ); // iframe 加载完成之后回收节点
}
return iframe;
});

var iframe1 = iframeFactory.create();
iframe1.src = 'http:// baidu.com';
var iframe2 = iframeFactory.create();
iframe2.src = 'http:// QQ.com';
setTimeout(function(){
var iframe3 = iframeFactory.create();
iframe3.src = 'http:// 163.com';
}, 3000 );

13.职责链模式

优点:解耦请求发送者和n个接收者之间的复杂关系,和灵活拆分重组
用途:作用域链、原型链、冒泡事件,都能找到职责链的影子

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
// chainOrder500{
fn:, 这个就是order500 → 判断是否返回nextSucessor,如果返回的话,说明没有完,继续调用下一个order200即(this.success)同时执行passRequest来继续下去
success
}
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购,得到100 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};

var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购,得到50 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};

var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买,无优惠券' );
}else{
console.log( '手机库存不足' );
}
};

// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
};

Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};

Chain.prototype.passRequest = function(){

var ret = this.fn.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
return ret;
};

var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );

chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足

14.中介者模式

机场指挥塔
缺点:中介者可能非常巨大,难以维护,除非变化的数据特别特别多,可以用一个中介来操作,否则不推荐使用

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
function Player( name, teamColor ){
this.name = name; // 角色名字
this.teamColor = teamColor; // 队伍颜色
this.state = 'alive'; // 玩家生存状态
};

Player.prototype.win = function(){
console.log( this.name + ' won ' );
};

Player.prototype.lose = function(){
console.log( this.name +' lost' );
};
/*******************玩家死亡*****************/
Player.prototype.die = function(){
this.state = 'dead';
playerDirector.reciveMessage( 'playerDead', this ); // 给中介者发送消息,玩家死亡
};
/*******************移除玩家*****************/
Player.prototype.remove = function(){
playerDirector.reciveMessage( 'removePlayer', this ); // 给中介者发送消息,移除一个玩家
};

/*******************玩家换队*****************/
Player.prototype.changeTeam = function( color ){
playerDirector.reciveMessage( 'changeTeam', this, color ); // 给中介者发送消息,玩家换队
};

// 这里是一个中介,指挥塔
var playerDirector= ( function(){
var players = {}, // 保存所有玩家
operations = {}; // 中介者可以执行的操作
/****************新增一个玩家***************************/
operations.addPlayer = function( player ){
var teamColor = player.teamColor; // 玩家的队伍颜色
players[ teamColor ] = players[ teamColor ] || []; // 如果该颜色的玩家还没有成立队伍,则

players[ teamColor ].push( player ); // 添加玩家进队伍
};
/****************移除一个玩家***************************/
operations.removePlayer = function( player ){
var teamColor = player.teamColor, // 玩家的队伍颜色
teamPlayers = players[ teamColor ] || []; // 该队伍所有成员
for ( var i = teamPlayers.length - 1; i >= 0; i-- ){ // 遍历删除
if ( teamPlayers[ i ] === player ){
teamPlayers.splice( i, 1 );
}
}
};

15.装饰者模式

简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Plane = function(){};

Plane.prototype.fire = function(){
console.log( '发射普通子弹' );
}

var MissileDecorator = function( plane ){
this.plane = plane;
}
MissileDecorator.prototype.fire = function(){
this.plane.fire(); //包含了一个普通的fire
console.log( '发射导弹' );
}
var AtomDecorator = function( plane ){
this.plane = plane;
}
AtomDecorator.prototype.fire = function(){
this.plane.fire();
console.log( '发射原子弹' );
}

javascript的装饰者:高阶函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var plane = {
fire: function(){
console.log( '发射普通子弹' );
}
}
var missileDecorator = function(){
console.log( '发射导弹' );
}
var atomDecorator = function(){
console.log( '发射原子弹' );
}
var fire1 = plane.fire;
plane.fire = function(){
fire1(); //向上追溯到fire
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function(){
fire2(); //向上追溯到fire1
atomDecorator();
}
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹

16.状态模式

主要说一下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
31
32
33
var Light = function(){
this.currState = FSM.off; // 设置当前状态
this.button = null;
};

Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
button.innerHTML = '已关灯';
this.button = document.body.appendChild( button );
this.button.onclick = function(){
self.currState.buttonWasPressed.call( self ); // 把请求委托给FSM 状态机
}
};
// 下边这个就是状态机啦
var FSM = {
off: {
buttonWasPressed: function(){
console.log( '关灯' );
this.button.innerHTML = '下一次按我是开灯';
this.currState = FSM.on;
}
},
on: {
buttonWasPressed: function(){
console.log( '开灯' );
this.button.innerHTML = '下一次按我是关灯';
this.currState = FSM.off;
}
}
};
var light = new Light();
light.init();

17.适配器模式

转接头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
var baiduMap = {
display: function(){
console.log( '开始渲染百度地图' );
}
};
// 适配器,让没有show方法的百度地图也通过show来访问,就是相当于多加了一层壳
var baiduMapAdapter = {
show: function(){
return baiduMap.display();

}
};

renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( baiduMapAdapter ); // 输出:开始渲染百度地图

18. 单一职责原则

SRP原则体现:一个对象(方法)只做一件事情

19. 最少知识原则

减少对象之间的交互,解耦。

20. 开放-封闭原则

可以增加代码,但是不可以改动源代码
多态就是其中一种

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
};
window.onload = ( window.onload || function(){} ).after(function(){ //重要的就这句了
console.log( document.getElementsByTagName( '*' ).length );
});

21.面向接口编程

做一些配型判断的防御

1
2
3
4
5
6
7
8
var setCommand = function( command ){
document.getElementById( 'exeCommand' ).onclick = function(){
if ( typeof command.execute !== 'function' ){ // 利用这个判断语句来做防御
throw new Error( "command 对象必须实现execute 方法" );
}
command.execute();
}
};

22.代码重构

22.1 提炼函数

核心:将逻辑放到一个函数内,直接引用这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var getUserInfo = function(){
ajax( 'http:// xxx.com/userInfo', function( data ){
console.log( 'userId: ' + data.userId );
console.log( 'userName: ' + data.userName );
console.log( 'nickName: ' + data.nickName );
});
};
//改成:
var getUserInfo = function(){
ajax( 'http:// xxx.com/userInfo', function( data ){
printDetails( data ); // 核心:提取
});
};

var printDetails = function( data ){
console.log( 'userId: ' + data.userId );
console.log( 'userName: ' + data.userName );
console.log( 'nickName: ' + data.nickName );
};

22.2 合并重复的条件片段

核心:不管if还是else都会执行的语句,提出来放在最后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var paging = function( currPage ){
if ( currPage <= 0 ){
currPage = 0;
jump( currPage ); // 跳转
}else if ( currPage >= totalPage ){
currPage = totalPage;
jump( currPage ); // 跳转
}else{
jump( currPage ); // 跳转
}
};
// 改成
var paging = function( currPage ){
if ( currPage <= 0 ){
currPage = 0;
}else if ( currPage >= totalPage ){
currPage = totalPage;
}
jump( currPage ); // 把jump 函数独立出来
};

22.3 把条件分支语句提炼成函数

核心:把判断语句提出来,弄成一个函数()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var getPrice = function( price ){
var date = new Date();
if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){ // 夏天
return price * 0.8;
}
return price;
};
// 改为
var isSummer = function(){
var date = new Date();
return date.getMonth() >= 6 && date.getMonth() <= 9;
};

var getPrice = function( price ){
if ( isSummer() ){ // 夏天:记得加()
return price * 0.8;
}
return price;
};

22.4 合理使用循环

核心:替代try-catch

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
var createXHR = function(){
var xhr;
try{
xhr = new ActiveXObject( 'MSXML2.XMLHttp.6.0' );
}catch(e){
try{
xhr = new ActiveXObject( 'MSXML2.XMLHttp.3.0' );
}catch(e){
xhr = new ActiveXObject( 'MSXML2.XMLHttp' );
}
}
return xhr;
};
var xhr = createXHR();
//下面我们灵活地运用循环,可以得到跟上面代码一样的效果:
var createXHR = function(){
var versions= [ 'MSXML2.XMLHttp.6.0ddd', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ];
for ( var i = 0, version; version = versions[ i++ ]; ){
try{
return new ActiveXObject( version );
}catch(e){
}
}
};
var xhr = createXHR();

22.5 提前让函数退出代替嵌套条件分支

核心:每个判断语句都加上return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var del = function( obj ){
var ret;
if ( !obj.isReadOnly ){ // 不为只读的才能被删除
if ( obj.isFolder ){ // 如果是文件夹
ret = deleteFolder( obj );
}else if ( obj.isFile ){ // 如果是文件
ret = deleteFile( obj );
}
}
return ret;
};
// 改为
var del = function( obj ){
if ( obj.isReadOnly ){ // 反转if 表达式
return; // 加上return
}
if ( obj.isFolder ){
return deleteFolder( obj ); // 加上return
}
if ( obj.isFile ){
return deleteFile( obj ); // 加上return
}
};

22.6 传递对象参数代替过长的参数列表

核心:将过多的参数放到一个obj里

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 setUserInfo = function( id, name, address, sex, mobile, qq ){
console.log( 'id= ' + id );
console.log( 'name= ' +name );
console.log( 'address= ' + address );
console.log( 'sex= ' + sex );
console.log( 'mobile= ' + mobile );
console.log( 'qq= ' + qq );
};
setUserInfo( 1314, 'sven', 'shenzhen', 'male', '137********', 377876679 );
// 改为
var setUserInfo = function( obj ){ // 参数改为obj
console.log( 'id= ' + obj.id );
console.log( 'name= ' + obj.name );
console.log( 'address= ' + obj.address );
console.log( 'sex= ' + obj.sex );
console.log( 'mobile= ' + obj.mobile );
console.log( 'qq= ' + obj.qq );
};
setUserInfo({
id: 1314,
name: 'sven',
address: 'shenzhen',
sex: 'male',
mobile: '137********',
qq: 377876679
});

22.7 尽量减少参数数量

核心:有个个别参数可以通过其他参数求出来的,就不要写了

1
2
3
4
5
var draw = function( width, height, square ){};
// 改为
var draw = function( width, height ){ //square是可以通过width和height算出来的,所以就不要当做参数来传入了
var square = width * height;
};

22.8 少用三目运算符

核心:只有单行的时候用三目

1
2
3
4
5
6
7
8
9
10
11
12
// 单行推荐用
var global = typeof window !== "undefined" ? window : this;
// 多行的就不推荐用了,还是用if-else
if ( !aup || !bup ) {
return a === doc ? -1 :
b === doc ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
0;
}

22.9 合理使用链式调用

核心:return 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
// 类
var User = function(){
this.id = null;
this.name = null;
};
User.prototype.setId = function( id ){
this.id = id;
return this; // 保持链式
};
User.prototype.setName = function( name ){

this.name = name;
return this; // 保持链式
};
console.log( new User().setId( 1314 ).setName( 'sven' ) );

// 面向对象
var User = {
id: null,
name: null,
setId: function( id ){
this.id = id;
return this; // 保持链式
},
setName: function( name ){
this.name = name;
return this; // 保持链式
}
};
console.log( User.setId( 1314 ).setName( 'sven' ) );

var user = new User();
user.setId( 1314 );
user.setName( 'sven' );

22.10 分解大型类

核心:本来写在原型链上的方法,单提出来做一个类,但是要嵌回原对象,使得又变为原对象的方法

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 Spirit = function( name ){
this.name = name;
};
// attack要被提出来作为一个单独的类
Spirit.prototype.attack = function( type ){ // 攻击
if ( type === 'waveBoxing' ){
console.log( this.name + ': 使用波动拳' );

}else if( type === 'whirlKick' ){
console.log( this.name + ': 使用旋风腿' );
}
};

var spirit = new Spirit( 'RYU' );
spirit.attack( 'waveBoxing' ); // 输出:RYU: 使用波动拳
spirit.attack( 'whirlKick' ); // 输出:RYU: 使用旋风腿

// 把attack单提出来了
var Attack = function( spirit ){
this.spirit = spirit;
};
Attack.prototype.start = function( type ){
return this.list[ type ].call( this );
};
Attack.prototype.list = {
waveBoxing: function(){
console.log( this.spirit.name + ': 使用波动拳' );
},
whirlKick: function(){
console.log( this.spirit.name + ': 使用旋风腿' );
}
};
// 重点是改写了Spirit
var Spirit = function( name ){
this.name = name;
this.attackObj = new Attack( this ); //重要的是这句,将attack放到Spirit的attack上
};


Spirit.prototype.attack = function( type ){ // 攻击
this.attackObj.start( type );
};
var spirit = new Spirit( 'RYU' );
spirit.attack( 'waveBoxing' ); // 输出:RYU: 使用波动拳
spirit.attack( 'whirlKick' ); // 输出:RYU: 使用旋风腿

22.用 return 退出多重循环

核心:return 后如果有要执行的语句,直接放在他后边就行了

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
var func = function(){
for ( var i = 0; i < 10; i++ ){
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
return;
}
}
}
console.log( i ); // 这句代码没有机会被执行
};
// 改成
var print = function( i ){
console.log( i );
};
var func = function(){
for ( var i = 0; i < 10; i++ ){
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
return print( i ); // 放在return后边就会被执行了
}
}
}
};

func();

六大原则

包含开闭原则在内,设计模式的六大原则,这里不详细介绍,简单列下:

单一原则 (SRP): 实现类要职责单一,一个类只做一件事或者一类事,不要将功能无法划分为一类的揉到一起,答应我好吗

里氏替换原则(LSP): 不要破坏继承体系,子类可以完全替换掉他们所继承的父类,可以理解为调用父类方法的地方换成子类也可以正常执行调用,爸爸打下的江山儿子继位得无压力好吗

依赖倒置原则(DIP):我说下我的理解,如果某套功能或者业务逻辑可能之后会出现并行的另外一种模式或者较大的调整,那不如把这部分逻辑抽象出来,创建一个包含相关方法的抽象类,而实现类继承这个抽象类来重写抽象类中的方法,完成具体的实现,调用这些功能方法的类不需要关心自己调用的这些个方法的具体实现,只管调用这些抽象类中定义好的形式上的方法即可,不与实际实现这些方法的类发生直接依赖关系,方便之后的实现逻辑的替换更改;

接口隔离原则(ISP) : 在设计抽象类的时候要精简单一,白话说就是,A需要依赖B提供的一些方法,A我只用B的3个方法,B就尽量不要给A用不到的方法啦;

迪米特法则(LoD)降低耦合,尽量减少对象之间的直接的交互,如果其中一个类需要调用另一个类的某一个方法的话,可通过一个关系类发起这个调用,这样一个模块修改时,就可以最大程度的减少波及。

开放-封闭原则(OCP)告诉我们要对扩展开放,对修改关闭,你可以继承扩展我所有的能力,到你手里你想咋改咋改,但是,别 动我 本人 好吗?好的

← Prev Next →