JavaScript 面向对象之单例模式

🌙
手机阅读
本文目录结构

前言

虽然单例模式看着最 low,但是我们工作中,最最最常用的,就是单例模式,单例模式也是后面高级一些模式的基础,要牢固的掌握。

描述一个独一无二的事务

假设我想在 JS 代码里描述我自己,那么我们可以下面的方式

let name = 'anbang';
let age = 18;
let sex = 'male';
console.log(`anbang age : ${age}`)

描述完我自己以后,我再描述我的一名哥们,可以按照同样的思路写

let name = 'trump';
let age = 70;
let sex = 'male';
console.log(`trump age : ${age}`)

这时候我们会发现一个问题,我和我的哥们如果在同一个代码文件里,就会冲突,比如下面这种。

//anbang
let name = 'anbang';
let age = 18;
let sex = 'male';
console.log(`anbang age : ${age}`)

//trump
let name = 'trump';
let age = 70;
let sex = 'male';
console.log(`trump age : ${age}`)

即使我们使用var来描述,虽然不报错,但是后面会覆盖之前的;所以我们只能下面这样子写

//anbang
let anbangName = 'anbang';
let anbangAge = 18;
let anbangSex = 'male';
console.log(`anbang age : ${anbangAge}`)

//trump
let trumpName = 'trump';
let trumpAge = 70;
let trumpSex = 'male';
console.log(`trump age : ${trumpAge}`)

但是如果都这么写,我们会发现每次都是写同样的的逻辑,这些需要重复做的事情,肯定有好的解决方案,否则这门编程语言就要被淘汰了!

程序的特点:无需重复劳动,提高效率!

总结:上面的 anbangName/anbangAge/anbangSex, 前面都是含有 anbang,都是先指定安邦以后再描述他的属性。

那么这时候我们就引入了对象的数据类型,可以通过对象来包起来;

对象描述

我们可以使用下面这种方式来描述,这也就会显得很优雅啦。

//anbang
let anbang = {
    name : 'anbang',
    age : 18,
    sex : 'male'
}
// 使用的时候,先指定anbang,然后再指定age,这也就很符合我们的初衷了
console.log(`anbang age : ${anbang.age}`)

//trump
let trump = {
    name : 'trump',
    age : 70,
    sex : 'male'
}
// 使用的时候,先指定我的哥们,然后再指定我哥们的age
console.log(`trump age : ${trump.age}`)

这里的anbang不单单是变量,因为它开辟了一个新的储存空间,这种形式叫做"命名空间"。

验证变量anbang开辟了新的储存空间:可以直接使用age是无法条用的会发现找不到信息,必须要通过anbang.age先找到 anbang,然后再找 age 才可以找到正确的值;这种验证就说明变量anbang开辟了新的储存空间。

每个命名空间都是独立的,互不冲突,我在anbang里可以写name, 我在我的哥们trump里也可以写name;

扩展:维基百科上对命名空间的解释:https://zh.wikipedia.org/zh-hans/

我们这个时候,就已经写出单例模式啦,这种用一个变量接收一个对象实例的方式,就属于单例模式;

单例设计模式

下面这种就是单例模式

//anbang
let anbang = {
    name : 'anbang',
    age : 18,
    sex : 'male'
}
console.log(`anbang age : ${anbang.age}`)

//trump
let trump = {
    name : 'trump',
    age : 70,
    sex : 'male'
}
console.log(`trump age : ${trump.age}`)

因为anbangtrump 这两个对象都属于Object这个类的实例,所以叫"单例模式",全称叫"单个实例的模式";

注意:实例和实例内的事务是不相同的!,就像我和特朗普都属于人类,我们都属于人类下的两个实例,我有一个鼻子两只手,特朗普也有一个鼻子两只手,但是你不能说我的手就是特朗普的手,虽然我们我们都有手,但是我们是不同的。

表现形式

表现形式:声明一个对象,并写入合适的值。

let anbang = {
    name : 'anbang',
    age : 18,
    sex : 'male'
}

作用

  1. 描述一个独一无二的事务,并且可以对一个事务进行归类存储;
  2. 避免了全局变量的冲突,让使用更加优雅,也更加语义化;

真实项目中的使用

第一种方式:普通变量(用于声明)

第一种方式,就是我们目前的用法

let anbang = {
    name : 'anbang',
    age : 18,
    sex : 'male'
}

第二种方式:函数(用于调用)

第二种就是常见的用法,用来分组和归类的,这样显得逻辑非常清晰。

let utility = {
    main: function() {
        console.log('执行 utility.main');
        this.getInfoBaseAjax(1);
        this.bindEvent();
    },
    bindEvent: function() {
        console.log('执行 utility.bindEvent');
    },
    getInfoBaseAjax: function(id) {
        console.log('执行 utility.getInfoBaseAjax');
    },
    other: function() {
        console.log('执行 utility.other');

    }
}
utility.main();

我见过好多人写代码是下面这种写的

垃圾代码的写法

function bindEvent() {
    console.log('执行 bindEvent');
};

function getInfoBaseAjax(id) {
    console.log('执行 getInfoBaseAjax');
}

function other() {
    console.log('执行 other');
}

// 通过AJAX获取值
getInfoBaseAjax();
// 绑定对象
bindEvent();

这种写法的人,你问他面向对象,创建的方式之类的,也都懂,甚至基于闭包的单例模式都懂,但是真实写代码却并不会灵活运用;这是非常可耻的!!

我们要学的稳,学的踏实,还要学以致用!!!

只要稍微有点编程思想的人,看到上面两种方法,肯定会觉得单例模式的当时更佳优雅!

第三种方式:基于闭包的单例模式(常用于代码封装)

let anbang = (() => {
    var fatherAssets = '有十座旷厂';

    function code() {
        return '安邦会写代码';
    }

    return {
        name: 'anbang',
        age: 18,
        sex: 'male',
        code: code
    }
})()

console.log(anbang);
console.log(anbang.age);
console.log(anbang.code());

运行上面会发现,可以有

{ name: 'anbang', age: 18, sex: 'male', code: [Function: code] }
18
安邦会写代码

但是我老爹的资产fatherAssets,那十座旷厂没有 return 出来,老爹没有告诉我,所以我也并不知道;只有里面 retrun 出来什么,我才知道有什么;

在上面的代码里,我老爹的那十座旷目前属于隐性资产;所以我现在一直在等待,也许有一天,我老爹告诉我"这些年都是在磨练我,其实我是一个富二代,家里有十座矿",哈哈。

回到主题!上面这种,通过函数运行,函数内数据与函数外没有直接关系,函数外不能直接访问函数内数据的机制就是闭包

这种闭包内返回一个对象实例的方式就叫"基于闭包的单例模式",也有叫高级单例模式,反正就是一个称呼,知道咋回事就可以了,在封装库的时候经常用到。

单例模式的应用

let Utility = {
    formatDate: function() {
        console.log('formatDate');
    },
    formatThousands: function() {
        console.log('formatThousands');
    },
    formatTest: function() {
        console.log('formatTest');
    }
}

面试题自测一下

关于基于闭包的单例模式,一定要对预解释了解的比较深入,如果你不知道自己了解的是否深入;

可以参考我以前学习 JS 笔记中的一道题目 基于闭包的单利模式-预解释面试题

题目如下,可以自己想一想,尝试能不能理解其中的原理。

var num = 20;
var obj = {
  num: 30,
  fn: (function (num) {
    this.num *= 3;
    num += 15;
    var num = 45;
    return function () {
      this.num *= 4;
      num += 20;
      console.log(num);
    }
  })(num)
};
var fn = obj.fn;
fn();
obj.fn();
console.log(window.num, obj.num);

单利模式的擅长领域(优点)

我们通过单例模式,一般用于高度定制化,没有规律的场景,比如下面的变量。

let anbang = {
    local:"Hangzhou",
    github:"https://github.com/anbang",
    website:"https://www.axihe.com/",
    bilibili:"https://space.bilibili.com/59312814",
    wechat:"yaolushan"
}
let myPhone = {
    brand:"apple",
    model:"iPhon Xs Max",
    color :"black"
}

单利模式不适应的领域(缺点)

本文举的例子中,可以观察到,下面其实是有规律的。

//anbang
let anbang = {
    name : 'anbang',
    age : 18,
    sex : 'male'
}
// 使用的时候,先指定anbang,然后再指定age,这也就很符合我们的初衷了
console.log(`anbang age : ${anbang.age}`)

//trump
let trump = {
    name : 'trump',
    age : 70,
    sex : 'male'
}
// 使用的时候,先指定我的哥们,然后再指定我哥们的age
console.log(`trump age : ${trump.age}`)

如果我们要描述 10 个人,按照单利模式的思想,我们需要写很多重复代码,上面这种形式,要写 40 行的代码;

如果我们真这么写了,就肯定会被同时骂死的,这种代码属于懒婆娘的裹脚布 - 又臭又长!

程序的特点:无需重复劳动,提高效率!

联想到生活:如果个人想要找个人借钱,需要借条,直接手写一张,双方签字;假设个人向银行借钱,银行不会每次和你手写一个借条,而且做一套机制,按照机制走,就可以了;

假设这套机制是一套合同模板 + 公章的机制,那么银行打印一张空合同,填写信息,然后盖公章,个人签字,这样就可以了;

这样的机制下,无论是 10 个人借,还是 1W 个人借,效率都会很高。

这就引出了,面向对象的 - 工厂模式,下一节介绍面向对象的工厂模式

AXIHE / 精选资源

浏览全部教程

面试题

学习网站

前端培训
自己甄别

前端书籍

关于朱安邦

我叫 朱安邦,阿西河的站长,在杭州。

以前是一名平面设计师,后来开始接接触前端开发,主要研究前端技术中的JS方向。

业余时间我喜欢分享和交流自己的技术,欢迎大家关注我的 Bilibili

关注我: Github / 知乎

于2021年离开前端领域,目前重心放在研究区块链上面了

我叫朱安邦,阿西河的站长

目前在杭州从事区块链周边的开发工作,机械专业,以前从事平面设计工作。

2014年底脱产在老家自学6个月的前端技术,自学期间几乎从未出过家门,最终找到了满意的前端工作。更多>

于2021年离开前端领域,目前从事区块链方面工作了