Week 50 Summary - JavaScript Design Patterns

Summary of the study of this article https://juejin.cn/book/6844733790204461070

Design Patterns

Design Patterns

constructor pattern

Multiple instance objects created have the same attributes.

function User(name , age, career) {
  this.name = name
  this.age = age
  this.career = career 
}
const user = new User(name, age, career)

Simple Factory Pattern

Multiple instance objects created have both the same attributes and different attributes.

function User(name , age, career, work) {
    this.name = name
    this.age = age
    this.career = career 
    this.work = work
}

function Factory(name, age, career) {
    let work
    switch(career) {
        case 'coder':
            work =  ['写代码','写系分', '修Bug'] 
            break
        case 'product manager':
            work = ['订会议室', '写PRD', '催更']
            break
        case 'boss':
            work = ['喝茶', '看报', '见客户']
        case 'xxx':
            // 其它工种的职责分配
            ...
            
    return new User(name, age, career, work)
}

abstract factory pattern

The abstract factory does not work, and the concrete factories in the abstract factory work. Follow the open-closed principle. Make the code open for extension and closed for modification.
Example: We create an assembly line for a mobile phone factory.

  1. Define the mobile abstract factory
class MobilePhoneFactory {
  // 提供操作系统的接口
  createOS(){
      throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
  }
  // 提供硬件的接口
  createHardWare(){
      throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
  }
}
  1. Define the specific factory of the mobile phone, and the specific factory inherits from the abstract factory
class FakeStarFactory extends MobilePhoneFactory {
  createOS() {
      // 提供安卓系统实例
      return new AndroidOS()
  }
  createHardWare() {
      // 提供高通硬件实例
      return new QualcommHardWare()
  }
}
  1. Abstract product that defines the mobile operating system
class OS {
    controlHardWare() {
        throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
    }
}
  1. Specific products that define the mobile operating system
class AndroidOS extends OS {
    controlHardWare() {
        console.log('我会用安卓的方式去操作硬件')
    }
}
  1. Abstract product that defines mobile phone hardware
class HardWare {
  // 手机硬件的共性方法,这里提取了“根据命令运转”这个共性
  operateByOrder() {
      throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
  }
}
  1. Specific products that define mobile phone hardware
class MiWare extends HardWare {
  operateByOrder() {
      console.log('我会用小米的方式去运转')
  }
}

If we want to create a new mobile phone, we can directly recreate a class to inherit different operating systems and hardware. This actually follows the principle of open and closed.

singleton pattern

Let only one instance of a class exist, no matter how many times we try to create it, it will only return you the only instance created for the first time.

  1. Using ES6 class to implement singleton mode
class Requset {
  constructor(){}
  static instance;
  static getInstance(){
    if(!this.instance){
      this.instance = new Requset();
    }
    return this.instance;
  }
}

const r1:Requset = Requset.getInstance();
const r2:Requset = Requset.getInstance();

console.log(r1 === r2);//true
  1. Using Closures to Implement the Singleton Pattern
SingleDog.getInstance = (function() {
    // 定义自由变量instance,模拟私有变量
    let instance = null
    return function() {
        // 判断自由变量是否为null
        if(!instance) {
            // 如果为null则new出唯一实例
            instance = new SingleDog()
        }
        return instance
    }
})()
  1. The singleton mode in Vuex
    Vuex implements an install method internally. When this method is called again, the Store will be injected into the Vue instance. Vuex uses the singleton mode to make the global Store unique.
let Vue // 这个Vue的作用和楼上的instance作用一样
export function install (_Vue) {
  // 判断传入的Vue实例对象是否已经被install过Vuex插件(是否有了唯一的state)
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  // 若没有,则为这个Vue实例对象install一个唯一的Vuex
  Vue = _Vue
  // 将Vuex的初始化逻辑写进Vue的钩子函数里
  applyMixin(Vue)
}

Singleton pattern exercise

  1. Implement Storage to make the object a singleton and encapsulate it based on localStorage. Implement the methods setItem(key,value) and getItem(key).
    Using ES6 class implementation
class Storage {
    constructor(){};
    static instance;
    static getInstance(){
        if(!this.instance){
            this.instance = new Storage();
        }
        return Storage.instance;
    };
    getItem(key){
        return localStorage.getItem(key);
    };
    setItem(key, value){
        return localStorage.setItem(key, value);
    }
}

Implemented using closures

function StorageBase () {}
StorageBase.prototype.getItem = function (key){
    return localStorage.getItem(key)
}
StorageBase.prototype.setItem = function (key, value) {
    return localStorage.setItem(key, value)
}
const Storage = (function(){
    let instance = null
    return function(){
        // 判断自由变量是否为null
        if(!instance) {
            // 如果为null则new出唯一实例
            instance = new StorageBase()
        }
        return instance
    }
})()
  1. Implement a global modal box
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>单例模式弹框</title>
</head>
<style>
    #modal {
        height: 200px;
        width: 200px;
        line-height: 200px;
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        border: 1px solid black;
        text-align: center;
    }
</style>
<body>
	<button id='open'>打开弹框</button>
	<button id='close'>关闭弹框</button>
</body>
<script>
    // 核心逻辑,这里采用了闭包思路来实现单例模式
    const Modal = (function() {
    	let modal = null
    	return function() {
            if(!modal) {
            	modal = document.createElement('div')
            	modal.innerHTML = '我是一个全局唯一的Modal'
            	modal.id = 'modal'
            	modal.style.display = 'none'
            	document.body.appendChild(modal)
            }
            return modal
    	}
    })()
    
    // 点击打开按钮展示模态框
    document.getElementById('open').addEventListener('click', function() {
        // 未点击则不创建modal实例,避免不必要的内存占用;此处不用 new Modal 的形式调用也可以,和 Storage 同理
    	const modal = new Modal()
    	modal.style.display = 'block'
    })
    
    // 点击关闭按钮隐藏模态框
    document.getElementById('close').addEventListener('click', function() {
    	const modal = new Modal()
    	if(modal) {
    	    modal.style.display = 'none'
    	}
    })
</script>
</html>

prototype pattern

There is only prototype mode in JavaScript, there is no class mode. Classes in JavaScript are syntactic sugar using prototype inheritance.
For example:

class Dog {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    eat(){
        console.log('吃骨头');
    }
}

The above code can be written in the following form

function Dog(name, age){
    this.name = name;
    this.age = age;
}
Dog.prototype.eat = function(){
    console.log('吃骨头');
}
  1. Deep copy of objects
    Note: There is no perfect solution for deep copy, each solution has its boundaries
  • The way the first method uses JSON.stringify and JSON.parse
const data = {
  name: 'tom',
  fn: ()=>{
    console.log('我是一个函数');
  }
}
const dataStr = JSON.stringify(data);
const copy = JSON.parse(dataStr);
console.log(copy);

The above method only copies the name parameter, but does not copy the fn parameter. It should be because JSON.stringify and JSON.parse are used for deep copying, and functions and regular expressions cannot be copied.

  • The second way to use recursion
function deepClone(obj){
  if(typeof obj!=='object'||obj===null){
    return obj;
  }
  let ans={};
  if(obj.constructor===Array){
    ans=[];
  }
  for(let key in obj){
    console.log(obj.hasOwnProperty(key));
    if(obj.hasOwnProperty(key)){
      ans[key] = deepClone(obj[key]);
    }
  }
  return ans;
}

decorator pattern

In order not to be disturbed by the existing business logic, the decorator mode implements a new logic
example: we click the button now, and the pop-up window will open. We want to turn the button gray after the pop-up window is opened, and then the text becomes "Go quickly Log in"

  1. ES5 implementation method

//旧的逻辑
function openModal() {
    const modal = new Modal()
    modal.style.display = 'block'
}
//新的逻辑
function changeButtonText() {
    // 按钮文案修改逻辑
    const btn = document.getElementById('open')
    btn.innerText = '快去登录'
}
function disableButton() {
    // 按钮置灰逻辑
    const btn =  document.getElementById('open')
    btn.setAttribute("disabled", true)
}
function changeButtonStatus() {
    // 新版本功能逻辑整合
    changeButtonText()
    disableButton()
}

document.getElementById('open').addEventListener('click', function() {
    openModal()
    changeButtonStatus()
})
  1. ES6 implementation method
class OpenButton {
    // 点击后展示弹框(旧逻辑)
    onClick() {
        const modal = new Modal()
    	modal.style.display = 'block'
    }
}
// 定义按钮对应的装饰器(新逻辑)
class Decorator {
    // 将旧的逻辑传入
    constructor(open_button) {
        this.open_button = open_button
    }
    
    onClick() {
        this.open_button.onClick()
        // “包装”了一层新逻辑
        this.changeButtonStatus()
    }
    
    changeButtonStatus() {
        this.changeButtonText()
        this.disableButton()
    }
    
    disableButton() {
        const btn =  document.getElementById('open')
        btn.setAttribute("disabled", true)
    }
    
    changeButtonText() {
        const btn = document.getElementById('open')
        btn.innerText = '快去登录'
    }
}

document.getElementById('open').addEventListener('click', function() {
    // openButton.onClick()
    decorator.onClick()
})

Decorators in ES7

The following code may not be supported by some browsers, and can be converted into js code in babeljs and then run.

function classDecorator(target){
  target.hasDecorator = true;
  return target;
}
@classDecorator
class Button {}


console.log('Button是否被装饰了', Button.hasDecorator);

adapter pattern

The adapter pattern is to transform the interface of a class into another interface that is expected.
For example: I have an earphone with a round hole, and I need to plug it into a mobile phone with a square headphone hole, so I need an adapter to allow the phone to plug in the earphone.
Encapsulate a fetch

export default class Http {
  //get方法
  static get(url){
    return new Promise((resolve, reject)=>{
      fetch(url)
      .then(response=>response.json())
      .then(result=>{
        resolve(result);
      })
      .catch(error=>{
        reject(error);
      })
    })
  }
  //post方法
  static post(url, data){
    return new Promise((resolve, reject)=>{
      fetch(url, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/x-www-wform-urlencoded'
        },
        body: this.changeData(data);
      })
      .then(response=>response.json())
      .then(result=>{
        resolve(result);
      })
      .catch(error=>{
        reject(error);
      })
    })
  }
  //body请求体的格式化方法
  static changeData(obj){
    var prop,str = '';
    var i = 0;
    for(prop in obj){
      if(!prop){
        return;
      }
      if(! === 0){
        str += prop + '=' + obj[prop];
      }else{
        str += '&' + prop + '=' + obj[prop];
      }
      return str;
    }
  }
}
  1. fetch adapts ajax
    ajax request is written as follows
Ajax('get', url, data, function(data){
    //成功的回调逻辑
},function(error){
    //失败的回调逻辑
});

If we want to use the above fetch to modify the ajax request, it would be very troublesome to modify one by one, then we can write an adapter

async function AjaxAdapter(type, url, data, success, failed){
  const type = type.toUpperCase();
  let result;
  try {
    if(type === 'GET'){
      result = await Http.get(url) || {};
    }else if(type === 'POST'){
      result = await Http.post(url, data) || {};
    }
    //假设请求对应的状态码是200
    result.code === 200 && success ? success(result) : failed(result.code);
  } catch (error) {
    if(failed){
      failed(error.code);
    }
  }
}

async function Ajax(type, url, data, success, failed){
  await AjaxAdapter(type, url, data, success, failed);
}

Proxy mode

The proxy mode is that under certain circumstances, one object cannot directly access another object, and a third party (agent) is needed to indirectly achieve the purpose of access

const girl = {
  a: 1
};
const proxy = new Proxy(girl, {
  get: (girl, key)=>{
    console.log(gril, 'key='+key);
  },
  set: (gril, key)=>{
    console.log(gril, key);
  }
});

const a = girl.a;
proxy.a = 2;

The Practice of the Proxy Pattern

  1. Event proxy
    The following code is implemented. When you click on which a tag, a pop-up window will display the words on the corresponding a tag. However, if there are many a tags, you need to bind an event to each a tag, and the performance overhead will be even greater.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>事件代理</title>
</head>
<body>
  <div id="father">
    <a href="">1</a>
    <a href="">2</a>
    <a href="">3</a>
    <a href="">4</a>
    <a href="">5</a>
    <a href="">6</a>
    <a href="">7</a>
    <a href="">8</a>
    <a href="">9</a>
    <a href="">10</a>
  </div>
  <script>
    const aNodes = document.getElementById('father').getElementsByTagName('a');
    const len = aNodes.length;
    for(let i=0;i<len;i++){
      aNodes[i].addEventListener('click', function(e){
        e.preventDefault();
        alert(`我是${aNodes[i].innerText}`);
      });
    }
  </script>
</body>
</html>

We use event proxy to modify js code

const father = document.getElementById('father');
    father.addEventListener('click', function(e){
      if(e.target.tagName === 'A'){
        e.preventDefault();
        alert(`我是${e.target.innerText}`);
      }
    });
  1. Virtual agent - implement lazy loading of pictures
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>虚拟代理</title>
</head>
<body>
  <img class="img" src="" alt="11" width="100" height="100">
  <script>
    class PreLoadImage {
      constructor(imgNode){
        //获取真实的DOM节点
        this.imgNode = imgNode;
      }
      //操作img节点的src属性
      setSrc(imgUrl){
        this.imgNode.src = imgUrl;
      }
    }

    class ProxyImage {
      static LOADING_URL = 'https://p6-passport.byteacctimg.com/img/user-avatar/620a16ed8f1917ba1825537525a22dce~300x300.image'
      constructor(targetImage){
        //目标Image,即PreLoadImage实例
        this.targetImage = targetImage;
      }
      serSrc(targetUrl){//targetUrl就是真实的图片路径
        //真实img节点初始化时展示的是一个占位图
        this.targetImage.setSrc(ProxyImage.LOADING_URL);
        //创建一个帮我们加载图片的虚拟Image实例
        const virtualImage = new Image();
        //监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
        virtualImage.onload = () => {
          setTimeout(()=>{
            this.targetImage.setSrc(targetUrl);
          },1000)
        }
        //设置src属性,虚拟Image实例开始加载图片
        virtualImage.src = targetUrl;
      }
    }
    const img = document.querySelector('.img');
    const set = new ProxyImage(new PreLoadImage(img));
    set.serSrc('');//设置真实的图片地址
  </script>
</body>
</html>
  1. caching proxy
const addAll = function(){
  console.log('进行一次新计算');
  let result = 0;
  const len = arguments.length;
  for(let i=0;i<len;i++){
    result += arguments[i];
  }
  return result;
}

// 为求和方法创建代理
const proxyAddAll = (function(){
  // 求和结果的缓存池
  const resultCache = {}
  return function() {
      // 将入参转化为一个唯一的入参字符串
      const args = Array.prototype.join.call(arguments, ',');
      
      // 检查本次入参是否有对应的计算结果
      if(args in resultCache) {
          // 如果有,则返回缓存池里现成的结果
          return resultCache[args]
      }
      return resultCache[args] = addAll(...arguments)
  }
})()

console.log(proxyAddAll(1,2,3));
console.log(proxyAddAll(1,2,3));
  1. Protected mode
    The protected mode is the proxy mode we demonstrated before, intercepting in getters and setters.

strategy pattern

Example: If the input is a, execute the logic of a, if it is b, execute the logic of b, we can write as follows.

function ans(tag){
  if(tag==='a'){
    //执行a的逻辑
  }else if(tag==='b'){
    //执行b的逻辑
  }
}

If there is more logic, the above code logic is too fat, and it violates the "open and closed principle". Can be modified into the following format.

  1. Modify Code Using Separation of Duties
function a(){
  //执行a的逻辑
}
function b(){
  //执行b的逻辑
}
function ans(tag){
  if(tag==='a'){
    a();
  }
  if(tag==='b'){
    b();
  }
}
  1. The above code has been modified by opening and closing. If you want to write new logic in ansObj, you can directly add it through the ansObj. attribute
const ansObj = {
  a(){
    //执行a的逻辑
  },
  b(){
    //执行b的逻辑
  }
}

function ans(tag){
  return ansObj[tag]();
}

state mode

The state pattern and the strategy pattern are very familiar. We also take the example of the strategy pattern as an example.

  1. Modify Code Using Separation of Duties
class ans {
  constructor(){
    this.state = 'init';
  }
  changeState(state){
    this.state = state;
    if(state==='a'){
      this.a();
    }else if(state==='b'){
      this.b();
    }
  }
  a(){
    //执行a的逻辑
  }
  b(){
    //执行b的逻辑
  }
}
  1. Modify code using open closed
const ansObj = {
  a(){
    //执行a的逻辑
  },
  b(){
    //执行b的逻辑
  }
}

class Ans {
  constructor(){
    this.state = 'init';
  }
  changeState(state){
    this.state = state;
    if(!ansObj[state])return;
    ansObj[state]();
  }
}

const test = new Ans();
test.changeState('a');
  1. Modify the open and closed code, let Ans establish a connection with ansObj
class Ans {
  constructor(){
    this.state = 'init';
  }
  const ansObj = {
    that:this,//在对象中使用this.that指向Ans中的this
    a(){
      //执行a的逻辑
    },
    b(){
      //执行b的逻辑
    }
}
  changeState(state){
    this.state = state;
    if(!this.ansObj[state])return;
    this.ansObj[state]();
  }
}

const test = new Ans();
test.changeState('a');

Observer mode (publish-subscribe mode)

Observer mode is a one-to-many dependency, allowing multiple observers to monitor a target object at the same time. If the state of the target object changes, all observers will be notified back, so that the observers can be automatically updated.

//定义发布者类
class Publisher {
  constructor(){
    this.observers = [];
    console.log('created');
  }
  //增加订阅者
  add(observer){
    this.observers.push(observer);
    console.log('add');
  }
  //移除订阅者
  remove(observer){
    this.observers.forEach((item, i)=>{
      if(item === observer){
        this.observers.splice(i, 1);
      }
    })
    console.log('remove');
  }
  //通知所有订阅者
  notify(){
    this.observers.forEach((observer)=>{
      observer.update(this);
    })
    console.log('invoked');
  }
}

//定义订阅者类
class Observer {
  constructor(){
    console.log('Observer created');
  }
  update(){
    console.log('Observer update');
  }
}

//定义一个具体的发布类
class PrdPublicher extends Publisher {
  constructor(props) {
    super(props);
    this.prdState = null;
    console.log('PrdPublicher created');
  }
  //获取当前的prdState
  getState(){
    console.log('PrdPublicher get');
    return this.prdState;
  }
  //设置prdState的值
  setState(state){
    this.prdState = state;
    this.notify();
    console.log('PrdPublicher set');
  }
}

class DeveloperObserver extends Observer {
  constructor() {
      super()
      // 需求文档一开始还不存在,prd初始为空对象
      this.prdState = {}
      console.log('DeveloperObserver created')
  }
  
  // 重写一个具体的update方法
  update(publisher) {
      console.log('DeveloperObserver.update invoked')
      // 更新需求文档
      this.prdState = publisher.getState()
      // 调用工作函数
      this.work()
  }
  
  // work方法,一个专门搬砖的方法
  work() {
      // 获取需求文档
      const prd = this.prdState
      // 开始基于需求文档提供的信息搬砖。。。
      console.log('996 begins...')
  }
}

//目标值
const aim = new PrdPublicher();
//观察者
const view1 = new DeveloperObserver();
const view2 = new DeveloperObserver();

//一对多,让一个目标值对应多个观察者
aim.add(view1);
aim.add(view2);

//当目标值中的值改变,观察者对应的值也都自动更新
aim.setState('1');

iterator pattern

The Iterator pattern provides a way to sequentially access the elements of an object without exposing the object's internal representation.

  1. Iterate through an array with the forEach method
const arr = [1,2,3];
arr.forEach((item,index)=>{
  console.log(`索引为${index}的元素是${item}`);
})

forEach is not a panacea. If forEach is used to traverse the pseudo-array, an error will be reported.
2. ES6's implementation of iterators
In ES6, there are not only Array (array) and Object (object), but also new Map and Set. So ES6 also introduced a unified interface mechanism - iterator (Iterator).

const arr = [1,2,3];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
  1. for...of... is to repeatedly call the next method of the iterator object.
const arr = [1,2,3];
for(item of arr) {
    console.log(`当前元素是${item}`)
}
  1. ES6 implements an iterator generation function
function *iteratorGenerator(){
  yield '1'
  yield '2'
  yield '3'
}
const iterator2 = iteratorGenerator();
console.log(iterator2.next());
console.log(iterator2.next());
console.log(iterator2.next());
  1. ES5 implements an iterator generating function
const arr = [1,2,3];
function iteratorGenerator2(list){
  //记录当前的索引
  var idx = 0;
  //集合的长度
  var len = list.length;
  return {
    next: function(){
      //如果索引值没有超过集合长度,done为false
      var done = idx >= len;
      //如果done为false则可以继续取值
      var value = !done ? list[idx++] : undefined;
      //将当前值和是否完毕(done)返回
      return {
        done: done,
        value: value
      }
    }
  }
}

var iterator3 = iteratorGenerator2(arr);
console.log(iterator3.next());
console.log(iterator3.next());
console.log(iterator3.next());

Guess you like

Origin blog.csdn.net/qq_51965698/article/details/126471544