JS 设计模式-工作常用的

单例模式

// 单例构造函数
function CreateSingleton (name) {
    this.name = name;
    this.getName();
};

// 获取实例的名字
CreateSingleton.prototype.getName = function() {
    console.log(this.name)
};
// 单例对象
var Singleton = (function(){
    var instance;
    return function (name) {
        if(!instance) {
            instance = new CreateSingleton(name);
        }
        return instance;
    }
})();

// 创建实例对象1
var a = new Singleton('a');
// 创建实例对象2
var b = new Singleton('b');

console.log(a===b);

工厂模式

//User类
class User {
  //构造器
  constructor(opt) {
    this.name = opt.name;
    this.viewPage = opt.viewPage;
  }

  //静态方法
  static getInstance(role) {
    switch (role) {
      case 'superAdmin':
        return new User({ name: '超级管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据', '权限管理'] });
        break;
      case 'admin':
        return new User({ name: '管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据'] });
        break;
      case 'user':
        return new User({ name: '普通用户', viewPage: ['首页', '通讯录', '发现页'] });
        break;
      default:
        throw new Error('参数错误, 可选参数:superAdmin、admin、user')
    }
  }
}

//调用
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');

发布/订阅者模式

手写 EventEmitter

class EventEmitter {
  constructor() {
    this.events = {};
  }
  on(eventName, func) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(func);
  }
  emit(eventName, ...params) {
    const events = this.events[eventName];
    if (events) {
      events.forEach(event => {
        event.apply(this, params)
      })
    }
  }
  once(eventName, func) {
    const cb = () => {
      func();
      this.off(eventName, func);
    }
    this.on(eventName, cb);
  }
  off(eventName, func) {
    if (this.events[eventName]) {
      if (!func) {
        this.events[eventName] = []
      } else {
        this.events[eventName].splice(this.events[eventName].indexOf(func), 1)
      }
    }
  }
}

装饰器模式

写 react 的时候经常会遇到,本质上它就是个包装函数,主要用于多个不同类之间共享或者扩展一些方法或者行为。

function isAnimal(target) {
    target.isAnimal = true;
  	return target;
}

@isAnimal
class Cat {
    ...
}

console.log(Cat.isAnimal);    // true

值得一提的是,装饰器居然还在 tc39 的 stage-2 阶段。

所以要用的话需要加 babel-plugin-transform-decorators-legacy 插件

babel --plugins transform-decorators-legacy es6.js > es5.js

策略模式

常用于避免写很多 if 条件语句的情况,打个比方算绩效奖金的函数

function calcSalary(salary, score) {
  if (score === '4') {
    return salary * 6
  } else if (score === '3.75') {
    return salary * 4
  } else if (score === '3.5+') {
    return salary * 3
  } else if (score === '3.5') {
    return salary * 2
  } else if (score === '3.5-') {
    return salary * 1
  } else if (score === '3.25') {
    return 0;
  }
}

这是阿里系的公司可能的计算年终奖的规则,那么某天想增加一个等级叫 3.75+,那又得往上面加 if 判断。就不希望这么做。所以可以使用策略模式优化

var defaultMap = {
  '4': function (salary) { return salary * 6 },
  '3.75': function (salary) { return salary * 4 },
  '3.5+': function (salary) { return salary * 3 },
  '3.5': function (salary) { return salary * 2 },
  '3.5-': function (salary) { return salary * 1 },
  '3.25': function (salary) { return 0 },
};

function calcSalary(salary, score, extendMap) {
  let map = Object.assign({}, defaultMap, extendMap);
  return map[score](salary);
}

calcSalary(10000, '3.75+', {
  '3.75+': function (salary) { return salary * 5 }
})

用策略模式也可以简单封装表单,举个例子,你可以在代码中去循环这个结构输出 DOM,当然这样做又会遇到其他问题另说。

var formStruct = [
  {
    id: 1,
    type: 'input',
    rule: /\d+/,
    placeholder: 'input something'
  },
  {
    id: 2,
    type: 'select',
    require: true,
    options: []
  },
  ........
]

适配器模式

摘录自前端小时-JavasScript设计模式浅析

适配器模式就相当于一个转换接口,大家想想我们手机充电器通常是二岔口的,但是电源只有三岔口的。这时候就需要一个适配器把三岔口的转换成二岔口的。

它的作用其实就是解决两个软件实体间的接口不兼容问题,使用之后就可以一起工作了。

var googleMap = {
  show: function () {
    console.log('googleMap show!');
  },
};
var baiduMap = {
  show: function () {
    console.log('baiduMap show!');
  },
};

var renderMap = function (map) {
  if (map.show instanceof Function) {
    map.show();
  }
};
renderMap(googleMap);
renderMap(baiduMap);

上面这段程序能够运行是因为百度地图和谷歌地图用的同一种show方法,但是我们在不知道对方使用的函数接口的时候,我们就不能这样用了(可能百度是使用了display方法来显示)。下面的baiduMapAdapter就是我们使用的适配器。

var googleMap = {
  show: function () {
    console.log('googleMap show!');
  },
};
var baiduMap = {
  display: function () {
    console.log('baiduMap show!');
  },
};

var renderMap = function (map) {
  if (map.show instanceof Function) {
    map.show();
  }
};

var baiduMapAdapter = {
  show: function () {
    return baiduMap.display();
  },
};
renderMap(googleMap);
renderMap(baiduMapAdapter);

代理模式

很久以前前端去绑定事件时,还很流行一个术语叫“事件委托”,就借用这个思想。

点击 li 标签显示它的内容,但是直接对 li 遍历添加事件对 JS 性能不太好,故而可以把事件监听设置在父元素 ul 上。

<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
<script>
  let ul = document.querySelector('#ul')
  ul.addEventListener('click', (event) => {
    console.log(event.target);
    if (event.target.tagName === 'li') {
      
    }
  })
</script>

迭代器模式

迭代器就是提供一种可以顺序访问的模式,forEach 就是一种常用的迭代器

var forEach = function (arr, callback) {
  for (var i = 0, l = arr.length; i < l; i++) {
    callback.call(arr[i], i, arr[i])
  }
}

// callback 顺序访问数组里的内容
forEach([1, 2, 3], function (item, n) {
  console.log([item, n]);
})

其他

等用到了再补充吧

posted @ 2020-03-15 23:12  Ever-Lose  阅读(298)  评论(0编辑  收藏  举报