• 单例模式,是一种常用的软件设计模式;

  • 单例模式就是确保一个类在任何情况下都只有一个实例,并提供一个全局访问点

类图

代码

ES6 单例模式

class Window {
  constructor(name) {
    this.name = name;
  }

  static getInstance(name) {
    if (!this.instance) {
      this.instance = new Window(name);
    }
    return this.instance;
  }
}

var w1 = Window.getInstance();
var w2 = Window.getInstance();
console.log(w1 === w2); // true

ES5 单例模式

let Window = function (name) {
  this.name = name;
}

Window.prototype.getName = function () {
  console.log(this.name);
}

Window.getInstance = (function () {
  let window = null; // 利用闭包,隐藏 window,不让外部获取到
  return function (name) {
    if (!window)
      window = new Window(name);
    return window;
  }
})();

let w1 = Window.getInstance('zhangsan');
let w2 = Window.getInstance('zhangsan');
console.log(w1 === w2); // true
/*
 * 问题:
 *  1. 使用者必须知道这是一个单例的类,必须主动调用 getInstance
 *  2. 并不能阻止使用者 new Window
 */

透明单例

// 解决了「ES5 单例模式」 的问题
let Window = (function () {
  let windowInstance;

  let Window = function (name) {
    if (windowInstance) {
      return windowInstance;
    } else {
      // 利用 new 的原理
      this.name = name;
      return (windowInstance = this);
    }
  }

  Window.prototype.getName = function () {
    console.log(this.name);
  }

  return Window;
})();

let w1 = new Window('zhangsan');
let w2 = new Window('zhangsan');
console.log(w1 === w2); // true
/*
 * 问题:
 *  1. 违反了 单一职责 原则
 */

单例与构建分离

// 解决了 「透明单例」 的问题
function Window(name) {
  this.name = name;
}
Window.prototype.getName = function () {
  console.log(this.name);
}

let createSingle = (function () {
  let instance;
  return function (name) {
    if (!instance) {
      instance = new Window();
    }
    return instance;
  }
})();

let w1 = new createSingle('zhangsan');
let w2 = new createSingle('zhangsan');
console.log(w1 === w2); // true
/*
 * 问题:
 *  1. 只能创建单一实例,不灵活
 */

封装变化

function Dialog(name) {
  this.name = name;
}

function Window(name) {
  this.name = name;
}
Window.prototype.getName = function () {
  console.log(this.name);
}

let createSingle = function (Constructor) {
  let instance;

  let SingleConstructor function () {
    if (!instance) {
      instance = new Constructor(...arguments);
    }

    return SingleConstructor;
  }
};

let CreateWindow = createSingle(Window);
let w1 = new createSingle('zhangsan');
let w2 = new createSingle('zhangsan');
console.log(w1 === w2); // true

let CreateDialog = createSingle(Dialog);
let d1 = new createSingle('zhangsan');
let d2 = new createSingle('zhangsan');
console.log(d1 === d2); // true

命名空间

  • 用户编写的代码与内部的类、函数、常量或第三方类、函数、常量之间的名字冲突;

  • 为很长的标识符名称创建一个别名(或简短)的名称,提高源代码的可读性;

let utils = {};

utils.def = function (namespace, fn) {
  let namespaces = namespace.split('.');
  let fnName = namespaces.pop();
  let current = utils;

  for (let i = 0; i < namespaces.length; i++) {
    let namespace = namespaces[i];
    if (!current[namespace]) {
      current[namespace] = {};
    }
    current = current[namespace];
  }

  current[fnName] = fn;
}

utils.def('dom.attr', function (key) {
  console.log('dom.attr');
});

utils.def('dom.html', function (html) {
  console.log('dom.html');
});

utils.def('string.trim', function () {
  console.log('string.trim');
});

/*
生成的 utils 命名空间
utils = {
  dom: {
    attr: fn,
    html: fn,
  },
  string: {
    trim: fn,
  },
}
*/

// 调用
utils.dom.attr('src');
utils.string.trim(' aaa ');

经典场景

jQuery

// 无论引入多少次 jQuery,都只会返回第一个 实例
if (window.jQuery != null) {
  return window.jQuery;
} else {
  //init~~~~~~~
}

模态窗口

class Login {
  constructor() {
    this.element = document.createElement('div');
    this.element.innerHTML = (
      `
        用户名 <input type="text"/>
        <button>登录</button>
      `
    );
    this.element.style.cssText = 'width: 100px; height: 100px; position: absolute; left: 50%; top: 50%; display: block;';
    document.body.appendChild(this.element);
  }
  show() {
    this.element.style.display = 'block';
  }
  hide() {
    this.element.style.display = 'none';
  }
}

Login.getInstance = (function () {
  let instance;
  return function () {
    if (!instance) {
      instance = new Login();
    }
    return instance;
  }
})();

document.getElementById('showBtn').addEventListener('click', function (event) {
  Login.getInstance().show();
});
document.getElementById('hideBtn').addEventListener('click', function (event) {
  Login.getInstance().hide();
});

LRU 缓存

  1. 为 LRU Cache 设计一个数据结构,它支持两个操作:

    • get(key):如果 key 在 cache 中,则返回对应的 value 值,否则返回 -1;
    • set(key, value):如果 key 不在 cache 中,则将该 (key, value) 插入 cache 中(注意,如果 cache 已满,则必须把最近最久未使用的元素从 cache 中删除);如果 key 在cache 中,则重置 value 的值;
  2. 实现代码:

    // Vue3 的 keepalive 组件就用了这个 LRU 管理组件的缓存
    class LRUCache {
      capacity: number
      map: Map<number, number> = new Map()
    
      constructor(capacity: number) {
        // 利用迭代器实现(map 是有序的)
        this.map = new Map()
        // 设置缓存最大个数
        this.capacity = capacity
      }
    
      get(key: number): number {
        if (this.map.has(key)) {
          let value = this.map.get(key)
          // 重新 set,相当于更新到 map 最后
          this.map.delete(key)
          this.map.set(key, value)
          return value
        } else {
          return -1
        }
      }
    
      put(key: number, value: number): void {
        // 如果有,就删了再赋值(相当于更新到 map 最后)
        if (this.map.has(key)) {
          this.map.delete(key)
        }
        this.map.set(key, value)
    
        // 判断是不是容量超了,淘汰机制
        if (this.map.size > this.capacity) {
          // this.map.keys() 返回一个 MapIterator {1, 3, 4} 迭代器对象
          this.map.delete(this.map.keys().next().value)
        }
      }
    }
    
    LRUCache lRUCache = new LRUCache(2);
    lRUCache.put(1, 1); // 缓存是 {1=1}
    lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
    lRUCache.get(1);    // 返回 1
    lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
    lRUCache.get(2);    // 返回 -1 (未找到)
    lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
    lRUCache.get(1);    // 返回 -1 (未找到)
    lRUCache.get(3);    // 返回 3
    lRUCache.get(4);    // 返回 4
    
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 类图
  2. 2. 代码
    1. 2.1. ES6 单例模式
    2. 2.2. ES5 单例模式
    3. 2.3. 透明单例
    4. 2.4. 单例与构建分离
    5. 2.5. 封装变化
    6. 2.6. 命名空间
  3. 3. 经典场景
    1. 3.1. jQuery
    2. 3.2. 模态窗口
    3. 3.3. LRU 缓存