「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。
设计模式
网上一搜索,设计模式太多了,23种!!!但是很多时候用了也不知道,或者根本没意识什么模式,有没有... 那么,整理几个面试常问的吧,go...go...go....
设计模式,可以解释为,从代码中找出公共变化的地方,然后把他封装起来,解决单一的意图;
那么,学习设计模式,在正确使用的前提,事半功倍;
我觉得有助于写出可复用和可维护性高的程序,有点因果关系;
下面,作者的解释说的挺好:
假设有一个空房间,我们要日复一日地往里 面放一些东西。最简单的办法当然是把这些东西 直接扔进去,但是时间久了,就会发现很难从这 个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也 许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式。
设计原则
- 单一职责原则
一个对象或方法只做一件事情。如果一个方法承担了过多的职责,那么在需求的改变过程中,需要改写这个方法的成本或者复杂度上升。
应该把对象或方法拆分,以较小的粒度处理。
- 最少知识原则(LKP)
即尽可能少地与其他实体发生相互作用,应当尽量减少对象之间的交互。
如果两个对象之间需要通信,建议通过第三方进行处理,那么这两个对象就不要发生直接的 相互联系。
- 开放-封闭原则(OCP)
类、模块、函数等尽可能是可以扩展的,但是不可修改。
当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,防止影响原系统的稳定,尽量避免改动程序的源代码。
1、单例模式
- 保证一个类仅有一个实例(已经定义(内存中存在)了则直接返回,未定义则创建实例),并提供一个访问它的全局访问点。
就是说:只要创建了,就不会更改了,以后一直读取都是它。
function A(opt){
this.name = opt.name
}
A.prototype.funA = function (){
return this.name+' 这是单例!'
}
var func = (function (){
var instance = null;
return function (opt){
if(!instance){ // 保证只创建一个实例
instance = new A(opt)
}
return instance;
}
})()
var a = func({name:'这个模式是:'}) // 需要时创建,而不是立即
简单封装一下:
var getInstance = function(fn) {
var instance = null;
return function(){
return instance || (instance = fn.call(this,arguments));
}
};
2、工厂模式
比较好理解,通俗来说通过工厂方法替代new操作一种方式,如
function func (opt){
var obj = new Object();
obj.name = opt.name;
obj.fn = function(){
return obj.name+' welcome!';
}
return obj;
}
var a = func({name:'vvmily'});
a.fn() // vvmily welcome!
或者
class A {
funA(name) {
return new B(name)
}
}
class B{
constructor(name) {
this.name = name
}
}
var a = new A()
var aFn = a.funA('vvmily')
console.log(aFn.name) // vvmily
var bFn = a.funA('vvmily1')
console.log(bFn.name) // vvmily1
3、最经典之一:发布-订阅模式(观察者模式)
- 一对多的关系,一:发布者,多:订阅(观察者),发布者(数据改变)发出消息并推送给订阅者执行相应的操作。
这个模式,很像一个小系统一样,observer 就是系统,使用时只关心observer.subscribe('a',callback)和observer.publish('a', value)即可。
下面举个例子说一下,有状态statusA和statusB,他们想让状态改变之后接收发布者发布的状态,故需要先订阅:
// 观察者
const observer = {
subscribes: [],
// 订阅集合
// 订阅
subscribe: function(type, fn) {
if (!this.subscribes[type]) {
this.subscribes[type] = [];
}
// 收集订阅者的处理
typeof fn === 'function' && this.subscribes[type].push(fn);
},
// 发布
publish: function() {
let type = [].shift.call(arguments); // 获取订阅者(名称)
// 多个行为
let fns = this.subscribes[type];
//发送n个通知,循环处理调用
for (var i = 0; i < fns.length; ++i) {
fns[i].apply(this, arguments);
}
}
};
// A订阅了某进度
observer.subscribe('statusA', function(status) {
console.log(status,"statusA");
});
// B订阅了某进度
observer.subscribe('statusB', function(status) {
console.log(status,"statusB");
});
// 进度结果出来了,需要发布通知,告诉已经订阅的A和B
observer.publish('statusA', 1); // 进度1
observer.publish('statusA', 2); // 进度2
- 补充:删除订阅
observer.remove('statusA')
// 删除订阅
remove: function(type, fn) {
// 删除全部
if (typeof type === 'undefined') {
this.subscribes = [];
return;
}
const fns = this.subscribes[type];
// 遍历删除
for (var i = 0; i < fns.length; ++i) {
if (fns[i] === fn) {
fns.splice(i, 1);
}
}
}
}
4、经典模式之一:代理模式
- 为一个对象提供一个代用品,当操作对象时,需要经过替代品去操作源对象
- 代理模式主要有三种:保护代理、虚拟代理、缓存代理。
就是说:代理就是一个中间商的作用,交易双方不直接交涉;很经典,在框架,如Vue中大量使用。
保护代理,对主体加工,或者保护作用
// 主体,发送消息
function proxyScore(score) {
return score/100
}
// 代理,对数据进行过滤
function proxyScore(score) {
if(score>60){
sendMsg(msg);
}else{
// 统一处理,干点啥
return 0
}
}
proxyScore(98); // 0.98
虚拟代理,经典防抖函数]实现
这里随手写一下吧,比如滚动则会这样使用window.onscroll = proxyHandle,防抖函数,点击前往。
function debounce(fn, delay=300) {
let timer = null;
return function() {
const arg = arguments;
// 每次操作时,清除上次的定时器
clearTimeout(timer);
timer = null;
// 定义新的定时器,一段时间后进行操作
timer = setTimeout(function() {
fn.apply(this, arg);
}, delay);
}
};
// 代理
const proxyHandle = (function() {
return debounce((e)=>{
// 干点啥
}, 500);
})();
缓存代理,可以为一些开销大的运算结果提供暂时的缓存,提升效率
// 主体
function strs() {
const arg = [].slice.call(arguments);
console.log("strs")
return arg.join('-')
}
// 代理
var proxyStrs = (function() {
let cache = [];
return function() {
let arg = [].slice.call(arguments).join('-');
if(cache.includes(arg)){
// 存在,则直接从内存中读取
return cache[arg]
}else{
cache.push(arg)
return strs.apply(this, arguments)
}
};
})();
console.log(
strs('a','b','c'),
strs('a','b','c'),
proxyStrs('a','b','c'),
proxyStrs('a','b','c')
) // strs()执行三次