近日,重新学习梳理了JS设计模式,特地在这里做下笔记记录下。

工厂模式

特点

简单一句话概括 就是 return 一个 new 实例

应用场景:

  • jQuery 创建
  • React.createElement();
  • vue.component();

单例模式

特点

  • 单例模式 用到了 Java 中的 private 特性

代码

class SingleObject {
  login() {
    console.log("login...");
  }
}

SingleObject.getInstance = (function() {
  let instance;
  return function() {
    if (!instance) {
      instance = new SingleObject();
    }
    return instance;
  };
})();

let obj1 = SingleObject.getInstance();
obj1.login();

let obj2 = SingleObject.getInstance();
obj2.login();

console.log("obj1===obj2", obj1 === obj2);

console.log("分割线");

let obj3 = new SingleObject();
obj3.login();
console.log("obj1===obj3", obj1 === obj3);

应用场景

1、 jQuery $ 只有一个

  if (window.jQuery !== null) {
    return window.jQuery;
  } else {
    //   进行初始化操作
  }

2、 登录框

    class LoginForm {
      constructor() {
        this.state = "hide";
      }

      show() {
        if (this.state === "show") {
          alert("已经显示");
          return;
        }
        this.state = "show";
        console.log("登录框已经显示");
      }

      hide() {
        if (this.state === "hide") {
          alert("已经隐藏");
          return;
        }
        this.state = "hide";
        console.log("登录框已经隐藏");
      }
    }
    LoginForm.getInstance = (function() {
      let instance;
      return function() {
        if (!instance) {
          instance = new LoginForm();
        }
        return instance;
      };
    })();

    let login1 = LoginForm.getInstance();
    login1.show();

    let login2 = LoginForm.getInstance();
    login2.show();

    console.log("login1===login2", login1 === login2);

3、 vuex 和 redux 中的 store state 是单例模式

适配器模式

特点

  • 旧接口格式 和 使用者不兼容
  • 中间加一个适配器转换接口

代码

class Adaptee {
  specificRequest() {
    return "德国标准插头";
  }
}

class Target {
  constructor() {
    this.adaptee = new Adaptee();
  }

  request() {
    let info = this.adaptee.specificRequest();
    return `${info} - 转换器 - 中国标准插头`;
  }
}

let target = new Target();
let res = target.request();
console.log(res);

应用场景

1、 封装旧接口

  // 自己封装的ajax 如下
  ajax({
    url: "/getData",
    type: "POST",
    dataType: "json",
    data: {
      id: "123"
    }
  }).done(function() {});
  // 但因为历史原因,代码中全是;
  // $.ajax({...})

  // 做一层适配器
  var $ = {
    ajax: function(option) {
      return ajax(option);
    }
  };

2、Vue computed 场景

<div id="example">
  <p>Origin Message:{{message}}</p>
  <p>Computed Message:{{reversedMessage}}</p>
</div>
  var vm = new Vue({
    el: "#example",
    data: {
      message: "Hello"
    },
    computed: {
      // 计算属性的 getter
      reversedMessage: function() {
        // `this`指向vm实例
        return this.message
          .split("")
          .reverse()
          .join("");
      }
    }
  });

装饰器模式

特点

  • 为对象添加新功能
  • 不改变其原有的结构和功能
  • 装饰器是一个函数

代码

// 基础代码
@decorator
class A {}
// 等同于
class A {}
a = decorator(A) || A;
// 装饰器传参 加参数
function testDesc(isDec) {
  return function(target) {
    target.isDec = isDec;
  };
}

@testDesc(true)
class Demo {
  //...
}
alert(Demo.isDec);

应用场景

1、 ES7 装饰器

  • 配置环境-验证装饰器环境是否成功
@testDesc
class Demo {}

function testDesc(target) {
    target.isDec = true;
}
alert(Demo.isDec);
  • 可以装饰 class类
//1、mixin 实例
function mixins(...list) {
  return function(target) {
    Object.assign(target.prototype, ...list);
  };
}

const Foo = {
  foo() {
    alert("foo");
  }
};

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo();
  • 可以装饰 方法function
// 例 1
function readonly(target, name, descriptor) {
    //descriptor 是描述对象 (Object.defineProperty中会用到),原来的值如下
    //{
    //  value:specifiedFunction,
    //  enumerable:false,
    //  configurable:true,
    // writable:true
    //}
    descriptor.writable = false;
    return descriptor;
}
class Person {
    constructor() {
      this.first = "A";
      this.last = "B";
    }

    // 装饰方法
    @readonly
    name() {
      return `${this.first} ${this.last}`;
    }
}
var p = new Person();
console.log(p.name());
//p.name=function(){} // 这里会报错,因为name 是只读属性

2、例 2

function log(target, name, descriptor) {
    var oldValue = descriptor.value;
    descriptor.value = function() {
      console.log(`Calling ${name} with`, arguments);
      return oldValue.apply(this, arguments);
    };
    return descriptor;
  }

class Math {
    //装饰方法
    @log
    add(a, b) {
      return a + b;
    }
  }

const math = new Math();
const result = math.add(2, 4);
console.log("result", result);
  • core-decorators
    第三方类库

代理模式

特点

  • 使用者无权访问目标对象
  • 中间加代理,通过代理做授权和控制

代码

class ReadImg {
  constructor(fileName) {
    this.fileName = fileName;
    this.loadFromDisk();
  }

  display() {
    console.log(`display...` + this.fileName);
  }

  loadFromDisk() {
    console.log(`load...` + this.fileName);
  }
}

class ProxyImg {
  constructor() {
    this.realImg = new ReadImg();
  }
  display() {
    this.realImg.display();
  }
}

let proxyImg = new ProxyImg("1.png");
proxyImg.display();

应用场景

1、javascript 点击事件

<div id="div1">
  <a href="#">a1</a>
  <a href="#">a2</a>
  <a href="#">a3</a>
  <a href="#">a4</a>
</div>

<script>
  var div1 = document.getElementById("div1");
  div1.addEventListener("click", function(e) {
    var target = e.target;
    if (target.nodeName === "A") {
      alert(target.innerHTML);
    }
  });
</script>

2、明星经纪人

// 明星
let star = {
  name: "刘德华",
  age: 50,
  phone: "15987452635"
};

// 经纪人
let agent = new Proxy(star, {
  get: function(target, key) {
    if (key === "phone") {
      //返回经纪人自己手机号
      return "15911111111";
    }

    if (key === "price") {
      // 明星不报价,经纪人报价
      return 120000;
    }

    return target[key];
  },
  set: function(target, key, value) {
    if (key === "customPrice") {
      if (val < 100000) {
        // 最低10万
        throw new Error("价格太低");
      } else {
        target[key] = value;
        return true;
      }
    }
  }
});
console.log(agent.name);
console.log(agent.age);
console.log(agent.phone);
console.log(agent.price);

agent.customPrice = 90000;
console.log("agent.customPrice", agent.customPrice);

代理模式 和 适配器模式 对比

  • 适配器模式:提供一个不同的接口(如不同版本的接口)
  • 代理模式:提供一模一样的接口

代理模式 和 装饰器模式 对比

  • 装饰器模式:扩展原有功能,原有功能不变且可直接使用
  • 代理模式:显示原有功能,但是经过限制或者阉割之后的

外观模式

特点

  • 为子系统中的一组接口提供了一个高层接口
  • 使用者使用这个高层接口

代码

function bindEvent(ele, type, selector, fn) {
  if (fn === null) {
    fn = selector;
    selector = null;
  }
  //...
}
// 调用
bindEvent(elem, "click", "#div1", fn);
bindEvent(elem, "click", fn);

观察者模式

特点

  • 发布订阅
  • 一对多
  • 被动接收,非主动

代码

class Subject {
  constructor() {
    this.state = 0;
    this.observers = [];
  }

  getState() {
    return this.state;
  }

  setState(state) {
    this.state = state;
    this.notifyAllObservers();
  }

  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update();
    });
  }

  attach(observer) {
    this.observer.push(observer);
  }
}
// 观察者模式
class Observer {
  constructor(name, subject) {
    this.name = name;
    this.subject = subject;
    this.subject.attach(this);
  }
  update() {
    console.log(`${this.name} update state:${this.subject.getState()}`);
  }
}

// 测试
let s = new Subject();
let o1 = new Observer("o1", s);
let o2 = new Observer("o2", s);
let o3 = new Observer("o3", s);

s.setState(1);

应用场景

1、网页绑定事件

<button id="btn1">btn</button>
<script>
  $("#btn1").click(function() {
    console.log("1");
  });
  $("#btn1").click(function() {
    console.log("2");
  });
  $("#btn1").click(function() {
    console.log("3");
  });
</script>

2、Promise

function loadImg() {
  var promise = new Promise(function(resolve, reject) {
    var img = document.createElement("img");
    img.onload = function() {
      resolve(img);
    };
    img.onerror = function() {
      reject("图片加载失败");
    };

    img.src = src;
  });

  return promise;
}

var src = "https://www.baidu.com/static/new.png";
var result = loadImg(src);
result
  .then(function(img) {
    console.log("width", img.width);
    return img;
  })
  .then(function(img) {
    console.log("height", img.height);
  });

3、jQuery callbacks

var callbacks = $.Callbacks();
callbacks.add(function(info) {
  console.log("fn1", info);
});

callbacks.add(function(info) {
  console.log("fn2", info);
});

callbacks.add(function(info) {
  console.log("fn3", info);
});

callbacks.fire("gogogo");
callbacks.fire("fire");

4、nodejs 自定义事件
(1) EventEmitter

const EventEmitter = require("events").EventEmitter;

const emitter1 = new EventEmitter();
emitter1.on("some", function() {
  // 监听some
  console.log("some event is occured 1");
});

emitter1.on("some", function() {
  // 监听some
  console.log("some event is occured 2");
});

emitter1.emit("some");

(2) 基于(1)的应用

const EventEmitter = require("events").EventEmitter;

// 继承
class Dog extends EventEmitter {
  constructor(name) {
    super();
    this.name = name;
  }
}

let simon = new Dog("simon");
simon.on("bark", function() {
  console.log(this.name, " barked-1");
});
simon.on("bark", function() {
  console.log(this.name, " barked-2");
});
setInterval(function() {
  simon.emit("bark");
}, 1000);

(3) 基于(1)的应用

//Stream 用到了自定义事件
var fs = require("fs");
var readStream = fs.createReadStream("./data/file1.txt");

var length = 0;
readStream.on("data", function(chunk) {
  length += chunk.toString().length;
});

readSteam.on("end", function() {
  console.log(length);
});

(4) 基于(1)的应用

var readline = require("readline");
var fs = require("fs");

var rl = readline.createInterface({
  input: fs.createReadStream("./data/file1.txt")
});

var lineNum = 0;
rl.on("line", function(line) {
  lineNum++;
});
rl.on("close", function() {
  console.log("lineNum", lineNum);
});

(5)nodejs 中:处理 http 请求;多进程通讯

function serverCallback(req, res) {
  var method = req.method.toLowerCase(); // 获取请求的方法
  if (method === "get") {
    // 省略三行,上文示例代码中处理GET请求的代码
  }
  if (method === "post") {
    // 接收post请求的内容
    var data = "";
    req.on("data", function(chunk) {
      // 一点点 接收内容
      data += chunk.toString();
    });

    req.on("end", function() {
      //
      res.writeHead(200, { "Content-Type": "text/html" });
      res.write(data);
      res.end();
    });
  }
}
//parent.js
var cp = require("child-process");
var n = cp.for("./sub.js");

n.on("message", function(m) {
  console.log("Parent got message: " + m);
});

n.send({ hello: "world" });

//sub.js
process.on("message", function(m) {
  console.log("child got message: " + m);
});

process.send({ foo: "bar" });

(6)vue 和 react 组件生命周期触发
(7)vue watch


前端熟练工
1.8k 声望66 粉丝

要做前端架构师的正在前行的人