看懂最常见的几种JavaScript设计模式,面试基本没什么问题了

226 阅读4分钟

「这是我参与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()执行三次