JavaScript的原型机制
JavaScript 本身是一门基于原型的面向对象语言,它的对象系统就是使用原型模式来搭建的,在这里称之为原型编程范型也许更合适。所有的
JavaScript
对象都是从某个对象上克隆而来的。
JavaScript
是通过克隆
Object.prototype
来得到新的对象,但实际上并不是每次都真正地克隆了一个新的对象。从内存方面的考虑出发,JavaScript
还做了一些额外的处理。
“对象的原型”,就
JavaScript
的真正实现来说,其实并不能说对象有 原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好 的说法是对象把请求委托给它的构造器的原型。
JavaScript
给对象提供了一个名为
__proto__
的隐藏属性,某个对象的
__proto__
属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。__proto__
就是对象跟“对象构造器的原型”联系起来的纽带。
虽然
JavaScript
的对象最初都是由
Object.prototype
对象克隆而来的,但对象构造器的原型并不仅限于 Object.prototype
上,而是可以动态指向其他对象。这样一来,当对象
a
需要借用对象 b
的能力时,可以有选择性地把对象
a
的构造器的原型指向对象
b
,从而达到继承的效果。
p21
var obj = { name: 'sven' };
var A = function(){};
A.prototype = obj;
var a = new A();
console.log( a.name ); //
输出:
sven
执行这段代码的时候,引擎做了哪些事情:
首先,尝试遍历对象
a
中的所有属性,但没有找到
name
这个属性。
查找
name
属性的这个请求被委托给对象
a
的构造器的原型,它被
a.
__proto__
记录着并且
指向
A.prototype
,而
A.prototype
被设置为对象
obj
。
在对象
obj
中找到了
name
属性,并返回它的值。
通过
Object.create
来创建对象的效率并不高,通常比通过构造函数创建对象要慢。
通过设置构造器的prototype 来实现原型继承的时候,除了根对象
Object.prototype
本身之外,任何对象都会有一个原型。而通过 Object.create( null )
可以创建出没有原型的对象。
小例子:
关于this
this 的指向大致可以分为:
作为对象的方法调用。
作为普通函数调用。
构造器调用。
Function.prototype.call
或
Function.prototype.apply
调用。
当函数不作为对象的属性被调用时,此时的 this 总是指向全局对象。
①window.name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
};
var getName = myObject.getName;//
使用另一个变量来引用
myObject.getName是普通函数调用
console.log( getName() ); // globalName
console.log( myObject.getName() ); //sven
②<html>
<body>
<div id="div1">
我是一个
div</div>
</body>
<script>
window.id = 'window';
document.getElementById( 'div1' ).onclick = function(){
alert ( this.id ); //
输出:
'div1'
var callback = function(){//函数中的方法
alert ( this.id ); // 输出:'window'
}
callback();//该方法不作为对象的属性被调用
};
</script>
</html>
想让它指向该 div
节点,法一
document.getElementById( 'div1' ).onclick = function(){
var that = this; // 保存 div 的引用
var callback = function(){
alert ( that.id ); //
输出:
'div1'
}
callback();
};
法二
<div id="div1">我是一个 div</div>
<script>
document.getElementById = (function (func) {
return function () {
console.log(arguments);
return func.apply(document, arguments);
}
})(document.getElementById);
var getId = document.getElementById;
var div = getId('div1');
console.log(div.id); // 输出: div1
</script>
将不变的部分和变化的部分隔开是每个设计模式的主题。
单例模式
单例模式的核心是
确保只有一个实例,并提供全局访问
。比如重复点击按钮只弹出一个弹出框,判断该弹出框是否已存在,就需要创建一个唯一对象,并判断它是否已赋值。
代理实现单例模式:把控制创建唯一对象的代码段在函数中移除出来交给代理类,这样和构造函数结合起来的单例类,使得当该类从单例类要变成一个普通的可产生多个实例的类时,不需要去修改到构造函数。
惰性单例:指在需要的时候才创建对象实例(调用时才创建)
策略模式
*策略模式的定义是:
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
*一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类 Context
,
Context
接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context
中要维持对某个策略对象的引用。
<script>
//不使用策略模式:
var calculateBonus = function (performanceLevel, salary) {
if (performanceLevel === 'S') {
return salary * 4;
}
if (performanceLevel === 'A') {
return salary * 3;
}
if (performanceLevel === 'B') {
return salary * 2;
}
};
calculateBonus('B', 20000); // 输出:40000
calculateBonus('S', 6000); // 输出:24000
//使用策略模式:消除了原程序中大片的条件分支语句。
//策略类:封装了具体的算法,并负责具体的计算过程。
var strategies = {
"S": function (salary) {
return salary * 4;
},
"A": function (salary) {
return salary * 3;
},
"B": function (salary) {
return salary * 2;
}
};
//环境类Context,Context 接受客户的请求,随后把请求委托给策略类。
var calculateBonus = function (level, salary) {
return strategies[level](salary);
};
console.log(calculateBonus('S', 20000)); // 输出:80000
console.log(calculateBonus('A', 10000)); // 输出:30000
</script>
代理模式
在
JavaScript
开发中最常用的是虚拟代理和缓存代理。
*代理
B
可以帮助
A 过滤掉一些请求,比如送花的人中年龄太大的或者没有宝马的,这种请求就可以直接在代理 B处被拒绝掉。这种代理叫作保护代理
。
A
和
B
一个充当白脸,一个充当黑脸。白脸
A
继续保持良好的女神形象,不希望直接拒绝任何人,于是找了黑脸 B
来控制对
A
的访问。
*虚拟代理:把一些开销很大的对象,延迟到 真正需要它的时候才去创建。比如在监听函数中,满足条件之后才创建需要的变量。运用:
实现图片预加载(等待图片加载完成之前先用其他图片代替)。
合并
HTTP 请求
(需要连续发送很多请求时先等待几秒再一起发送)
。:
<input type="checkbox" id="1"></input>1
<input type="checkbox" id="2"></input>2
<input type="checkbox" id="3"></input>3
<input type="checkbox" id="4"></input>4
<input type="checkbox" id="5"></input>5
<input type="checkbox" id="6"></input>6
<input type="checkbox" id="7"></input>7
<input type="checkbox" id="8"></input>8
<input type="checkbox" id="9"></input>9
<script>
var synchronousFile = function (id) {
console.log('开始同步文件,id 为: ' + id);
};
//代理函数:收集一段时间之内发的请求,再一次性发给服务器
var proxySynchronousFile = (function () {
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function (id) {
cache.push(id);
if (timer) { // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function () {
synchronousFile(cache.join(',')); // 2 秒后向本体发送需要同步的 ID 集合
clearTimeout(timer); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000);
}
})();
var checkbox = document.getElementsByTagName('input');
for (var i = 0, c; c = checkbox[i++];) {
c.onclick = function () {
if (this.checked === true) {
proxySynchronousFile(this.id);
}
}
};
</script>
**保护代理用于控制不同权限的对象对目标对象的访问,但在
JavaScript
并不容易实现保护代理,因为我们无法判断谁访问了某个对象。而虚拟代理是最常用的一种代理模式
缓存代理:缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。
用于ajax异步请求数据:在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。
用于ajax异步请求数据:在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。
高阶函数动态创建缓存代理:
/**************** 计算乘积 *****************/
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 = {};
return function () {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = fn.apply(this, arguments);
}
};
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
迭代器模式
发布-订阅模式(观察者模式)
DOM事件
自定义事件:可以往回调函数里填入一些参数,订阅者可以接收这些参数。
/*11自定义事件基础版*/
var salesOffices = {}; // 定义售楼处
salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function (fn) { // 增加订阅者
this.clientList.push(fn); // 订阅的消息添加进缓存列表
};
salesOffices.trigger = function () { // 发布消息
for (var i = 0, fn; fn = this.clientList[i++];) {
fn.apply(this, arguments); // (2) // arguments 是发布消息时带上的参数
}
};
// 下面我们来进行一些简单的测试:
salesOffices.listen(function (price, squareMeter) { // 小明订阅消息
console.log('价格= ' + price);
console.log('squareMeter= ' + squareMeter);
});
salesOffices.listen(function (price, squareMeter) { // 小红订阅消息
console.log('价格= ' + price);
console.log('squareMeter= ' + squareMeter);
});
salesOffices.trigger(2000000, 88); // 输出:200 万,88 平方米
salesOffices.trigger(3000000, 110); // 输出:300 万,110 平方米
/*22增加一个标示 key,让订阅者只订阅自己感兴趣的消息*/
var salesOffices = {}; // 定义售楼处
salesOffices.clientList = {}; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function (key, fn) {
if (!this.clientList[key]) { // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表
};
salesOffices.trigger = function () { // 发布消息
var key = Array.prototype.shift.call(arguments), // 取出消息类型
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 是发布消息时附送的参数
}
};
salesOffices.listen('squareMeter88', function (price) { // 小明订阅 88 平方米房子的消息
console.log('价格= ' + price); // 输出: 2000000
});
salesOffices.listen('squareMeter110', function (price) { // 小红订阅 110 平方米房子的消息
console.log('价格= ' + price); // 输出: 3000000
});
salesOffices.trigger('squareMeter88', 2000000); // 发布 88 平方米房子的价格
salesOffices.trigger('squareMeter110', 3000000); // 发布 110 平方米房子的价格
/*33把发布—订阅的功能提取出来,放在一个单独的对象内*/
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.remove = function (key, fn) {
var fns = this.clientList[key];
if (!fns) { // 如果 key 对应的消息没有被人订阅,则直接返回
return false;
}
if (!fn) { // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) { // 反向遍历订阅的回调函数列表
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1); // 删除订阅者的回调函数
}
}
}
};
var installEvent = function (obj) {
for (var i in event) {
obj[i] = event[i];
}
};
var salesOffices = {};
installEvent(salesOffices);//这样可以一直赋值给多个对象(让多个售楼处都拥有该功能)
salesOffices.listen('squareMeter88', fn1 = function (price) { // 小明订阅消息
console.log('价格= ' + price);
});
salesOffices.listen('squareMeter100', function (price) { // 小红订阅消息
console.log('价格= ' + price);
});
salesOffices.remove( 'squareMeter88', fn1 ); // 删除小明的订阅
salesOffices.trigger('squareMeter88', 2000000); // 输出:2000000 (删除之后无法输出)
salesOffices.trigger('squareMeter100', 3000000); // 输出:3000000
/*44全局的发布-订阅对象(不需要一直创建多个对象,如salesOffices对象,而是使用中介的方式(全局)处理不同楼盘和买房者)*/
var Event = (function () {
var clientList = {},
listen,
trigger,
remove;
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = [];
}
clientList[key].push(fn);
};
trigger = function () {
var key = Array.prototype.shift.call(arguments),
fns = clientList[key];
if (!fns || fns.length === 0) {
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
}
};
remove = function (key, fn) {
var fns = clientList[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1);
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
Event.listen('squareMeter88', function (price) { // 小红订阅消息
console.log('价格= ' + price); // 输出:'价格=2000000'
});
Event.trigger('squareMeter88', 2000000); // 售楼处发布消息
**使用该模式的案例:
网站登录:每次登录需要ajax异步请求获取用户的登录信息,但其他模块中需要用到登录后返回的信息,可以直接用发布-订阅模式,在登录成功时发布(trigger)一个消息,其他模块中订阅(listen)监听该消息并操作。
**未订阅先发布的情况:
有时候还没有人订阅,但是已经发布了消息,可以先建立一个存放离线事件的堆栈,暂时把发布事件的动作包裹在一个函数里,等有对象来订阅事件时再遍历堆栈并依次执行该包装函数,即重新发布事件。当然离线事件的生命周期只有一次,就像 QQ 的未读消息只会被重 新阅读一次,所以刚才的操作我们只能进行一次。
**推模型和拉模型:
推模型是指在事件发生时,
发布者一次性把所有更改的状态和数据都推送给订阅者。拉模型不同的地方是,发布者仅仅通知
订阅者事件已经发生了,此外发布者要提供一些公开的接口供订阅者来主动拉取数据。拉模型的
好处是可以让订阅者“按需获取”,但同时增加了代码量和复杂度。
JavaScript中一般都会选择推模型, 使用 Function.prototype.apply
方法把所有参数都推送给订阅者。
**发布—订阅模式优缺点:
优点:
- 一为时间上的解耦,二为对象之间的解耦。(楼盘有新动向时再发消息给买房者,而不需要买房者存储销售的电话,每天都询问楼盘动向【时间】。并且销售辞职或买房者是谁等对象信息也不会影响该消息传送【对象】)
- 三可以用在异步编程中,也可帮助实现一些别的设计模式,比如中介者模式。
- 四架构上来看,无论是 MVC 还是 MVVM, 都少不了发布—订阅模式的参与。
缺点:
1.创建订阅者本身要消耗一定的时间和内存,而 且当你订阅一个消息后,也许此消息最后都未发
生,但这个订阅者会始终存在于内存中。
2.发布
—
订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联
系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一 起的时候,要跟踪一个 bug 不是件轻松的事情。
命令模式
有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。(比如按钮数量很多,则点击按钮和按钮的功能之间,可以通过一个统一的函数来连接所有不同的按钮以及对应的功能,以此降低耦合度)
<button id="button1">点击按钮 1</button>
<button id="button2">点击按钮 2</button>
<button id="button3">点击按钮 3</button>
<script>
var button1 = document.getElementById('button1');
var button2 = document.getElementById('button2')
var button3 = document.getElementById('button3');
//使用闭包的方式:
/*关键点:只需要预留好安装命令的接口(setCommand),不需要关心其他,于是把请求发送者和请求接收者解耦开。*/
var setCommand = function (button, func) {
button.onclick = function () {
func();
}
};
var MenuBar = {
refresh: function () {
console.log('刷新菜单目录');
}
};
var SubMenu = {
add: function () {
console.log('增加子菜单');
},
del: function () {
console.log('删除子菜单');
}
};
var RefreshMenuBarCommand = function (receiver) {
return function () {
receiver.refresh();
}
};
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
组合模式
不同功能用不同的对象(子命令)来表示,最终再创建一个对象(宏命令),将所有子命令都放进去,调用时只需要调用宏命令就可以实现所有子命令的功能。
模板方法模式
原型链上设置好对象父类,后面需要用到的时候再继承并重写,和其他语言的抽象类相似。
钩子方法:
在父类中容易变化的地方放置钩子,钩子可以有一个默认的实现,究竟要不要“挂钩”,这由子类自 行决定。允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件,高层组件对待底层组件的方式,跟演艺公司对待新人演员一样,都是“别调用我们,我们会调用你”。
模板方法模式是基于继承的一种设计模式,父类封装了子类的算法框架和方法的执行顺序,子类继承父类之后,父类通知子类执行这些方法,好莱坞原则很好地诠释了这种设计技巧,即高层组件调用底层组件。
享元模式
(减少创建的对象数量)
在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题。
用享元模式的关键是如何区别内部状态和外部状态。可以被对象共享的属性通常被划分为内部状态,如同不管什么样式的衣服,都可以按照性别不同,穿在同一个男模特或者女模特身上,模特的性别就可以作为内部状态储存在共享对象的内部。而外部状态取决于具体的场景,并根据场景而变化,就像例子中每件衣服都是不同的,它们不能被一些对象共享,因此只能被划分为外部状态。
性别是内部状态,内衣是外部状态(不需要在遍历的时候创建对象),通过区分这两种状态,大大减少了系统中的对象数量。
职责链模式
职责链模式的最大优点就是解耦了请求发送者和
N
个接收者之间的复杂关系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。当第一个节点不符合要求,会自动传给职责链的下一个节点
**使用AOP实现职责链????p187
职责链模式可以很好地帮助我们管理代码,降低发起请求的对象和处理请求的对象之间的耦合性。
/*无使用职责链*/
var order = function (orderType, pay, stock) {
if (orderType === 1) { // 500 元定金购买模式
if (pay === true) { // 已支付定金
console.log('500 元定金预购, 得到 100 优惠券');
} else { // 未支付定金,降级到普通购买模式
if (stock > 0) { // 用于普通购买的手机还有库存
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
}
else if (orderType === 2) { // 200 元定金购买模式
if (pay === true) {
console.log('200 元定金预购, 得到 50 优惠券');
} else {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
}
else if (orderType === 3) {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
};
order(1, true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
/*用职责链模式重构代码*/
// 500 元订单
//缺点:请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中,违反开放-封闭原则
var order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购, 得到 100 优惠券');
} else {
order200(orderType, pay, stock); // 将请求传递给 200 元订单
}
};
// 200 元订单
var order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200 元定金预购, 得到 50 优惠券');
} else {
orderNormal(orderType, pay, stock); // 将请求传递给普通订单
}
};
// 普通购买订单
var orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
};
// 测试结果:
order500(1, true, 500); // 输出:500 元定金预购, 得到 100 优惠券
order500(1, false, 500); // 输出:普通购买, 无优惠券
order500(2, true, 500); // 输出:200 元定金预购, 得到 500 优惠券
order500(3, false, 500); // 输出:普通购买, 无优惠券
order500(3, false, 0); // 输出:手机库存不足
/*灵活可拆分的职责链节点*/
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('手机库存不足');
}
};
var Chain = function (fn) {
this.fn = fn;
this.successor = null;
};
//异步的职责链p185:数同步返回"nextSuccessor"已经没有意义了。需要添加一个原型方法,在异步触发时可以用
// Chain.prototype.next = function () {
// return this.successor && this.successor.passRequest.apply(this.successor, arguments);
// };
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); // 输出:手机库存不足
装饰者模式
装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会。
/*给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象
之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(fire
方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的
下一个对象。*/
let Plane = function() {}
Plane.prototype.fire = function() {
console.log('plane');
}
let PlaneOne = function(plane) {
this.plane=plane
}
PlaneOne.prototype.fire=function() {
this.plane.fire()
console.log('planeOne');
}
let plane = new Plane()
let planeOne = new PlaneOne(plane)
planeOne.fire()
/*但可以直接改写对象或者对象的某个方法,并不需要使用“类”来实现装饰者模式*/
var plane = {
fire: function () {
console.log('发射普通子弹');
}
}
var missileDecorator = function () {
console.log('发射导弹');
}
var atomDecorator = function () {
console.log('发射原子弹');
}
var fire1 = plane.fire;
plane.fire = function () {
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function () {
fire2();
atomDecorator();
}
plane.fire();
fire1()
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
装饰者模式中使用AOP:
把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起。(就是将不同功能分离在不同函数中,再使用AOP方式按顺序调用这些功能,并且其this指向明确),解析代码:
/*11*/
//报错,this指向不对(被劫持)
var _getElementById = document.getElementById;
document.getElementById = function (id) {
alert(1);
return _getElementById(id); // (1)
}
var button = document.getElementById('button');
//改变this指向,但该做法不方便
var _getElementById = document.getElementById;
document.getElementById = function () {
alert(1);
return _getElementById.apply(document,arguments); // (1)
}
var button = document.getElementById('button');
/*22*/
//不使用AOP时新增新的onload事件,必须维护中间变量_onload(可能不止一个)
window.onload = function () {
alert(1);
}
var _onload = window.onload || function () { };
window.onload = function () {
_onload();
alert(2);
}
/*上面两者的解决方式:AOP*/
Function.prototype.before = function (beforefn) {
var __self = this;
return function () {
beforefn.apply(this, arguments);//先执行参数
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 button = document.getElementById('button');
// document.getElementById = document.getElementById.before(function () {
// console.log(3);
// }).after(function () {
// console.log(1);
// });
// console.log(button);
window.onload = function () {
alert(1);
}
window.onload = (window.onload || function () { }).after(function () {
alert(2);
}).after(function () {
alert(3);
}).after(function () {
alert(4);
});
/*担心上面的方法会污染原型的话:*/
var before = function (fn, beforefn) {
return function () {
beforefn.apply(this, arguments);
return fn.apply(this, arguments);
}
}
var a = before(
function () { alert(3) },
function () { alert(2) }
);
a = before(a, function () { alert(1); });
a();
装饰者模式和代理模式的区别:
代理模式 强调一种一开始就可以被确定的关系,而装饰者模式用于一开始并不能确定对象的全部功能,在后期时通过参数再传入另一个功能函数,它可以形成一条长长的装饰链。
状态模式
状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。
单一职责原则:
就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变 化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多, 等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。