51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 4019|回复: 0
打印 上一主题 下一主题

[转贴] 用JS来理解设计模式——单例模式

[复制链接]
  • TA的每日心情
    擦汗
    3 天前
  • 签到天数: 1042 天

    连续签到: 4 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-2-22 10:15:44 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    单例模式
      单例模式确保一个类只有一个实例,并提供一个全局访问点。
      单例模式应该算是一个设计模式中比较简单且好用的模式,大部分人或多或少都接触过这个模式。所以也不举什么例子了,先来问几个问题,如果都了解的话,也就不用往下看了。
      · 单例模式有什么用,什么时候用?
      · 单例模式怎么写?
      · 在JS里用单例模式时可能会出什么问题?
      单例模式有什么用,什么时候用?
      很简单,在上面的定义以及模式的名字就能看出来,这个模式的用处就是保证我们拿到的对象永远是同一个,不论是在代码的什么地方,也不论是在何时。
      举几个例子,比方说一个提示弹窗,一般来说这种提示弹窗通常不会同时展示多个出来,但假如短时间内多次调用生成提示弹窗的方法,则有可能会出现多个提示弹窗。但假若采用了单例模式来,我们拿到的永远是同一个提示弹窗,只会在同一个提示弹窗上进行修改,就不用担心多个弹窗出现的情况。
      再比方说,当我们创建的一个对象所花销的时间较长,且这个对象属于可复用的,又可能需要频繁使用时,就可以采用单例模式,比较常见的就是创建编辑器这类对象等等。
      书里有提到一个大家可能会有的问题,那就是写一个大家约定好的全局变量不也一样吗?因为这样也能够实现单例模式所需要的效果,无论是在何时何地拿到的都是同一个对象。确实这样子也是可以做到单例的效果,但是有几个问题。
      1.假如创建这个对象过程非常消耗资源,然后我们却一直没用使用到,那么不就是形成浪费了吗,即浪费了生成的时间,也浪费了其占据的内存空间。
      2.如何保证这个对象只会被实例化一次?生成这个对象的方法会不会暴露出来,使得其他开发人员选择在其他地方自己调用?
      这里也说到了一个很重要的点,就是构造这个对象的方法应当是“私有”的,不能够被直接调用,否则这个方法也没什么意义了。
      单例模式怎么写?
      首先需要和大家说明,我写这个代码是根据自己的理解来写的,写之前没参考别人的写法,所以可能会有一些问题,欢迎大家指出。
    1.  let getTestInstance = (function () {

    2.     let instance = null;

    3.     class Test {

    4.       constructor (name) {

    5.         this.name = name || 'test'

    6.       }

    7.       changeName (name) {

    8.         this.name = name;

    9.       }

    10.     }

    11.     return function (...args) {

    12.       if (!instance) instance = new Test(...args);

    13.       return instance;

    14.     }

    15.   })();

    16.   let a = getTestInstance();

    17.   console.log(a.name); // test

    18.   a.changeName('name change');

    19.   console.log(a.name); // name change

    20.   let b = getTestInstance();

    21.   console.log(b.name); // name change

    22.   b.changeName('name change2');

    23.   console.log(b.name); // name change2

    24.   console.log(a.name); // name change2

    25.   console.log(a === b); // true
    复制代码
    这里的实现比较简单,主要是通过闭包,将我们的单例和构造函数“私有化”,无法直接访问,从而保证拿到的都是同一个对象,且只能通过getTestInstance方法来获取。这样我们就能确保一个类只有一个实例,且只能通过我提供的这个访问点来获取,这样就符合了单例模式的要求。  单例模式确保一个类只有一个实例,并提供一个全局访问点。
      我们可以看到变量a与变量b其实指向的都是同一个实例,因此不论是a对实例的改变或是b,都会互相影响。
      这里的代码实现例子是一个简单的例子,不过还有一些特殊的情况需要考虑。
      在JS里用单例模式时可能会出什么问题?
      1.多线程问题
      2.异步生成实例问题
      多线程问题
      多线程问题指的是假若有几处代码同时调用了获取实例方法,且实例尚且未生成,那么可能出现这几处的实例不一致的情况。众所周知JS是一个单线程模型,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。
      不过现在有了webWorker,JS也拥有了多线程环境,那么我们就可以进行尝试在JS多线程中是否会存在单例模式的多线程问题。
      思路是:新建多个webworker,并且在webWorker中引入getTestInstance方法,然后将webWorker中的生成的实例返回给主流程,在主流程中进行对象比较。
      然而经过尝试发现,在webWorker中使用importScripts引入的js是互相独立的,这意味着生成的实例都是不一样的,那么引入js这条路子就断了。
      因此我就想办法将主流程中的getTestInstance方法传入到webWorker里面,其中我采用了两种方法,但都以失败告终。分别是:
      1.将getTestInstance方法用postMessage传入。
      2.由于webWorker中线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程拥有navigator对象和location对象。利用这个特点,我尝试将getTestInstance方法挂在location对象上。
      但两者的结果分别是:
      1.无法将方法传入到webWoker,这里应该是底层限制,无法解决。
    1.  app.js:15 Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': function (...args) {

    2.         if (!instance) instance = new Test(...args);

    3.         return instance;

    4.       } could not be cloned.
    复制代码
    2.虽然webWorker确实拥有location对象,但它的location对象很特殊,是WorkerLocation,且不同Worker之间的WorkerLocation都是独立的。因此无法在location对象上获取到getTestInstance方法。  因此可以片面的说在JS多线程中无法使用单例模式。
      异步生成实例问题
      这个问题主要是讨论如果生成实例的方法是一个异步的方法,那么在异步方法执行完成前,有别处代码调用了获取实例方法,那么是不是会产生多次调用或实例不一致的问题?
      因此实现上有所不同,但其实也很简单,就是返回的值变成Promise且多加几个状态控制而已,下面是实现代码,如有遗漏或错误,欢迎指出。
    1. let getTestInstance = (function () {

    2.       let instance = null;

    3.       let loading = false; // 判断是否正在生成中

    4.       let loadingPrmoise = null; // 用于记录第一次生成的promise

    5.       class Test {

    6.         constructor (name) {

    7.           this.name = name || 'test'

    8.         }

    9.         changeName (name) {

    10.           this.name = name;

    11.         }

    12.       }

    13.       return function (...args) {

    14.         if (instance) return Promise.resolve(instance);

    15.         if (loading) return loadingPrmoise;

    16.         loading = true;

    17.         // 一个2S的生成实例过程

    18.         let promise = new Promise((resolve, reject) => {

    19.           setTimeout(() => {

    20.             instance = new Test(...args);

    21.             loading = false;

    22.             resolve(instance);

    23.           }, 2000)

    24.         });

    25.         loadingPrmoise = promise;

    26.         return promise;

    27.       }

    28.     })();

    29.     let a,b;

    30.     // 生成实例需要2S

    31.     getTestInstance().then(res => {

    32.       a = res;

    33.     });

    34.     // 1S秒再次获取实例

    35.     setTimeout(() => {

    36.       getTestInstance().then(res => {

    37.         b = res;

    38.         console.log(a === b) // true

    39.         getTestInbstance().then(res => {

    40.             console.log(a === res); // true

    41.         })

    42.       });

    43.     }, 1000);
    复制代码






    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏1
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-10 06:41 , Processed in 0.057560 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表