JavaScript 设计模式--单例模式

前言

在JavaScript的江湖里,对象与类的关系犹如水墨画中的留白------看似无形却充满玄机。当我们用new Function()创造构造函数时,实际上在内存中勾勒出一个由原型链编织的立体网络。您是否曾困惑:为何给构造函数"本身"添加的方法,实例却无法触及?这背后藏着JavaScript对象模型的精妙设计:构造函数作为函数对象,其自身属性与方法如同悬挂在函数对象上的装饰品,而实例只能通过__proto__这把梯子,攀爬到构造函数的prototype属性上去采摘方法果实。

js 复制代码
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return `(${this.x},${this.y})`;
};

var p = new Point(x, y);
console.log(p.toString());

这段代码通过给构造函数原型对象上添加方法,实例化对象,这样做九不需要重复执行构造函数身上所挂载的方法和属性

js 复制代码
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return `(${this.x},${this.y})`;
};

Point.toSum = function (p1, p2) {
  return p1 + p2;
};

var p = new Point(x, y);
console.log(p.toString());

console.log(p.toSum(2, 3));//访问不到该方法

这里的toSum() 实例对象是访问不到的,为什么呢? 这里的函数是可以看成对象的, 万物皆对象嘛,具体为什么?因为Point是由new Funtion()创建的函数对象(原型链上),既然是对象,我们就可以在对象身上添加属性和方法对吧,注意在函数上添加的方法和函数里面写的方法不一样,一个是函数身上的方法,一个是函数里面写的方法,明白new的五个步骤(实例对象的对象原型===构造函数的原型对象),就知道我们可以通过实例对象使用函数里面的方法和函数原型对象上所挂载的属性和方法,但是new的五个步骤里面没有使我们可以访问到函数身上添加的方法(除了在函数身上的原型对象身上添加的属性和方法)。

所以我们这里只能通过Point.toSum(2,3)的形式调用

但是这里其实可以通过来实现p.toSum(2,3)的调用,具体怎么实现呢?

设计模式

一种编程的思想,如何设计前后逻辑

class类

对构造函数和原型的一个新的语法设定

新的语法

那么类是怎么实现像构造函数一样的原型对象的呢?

js 复制代码
class Point {
  constructor(x, y) {
    //构造器函数
    this.x = x;
    this.y = y;
  }

  toString() {
    return `(${this.x} ${this.y})`;
  }
   toSum(a, b) {
   
    return a + b;
  }
}

var p = new Point(1, 2);
console.log(p.toString());

这里直接在类里面添加一个toString方法

再通过new 一个类来创建实例对象,直接实例对象.方法()就可以调用方法

这里可以在所有方法前面添加static,来与不使用类的情况一样(同样实例对象就访问不到toString()方法,这时只能通过函数名来调用toSum()和toString()),加了static 的方法就是私有的方法只能被类访问了

单例模式

  • 一个类被new 多次 ,创建的实例对象却是同一个 类的语法:

v8引擎在判断两个值是否相等时,是判断栈里的之是否相等,引用类型指针存储在栈,而内容存储在堆里面,指针不相等,基本数据类型在栈存储,值是相等的

[JavaScript 数据类型与内存分配机制探究数据类型](https://juejin.cn/post/7513868165252743178 "https://juejin.cn/post/7513868165252743178")

js 复制代码
class SingleDog {
  show() {
    console.log("我是单例对象");
  }
  
}
const instance = new SingleDog();
const instance2 = new SingleDog();

怎么让创建的两个实例对象是同一个呢?、

第一种方法:通过往类上面添加属性

js 复制代码
class SingleDog {
  show() {
    console.log("我是单例对象");
  }
  static getInstance() {
    if (!SingleDog.instance) {
      SingleDog.instance = new SingleDog();
    }
    return SingleDog.instance;
  }
  
}
const instance = SingleDog().getInstance()
const instance2 = SingleDog().getInstance()

通过在类上添加一个静态方法,该方法是返回一个新对象,通过if判断静态方法创建的对象是否存在,不存在就类上添加一个对象并返回,存在就直接返回类上面的对象。

第二种方法:通过闭包实现

js 复制代码
class SingleDog {
//通过闭包来实现
SingleDog.getInstance = (function () {
  let instance = null;
  return function () {
    if (!instance) {
      instance = new SingleDog();
    }
    return instance;
  };
})();
}
const s1 = SingleDog.getInstance; //()是调用 return  的函数
const s2 = SingleDog.getInstance;

console.log(s1 === s2);

SingleDog.getInstance找到getInstance的值,其值是一个自执行函数,当使用这个值得时候会立即返回一个对象并将该对象保存到闭包里面,下一次使用SingleDog.getInstance属性时就会返回存在闭包里面的对象而不是重新创建。

单例模式的用处:优化重复代码,提高效率

js 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>单例模式弹框</title>
    <style>
      #model {
        width: 200px;
        height: 200px;
        background-color: pink;
        line-height: 200px;
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        /* 向上向左平移自身长度一半的距离 */
        border: 1px solid red;
        text-align: center;
        /* fixed以屏幕中心侧为原点 */
      }
    </style>
  </head>
  <body>
    <button id="btn1">open1</button>
    <button id="btn2">open2</button>
    <button id="btn2">open3</button>

    <script>
    

      const Model = (function () {
        //return 函数体
        let model = null;
        return function () {
          if (!model) {
            model = document.createElement("div");
            model.id = "model";
            model.innerHTML = "我是弹框";
            model.style.display = "none";
            document.body.appendChild(model);
          }
          return model;
        };
      })();

      btn1.addEventListener("click", function () {
        const model = Model();
        model.style.display = "block";
      });

      btn2.addEventListener("click", function () {
        const model = Model();
        model.style.display = "none";
      });
      btn3.addEventListener("click", function () {
        const model = Model();
        model.style.display = "block";
      });
    </script>
  </body>
</html>

结语:设计模式的核心智慧

在JavaScript的世界里,理解对象与类的关系如同掌握一门语言的语法规则。构造函数、原型链、类语法这些概念,本质上是解决代码复用与结构优化的不同方案:

  1. 原型链的本质

    每个构造函数都自带一个"工具库"(prototype属性),实例对象通过隐式链接(proto)共享这些工具。这种设计让所有实例天然具备某些能力(如toString方法),但直接挂在构造函数上的方法(如静态工具函数)则像"厂长专属工具",实例无法直接使用。

  2. 类语法的进化

    ES6引入的类语法并非全新概念,而是将原型链的复杂关系封装成更直观的语法糖。通过class关键字,我们可以清晰区分:

    • 构造器(初始化实例)
    • 实例方法(所有实例共享的行为)
    • 静态方法(类级别的工具函数)
  3. 单例模式的价值

    当需要确保"全局只有一个实例"时(如弹窗组件、配置管理器),单例模式通过闭包或静态方法实现"一次创建,终身使用"。这种设计避免重复创建对象的性能浪费,是资源管理的经典方案。

设计模式的本质

它们是前人从实战中提炼的"代码智慧",旨在用更优雅的方式解决重复问题。理解这些模式,能让代码:

  • 结构更清晰(如类语法)
  • 性能更高效(如单例模式)
  • 维护更轻松(如原型方法共享)
相关推荐
烛阴4 分钟前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
中微子11 分钟前
JavaScript 事件与 React 合成事件完全指南:从入门到精通
前端
Hexene...20 分钟前
【前端Vue】如何实现echarts图表根据父元素宽度自适应大小
前端·vue.js·echarts
初遇你时动了情21 分钟前
腾讯地图 vue3 使用 封装 地图组件
javascript·vue.js·腾讯地图
dssxyz25 分钟前
uniapp打包微信小程序主包过大问题_uniapp 微信小程序时主包太大和vendor.js过大
javascript·微信小程序·uni-app
天天扭码1 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
xw51 小时前
我犯了错,我于是为我的uni-app项目引入环境标志
前端·uni-app
!win !1 小时前
被老板怼后,我为uni-app项目引入环境标志
前端·小程序·uni-app
Burt1 小时前
tsdown vs tsup, 豆包回答一坨屎,还是google AI厉害
前端
群联云防护小杜2 小时前
构建分布式高防架构实现业务零中断
前端·网络·分布式·tcp/ip·安全·游戏·架构