《JavaScript设计模式与开发实战笔记一模式基础》

《JavaScript设计模式与开发实战笔记一模式基础》

@(记录)[读书笔记]

一:简介JavaScript设计模式

多态技术:

**多态的实际含义是**:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。
换句话说就是给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。
多态是面向对象编程中的一个重要技术;
使用多态的好处是,你可以不用考虑对象的类型,直接使用对象的行为就可以了;

简单多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var makeSound = function(animal) {
animal.sound();
}
var Duck = function() {}
Duck.prototype.sound = function() {
console.log('speak duck');
}
var Dog = function() {}
Dog.prototype.sound = function() {
console.log('dog speak');
}
makeSound(new Duck());//speak duck
makeSound(new Dog());//dog speak

多态的思想实际上就是把“做什么”和“谁去做”分离开来。
JavaScript是弱类型语言,同时修改原型的方法会重写里面的方法,
不同于Java中多态继承,通过继续父类的方法,然后通过修改函数参数类型,数量来达到多态效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var googleMap = {
show: function() {
console.log('google');
}
}
var baiduMap = {
show: function() {
console.log('baidu');
}
}
var renderMap = function(type) {
if (type == 'google') {
googleMap.show();
} else if (type == 'baidu') {
baiduMap.show();
}
}
renderMap('google');//google
renderMap('baidu');//baidu,同一个函数,不同参数对应的行为不同

1
2
3
4
5
6
7
8
9
//JavaScript中模拟数据的封装性:public,private
var object = (function() {
var __name = 'mewhat'; //在匿名立即执行函数中,外界不能访问里面的数据,相当于private
return { //返回的对象是给别人调用的,是公开的,所以相当于public
getName: function() {
return __name;
}
}
})();

了解ECMAScript中的let和Symbol
https://github.com/lukehoban/es6features
封装不仅仅是隐藏数据,还有实现细节,设计细节已经隐藏对象的类型

介绍了prototype,原型模式

JavaScript遵循的原型编程规则:

1,所有的数据都是对象
基本类型(undefined,number,boolean,string,function,object)
JavaScript中大多数数据是对象,而对象基本是基于Object对象创建的,
JavaScript中的根对象是Object.prototype对象。

创建对象有两种方法

1
2
3
var obj = new Object();
//or
var obj = {};

Object提供创建对象的方法create
对象的创建

1
2
3
4
5
Object.create = Object.create || function(obj) {
var F = function(){};
F.prototype = obj;
return new F();
}
1
2
3
4
var obj1 = new Object();
var obj2 = {};
console.log(Object.getPrototypeOf(obj1) === Object.prototype);//true
console.log(Object.getPrototypeOf(obj2) === Object.prototype);//true

2,要得到一个对象,不是通过实例化,而是找到一个对象作为原型并克隆它

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
var a = new Person('sven');
console.log(a.name);//sven
console.log(a.getName());//sven
console.log(Object.getPrototypeOf(a) === Person.prototype);//true

通过new调用Person构造函数创建一个对象,其实是先克隆Object.prototype然后在做一些
其他的额外操作(JavaScript是通过克隆Object.prototype来得到新对象,但实际上并不是每一次
都真正克隆了一个新的对象,从内存方面的考虑出发,JavaScript做了一些额外的处理,
具体细节可以看《JavaScript语言精髓与编程实践》)

3,对象会记住它的原型

1
2
3
//__proto__属性指向对象的构造器的原型对象
var a = new Object();
console.log(a.__proto__ === Object.prototype);//true

4,如果对对象无法响应某个请求,它会把这个请求委托给它自己的原型系统
具体的说就是,对象中没有某个属性,就会沿着对象的原型链查找这个属性,直到Object.prototype,还是没有找到的话直接返回undefined

二:this,call,apply

this可以参考之前写的这篇笔记JavaScript中this的用法

call和apply的区别主要是传参的不同,apply可以传递数组,而call只能一个个传递。
apply接收两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下表的集合
当使用apply和call的时候,如果第一个参数是null,函数体内的this会指向默认的宿主对象,在浏览器中则是window

1
2
3
4
var func = function(a,b,c) {
console.log(this === window);
}
func.apply(null, [1,2,3]);//true

严格模式下函数体内的this为null

1
2
3
4
var func = function(a) {
"use strict"
console.log(this === null);
}

使用call,apply改变函数内this的指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var it = 1;
document.getElementsByTagName('body')[0].onclick = function() {
var it = 0;
var func = function() {
console.log(this.it);
}
func();
}
//在函数体内调用函数,则函数内的this指向window,所以输出1
//改变指向
document.getElementsByTagName('body')[0].onclick = function() {
var func = function() {
console.log(this);//指body这个节点
}
func.call(this);//this指的是body这个节点
}
//使用var self = 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
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//在window作用域中通过call函数,调用extent作用域的value变量
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
//面向对象版本
var extent = {
value: 0,
call: function() {
this.value++;
console.log(this.value);
}
};
extent.call();//1
extent.call();//2
extent.call();//3
//使用闭包
var Tv = {
open: function() {
console.log("open tv");
},
close: function() {
console.log("close tv");
}
};
var createCommand = function(receiver) {
var execute = function() {
return receiver.open();//执行命令,打开电视
}
var undo = function() {
return receiver.close();//执行命令,关闭电视
}
return {
execute: execute,
undo: undo
}
};
var setCommand = function(command) {
document.getElementsByTagName('body')[0].onclick = function() {
command.execute();
}
document.getElementsByTagName('body')[0].onclick = function () {
command.undo();
}
};
setCommand(createCommand(Tv));
//使用闭包封装变量
var mult = (function() {
var cache = {};//设置缓存,避免重复计算
var calculate = function() {//封闭calculate函数。可复用的小函数提取出来用闭包封装
var a = 1;
for(var i = 0,l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
}
return function() {
var args = Array.prototype.join.call(arguments, ',');//arguments对象调用Array的join方法
if (args in cache) {
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
})();
console.log(mult(1,2,3));
//延长局部变量的寿命
var report = function(src) {
var img = new Image();
img.src = src;
}
report('http://xxx.com/getUserInfo');

eg:
为了解决img(new Image())上传丢失的问题(img是report函数中的局部变量,当report函数的调用结束后,img局部变量随即被销毁,而此时回去还没来得及发出HTTP请求,所以这次请求就会丢失)。使用闭包封装img,立即调用

1
2
3
4
5
6
7
8
var report = (function() {
var imgs = [];
return function(src) {
var img = new Image();
imgs.push(img);
img.src = src;
}
})();

###高阶函数
1,函数可以作为参数被传递
2,函数可以作为返回值输出

把函数作为参数传递,代表我们可以抽离一部分容易变化的业务逻辑,
把这部分逻辑放在函数参数中。

ajax中使用回调函数

1
2
3
4
5
6
7
8
var getUserInfo = function(userId, callback) {
$.ajax('/path?' + userId, function(data) {
callback(data);
});
}
getUserInfo(12456, function(data) {
console.log(data.username);
});

高阶函数实现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
29
30
31
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

函数的柯力化

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 currying = function(fn) {
var args = [];
return function() {
if (arguments.length === 0) {
return fn.apply(this,args); //如果函数没有参数,则求args中的和
} else { //如果函数有参数,则args数组对象借用push方法,将arguments数组的值传递进args数组中
[].push.apply(args, arguments);
return arguments.callee; //递归调用arguments
}
}
}
var cost = (function() {
var money = 0;
return function() {
for(var i = 0, l = arguments.length;i < l; i++) {
money += arguments[i];
}
return money;
}
})();
var cost = currying(cost);//转化为柯里函数
cost(100);
cost(200);
cost(300);
console.log(cost()); //求值,输出600;

非柯里化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function() {
Array.prototype.push.call(arguments, 4);//arguments借用Array.prototype.push 方法
console.log(arguments);
})([1,2,3]);
Function.prototype.uncurrying = function() {
var _self = this;
return function() {
var obj = Array.prototype.shift.call(arguments);
return _self.apply(obj, arguments);
}
};

通过函数节流提高JavaScript的性能。(事实上可以通过setTimeout来实现,规定时间间隔只触发什么事件),其实就是限制函数被频繁调用。

eg:
函数节流代码:原理是,将即将被执行的函数用setTimeout延迟一段时间执行。如果这次延迟执行还没有完成,则忽略接下来调用该函数的请求。

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
var throttle = function(fn, interval) {
var _self = fn, //保存需要被延迟执行的函数
timer, //定时器
firstTime = true; //是否是第一次调用
return function() {
var args = arguments,
_me = this;
if (firstTime) { //如果是第一次调用,不需要延迟执行
_self.apply(_me, args);
return firstTime = false;
}
if (timer) { //如果定时器还在,说明前一次延迟执行还没有完成
return false;
}
timer = setTimeout(function() { //延迟一段时间执行
clearTimeout(timer);
timer = null;
_self.apply(_me, args);
}, interval || 500);
};
};
window.onresize = throttle(function() {
console.log(1);
},500);

创建大量DOM造成卡顿的解决方案;(规定时间内创建多少个DOM节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var timeChunk = function(ary, fn, count) {
var obj,
t;
var len = ary.length;
var start = function() {
for(var i = 0; i < Math.min(count || a, ary.length); i++) {
var obj = ary.shift();
fn(obj);
}
};
return function() {
t = setInterval(function() {
if (ary.length === 0) { //如果全部节点都已经被创建好了
return clearInterval(t);
}
start();
}, 200); //分批执行的时间间隔,也可以用参数的形式传入
};
};

测试test:
需要创建1000个节点,每批创建8个

1
2
3
4
5
6
7
8
9
10
11
12
13
var ary = [];
for(var i = 1; i < 1000; i++) {
arr.push(i);
}
var renderFriendList = timeChunk(ary, function(n) {
var div = document.createElement('div');
div.innerHTML = n;
document.body.appendChild(div);
}, 8);
renderFriendList();

惰性加载函数

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
//添加事件
var addEvent = function(elem, type, handler) {
if (window.addEventListener) {
return elem.addEventListener(type, handler, false);
}
if (window.attachEvent) {
return elem.attachEvent('on' + type, handler);
}
};
//提前到代码加载的时候就判断
var addEvent = (function() {
if (window.addEventListener) {
return function(elem, type, handler) {
elem.addEventListener(type, handler, false);
}
}
if (window.attachEvent) {
return function(elem, type, handler) {
elem.attachEvent('on' + type, handler, false);
}
}
})
//惰性加载(判断内重写函数,条件判断不再存在)
var addEvent = function(elem, type, handler) {
if (window.addEventListener) {
addEvent = function(elem, type, handler) {
elem.addEventListener(type, handler, false);
}
}
if (window.attachEvent) {
addEvent = function(elem, type, handler) {
elem.attachEvent(type, handler, false);
}
}
}

PS: 寒假的任务一《JavaScript设计模式与开发实战》已经完成了,所以最近在整理一些相关模式的笔记,再次吸收消化。
下一步是《单页Web应用——JavaScript从前端到后端》
我只是希望自己在寒假这么长的时间里保持状态,保持学习的那股劲,虽然寒假家里效率不高,但是鸡汤喝多了,是需要消化的。

坚持原创技术分享,您的支持将鼓励我继续创作!