🔥如何在函数式编程中使用设计模式-单例模式

我们大部分前端在学习设计模式时使用的都是使用class类来模拟场景,但是在hooks遍地走的今天,大部分时候并不推崇在业务开发中使用类来处理逻辑,更多是使用处理函数式编程。 而函数式编程要怎样合理运用设计模式呢?

定义

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

场景

要实现一个单例模式并不复杂,无非就是使用一个变量来标识是否为某个类创建过对象。试想一下,如果我们需要在一个表格中添加一个绑定事件,而这个表格又是异步获取数据的,我们更希望在它获取完数据render的时候去绑定事件,但我们又不希望每次render都去重新绑定点击事件,只需要在第一次ajax后绑定即可。这时候单例模式就派上用场了。

实现:类开发逐渐过渡到函数式开发

我们先用易于理解的类开发来实现一个单例模式,代码如下:

js 复制代码
var Singleton = function( name ){
    this.name = name;
};

Singleton.prototype.getName = function(){
    alert ( this.name );
};

Singleton.getInstance = (function(){
var instance = null;// 是否实例化过的标识
return function( name ){
    if ( !instance ){
    instance = new Singleton( name );
    }
    return instance;
}
})();

上面的代码每次获取实例时:

js 复制代码
const a = Singleton.getInstance('peng1')
const b = Singleton.getInstance('peng2')
console.log(a.getName() === b.getName()) //true

我们已经完成了一个较为标准的单例模式,但是每次类实例化时用的都是自己的方法,这种操作并不透明。 接下来我们来实现一个透明的单例模式,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。 代码如下:

js 复制代码
var CreateDiv = (function(){
    var instance;
    var CreateDiv = function( html ){
        if ( instance ){
        return instance;
        }
        this.html = html;
        this.init();
        return instance = this;
    };
    CreateDiv.prototype.init = function(){
        var div = document.createElement( 'div' );
        div.innerHTML = this.html;
        document.body.appendChild( div );
    };
    return CreateDiv;
})();

var a = new CreateDiv( 'sven1' );
var b = new CreateDiv( 'sven2' );

console.log(a.html === b.html) //true

现在我们可以像类一样使用单例模式了,但它同样有一些缺点。为了把 instance 封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回,真正的 Singleton 构造方法,这增加了一些程序的复杂度,并且耦合性太高,违反了单一职责性原则。 我们现在尝试将它的两个功能(实例化和创建单一实例)拆开。现在我们通过引入代理类的方式,来解决上面提到的问题。

js 复制代码
var CreateDiv = function( html ){
    this.html = html;
    this.init();
};

CreateDiv.prototype.init = function(){
    var div = document.createElement( 'div' );
    div.innerHTML = this.html;
    document.body.appendChild( div );
};

//接下来引入代理类 proxySingletonCreateDiv:

var ProxySingletonCreateDiv = (function(){
    var instance;
    return function( html ){
        if ( !instance ){
            instance = new CreateDiv( html );
        }
        return instance;
    }
})();

var a = new ProxySingletonCreateDiv( 'sven1' );
var b = new ProxySingletonCreateDiv( 'sven2' );

console.log(a.html === b.html) //true

这样一来,两个互不关联的功能就拆开了。不过这是基于"类"的单例模式,前面说过,基于"类"的单例模式在 JavaScript 中并不适用。

实现-函数式开发解决实际问题

场景一:当点击按钮时,出现浮窗。要求:只有点击时才实例化浮窗,实现惰性加载。多次点击不要重复销毁和创建浮窗,实现单例模式。 代码如下:

js 复制代码
var createLoginLayer = (function(){
    var div;//实现单例的标识
    return function(){
        if ( !div ){
        div = document.createElement( 'div' );
        div.innerHTML = '我是登录浮窗';
        div.style.display = 'none';
        document.body.appendChild( div );
        }
        return div;
    }
})();
document.getElementById( 'loginBtn' ).onclick = function(){
    var loginLayer = createLoginLayer();
    loginLayer.style.display = 'block';//惰性加载
};

上面的代码中全程没有使用到类以及类的实例化,但我们实实在在使用上了设计模式-惰性单例模式。不过还有优化空间。我们确实完成了一个可用的惰性单例,但是我们发现它还有如下一些问题。这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在 createLoginLayer对象内部。 我们将两个职责来进行拆分:

js 复制代码
// 一个普通的函数创建实例
var createLoginLayer = function(){
    var div = document.createElement( 'div' );
    div.innerHTML = '我是登录浮窗';
    div.style.display = 'none';
    document.body.appendChild( div );
    return div;
};
// 通用的单例函数:

var getSingle = function( fn ){
    var result;
    return function(){
        return result || ( result = fn .apply(this, arguments ) );
    }
};
//使用
var createSingleLoginLayer = getSingle( createLoginLayer );
document.getElementById( 'loginBtn' ).onclick = function(){
    var loginLayer = createSingleLoginLayer();
    loginLayer.style.display = 'block';
};

createSingleLoginLayer得到的是一个函数,这个函数只会创建一次div,基本符合我们的需求。 回到开头:同样的当我们需要在一个异步列表中需要只绑定一次事件时,只需要将绑定事件的函数传入getSingle中得到一个只会执行一次的函数即可。后面即使需要多次渲染列表,绑定事件的函数也不会再次去执行。

js 复制代码
var bindEvent = getSingle(function(){
document.getElementById( 'div1' ).onclick = function(){
alert ( 'click' );
}
return true;
});
var render = function(){
console.log( '开始渲染列表' );
bindEvent();
};

render();

render();

render();
相关推荐
顾辰逸you几秒前
uniapp--咸虾米壁纸(三)
前端·微信小程序
北鸟南游4 分钟前
用现有bootstrap的模板,改造成nuxt3项目
前端·bootstrap·nuxt.js
前端老鹰6 分钟前
JavaScript Intl.RelativeTimeFormat:自动生成 “3 分钟前” 的国际化工具
前端·javascript
梦想CAD控件6 分钟前
(在线CAD插件)网页CAD实现图纸表格智能提取
前端·javascript·全栈
木子雨廷24 分钟前
Flutter 开发一个plugin
前端·flutter
重生之我是一名前端程序员26 分钟前
websocket + xterm 前端实现网页版终端
前端·websocket
sorryhc28 分钟前
【AI解读源码系列】ant design mobile——Space间距
前端·javascript·react.js
uhakadotcom42 分钟前
NPM与NPX的区别是什么?
前端·面试·github
GAMC1 小时前
如何修改node_modules的组件不被install替换?可以使用patch-package
前端
页面仔Dony1 小时前
webpack 与 Vite 深度对比
前端·前端工程化