Skip to content
New issue

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

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

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

No branches or pull requests

1 participant