Javascript设计模式
in Javascript with 0 comment

Javascript设计模式

in Javascript with 0 comment

单例模式

单例模式定义:保证一个类仅有一个实例,并提供访问此实例的全局访问点。保证对象不被重复创建,以达到降低开销的目的。

const Single = function() {};

Single.getInstance = (function() {
  // 由于es6没有静态类型,故闭包: 函数外部无法访问 instance
  let instance = null;
  return function() {
    // 检查是否存在实例
    if (!instance) {
      instance = new Single();
    }
    return instance;
  };
})();

let s1 = Single.getInstance();
let s2 = Single.getInstance();

console.log(s1 === s2);

工厂模式

定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,就是把new对象的操作包裹一层,对外提供一个可以根据不同参数创建不同对象的函数。

/**
 * 实体类:Dog、Cat
 */

class Dog {
  run() {
    console.log("狗");
  }
}

class Cat {
  run() {
    console.log("猫");
  }
}

/**
 * 工厂类:Animal
 */

class Animal {
  constructor(name) {
    name = name.toLocaleLowerCase();
    switch (name) {
      case "dog":
        return new Dog();
      case "cat":
        return new Cat();
      default:
        throw TypeError("class name wrong");
    }
  }
}

/**
 * 以下是测试代码
 */

const cat = new Animal("cat");
cat.run();
const dog = new Animal("dog");
dog.run();

订阅-发布模式

定义了对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都可以得到通知,它拥有“时间解耦”和“空间解耦”的优点。

订阅-发布模式和观察者模式概念相似,但在订阅-发布模式中,订阅者和发布者之间多了一层中间件:一个被抽象出来的信息调度中心。

JS 中一般用事件模型来代替传统的发布-订阅模式。任何一个对象的原型链被指向Event的时候,这个对象便可以绑定自定义事件和对应的回调函数。

const Event = {
  clientList: {},

  // 绑定事件监听
  listen(key, fn) {
    if (!this.clientList[key]) {
      this.clientList[key] = [];
    }
    this.clientList[key].push(fn);
    return true;
  },

  // 触发对应事件
  trigger() {
    const key = Array.prototype.shift.apply(arguments),
      fns = this.clientList[key];

    if (!fns || fns.length === 0) {
      return false;
    }

    for (let fn of fns) {
      fn.apply(null, arguments);
    }

    return true;
  },

  // 移除相关事件
  remove(key, fn) {
    let fns = this.clientList[key];

    // 如果之前没有绑定事件
    // 或者没有指明要移除的事件
    // 直接返回
    if (!fns || !fn) {
      return false;
    }

    // 反向遍历移除置指定事件函数
    for (let l = fns.length - 1; l >= 0; l--) {
      let _fn = fns[l];
      if (_fn === fn) {
        fns.splice(l, 1);
      }
    }

    return true;
  }
};

// 为对象动态安装 发布-订阅 功能
const installEvent = obj => {
  for (let key in Event) {
    obj[key] = Event[key];
  }
};

let salesOffices = {};
installEvent(salesOffices);

// 绑定自定义事件和回调函数

salesOffices.listen(
  "event01",
  (fn1 = price => {
    console.log("Price is", price, "at event01");
  })
);

salesOffices.listen(
  "event02",
  (fn2 = price => {
    console.log("Price is", price, "at event02");
  })
);

salesOffices.trigger("event01", 1000);
salesOffices.trigger("event02", 2000);

salesOffices.remove("event01", fn1);

// 输出: false
// 说明删除成功
console.log(salesOffices.trigger("event01", 1000));

享元模式

享元模式:运用共享技术来减少创建对象的数量,从而减少内存占用、提高性能。
享元模式提醒我们将一个对象的属性划分为内部和外部状态。

//代码实现(未使用享元模式)
var Model = function( sex, underwear){
    this.sex = sex;
    this.underwear = underwear;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
    var maleModel = new Model( 'male', 'underwear' + i );     
    maleModel.takePhoto();
 };
for ( var j = 1; j <= 50; j++ ){
    var femaleModel= new Model( 'female', 'underwear' + j );
    femaleModel.takePhoto(); 
};

//代码重构(享元模式)
/*只需要区别男女模特
那我们先把 underwear 参数从构造函数中 移除,构造函数只接收 sex 参数*/
var Model = function( sex ){ 
    this.sex = sex;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
/*分别创建一个男模特对象和一个女模特对象*/
var maleModel = new Model( 'male' ), 
    femaleModel = new Model( 'female' );
/*给男模特依次穿上所有的男装,并进行拍照*/
for ( var i = 1; i <= 50; i++ ){ 
    maleModel.underwear = 'underwear' + i; 
    maleModel.takePhoto();
};
/*给女模特依次穿上所有的女装,并进行拍照*/
for ( var j = 1; j <= 50; j++ ){ 
    femaleModel.underwear = 'underwear' + j; 
    femaleModel.takePhoto();
};
//只需要两个对象便完成了同样的功能

享元模式的替代方案 —— 对象池,它跟享元模式有一些相似之处,但没有分离内部状态和外 部状态这个过程。对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接 new,而是转从对象池里获取。如 果对象池里没有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责之后, 再进入 池子等待被下次获取。

// 对象池
class ObjectPool {
  constructor() {
    this._pool = []; //
  }

  // 创建对象
  create(Obj) {
    return this._pool.length === 0
      ? new Obj(this) // 对象池中没有空闲对象,则创建一个新的对象
      : this._pool.shift(); // 对象池中有空闲对象,直接取出,无需再次创建
  }

  // 对象回收
  recover(obj) {
    return this._pool.push(obj);
  }

  // 对象池大小
  size() {
    return this._pool.length;
  }
}

// 模拟文件对象
class File {
  constructor(pool) {
    this.pool = pool;
  }

  // 模拟下载操作
  download() {
    console.log(`+ 从 ${this.src} 开始下载 ${this.name}`);
    setTimeout(() => {
      console.log(`- ${this.name} 下载完毕`); // 下载完毕后, 将对象重新放入对象池
      this.pool.recover(this);
    }, 100);
  }
}

/****************** 以下是测试函数 **********************/

let objPool = new ObjectPool();

let file1 = objPool.create(File);
file1.name = "文件1";
file1.src = "https://download1.com";
file1.download();

let file2 = objPool.create(File);
file2.name = "文件2";
file2.src = "https://download2.com";
file2.download();

setTimeout(() => {
  let file3 = objPool.create(File);
  file3.name = "文件3";
  file3.src = "https://download3.com";
  file3.download();
}, 200);

setTimeout(
  () =>
    console.log(
      `${"*".repeat(50)}\n下载了3个文件,但其实只创建了${objPool.size()}个对象`
    ),
  1000
);

输出结果

+ 从 https://download1.com 开始下载 文件1
+ 从 https://download2.com 开始下载 文件2
- 文件1 下载完毕
- 文件2 下载完毕
+ 从 https://download3.com 开始下载 文件3
- 文件3 下载完毕
**************************************************
下载了3个文件,但其实只创建了2个对象

参考文章
https://juejin.im/post/5c2e10a76fb9a049c0432697

Responses