Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 实现常用设计模式 #36

Open
fantasticit opened this issue Aug 9, 2019 · 0 comments
Open

JavaScript 实现常用设计模式 #36

fantasticit opened this issue Aug 9, 2019 · 0 comments

Comments

@fantasticit
Copy link
Owner

单例模式

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

实现单例模式

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)
    };
  }
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant