经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
前端设计模式:单例模式(Singleton)
来源:cnblogs  作者:安木夕  时间:2023/9/19 8:41:30  对本文有异议

image.png


00、基本概念

单例模式(Singleton Pattern),也称单体模式,就是全局(或某一作用域范围)唯一实例,大家共享、复用一个实例对象,也可减少内存开销。单例模式应该是最基础、也最常见的设计模式了。

image.png

?常见场景

  • 全局状态vuex,Jquery中的全局对象$,浏览器中的window、document 都算是单例。
  • 公共的服务、全局配置、缓存、登录框等,全局复用一个对象。

所以实现单例模式的关键就是保障对象实例只创建一次,后续的引用都是同一个实例对象。相比于Java、C#等语言,JavaScript单线程,也没有类,单例实现还是比较容易,基于JS语言特性,有多种实现思路。

实现方式 说明
全局对象 全局环境下的var变量,或者直接挂载到全局对象window上。使用简单,但会存在全局污染,也不优雅,??不推荐!
构造函数.静态方法getInstance 使用构造函数的静态方法getInstance()来获取实例,唯一实例对象存储在构造函数的instance上。
虽有一定耦合,Class版本还是一种不错的方式
闭包-new 利用JS的闭包(万恶的闭包)来保存那个唯一对象实例,这样就可以new来获取唯一实例对象了
ES6模块Module ES6的模块其实就是单例模式,模块中导出的对象就是单例的,多次导入其实是同一个引用。

01、全局对象(不推荐)

创建一个全局对象,浏览器中全局对象一般挂载在Window上,如JQuery、loadsh就是如此实现的。

  • 在全局环境中用 var 字面量申明一个对象,利用了var的变量提升 + 全局属性的特点(全局环境下的var变量会自动成为全局属性),所以慎用var
  • 直接挂载到全局对象window上。
  1. window.jQuery = window.$ = jQuery;
  2. window._ = lodash;
  3. // 全局环境下的var变量会自动成为全局属性
  4. var singleUser = {
  5. name: 'sam',
  6. id: 1001
  7. }
  8. // 使用
  9. console.log(singleUser.name) // sam
  10. console.log(window.singleUser.name) // sam

02、构造函数.静态方法getInstance

统一一个入口获取对象实例,入口就是为构造函数的静态方法getInstance()(当然命名随意),在该函数中判断(静态)对象instance是否初始化,没有则创建,有则直接返回。所以实际上的唯一实例是作为静态属性,保存在构造器的instance属性上,类似Math.PI

  1. function GlobalUser(name) {
  2. this.name = name
  3. this.id = 1002
  4. }
  5. // 基于构造函数的静态函数作为统一入口,Constructor.getInstance()
  6. GlobalUser.getInstance = function(name) {
  7. // 注意这里的this指向的是构造函数GlobalUser
  8. if (this.instance) return this.instance
  9. // 第一次没有创建
  10. return this.instance = new GlobalUser(name)
  11. }
  12. console.log(GlobalUser.getInstance('张三').name) // 张三
  13. console.log(GlobalUser.getInstance('李四').name) // 张三,依然是张三,复用了第一次创建的实例
  14. console.log(GlobalUser.getInstance() === GlobalUser.getInstance()) // true

ES6的Class 版本的,原理和上面一样,因为Class本质上也是基于原型的构造函数,但实现起来更优雅一些,推荐使用。

  1. class GlobalUser {
  2. constructor(name) {
  3. this.name = name
  4. this.id = 1002
  5. }
  6. static getInstance(name) {
  7. //静态方法属于类本身,这里的this也就指向类本身
  8. if (!this.instance)
  9. this.instance = new GlobalUser(name)
  10. return this.instance;
  11. }
  12. }
  13. console.log(GlobalUser.getInstance('张三').name) // 张三
  14. console.log(GlobalUser.getInstance('李四').name) // 张三,依然是张三,复用了第一次创建的实例
  15. console.log(GlobalUser.getInstance() === GlobalUser.getInstance()) // true

03、闭包-new

核心思路就是利用JS的闭包(万恶的闭包)来保存那个唯一对象实例,这样就可以new来获取唯一实例对象了!基于闭包的实现方式是比较多的,下面示例只是其中一种,但基本原理都是利用闭包来保存那个“唯一实例”。

  1. let GlobalUser = (function() {
  2. let instance // 闭包保存的唯一实例对象
  3. return function(name) {
  4. if (instance) return instance
  5. // (首次)创建实例
  6. instance = { name: '张三', id: 1003 }
  7. return instance
  8. }
  9. })() // 立即执行,外层函数的价值就是他的闭包变量instance
  10. console.log(new GlobalUser('张三').name) // 张三
  11. console.log(new GlobalUser('李四').name) // 张三,依然是张三,复用了第一次创建的实例
  12. console.log(new GlobalUser() === new GlobalUser()) // true

断点输出一下日志可以看到GlobalUser的构造函数闭包

image.png

闭包版本还可以继续改进下,做成一个通用版本的单例工厂:把具体的对象示例构造器封装一下。

  1. // 一个通用单例工厂,参数为构造器函数、Class类
  2. let Singleton = function(Constructor) {
  3. let instance
  4. return function(...args) {
  5. if (instance) return instance
  6. // (首次)创建实例
  7. instance = new Constructor(...args)
  8. return instance
  9. }
  10. }
  11. // 构造函数
  12. function User(name) {
  13. this.name = name
  14. }
  15. class Config {
  16. constructor(title) {
  17. this.title = title
  18. }
  19. }
  20. // 使用
  21. let SingleUser = Singleton(User)
  22. let u1 = new SingleUser('sam')
  23. let u2 = new SingleUser('zhangsan')
  24. console.log(u1 === u2, u1, u2) //true User {name: 'sam'} User {name: 'sam'}
  25. let GlobalConfig = Singleton(Config)
  26. console.log(new GlobalConfig('设计') === new GlobalConfig('模式')) // true

04、ES6模块Module

ES6的模块其实就是单例模式,模块中导出的对象就是单例的,多次导入其实是同一个引用。

回顾一下ESM:参考《ESModule模块化

  • ?? Singleton 模式:import模块的代码只会执行一次,同一个url文件只会第一次导入时执行代码。后续任何地方import都不会执行模块代码了,也就是说,import语句是 Singleton 模式的。
  • ?? 只读-共享:模块导入的接口的是只读的,不能修改。当然引用对象的属性值是可以修改的,不建议这么干,注意模块是共享的,导出的是一个引用,修改后其他方也会生效。

因此用ESM实现单例就比较简单了:

  1. // 模块申明 config.js
  2. export default {
  3. title: '设计模式'
  4. }
  5. // 使用
  6. import config from './config.js'
  7. console.log(config) // {title: '设计模式'}
  8. config.title = '修改一下'
  9. import config2 from './config.js'
  10. console.log(config, config2) // {title: '修改一下'} {title: '修改一下'}

参考资料


??版权申明:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!原文编辑地址-语雀

原文链接:https://www.cnblogs.com/anding/p/17625710.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号