阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      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 个人借,效率都会很高。

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

      目录
      目录