Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JS设计模式之单例模式

JS设计模式之单例模式

作者头像
用户1687375
发布于 2019-08-14 08:28:53
发布于 2019-08-14 08:28:53
2K00
代码可运行
举报
文章被收录于专栏:较真的前端较真的前端
运行总次数:0
代码可运行
本文首发于冰山工作室。

欢迎关注冰山工作室,和更多前端小伙伴一起玩耍。

设计模式前言

起源

首先要说明的是设计模式期初并非软件工程中的概念,而是起源于建筑领域。建筑学大师(克里斯托夫·亚历山大)曾经花了很长时间(传闻说20年)研究为了解决同一问题而采用的不同的建筑结构,在这些结构当中有很多优秀的设计,而在这些设计当中又有很多相似性,因此他用“模式”来描述这种相似性。并写了一本书《模式语言》。对整个建筑领域产生了很深远的影响。

而在编程领域,类似的情况会更加的多。微软杰出工程师(艾瑞克·伽玛)受到克里斯托夫·亚历山大和他的《模式语言》的启发,把这种模式的概念移植到了软件开发中,并且针对C++提出了23种设计模式,写成了《设计模式:可复用面向对象软件的基础》一书。

定义

原文定义:在面向对象的程序设计中,针对特定问题的简洁和优雅解决方案。

解释:给解决方案取个好听的名字

作用

  • 一定会增加代码量
  • 一定会增加复杂度
  • 有可能提升可维护性
  • 有可能降低沟通成本

JS中的设计模式

并不是所有的设计模式都适用于任何开发语言,每种语言因为本身的设计初衷就不相同,有些设计模式在C语言里非常适用,但是在JS里有更简单的解决方案,在这种情况下就没有必要一定按照设计模式中的描述通过强制模拟的方式来实现。比如我们常说JS中函数是一等公民,可以当做对象来使用,也可以当做参数来传递,还可以当成类来使用,而这些特性在很多静态类型语言中需要用特定的方式来实现,因此在JS中很多模式是解释器本身就实现的,不需要做额外的工作。

如何理解和使用设计模式

首先应该了解各种设计模式解决的问题,当你在开发的过程中遇到问题的时候,除了常规的解决方案之外,可以有更多的选择,当你去开发一个框架或者开发一套架构的时候能够考虑到更多的情况,并且设计出更容易拓展,更好维护的结构。

学习设计模式的前提

  • 深入的了解函数 作用域 闭包
  • 熟练应用this call bind apply
  • 熟练使用高级函数(纯函数 高阶函数 记忆函数 偏函数....)
  • 掌握函数式变成的思想以及理解其使用的意义

单例模式

普通构造函数

单例模式是指一个构造函数,无论new多少次,返回的都是同一个实例,比如alert,在我们使用的时候页面上只会有一个alert弹窗。先来看一个普通的构造函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Test(){}
let a=new Test;
let b=new Test;
console.log(a===b)//false

通过函数属性构造单例

如果我们希望a,b是完全相等的应该怎样做?

可以在构造函数上增加一个instance属性来保存实例,并增加一个getInstance方法来获取实例。如果实例存在则直接返回,如果不存在则创建一个保存在instance属性中并返回。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Single(){}
Single.getInstance=function(){
    if(!this.instance){
        this.instance=new Single();
    }
    return this.instance
};
let a=Single.getInstance();
let b=Single.getInstance();
console.log(a===b);//true

这样做虽然解决的问题,但是又造成了两个新的问题

  • 第一:没有对参数进行处理
  • 第二:并不是是用new方法来创建的实例,和常规操作不符。

解决传参问题

先来解决第一个问题,参数的处理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Alert(content){
    this.content=content;
}
Alert.getInstance=function(content){
    if(!this.instance){
        this.instance=new Alert(content);
    }else{
        this.apply(this.instance,arguments)
    }
    return this.instance
};
Alert.prototype.showContent=function(){
    console.log(this.content)
}
let a=Alert.getInstance('this is a');
a.showContent();//this is a
let b=Alert.getInstance('this is b');
b.showContent();//this is b
console.log(a===b);//true

通过new操作符实现单例构造

这样就解决了传参的问题,接下来我们来解决是用new操作符的问题。由于要通过变量对生成的实例进行保存,又不能污染全局环境,所以这里我们使用IFFE来执行,并返回构造函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let Alert=(function(){
    let instance;
    function Alert(content){
        if(!instance){
            instance=this;
        }
        instance.init(content);
        return instance;
    }
    Alert.prototype.init=function(content){
        this.content=content;
    };
    return Alert;
})();
let a=new Alert('this is a');
a.showContent();//this is a
let b=new Alert('this is b');
b.showContent();//this is b
console.log(a===b);//true

省略new操作符

至此基础的单例就完成了,但还远远没有结束,在我们使用一些基础对象的时候,如数组,我们可以使用New Array的方式,也可以不使用new操作符 直接使用Array也是可以的,因此我们对函数进行改造;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let Alert=(function(){
    let instance;
    function Alert(content){
        if(this instanceof Alert){
            if(!instance){
                instance=this;
            }
        }else{
            if(!instance){
                instance=new Alert(content);
            }
        }
        instance.init(content);
        return instance;
    }
    Alert.prototype.init=function(content){
        this.content=content;
    };
    return Alert;
})();
let a=Alert('a');
console.log(a.content);//a
let b=Alert('b');
console.log(b.content)//b
console.log(a===b);//true

对上面的代码进行一下优化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let Alert=(function(){
    let instance;
    function Alert(content){
        instance=instance||(
            this instanceof Alert?this:new Alert(content)
        )
        instance.init(content);
        return instance;
    }
    Alert.prototype.init=function(content){
        this.content=content;
    };
    return Alert;
})();

前文说到设计模式是基于C++提出的,而每种语言又有自己的独特性,比如JS中“一切皆对象”,而对象本身就是一种单例,任何对象只要指针不同就不相等,我们前面做的仅仅是通过一个独立的变量来保存结果并返回,借助ES6的import export可实现的更加简单,也不会涉及到全局污染,如此看来似乎并不能体现出单例的优势,所以下面我们来说单例模式中最重要的概念----惰性单例

惰性单例

上面的案例只是一个理论上的DEMO,在实际的开发中并没有太大的用途,接下来我们来看一下单例模式最核心的应用,惰性单例。我们平时如果只做弹窗,一定是自己通过DOM节点来模拟一个弹窗,所以必然涉及到DOM操作,也就是在上面的代码中的init函数中去创建DOM元素,但是这样操作就会导致每次创建实例的时候都创建一次DOM节点,这显然是不正确的,因此,我们可以把DOM的创建过程提到函数顶部,也就是程序一开始直接创建一个DOM节点,仅在init中去修改里面的内容。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let Alert=(function(){
    let instance=null;
    let dom=document.createElement('div');
    dom.style.display='none';
    document.body.appendChild(dom);
    function Alert(content){
        instance=instance||(
            this instanceof Alert?this:new Alert(content)
        )
        instance.init(content);
        return instance;
    }
    Alert.prototype.init=function(content){
        dom.style.display='block';
        dom.innerText=content;
    };
    Alert.prototype.hide=function(content){
        dom.style.display='none'
    };
    return Alert;
})();
let a=new Alert('this is a');

虽然功能都实现了,但是在页面打开之后就在网页中建立的DOM节点,造成不必要的浪费,其实我们完全可以在生成alert实例的时候再去生成这些dom节点,因此我们可以再次使用单例将创建DOM节点的操作制作成一个单例。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let Alert=(function(){
    let instance=null;
    let dom;
    function creatDom(){
        if(!dom){
            dom=document.createElement('div');
            dom.style.display='none';
            document.body.appendChild(dom);
        }
        return dom;
    }
    function Alert(content){
        instance=instance||(
            this instanceof Alert?this:new Alert(content)
        )
        instance.init(content);
        return instance;
    }
    Alert.prototype.init=function(content){
        creatDom();
        dom.style.display='block';
        dom.innerText=content;
    };
    Alert.prototype.hide=function(content){
        dom.style.display='none'
    };
    return Alert;
})();
let a=new Alert('this is a');

现在功能都完成了,但还是存在一些问题,creatDom操作违背了设计模式中的“单一职责”原则,这个函数应该只负责创建节点,以便在其他地方复用,我们更希望creatDom的操作是这样的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function creatDom(){
        let dom=document.createElement('div');
        dom.style.display='none';
        document.body.appendChild(dom);
        return dom;
    }

因此我们可以将单例的逻辑提取出来,制作成高阶单例函数,当我们需要创建单例的时候直接调用这个函数就可以了,这里我们将creatDom作为参数传递给getSingle来使用,这种方式也被称为通用惰性单例。

通用惰性单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let getSingle=(function(){
    let single;
    return function(fn){
        return single||(single=fn.apply(this,arguments))
    }
})();
function creatDom(){
    let dom=document.createElement('div');
    dom.style.display='none';
    document.body.appendChild(dom);
    return dom;
}
let Alert=(function(){
    let instance=null;
    let dom;
    function Alert(content){
        instance=instance||(
            this instanceof Alert?this:new Alert(content)
        )
        instance.init(content);
        return instance;
    }
    Alert.prototype.init=function(content){
        dom=getSingle(creatDom);
        dom.style.display='block';
        dom.innerText=content;
    };
    Alert.prototype.hide=function(content){
        dom.style.display='none'
    };
    return Alert;
})();

你可以将上面的这个代码块的内容粘贴到控制台,然后运行以下测试代码查看效果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let a=Alert('123')//页面上插入一个DIV内容为123
let b=new Alert('345')//123变成456
b.hide()//div隐藏
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 较真的前端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
使用合适的设计模式一步步优化前端代码
该文介绍了设计模式及其在JavaScript中的应用,包括单例模式、观察者模式、工厂模式等。
iKcamp
2018/01/04
7950
使用合适的设计模式一步步优化前端代码
JavaScript设计模式 单例模式
面向对象的单例模式,是通过new关键字来实例化我们想要的对象,并将其赋值给instance。
菜的黑人牙膏
2019/01/21
5190
JavaScript设计模式之单例模式
JavaScript 设计模式 之旅 设计模式开篇 日常开发中,我们都很注重开发技巧,好的开发 技巧可以事半功倍得解决此刻得问题。 那么这些技巧如何来得呢? 我的理解: 经过不断踩坑,解BUG,总结出来一些处理对应问题解决方案,这就所谓的 技巧。 说起设计模式,其实我们日常开始中也经常用到,只是你不知道用的解决方案方案对应的设计模式名称. 学习设计模式的作用 在软件设计中,模式是一些经过了大量实际项目验证的优秀解决方案。熟悉这些模式的程序员,对某些模式的理解也会自然的形成条件反射。当遇到合适的场景出现时,
程序员海军
2021/10/08
3630
JavaScript设计模式之单例模式
前端仔学学设计模式--单例模式
设计模式知识提取将分为N篇文章,本篇文章是个开篇文,后期会进行其他相关的同步(会就分享,不会就折腾),旨在提升技能,更好地享受敲键盘的快感~
Jimmy_is_jimmy
2019/07/31
3180
设计模式之单例模式
单例模式是创建对象最简单的方式。单例模式的定义 是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
一粒小麦
2019/10/23
6250
设计模式之单例模式
javascript设计模式一: 单例模式
作为一个半路出家的前端,随着项目经验的积累,也越来越意识到原生js的博大精深,最近正在研究js设计模式,接下来每学一个设计模式就是写篇文章做笔记,其实主要还是代码和设计思想的结合,努力体会,多思考合适自己项目中的应用场景,争取实际应用到实际项目中。
前端_AWhile
2019/08/29
5000
JavaScript 设计模式 —— 单例模式
从事开发岗位也有一年多的时间了,见识过陈年老项目,也从零到一搭建过一个项目。随着项目和业务的不断扩张,写下的代码如果没有进行设计,就渐渐变成了 emm ... x 山,怎么写都不对劲,过段时间就想着重构。
前端LeBron
2022/11/21
4240
JavaScript 设计模式 —— 单例模式
「设计模式 JavaScript 描述」单例模式
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我 们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
用户8921923
2022/10/24
8580
手写JavaScript常见5种设计模式
简介:建造者模式(builder pattern)比较简单,它属于创建型模式的一种。
helloworld1024
2022/10/06
2710
JS 单例模式
单例模式 (Singleton) 的实现在于保证一个特定类只有一个实例,第二次使用同一个类创建新对象的时候,应该得到与第一次创建对象完全相同的对象。 当创建一个新对象时,实际上没有其他对象与其类似,因为新对象已经是单例了 {a:1} === {a:1} // false 。
前端下午茶
2018/10/22
1.7K0
JavaScript-设计模式·设计模式(上)
本篇是《JavaScript 设计模式与开发实践》第二部分读书笔记,总结前 7 种设计模式:单例模式、策略模式、代理模式、迭代器模式、发布-订阅模式、命令模式、组合模式。
数媒派
2022/12/01
4910
JavaScript设计模式与开发实践 - 单例模式
引言 本文摘自《JavaScript设计模式与开发实践》 在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。 在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。 单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等。 模式定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 模
laixiangran
2018/04/11
7050
【设计模式】我这样学习设计模式-单例模式
限制类实例化次数只能一次,一个类只有一个实例,并提供一个访问它的全局访问点。适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用。不适用动态扩展对象,或需创建多个相似对象的场景。
一尾流莺
2022/12/10
2780
【设计模式】我这样学习设计模式-单例模式
JS常用设计模式解析01-单例模式
考虑实现如下功能,点击一个按钮后出现一个遮罩层。 原始办法:我们只需要实现一个创建遮罩层的函数并将其作为按钮点击的回调事件即可。如下:
love丁酥酥
2018/08/27
7210
JS常用设计模式解析01-单例模式
【设计模式】工作中会用到的单例模式
单例模式,很常用也非常重要,将单例模式应用于程序开发设计,可减少重复代码,提升程序效率,同时单例的唯一性也使得数据流更加清晰,便于维护管理。
小东同学
2022/07/29
5190
【设计模式】工作中会用到的单例模式
不知道怎么封装代码?看看这几种设计模式吧!
我们经常听说:“写代码要有良好的封装,要高内聚,低耦合”。那怎样才算良好的封装,我们为什么要封装呢?其实封装有这样几个好处:
coder_koala
2020/08/27
1K0
不知道怎么封装代码?看看这几种设计模式吧!
JS 设计模式之单例模式(创建型)
一般情况下,当我们创建了一个类(本质是构造函数)后,可以通过 new 关键字调用构造函数进而生成任意多的实例对象。像这样:
Leophen
2021/06/21
6900
JS 设计模式之单例模式(创建型)
JavaScript 设计模式学习第七篇- 单例模式
单例模式可能是设计模式里面最简单的模式了,虽然简单,但在我们日常生活和编程中却经常接触到,本节我们一起来学习一下。
越陌度阡
2020/11/26
5020
JavaScript 设计模式学习第七篇- 单例模式
JavaScript设计模式之单例模式
JavaScript 设计模式 之旅 设计模式开篇 日常开发中,我们都很注重开发技巧,好的开发 技巧可以事半功倍解决此刻的问题。 那么这些技巧如何来得呢? 我的理解:经过不断踩坑,解BUG,总结出来一些处理对应问题解决方案,这就所谓的 技巧。 说起设计模式,其实我们日常开始中也经常用到,只是你不知道用的解决方案方案对应的设计模式名称. 学习设计模式的作用 在软件设计中,模式是一些经过了大量实际项目验证的优秀解决方案。熟悉这些模式的程序员,对某些模式的理解也会自然的形成条件反射。当遇到合适的场景出现时,
程序员海军
2021/10/11
3370
JavaScript设计模式之单例模式
JS常用的几种设计模式
面试常常问到设计模式,设计模式在实际业务中即使有用到,但是依然感受不到它的存在,往往在框架中会有更多体现,比如vue2源码,内部还是有很多设计思想,比如观察者模式,模版模式等,我们在业务上一些通用的工具类也会用到单例,在大量的条件判断也会考虑策略者模式,这两种用得比较多。好记性不如烂笔头,又重新回顾了一遍设计模式,虽然仅仅掌握了几种熟悉的设计模式,但是希望在复杂的业务上,能想起那些不太常用的设计模式。
Maic
2022/12/21
8290
JS常用的几种设计模式
相关推荐
使用合适的设计模式一步步优化前端代码
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验