适配器模式是将一个类(对象)的接口(方法或者属性)转化成另一个接口,使得本来因为接口不兼容而不能一块儿工做的那些类(对象)能够一块儿工做javascript
举个例子:java
飞机类和火车类,他们都是交通运输工具,都适用于中长途,但就行驶方式来讲,火车是在地上跑的,飞机是在天上飞的。若是要让火车在天上飞(flying),则能够复用飞机的飞行功能,但其具体的行驶动做仍是应该在地上跑(running),此时,咱们就能够建立一个火车的适配器,可以让火车也支持 flying 方法,但其内部仍是调用的 runningsegmentfault
// 抽象工厂方法(实现子类继承抽象类的工厂) var VehicleFactory = function (subType, superType) { // 判断抽象工厂中是否有该抽象类 if (typeof VehicleFactory[superType] === "function") { // 缓存类(寄生式继承) function F() {} // 继承父类属性和方法 F.prototype = VehicleFactory[superType].prototype; // 子类原型继承父类 var p = new F(); // 将子类constructor指向子类 p.constructor = subType; // 设置子类的原型 subType.prototype = p; } else { // 不存在改抽象类则抛出错误 throw new Error("未建立该抽象类"); } }; // 飞机抽象类(抽象类是一种声明但不能使用的类) VehicleFactory.Airplane = function () {}; VehicleFactory.Airplane.prototype = { flying: function () { throw new Error("该方法未定义!"); }, transportation: function () { throw new Error("该方法未定义!"); }, }; // 火车抽象类 VehicleFactory.Train = function () {}; VehicleFactory.Train.prototype = { running: function () { throw new Error("该方法未定义!"); }, transportation: function () { throw new Error("该方法未定义!"); }, };
// 飞机 var CivilAircraft = function () { VehicleFactory.Airplane.call(this); }; VehicleFactory(CivilAircraft, "Airplane"); // 原型是Airplane CivilAircraft.prototype.transportation = function () { console.log("速度很快的交通工具!"); }; CivilAircraft.prototype.flying = function () { console.log("可以飞起来!"); }; // 火车 var CivilTrain = function () { VehicleFactory.Train.call(this); }; VehicleFactory(CivilTrain, "Train"); // 原型是Train CivilTrain.prototype.transportation = function () { console.log("比飞机慢的交通工具!"); }; CivilTrain.prototype.running = function () { console.log("在地上驰骋!"); };
// 火车适配器 var TrainAdapter = function (oTrain) { VehicleFactory.Airplane.call(this); this.oTrain = oTrain; }; TrainAdapter.prototype = new VehicleFactory.Airplane(); TrainAdapter.prototype.flying = function () { this.oTrain.running(); }; TrainAdapter.prototype.transportation = function () { this.oTrain.transportation(); };
该构造函数接受一个火车的实例对象,而后使用 VehicleFactory.Airplane 进行 apply,其适配器原型是 VehicleFactory.Airplane,而后要从新修改其原型的 flying 方法,以便内部调用 oTrain.running()方法后端
var oCivilAircraft = new CivilAircraft(); var oCivilTrain = new CivilTrain(); var oTrainAdapter = new TrainAdapter(oCivilTrain); //原有的飞机行为 oCivilAircraft.flying(); // 可以飞起来! oCivilAircraft.transportation(); // 速度很快的交通工具! //原有的火车行为 oCivilTrain.running(); // 在地上驰骋! oCivilTrain.transportation(); // 比飞机慢的交通工具! //适配器火车的行为(火车调用飞机的方法名称) oTrainAdapter.transportation(); // 比飞机慢的交通工具! oTrainAdapter.flying(); // 在地上驰骋!
验证成功,可是此处也暴露出这种适配器有一个问题,就是火车类本来的 transportation 方法在适配器中也须要重写一遍。若是须要适配器在继承火车类的基础上扩展全部飞机类的行为时,能够考虑使用多继承设计模式
适配器还有一些其余应用,在《JavaScript设计模式》这本书上是以 A 框架适配 JQuery 框架作介绍的。若是两个框架的api很是的类似,那么用 window.A = A = jQuery便可实现两个框架的适配,这样能够在不改变原来代码的状况下正确的运行新框架的接口。除此以外,适配器模式还可用于:api
使用一个已经存在的对象,但其方法或属性接口不符合你的要求;浏览器
先后端数据传递,把后端数据适配成咱们可用的数据格式再使用;缓存
你想建立一个可复用的对象,该对象能够与其它不相关的对象或不可见对象(即接口方法或属性不兼容的对象)协同工做;app
想使用已经存在的对象,可是不能对每个都进行原型继承以匹配它的接口。对象适配器能够适配它的父对象接口方法或属性。框架
装饰者模式就是在不改变原对象的基础上,经过对其进行包装扩展(添加属性或者方法)使原有对象能够知足更复杂的需求
举个例子:
如今有一个前人完成的项目,产品经理说要加新需求,当用户点击输入框时,若是输入框输入的内容有限制,那么在输入框下提示相应文案,且不一样的输入框提示文案不相同。如名字输入框提示输入数字字母,电话输入框提示输入纯数字等等。若是输入框不少,那么一条一条查找代码并进行修改将很是麻烦
这时候咱们可使用装饰者模式,在原有的功能的基础上添加一些新功能来知足需求,这时候咱们就不须要重写或者修改原来定义的方法
// 装饰者 var decorator = function (input, fn) { // 获取事件源 var input = document.getElementById(input); // 若是事件源已经绑定事件 if (typeof input.onclick === "function") { // 缓存事件源原有回调函数 var oldClickFn = input.onClick; // 为事件源定义新的事件 input.onClick = function () { // 事件源原有回调函数 oldClickFn(); // 执行事件源新增回调函数 fn(); }; } else { // 事件源未绑定事件,直接为事件源添加新增回调函数 input.onClick = fn; } };
此时,咱们在原有基础上为项目添加新功能时,能够不用深刻了解原来的代码,只要调用这个装饰者并传入你新增的方法就能够了
// 姓名框新增功能 decorator('name_input', function() { console.log('姓名输入框只能输入汉字和英文!') }) // 电话框新增功能 decorator('tel_input', function() { console.log('电话输入框只能输入纯数字!) })
装饰者模式很简单,就是对原有对象的属性和方法的添加。可是装饰者模式很强大,由于它能够对原有功能进行扩展,好比在一些框架中对浏览器原有方法的扩展再封装就是用到了装饰者这个模式。
学习中发现的问题:在适配器模式中,书中写到
window.A = A = jQuery;
这个连等式是怎么运行的呢?下面这个程序运行结果是什么呢?
var a = { n: 1 }; a.x = a = { n: 2 }; console.log(a.x); // 输出?
上面这个问题也颇有趣,它输出的答案是undefined,为何呢?由于在开始运行的时候,它初始化了一个a.x的变量,而且它的值为undefined,且这个a的值指向的是原来的{ n: 1},而赋值号(=)是从右到左执行,所以此时右边的a = {n: 2},a指向了一个新的地址,该地址里面保存的值为{ n: 2},而后a.x又被赋了一个值为{n: 2},注意此a非彼a,咱们能够用一个中间变量看一下: