高频面试题,设计模式
将不变的部分和可变的部分隔离开来是每个设计模式的主题。
1、单例模式
单例模式指的是只创建一个实例或者只执行一次逻辑。单例的实现通常有以下两点:
- 单例函数:被创建一个实例或被执行一次逻辑,例如:全局只有一个登陆弹窗、首次进入加载loading效果
- 管理单例:通过闭包的方式持有一个result,如果result存在则直接返回,如果不存在则进行实例的创建或逻辑的执行
// 创建单例
var instanceFun = function () {
console.log('做些啥');
return true;
}
// 管理单例
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments))
};
}
// 创建单例和单例函数的方法进行结合
var createSingle = getSingle(instanceFun)
createSingle()
createSingle()
createSingle()
以上例子中,通过getSingle持有一个结果result,需要一次执行的实例有各种各样,比如唯一登录框;在getSingle中首次执行不存在故而执行result = fn.apply(this, arguments)进行单例逻辑执行并赋值给result,再次执行时restult存在直接返回,进而达到只执行或创建一次的目的。
2、策略模式
策略模式指的是指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。策略模式有以下特点:
- 定义了一组算法
- 算法可以相互取代
// 定义策略
var strategies = {
'plane': function() {
return '3h'
},
'train': function() {
return '20h'
},
'bicycle': function() {
return '168h'
},
}
// 管理策略
var getTime = function(vehicle) {
return strategies[vehicle]()
}
// 策略执行
console.log(getTime('plane'))
console.log(getTime('train'))
console.log(getTime('bicycle'))
以上例子中,将从兰州到北京采用不同的交通工具需要的时间通过strategies定义了一组算法,plan表示需要3h,train表示需要20h,bicycle表示主要168h。然后,通过getTime用来管理这组算法,当传入的交通工具vehicle发生变化时会执行不同的算法,返回不同的时间值。
3、代理模式
代理模式指得在访问本体之前为了保护或者缓存本体等目的增加了一个代理,代理和本体有这相同的接口。
// 定义本体
var add = function () {
var sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
return sum;
}
// 定义代理
var proxyAdd = (function () {
var cache = {};
return function () {
var args = Array.prototype.join.call(arguments, '-')
return cache[args] || (cache[args] = add.apply(this, arguments));
}
})()
// 执行代理
console.log(proxyAdd(1, 2, 3, 4));
console.log(proxyAdd(1, 2, 3, 4));
console.log(proxyAdd(1, 2, 3, 4));
console.log(proxyAdd(1, 2, 3, 4));
从当前例子可以看出,本体add和代理proxyAdd都是相同的传参方式,但是,访问本体每次都会计算一次加和,访问代理会直接将已计算过的值返回,进而达到通过缓存代理减少重复计算的目的。
4、迭代器模式
迭代器模式指的是提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部。
// 定义迭代器
var iterator = function (arr, callback) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i, arr)
}
}
var personList = [{
name: 'A',
age: 12
}, {
name: 'B',
age: 15
}, {
name: 'C',
age: 20
}];
// 执行迭代器
iterator(personList, function (val, index, arr) {
// val:当前值,index:当前索引,arr:当前数组
val.age = val.age + '岁';
});
console.log(personList);
当前例子中定义了一个迭代器iterator用来执行迭代逻辑,参数为迭代数组和迭代逻辑。例子personList中在每次迭代的时候,都可以获取到每次迭代的值、索引和数组,这里的功能是将每个元素age加上单位‘岁’。
5、发布订阅者模式
发布订阅者模式又叫观察者模式,指的是一种一对多的依赖关系,当发布者进行消息发布的时候,会通知到订阅其消息的所有订阅者,订阅者收到消息后会根据情况进行逻辑的执行。
// 定义发布者
class Publisher {
// 订阅者列表
constructor() {
this.subs = [];
}
// 发布者收集订阅者
addSub(sub) {
this.subs.push(sub);
}
// 发布者给订阅者发布消息
notify() {
let subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
// 定义订阅者
class Watcher {
// 订阅者收到消息后的行为fn
constructor(fn) {
this.fn = fn;
}
// 订阅者接收消息
update() {
this.fn();
}
}
// 创建发布者
var publisher = new Publisher();
// 创建订阅者
var watcher1 = new Watcher(function () {
console.log('做点啥1');
})
var watcher2 = new Watcher(function () {
console.log('做点啥2');
})
var watcher3 = new Watcher(function () {
console.log('做点啥3');
})
// 发布者收集订阅者
this.publisher.addSub(watcher1)
this.publisher.addSub(watcher2)
this.publisher.addSub(watcher3)
// 发布者通知订阅者
this.publisher.notify();
当前例子中,我们定义了发布者类Publisher,其中有订阅者列表subs,添加订阅者的方法addSub,通知所有订阅者的方法notify。又定义了订阅者Watcher,其中是fn表示当前订阅者收到消息时的行为,update表示接收到消息时唤起自身行为。
接着,我们创建了发布者实例publisher,我们定义的3个Watcher分别为watcher1、watcher2和watcher3,将其通过addSub的方法加入到发布者列表中,在发布者进行消息通知notify的时候,3个订阅者收到消息会进行update的执行。
6、模板方法模式
模板方法模式指的是在抽象父类中封装了子类算法框架,并且定义了一些公共的属性,实现了公共的方法。子类通过继承的方式继承了抽象父类,也继承了整个算法结构,子类也可进行属性或者方法的改写。
// 定义抽象类
var Beverage = function () {};
Beverage.prototype.boilWater = function () {
console.log('烧开水')
}
Beverage.prototype.brew = function () {
throw new Error('brew方法需要重写')
}
Beverage.prototype.pourInCup = function () {
throw new Error('pourInCup方法需要重写')
}
Beverage.prototype.addCondiments = function () {
throw new Error('addCondiments方法需要重写')
}
Beverage.prototype.init = function () {
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
// 定义实现类
var Tea = function () {}
Tea.prototype = Object.create(Beverage.prototype);
Tea.prototype.constructor = Tea;
Tea.prototype.brew = function () {
console.log('用沸水泡茶')
}
Tea.prototype.pourInCup = function () {
console.log('将茶倒进杯子')
}
Tea.prototype.addCondiments = function () {
console.log('加冰糖')
}
var tea = new Tea();
tea.init()
例子中定义了饮料泡制的抽象父类Beverage,其中定义了公共的方法boilWater、brew、pourInCup和addCondiments,并且定义了子类需要继承的执行流程init。通过继承的方式产生的泡茶业务的子类Tea重写除boilWater外的所有方法,并且按照父类定义的流程执行了泡茶的逻辑,达到了父类控制子类具体实现的目的。触类旁通,冲咖啡、泡柠檬等也可以继承Beverage父类,并重写各个流程的具体实现。
7、享元模式
享元模式指的是通过减少对象的数量以减少内存占用和提高性能。
// 椅子构造函数
var Seat = function () {}
Seat.prototype.toLearn = function () {
console.log('我在图书馆学习...')
}
// 共有学生20000人
var seatList = []
for (let i = 0; i < 20000; i++) {
seatList.push(new Seat())
}
以上例子假设学校有20000学生,那么,图书馆如果为每人准备一把的话,就得创建20000个椅子实例。显然,实际情况是并不会所有学生同一时刻去图书馆学习的。
// 共有学生20000人,但同一时刻最多上自习的人有1000个
var seatList = []
for (let i = 0; i < 1000; i++) {
seatList.push(new Seat())
}
那么利用享元模式进行改造,只需要创建1000个椅子实例就可以了,享元模式的核心在于对象的共享和重复使用。