小册 JavaScript 设计模式核⼼原理与应⽤实践

  • 重要: 工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)、单例模式、原型模式、构造器模式

    ⭐工厂模式

    简单工厂模式

    工厂方法模式

    工厂模式其实就是将创建对象的过程单独封装

    工厂模式的目的,就是为了实现无脑传参

  1. // 构造函数
  2. function User(name , age, career, work) {
  3. this.name = name
  4. this.age = age
  5. this.career = career
  6. this.work = work
  7. }
  8. function Factory(name, age, career) {
  9. let work
  10. switch(career) {
  11. case 'coder':
  12. work = ['写代码','写系分', '修Bug']
  13. break
  14. case 'product manager':
  15. work = ['订会议室', '写PRD', '催更']
  16. break
  17. case 'boss':
  18. work = ['喝茶', '看报', '见客户']
  19. case 'xxx':
  20. // 其它工种的职责分配
  21. ...
  22. return new User(name, age, career, work)
  23. }

将创建对象的过程单独封装,这样的操作就是工厂模式。同时它的应用场景也非常容易识别:有构造函数的地方,我们就应该想到简单工厂;在写了大量构造函数、调用了大量的 new、自觉非常不爽的情况下,我们就应该思考是不是可以掏出工厂模式重构我们的代码了。

抽象工厂模式

抽象工厂目前来说在JS世界里也应用得并不广泛

抽象工厂模式的定义,是围绕一个超级工厂创建其他工厂

  • 抽象工厂: 用于声明最终目标产品的共性。
  • 具体工厂: 继承自抽象工厂、实现了抽象工厂里声明的那些方法。 ```javascript // 定义操作系统这类产品的抽象产品类 class OS { controlHardWare() {
    1. throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
    } }

// 定义具体操作系统的具体产品类 class AndroidOS extends OS { controlHardWare() { console.log(‘我会用安卓的方式去操作硬件’) } }

class AppleOS extends OS { controlHardWare() { console.log(‘我会用🍎的方式去操作硬件’) } } …

class MobilePhoneFactory { // 提供操作系统的接口 createOS(){ throw new Error(“抽象工厂方法不允许直接调用,你需要将我重写!”); } // 提供硬件的接口 createHardWare(){ throw new Error(“抽象工厂方法不允许直接调用,你需要将我重写!”); } }

// 具体工厂继承自抽象工厂 class FakeStarFactory extends MobilePhoneFactory { createOS() { // 提供安卓系统实例 return new AndroidOS() } createHardWare() { // 提供高通硬件实例 return new QualcommHardWare() } }

// 这是我的手机 const myPhone = new FakeStarFactory() // 让它拥有操作系统 const myOS = myPhone.createOS() // 让它拥有硬件 const myHardWare = myPhone.createHardWare() // 启动操作系统(输出‘我会用安卓的方式去操作硬件’) myOS.controlHardWare() // 唤醒硬件(输出‘我会用高通的方式去运转’) myHardWare.operateByOrder()

  1. > **抽象工厂是佐证“开放封闭原则”的良好素材**
  2. <br />
  3. <a name="C00YM"></a>
  4. # ⭐单例模式
  5. **单例模式就是保证一个类仅有一个实例,并提供一个访问它的全局访问点**
  6. > 单例模式想要做到的是,**不管我们尝试去创建多少次,它都只给你返回第一次所创建的那唯一的一个实例**。
  7. > 要做到这一点,就需要构造函数**具备判断自己是否已经创建过一个实例**的能力。
  8. ```javascript
  9. class SingleDog {
  10. show() {
  11. console.log('我是一个单例对象')
  12. }
  13. static getInstance() {
  14. // 判断是否已经new过1个实例
  15. if (!SingleDog.instance) {
  16. // 若这个唯一的实例不存在,那么先创建它
  17. SingleDog.instance = new SingleDog()
  18. }
  19. // 如果这个唯一的实例已经存在,则直接返回
  20. return SingleDog.instance
  21. }
  22. }
  23. const s1 = SingleDog.getInstance()
  24. const s2 = SingleDog.getInstance()
  25. // true
  26. s1 === s2

实现一个 Storage

实现Storage,使得该对象为单例,基于 localStorage 进行封装。实现方法 setItem(key,value) 和 getItem(key)。

  1. //静态方法版本:
  2. class Storage{
  3. static getInstance(){
  4. if(!Storage.instance){
  5. Storage.instance = new Storage()
  6. }
  7. return Storage.instance
  8. }
  9. getItem(key){
  10. return localStorage.getItem(key)
  11. }
  12. setItem(key,value){
  13. localStorage.setItem(key,value)
  14. }
  15. }
  16. const storage2 = Storage.getInstance()
  17. storage1.setItem('name', '李雷')
  18. // 李雷
  19. storage1.getItem('name')
  20. // 也是李雷
  21. storage2.getItem('name')
  22. // 返回true
  23. storage1 === storage2
  24. //闭包版
  25. // 先实现一个基础的StorageBase类,把getItem和setItem方法放在它的原型链上
  26. function StorageBase(){}
  27. StorageBase.prototype.getItem = function(key){
  28. return localStorage.getItem(key)
  29. }
  30. StorageBase.prototype.setItem = function(key,value){
  31. return localStorage.setItem(key,value)
  32. }
  33. // 以闭包的形式创建一个引用自由变量的构造函数
  34. const Storage = (functon(){
  35. let instance = null
  36. return function(){
  37. if(!instance){
  38. instance = new StorageBase()
  39. }
  40. return instance
  41. }
  42. })()
  43. // 这里其实不用 new Storage 的形式调用,直接 Storage() 也会有一样的效果
  44. const storage1 = new Storage()
  45. const storage2 = new Storage()
  46. storage1.setItem('name', '李雷')
  47. // 李雷
  48. storage1.getItem('name')
  49. // 也是李雷
  50. storage2.getItem('name')
  51. // 返回true
  52. storage1 === storage2

实现一个模态框

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>单例模式弹框</title>
  6. </head>
  7. <style>
  8. #modal {
  9. height: 200px;
  10. width: 200px;
  11. line-height: 200px;
  12. position: fixed;
  13. left: 50%;
  14. top: 50%;
  15. transform: translate(-50%, -50%);
  16. border: 1px solid black;
  17. text-align: center;
  18. }
  19. </style>
  20. <body>
  21. <button id='open'>打开弹框</button>
  22. <button id='close'>关闭弹框</button>
  23. </body>
  24. <script>
  25. // 核心逻辑,这里采用了闭包思路来实现单例模式
  26. const Modal = (function() {
  27. let modal = null
  28. return function() {
  29. if(!modal) {
  30. modal = document.createElement('div')
  31. modal.innerHTML = '我是一个全局唯一的Modal'
  32. modal.id = 'modal'
  33. modal.style.display = 'none'
  34. document.body.appendChild(modal)
  35. }
  36. return modal
  37. }
  38. })()
  39. // 点击打开按钮展示模态框
  40. document.getElementById('open').addEventListener('click', function() {
  41. // 未点击则不创建modal实例,避免不必要的内存占用;
  42. //此处不用 new Modal 的形式调用也可以,和 Storage 同理
  43. const modal = new Modal()
  44. modal.style.display = 'block'
  45. })
  46. // 点击关闭按钮隐藏模态框
  47. document.getElementById('close').addEventListener('click', function() {
  48. const modal = new Modal()
  49. if(modal) {
  50. modal.style.display = 'none'
  51. }
  52. })
  53. </script>
  54. </html>

原型模式

原型模式不仅是一种设计模式,它还是一种编程范式(programming paradigm),是 JavaScript 面向对象系统实现的根基。

在原型模式下,当我们想要创建一个对象时,会先找到一个对象作为原型,然后通过克隆原型的方式来创建出一个与原型一样(共享一套数据/方法)的对象。

在 JavaScript 里,Object.create方法就是原型模式的天然实现——准确地说,只要我们还在借助Prototype来实现对象的创建和原型的继承,那么我们就是在应用原型模式。

构造器模式