JavaScript单例模式解析:从惰性创建到Vue源码中的去实例化实践

作者:南栀

时间:2026-10-26

浏览:638

本文深入解析JavaScript单例模式的两种分类:普通单例与惰性单例。通过DOM创建案例演示如何利用闭包实现“用时才创建”的惰性单例,并进一步结合Vue源码,分析Vue.use方法如何通过检查已安装插件数组实现插件的单例安装,探讨单例模式中“去实例化”只保留“单”逻辑的设计思想。

文章目录

一、单例模式的分类

单例模式的核心在于确保一个特定的运行环境中仅存在一个实例,且该实例可被当前环境访问。这个“环境”的范围可大可小:微观上可以是某个函数作用域或块级作用域,宏观上则可以是全局的window或global对象。若依据实例生成的时机对单例模式进行划分,主要包含以下两类:

  • 普通单例模式:在环境初始化阶段即完成实例的创建。
  • 惰性单例模式:仅在特定触发条件下才进行实例的创建。

二、惰性单例模式

基于单例模式的定义——即一个环境中有且仅有一个实例,我们可以推导出一种特殊形态:只有当该实例真正被使用时,才执行创建操作。这种模式被称为惰性单例模式。

  • 所谓“惰性”,是指实例的创建行为被推迟到实际使用时刻。
  • 所谓“单例”,是指确保当前作用域内始终只存在唯一的实例对象。

弹窗案例:在页面打开的时候,页面中有登录按钮,在点击时,需要创建一个dom节点。 (1)首先,实现一个功能函数:创建节点的函数

var domFun = function () {
    var dom = document.createElement('div');
    dom.innerHTML = '登录窗口demo'
    dom.style.display = 'none';
    document.body.appendChild(dom);
    return dom;
}

(2)其次,创建管理单例的函数

var createSingle = function (fn) {
    var result;
    return function () {
        return result || (result = fn.apply(this, arguments));
    }
}

利用闭包机制,使变量result在当前作用域内持久存在而不被销毁。若result已存在,则直接返回该值;若不存在,则调用传入的功能函数fn,并将结果赋值给result。这一机制确保了当前环境中始终只有一个实例。 (3)将创建节点的函数和管理单例的函数糅合

var createSingleDom = createSingle(domFun)

(4)最后,在点击按钮的时候,进行实例的创建

var loginBtn = document.querySelector('.login')
loginBtn.onclick = function () {
    var loginDom = createSingleDom()
    console.log(loginDom)
    loginDom.style.display = 'block'
}

上述流程实现了仅在点击登录按钮时才生成节点的效果,体现了惰性单例的特征。

该过程将惰性实例的创建逻辑拆解为多个步骤,提升了代码的可修改性和可维护性。如果后续需要创建的不再是div节点,而是image图片、iframe嵌套网页或倒计时组件等其他内容,只需替换步骤(1)中的具体实现即可。

在此示例中,我们严格遵循了“单一实例”这一核心约束。

二、普通单例模式

在vue中的应用:我们知道vue是一个渐进式框架,主要实现视图的渲染,其他的功能可以可以通过第三方插件按需引入,比如路由插件vue-router和全局状态管理vuex。

  • 路由vue-router的使用
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
  • 状态vuex的使用
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

vue通过Vue.use(Vuex)的方式进行vue-router和vuex的安装,Vue.use的代码如下:

Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    // 去掉参数列表中的第一个
    const args = toArray(arguments, 1)
    // 将Vue作为this推入到参数的首位
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
}

可以看出,首次执行vue.use(xxx)的时候,会将该插件推入到数组installedPlugins中去,再次执行vue.use(xxx)的时候,installedPlugins.indexOf(plugin) > -1为true,则终止后续逻辑的执行。也就说,vue.use(xxx)只会让该插件执行一次install的安装。

再看 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])),是不是发现,这里也是单例模式的影子,当this._installedPlugins不存在时为其赋值为[],当前存在时直接返回。

单例模式,去掉具体的例,就剩单模式。就像当前例子中,install只执行一次,this._installedPlugins只初始化一次一样,就体现了一个单字。那这算不算单(例)模式呢?

总结

单例模式是不是可以去实例,只留单呢?这样,很多只执行一次的逻辑也就和单例模式能搭上关系了。仁者见仁智者见智,能不能去,纯属个人想法。因为,优秀的单例模式,是经过无数的程序案例得出的经验性汇总。

以上就是JavaScript单例模式能不能去实例只留单原理解析的详细内容,更多关于JavaScript单例模式去实例的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章