什么是适配器模式
适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。
适配器的别名是包装器(wrapper),这是一个相对简单的模式。在程序开发中有许多这样的场景:当我们试图调用模块或者对象的某个接口时,却发现这个接口的格式并不符合目前的需求。
这时候有两种解决办法:第一种是修改原来的接口实现,但如果原来的模块很复杂,或者我们拿到的模块是一段别人编写的经过压缩的代码,修改原接口就显得太不现实了。
第二种办法是创建一个适配器,将原接口转换为客户希望的另一个接口,客户只需要和适配器打交道。
核心思想
解决两个已有接口之间不匹配的问题
具体实现
// 渲染数据,格式限制为数组了
function renderData(data) {
data.forEach(function (item) {
console.log(item);
});
}
// 对非数组的进行转换适配
function arrayAdapter(data) {
if (typeof data !== "object") {
return [];
}
if (Object.prototype.toString.call(data) === "[object Array]") {
return data;
}
var temp = [];
for (var item in data) {
if (data.hasOwnProperty(item)) {
temp.push(data[item]);
}
}
return temp;
}
var data = {
0: "A",
1: "B",
2: "C",
};
renderData(arrayAdapter(data)); // A B C
什么是装饰器模式
以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责
核心思想
是为对象动态加入行为,经过多重包装,可以形成一条装饰链
具体实现
最简单的装饰者,就是重写对象的属性
var A = {
score: 10,
};
A.score = "分数:" + A.score;
// 也可以使用传统面向对象的方法来实现装饰,添加技能:
function Person() {}
Person.prototype.skill = function () {
console.log("数学");
};
// 装饰器,还会音乐
function MusicDecorator(person) {
this.person = person;
}
MusicDecorator.prototype.skill = function () {
this.person.skill();
console.log("音乐");
};
// 装饰器,还会跑步
function RunDecorator(person) {
this.person = person;
}
RunDecorator.prototype.skill = function () {
this.person.skill();
console.log("跑步");
};
var person = new Person();
// 装饰一下
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);
person.skill(); // 数学
person1.skill(); // 数学 音乐 跑步
也可 我们也可以使用更通用的装饰函数:
// 装饰器,在当前函数执行前先执行另一个函数
function decoratorBefore(fn, beforeFn) {
return function () {
var ret = beforeFn.apply(this, arguments);
// 在前一个函数中判断,不需要执行当前函数
if (ret !== false) {
fn.apply(this, arguments);
}
};
}
function skill() {
console.log("数学");
}
function skillMusic() {
console.log("音乐");
}
function skillRun() {
console.log("跑步");
}
var skillDecorator = decoratorBefore(skill, skillMusic);
skillDecorator = decoratorBefore(skillDecorator, skillRun);
skillDecorator(); // 跑步 音乐 数学
什么是代理模式 ?
当客户不方便直接访问一个 对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。
替身对象对请求做出一些处理之后,再把请求转交给本体对象代理和本体的接口具有一致性,本体定义了关键功能,而代理是提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。
核心思想
为一个对象提供一个代用品或占位符,以便控制对它的访问
具体实现
代理模式主要有三种:保护代理、虚拟代理、缓存代理
保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子:
// 主体,发送消息
function sendMsg(msg) {
console.log(msg);
}
// 代理,对消息进行过滤
function proxySendMsg(msg) {
// 无消息则直接返回
if (typeof msg === "undefined") {
console.log("deny");
return;
}
// 有消息则进行过滤
msg = ("" + msg).replace(/泥\s*煤/g, "");
sendMsg(msg);
}
sendMsg("泥煤呀泥 煤呀"); // 泥煤呀泥 煤呀
proxySendMsg("泥煤呀泥 煤"); // 呀
proxySendMsg(); // deny
它的意图很明显,在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这属于护代理的形式。有消息的时候对敏感字符进行了处理,这属于虚拟代理的模式。
虚拟代理在控制对主体的访问时,加入了一些额外的操作,如在滚动事件触发的时候,也许不需要频繁触发,我们可以引入函数节流,这是一种虚拟代理的实现:
// 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理
function debounce(fn, delay) {
delay = delay || 200;
var timer = null;
return function () {
var arg = arguments;
// 每次操作时,清除上次的定时器
clearTimeout(timer);
timer = null;
// 定义新的定时器,一段时间后进行操作
timer = setTimeout(function () {
fn.apply(this, arg);
}, delay);
};
}
var count = 0;
// 主体
function scrollHandle(e) {
console.log(e.type, ++count); // scroll
}
// 代理
var proxyScrollHandle = (function () {
return debounce(scrollHandle, 500);
})();
window.onscroll = proxyScrollHandle;
// 缓存代理可以为一些开销大的运算结果提供暂时的缓存,提升效率。来个栗子——缓存加法操作:
// 主体
function add() {
var arg = [].slice.call(arguments);
return arg.reduce(function (a, b) {
return a + b;
});
}
// 代理
var proxyAdd = (function () {
var cache = [];
return function () {
var arg = [].slice.call(arguments).join(",");
// 如果有,则直接从缓存返回
if (cache[arg]) {
return cache[arg];
} else {
var ret = add.apply(this, arguments);
return ret;
}
};
})();
console.log(
add(1, 2, 3, 4),
add(1, 2, 3, 4),
proxyAdd(10, 20, 30, 40),
proxyAdd(10, 20, 30, 40)
); // 10 10 100 100
什么是外观模式
为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使子系统更加容易使用
核心思想
可以通过请求外观接口来达到访问子系统,也可以选择越过外观来直接访问子系统
具体实现
外观模式在 JS 中,可以认为是一组函数的集合
// 三个处理函数
function start() {
console.log("start");
}
function doing() {
console.log("doing");
}
function end() {
console.log("end");
}
// 外观函数,将一些处理统一起来,方便调用
function execute() {
start();
doing();
end();
}
// 调用init开始执行
function init() {
// 此处直接调用了高层函数,也可以选择越过它直接调用相关的函数
execute();
}
init(); // start doing end
什么是迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
核心思想
在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素
具体实现
JS 中数组的 map forEach 已经内置了迭代器
[1, 2, 3].forEach(function (item, index, arr) {
console.log(item, index, arr);
});
不过对于对象的遍历,往往不能与数组一样使用同一的遍历代码,我们可以封装一下:
function each(obj, cb) {
var value;
if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; ++i) {
value = cb.call(obj[i], i, obj[i]);
if (value === false) {
break;
}
}
} else {
for (var i in obj) {
value = cb.call(obj[i], i, obj[i]);
if (value === false) {
break;
}
}
}
}
each([1, 2, 3], function (index, value) {
console.log(index, value);
});
each({ a: 1, b: 2 }, function (index, value) {
console.log(index, value);
});
// 0 1
// 1 2
// 2 3
// a 1
// b 2
再来看一个例子——强行地使用迭代器,来了解一下迭代器也可以替换频繁的条件语句,虽然例子不太好,但在其他负责的分支判断情况下,也是值得考虑的:
function getManager() {
var year = new Date().getFullYear();
if (year <= 2000) {
console.log("A");
} else if (year >= 2100) {
console.log("C");
} else {
console.log("B");
}
}
getManager(); // B
将每个条件语句拆分出逻辑函数,放入迭代器中迭代
function year2000() {
var year = new Date().getFullYear();
if (year <= 2000) {
console.log("A");
}
return false;
}
function year2100() {
var year = new Date().getFullYear();
if (year >= 2100) {
console.log("C");
}
return false;
}
function year() {
var year = new Date().getFullYear();
if (year > 2000 && year < 2100) {
console.log("B");
}
return false;
}
function iteratorYear() {
for (var i = 0; i < arguments.length; ++i) {
var ret = arguments[i]();
if (ret !== false) {
return ret;
}
}
}
var manager = iteratorYear(year2000, year2100, year); // B