Skip to content

JavaScript 实现常用设计模式 #36

Description

@fantasticit

单例模式

单例模式需确保只有一个实例且可以全局访问。

实现单例模式

let __instance = (function() {
  let instance;
  return newInstance => {
    if (newInstance) instance = newInstance;
    return instance;
  };
})();

class Singleton {
  constructor(name) {
    this.name = name;

    if (!__instance()) {
      __instance(this);
    }

    return __instance();
  }
}

测试:

const s1 = new Singleton("s1");
const s2 = new Singleton("s2");

assert.strictEqual(s1, s2);
assert.strictEqual(s1.name, s2.name);

实践

单例模式需要满足只有一个实例且可全局访问即可,可以使用 JavaScript 的闭包来实现。接下来以弹窗为例:

function createPopup(content) {
  const div = document.createElement("div");
  div.innerHTML = content;
  return div;
}

将单例模式和创建弹窗代码解耦:

function createSingleton (fn) {
  let result
  return function () {
    return result || result = fn.apply(this, arguments)
  }
}

const createSingletonPopup = createSingleton(createPopup)

发布订阅模式

事件发布订阅模式可以帮助完成更松的解耦。

EventEmiiter 的简单实现

class EventEmitter {
  constructor() {
    this.listener = new Map();
  }

  on(type, fn) {
    const subs = this.listener.get(type);

    if (!subs) {
      this.listener.set(type, [fn]);
    } else {
      this.listener.set(type, [].concat(subs, fn));
    }
  }

  emit(...args) {
    const type = args[0];

    for (let listener of this.listener.get(type)) {
      listener(...args.slice(1));
    }
  }
}

测试代码:

const em = new EventEmitter();
em.on("click", counter => {
  assert.equal(counter, 1);
});
em.emit("click", 1);

代理模式

代理对象和本体对象具有一致的接口,对使用者友好。代理模式的种类有很多种,在 JavaScript 中常见的是:虚拟代理和缓存代理。

虚拟代理实现图片预加载

const myImg = (() => {
  const img = document.appendChild("img");
  document.body.appendChild(img);

  return {
    setSrc: src => (img.src = src)
  };
})();

const proxyImage = (() => {
  const img = new Image();
  img.onload = function() {
    myImg.setSrc(this.src);
  };

  return {
    setSrc: src => {
      myImg.setSrc("loading.png");
      img.src = src;
    }
  };
})();

proxyImage.setSrc("loaded.jpg");

缓存代理实现乘积计算

const multiply = function(...args) {
  return args.reduce((accu, curr) => (accu *= curr), 1);
};

const proxyMultiply = (() => {
  const cache = {};
  return (...args) => {
    let tag = args.join(",");

    if (cache[tag]) {
      return cache[tag];
    }

    return (cache[tag] = multiply.apply(null, args));
  };
})();

策略模式

顾名思义,根据不同的参数(或配置)有不同的策略(函数)。

表单验证

以表单验证为例,不同的字段应有不同的验证方法,即不同的策略。

class Checker {
  constructor(check, message) {
    this.check = check;
    this.message = message;
  }
}

class Validator {
  constructor(config) {
    this.config = config;
    this.messages = [];
  }

  validate(data) {
    for (let [k, v] of Object.entries(data)) {
      const type = this.config.get(k);
      const checker = Validator[type];
      const result = checker.check(v);

      if (!result) {
        this.messages.push(checker.message(v));
      }
    }

    return this;
  }

  isError() {
    return this.messages.length > 0;
  }
}

测试代码:

const data = {
  name: "startegy",
  age: 0
};

const config = new Map([["name", "isNotEmpty"], ["age", "isGreaterThan"]]);

Validator.isNotEmpty = new Strategy.Checker(
  val => val.length > 0,
  val => `The ${val} is empty`
);

Validator.isGreaterThan = new Strategy.Checker(
  number => number > 20,
  number => `The number ${number} is less than 20`
);

assert.equal(new Validator(config).validate(data).isError(), true);

迭代器模式

能访问到聚合对象的顺序和元素。

ES6 的 iterator 接口

const data = {
  data: [1, 2, 3, 4, 5, 6],
  [Symbol.iterator]() {
    const len = this.data.length;
    let index = 0;

    return {
      next: () => {
        return index < len
          ? { value: this.data[index++], done: false }
          : { value: undefined, done: true };
      },
      rewind: () => (index = 0)
    };
  }
};

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions