# js设计模式
设计模式使代码编制真正工程化,设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
# js面向对象
类和实例
- 继承 子类继承父类 extends super
- 封装 数据的权限和保密 public protected(对子类开放) private =>es暂不支持,可以用ts演示(2025年已经支持私有#)
- 减少耦合,不该外露的不外露
- 利于数据接口权限管理
- 多态 统一接口不同实现
- 保持子类开放性灵活性
- 面向接口编程
# 为什么用面向对象
简单抽象 数据结构化
实际应用jquery
# UML图
统一建模语言 地址 (opens new window)
uml图也分很多类型,这里是其中的一项uml类图
加号代表public共有,减号代表private私有,#号代表protected受保护
泛化(表继承)和关联(表引用)

白色箭头表示了两个类间的关联关系;黑色箭头表示了依赖关系。
# 五大设计原则SOLID
- S-单一职责 一个程序一件事 拆分开
- O-开放封闭
扩展开放,修改封闭 - L-里氏置换 子类能覆盖父类,父类出现的地方子类可以出现
- I-接口独立 单一独立
- D-依赖导致 依赖抽象不依赖具体,只关注接口不关注具体类的实现
应用如S和O promise
# 面试题1
// 打车时,可以打专车或者快车。任何车都有车牌号和名称
// 不同车价格不同,快车每公里1元,专车每公里2元
// 行程开始时,显示车辆信息
// 行程结束时,显示打车金额(假定行程就5公里)
class Car {
constructor(number, name) {
this.number = number
this.name = name
}
}
class Kuaiche extends Car {
constructor(number, name) {
super(number, name)
this.price = 1
}
}
class Zhuanche extends Car {
constructor(number, name) {
super(number, name)
this.price = 2
}
}
class Trip {
constructor(car) {
this.car = car
}
start() {
console.log(`行程开始,名称: ${this.car.name}, 车牌号: ${this.car.price}`)
}
end() {
console.log('行程结束,价格: ' + (this.car.price * 5))
}
}
let car = new Kuaiche(100, '桑塔纳')
let trip = new Trip(car)
trip.start()
trip.end()
//某停车场,分3层,每层100车位
// 每个车位都能监控到车辆的驶入和离开
// 车辆进入前,显示每层的空余车位数
// 车辆进入时,摄像头可识别车牌号和时间
// 车辆出来是,出口显示车牌号和停车时长
// 车
class Car {
constructor(num) {
this.num = num
}
}
// 入口摄像头
class Camera {
shot(car) {
return {
num: car.num,
inTime: Date.now()
}
}
}
// 出口显示器
class Screen {
show(car, inTime) {
console.log('车牌号', car.num)
console.log('停车时间', Date.now() - inTime)
}
}
// 停车场
class Park {
constructor(floors) {
this.floors = floors || []
this.camera = new Camera()
this.screen = new Screen()
this.carList = {}
}
in(car) {
// 获取摄像头的信息:号码 时间
const info = this.camera.shot(car)
// 停到某个车位
const i = parseInt(Math.random() * 100 % 100)
const place = this.floors[0].places[i]
place.in()
info.place = place
// 记录信息
this.carList[car.num] = info
}
out(car) {
// 获取信息
const info = this.carList[car.num]
const place = info.place
place.out()
// 显示时间
this.screen.show(car, info.inTime)
// 删除信息存储
delete this.carList[car.num]
}
emptyNum() {
return this.floors.map(floor => {
return `${floor.index} 层还有 ${floor.emptyPlaceNum()} 个车位`
}).join('\n')
}
}
// 层
class Floor {
constructor(index, places) {
this.index = index
this.places = places || []
}
emptyPlaceNum() {
let num = 0
this.places.forEach(p => {
if (p.empty) {
num = num + 1
}
})
return num
}
}
// 车位
class Place {
constructor() {
this.empty = true
}
in() {
this.empty = false
}
out() {
this.empty = true
}
}
// 测试代码------------------------------
// 初始化停车场
const floors = []
for (let i = 0; i < 3; i++) {
const places = []
for (let j = 0; j < 100; j++) {
places[j] = new Place()
}
floors[i] = new Floor(i + 1, places)
}
const park = new Park(floors)
// 初始化车辆
const car1 = new Car('A1')
const car2 = new Car('A2')
const car3 = new Car('A3')
console.log('第一辆车进入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二辆车进入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一辆车离开')
park.out(car1)
console.log('第二辆车离开')
park.out(car2)
console.log('第三辆车进入')
console.log(park.emptyNum())
park.in(car3)
console.log('第三辆车离开')
park.out(car3)
举个栗子:
//checkType('165226226326','mobile')
//result:false
let checkType=function(str, type) {
switch (type) {
case 'email':
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)
case 'mobile':
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
case 'tel':
return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
default:
return true;
}
}
有以下两个问题:
如果想添加其他规则就得在函数里面增加 case 。添加一个规则就修改一次!这样违反了开放-封闭原则(对扩展开放,对修改关闭)。而且这样也会导致整个 API 变得臃肿,难维护。 比如A页面需要添加一个金额的校验,B页面需要一个日期的校验,但是金额的校验只在A页面需要,日期的校验只在B页面需要。如果一直添加 case 。就是导致A页面把只在B页面需要的校验规则也添加进去,造成不必要的开销。B页面也同理。
建议的方式是给这个 API 增加一个扩展的接口:
let checkType=(function(){
let rules={
email(str){
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
},
mobile(str){
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
}
};
//暴露接口
return {
//校验
check(str, type){
return rules[type]?rules[type](str):false;
},
//添加规则
addRule(type,fn){
rules[type]=fn;
}
}
})();
//调用方式
//使用mobile校验规则
console.log(checkType.check('188170239','mobile'));
//添加金额校验规则
checkType.addRule('money',function (str) {
return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金额校验规则
console.log(checkType.check('18.36','money'));
# 工厂模式
分为 简单工厂模式,工厂方法模式,抽象工厂模式。
场景: jq-$("div") React.createElement vue异步组件 【图表动态生成文本】【组件库封装】;
在工厂模式中,在创建对象时不会暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
- 简单工厂模式,也可以叫静态工厂模式,用一个工厂对象创建同一类对象类的实例。
class SimpleFactory {
constructor(opt) {
this.role = opt.role;
this.permissions = opt.permissions;
}
// 静态方法
static create(role) {
switch (role) {
case 'admin':
return new SimpleFactory({
role: '管理员',
permissions: ['设置', '删除', '新增', '创建', '开发', '推送', '提问', '评论']
});
break;
case 'developer':
return new SimpleFactory({
role: '开发者',
permissions: ['开发', '推送', '提问', '评论']
});
break;
default:
throw new Error('参数只能为 admin 或 developer');
}
}
show() {
const str = `是一个${this.role}, 权限:${this.permissions.join(', ')}`;
console.log(str);
}
}
// 实例
const xm = SimpleFactory.create('admin');
xm.show();//是一个管理员, 权限:设置, 删除, 新增, 创建, 开发, 推送, 提问, 评论
const xh = SimpleFactory.create('developer');
xh.show();//是一个开发者, 权限:开发, 推送, 提问, 评论
const xl = SimpleFactory.create('guest');
xl.show();//demo.html:33 Uncaught Error: 参数只能为 admin 或 developerat SimpleFactory.create
作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
- 工厂方法模式
// 0.0.2/es5.function.factory.js
function FunctionFactory(role) {
if (!(['admin', 'developer'].indexOf(role) > -1)) {
throw new Error('参数只能为 admin 或 developer');
}
// 安全的工厂方法
// console.log(this) //初次进来是window,然后在下面被new之后走入if分支
if (this instanceof FunctionFactory) {
return this[role]();
}
return new FunctionFactory(role);
}
FunctionFactory.prototype.show = function() {
var str = '是一个' + this.role + ', 权限:' + this.permissions.join(', ');
console.log(str)
}
FunctionFactory.prototype.admin = function(permissions) {
this.role = '管理员';
this.permissions = ['设置', '删除', '新增', '创建', '开发', '推送', '提问', '评论'];
}
FunctionFactory.prototype.developer = function(permissions) {
this.role = '开发者';
this.permissions = ['开发', '推送', '提问', '评论'];
}
var xm = FunctionFactory('admin');
xm.show();
var xh = FunctionFactory('developer');
xh.show();
var xl = FunctionFactory('guest');
xl.show();
将实际具体的工作放在函数的
原型上完成,核心类就成了抽象类。这样添加新的类时就无需修改工厂方法,只需要将子类注册进工厂方法的原型对象中即可。
- 抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。
// 0.0.2/jquery.factory.js
// 工厂模式
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice;
let dom = slice.call(document.querySelectorAll(selector));
let len = dom ? dom.length : 0;
for (let i = 0; i < len; i++) {
this[i] = dom[i];
}
this.length = len
this.selector = selector || ''
}
addClass(name) {
console.log(name)
}
html(data) {}
// 省略多个 API
}
// 工厂模式
window.$ = function(selector) {
return new jQuery(selector);
}
// 实例
const $li = $('li')
$li.addClass('item');
// 定义抽象交通工具类
class AbstractVehicle {
constructor(name) {
this.name = name;
}
// 抽象方法,需要在子类中实现
drive() {
throw new Error('子类必须实现drive方法');
}
}
// 汽车类,继承自AbstractVehicle
class Car extends AbstractVehicle {
drive() {
return `${this.name} is driving.`;
}
}
// 飞机类,继承自AbstractVehicle
class Plane extends AbstractVehicle {
drive() {
return `${this.name} is flying.`;
}
}
// 定义抽象工厂类
class AbstractFactory {
// 抽象方法,需要在子类中实现
createVehicle(name) {
throw new Error('子类必须实现createVehicle方法');
}
}
// 汽车工厂类,继承自AbstractFactory
class CarFactory extends AbstractFactory {
createVehicle(name) {
return new Car(name);
}
}
// 飞机工厂类,继承自AbstractFactory
class PlaneFactory extends AbstractFactory {
createVehicle(name) {
return new Plane(name);
}
}
// 使用抽象工厂模式
function createVehicle(factory, name) {
const vehicle = factory.createVehicle(name);
return vehicle.drive();
}
// 创建汽车工厂并生成汽车
const carFactory = new CarFactory();
console.log(createVehicle(carFactory, 'Tesla')); // 输出:Tesla is driving.
// 创建飞机工厂并生成飞机
const planeFactory = new PlaneFactory();
console.log(createVehicle(planeFactory, 'Boeing')); // 输出:Boeing is flying.
TIP
简单工厂模式就是你给工厂什么,工厂就给你生产什么;
工厂方法模式就是你找工厂生产产品,工厂是外包给下级分工厂来代加工,需要先评估一下能不能代加工;能做就接,不能做就找其他工厂;
抽象工厂模式就是工厂接了某项产品订单但是做不了,上级集团公司新建一个工厂来专门代加工某项产品;
工厂模式是一种常用的设计模式,它提供了一种在不指定具体类的情况下创建对象的方法。在前端开发中,工厂模式可以用于创建和管理各种类型的对象,例如 DOM 元素、组件库、工具类等。
假设我们有一个需要创建不同形状(圆形、矩形)的绘图应用。我们可以使用工厂模式来创建这些形状。
// 形状基类
class Shape {
constructor(name) {
this.name = name;
}
draw() {
console.log('Drawing a shape...');
}
}
// 圆形类
class Circle extends Shape {
constructor(name, radius) {
super(name);
this.radius = radius;
}
draw() {
console.log(`Drawing a circle with radius ${this.radius}`);
}
}
// 矩形类
class Rectangle extends Shape {
constructor(name, width, height) {
super(name);
this.width = width;
this.height = height;
}
draw() {
console.log(`Drawing a rectangle with width ${this.width} and height ${this.height}`);
}
}
// 形状工厂类
class ShapeFactory {
static createShape(shapeType, ...args) {
switch (shapeType) {
case 'circle':
return new Circle(...args);
case 'rectangle':
return new Rectangle(...args);
default:
throw new Error('Invalid shape type');
}
}
}
// 使用工厂类创建形状
const circle = ShapeFactory.createShape('circle', 'Circle 1', 5);
circle.draw(); // 输出: Drawing a circle with radius 5
const rectangle = ShapeFactory.createShape('rectangle', 'Rectangle 1', 10, 20);
rectangle.draw(); // 输出: Drawing a rectangle with width 10 and height 20
// DOM元素工厂类
class DOMElementFactory {
// 创建一个div元素
static createDiv(className, content) {
const div = document.createElement('div');
div.className = className;
div.textContent = content;
return div;
}
// 创建一个button元素
static createButton(id, text, onClick) {
const button = document.createElement('button');
button.id = id;
button.textContent = text;
button.addEventListener('click', onClick);
return button;
}
// 创建其他类型的元素...
}
// 使用工厂类创建DOM元素
const myDiv = DOMElementFactory.createDiv('my-class', 'Hello, world!');
document.body.appendChild(myDiv); // 将div添加到body中
const myButton = DOMElementFactory.createButton('my-button', 'Click me', function() {
alert('Button clicked!');
});
document.body.appendChild(myButton); // 将button添加到body中
使用工厂模式的好处是,当需要创建新的DOM元素时,只需要调用相应的工厂方法,而无需重复编写创建和配置元素的代码。这使得代码更加清晰、可维护,并且减少了错误的可能性。同时,如果需要修改元素的创建逻辑或添加新的元素类型,我们只需要在工厂类中进行修改,而无需修改使用这些元素的代码。
工厂模式缺点:获取不到对象的真实类型!!!
# 单例模式
系统中唯一被使用,一个类只有一个实例
前端单例模式通常用于确保一个类只有一个实例,并提供一个全局访问点。这在某些情况下非常有用,比如管理全局状态、缓存数据、日志记录等。
在前端开发中,虽然 Vuex 和 Redux 并非严格意义上的传统单例模式实现,但从功能和设计上来看,它们符合单例模式的一些特征,所以Vuex 和 Redux 都可以被看作是单例模式的实现,因为它们都遵循了单一数据源的概念,即整个应用的状态被存储在一个单一的对象中,并且通过特定的规则来更新这个状态。
举例 [登录框] [购物车] [cache] [jquery] [moment] [lodash]
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)
js目前无法做到强制性,需要遵守规则,如果非要刻意的使用obj3=new SingleObject也无法阻止
# 适配器模式
旧接口格式和使用者不兼容,中间加一个适配转换接口
场景 封装旧接口 vue中的computed
// 适配器模式
class Adaptee {
specificRequest () {
return '德国标准插头'
}
}
class Target {
constructor () {
this.adaptee = new Adaptee()
}
request () {
let info = this.adaptee.specificRequest()
return `${info}->适配器->中国标准插头`
}
}
// 测试
let target = new Target()
console.log(target.request())
//德国标准插头->适配器->中国标准插头
//修改js中的$
var $={
ajax:function(options){
return ajax(options)
}
}
# 装饰器模式
为对象添加新功能,且不改变原有的结构和功能
class Circle {
draw() {
console.log("画一个圆形");
}
}
class Decorator {
constructor(circle){
this.circle = circle;
}
draw() {
this.circle.draw();
this.setRedBorder(this.circle);
}
setRedBorder(circle) {
console.log("设置红色边框")
}
}
// 测试代码
let circle = new Circle();
circle.draw()
let dec = new Decorator(circle);
dec.setRedBorder();
# es装饰器的原理
// 装饰器的原理
@decorator
class A {}
//等同于
class A {}
A = decorator(A) || A;
# 装饰类
// 一个简单的demo
function testDec(target) {
target.isDec = "tk"
}
@testDec
class Demo {}
console.log(Demo.isDec)//tk
# 传参
function testDec(isDec) {
return function(target){
target.isDec = isDec;
}
}
@testDec(true)
class Demo {}
alert(Demo.isDec)
# 混合装饰器
做一个常用的mixins混合装饰器,来把一个类里面属性和方法全部添加到另一个类上
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() // 'foo'
# 装饰方法
让某个方法只读,不能修改
function readonly(target, name, descriptor){
console.log(target, name, descriptor)
//{constructor: ƒ, testname: ƒ}
//"testname"
//{value: ƒ, writable: true, enumerable: false, configurable: true}
descriptor.writable = false;
return descriptor;
}
class Person {
constructor() {
this.first = 'A'
this.last = 'B'
}
@readonly
testname() { return `${this.first} ${this.last}` }
}
var p = new Person()
console.log(p.testname())
p.testname = function () {} // 这里会报错,因为 name 是只读属性
装饰类的时候,一般主要是看target(装饰对象)第一个参数。装饰方法的时候,一般主要看的是descriptor(描述)第三个参数。
# core-decorators
npm install core-decorators --save
import { deprecate } from 'core-decorators';
class Person {
@deprecate
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
facepalmHarder() {}
}
let person = new Person();
person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming
person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.
# 代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问
使用者无权访问目标对象,中间加代理,通过代理做授权和控制,如生活中的链家
思想 网页事件代理 addeventListener $.proxy es6 proxy axios中拦截器
分为缓存代理和虚拟代理
缓存代理用于存储对昂贵操作的结果,以便在稍后的调用中重用这些结果,从而提高性能。它通常用于缓存函数调用的结果,或者缓存对象属性的值。
虚拟代理用于控制对大型对象或资源密集型对象的访问。它通常用于延迟创建对象,直到真正需要时。
// 明星
let star = {
// name和age可以返回,电话就需要经纪人代理给拦截了。
name: '张XX',
age: 25,
phone: '13910733521'
}
// 经纪人
let agent = new Proxy(star, {
get: function (target, key) {
if (key === 'phone') {
// 返回经纪人自己的手机号
return '18611112222'
}
if (key === 'price') {
// 明星不报价,经纪人报价
return 120000
}
return target[key]
},
set: function (target, key, val) {
if (key === 'customPrice') {
if (val < 100000) {
// 最低 10w
throw new Error('价格太低')
} else {
target[key] = val
return true
}
}
}
})
// 主办方去找明星的经纪人
console.log(agent.name) // 经纪人返回明星的姓名
console.log(agent.age) // 明星的姓名
console.log(agent.phone) // 经纪人的电话,经纪人拦截了phone。
console.log(agent.price) // 明星搞艺术,不谈钱,经纪人谈钱。
// 想自己提供报价(砍价,或者高价争抢)
agent.customPrice = 150000
// agent.customPrice = 90000 // 报错:价格太低
console.log('customPrice', agent.customPrice)
# 外观模式
为子系统中的一组接口提供了一个高层接口,使用者使用这个高层接口
外观模式不符合单一职责原则和开放封闭原则,需谨慎使用
function bindEvent(elem, type, selector, fn) {
if ( fn == null) {
fn = selector
selector = null
}
// *****
}
// 调用
bindEvent(elem, 'click', '#div1', fn)
bindEvent(elem, 'click', fn)
# 观察者模式(类似发布订阅)
观察者模式定义了对象的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将会得到通知,并自动更新。
观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯
场景 网页事件绑定 nodejs自定义事件 vue-watch 相似Promise jqeurycallbacks vue/react生命周期
左侧是Observer,就是观察者,它有一个update方法,当观察者需要被触发的时候执行update。右侧是主题,主题可以绑定多个观察者,放在observers里面。主题可以获取状态(getState()),也可以设置状态(setState())当状态设置完成后,他会触发所有的观察者(notifyAllObservers()),触发所有观察者里面的update方法。观察者定义好以后,它就等待被更新,等待被触发,当然,前提是它已经定义好了。注意一对多,主题是一个,但是观察者可能是多个。
// 主题,接收状态变化,触发每个观察者
class Subject {
constructor() {
this.state = 0
this.observers = []
}
getState() {
return this.state
}
setState(state) {
this.state = state
this.notifyAllObservers()
}
attach(observer) {
this.observers.push(observer)
}
notifyAllObservers() {
this.observers.forEach(observer => {
observer.update()
})
}
}
// 观察者,等待被触发
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)
s.setState(2)
s.setState(3)
<button id="btn1">btn</button>
<script>
$('#app').click(function(){
console.log(11);
})
$('#app').click(function(){
console.log(22);
})
$('#app').click(function(){
console.log(33);
})
</script>
<!-- // 每次点击都会打印 11 22 33 -->
// 定义一个主体对象
class Subject {
constructor() {
this.Observers = [];
}
add(observer) { //添加
this.Observers.push(observer)
}
remove(observer) {//移除
this.Observers.filter(item => item === observer);
}
notify() {
this.Observers.forEach(item => {
item.update();
})
}
}
//定义观察着对象
class Observer {
constructor(name) {
this.name = name;
}
update() {
console.log(`my name is:${this.name}`);
}
}
//测试
let sub = new Subject();
let obs1 = new Observer('leaf111');
let obs2 = new Observer('leaf222');
sub.add(obs1);
sub.add(obs2);
sub.notify();
# 发布订阅模式
发布-订阅是一种消息范式,消息的发送者(发布者)不会直接发送给特定的接受者(订阅者)。而是将发布的消息分为不同的泪别,无需了解哪些订阅者(如果有的话)可能存在同样的,订阅者可以表达对一个或多个类别的兴趣,只接受感兴趣的消息,无需了解哪些发布者存在
//发布订阅
let pubSub = {
subs: [],
subscribe(key, fn) { //订阅
if (!this.subs[key]) {
this.subs[key] = [];
}
this.subs[key].push(fn);
},
publish(...arg) {//发布
let args = arg;
let key = args.shift();
let fns = this.subs[key];
if (!fns || fns.length <= 0) return;
for (let i = 0, len = fns.length; i < len; i++) {
fns[i](args);
}
},
unSubscribe(key) {
delete this.subs[key]
}
}
//测试
pubSub.subscribe('name', name => {
console.log(`your name is ${name}`);
})
pubSub.subscribe('gender', gender => {
console.log(`your name is ${gender}`);
})
pubSub.publish('name', 'leaf333'); // your name is leaf333
pubSub.publish('gender', '18'); // your gender is 18
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>jQuery callbacks</p>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
// 自定义事件,自定义回调
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')
</script>
</body>
</html>
const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
emitter1.on('some', () => {
// 监听 some 事件
console.log('some event is occured 1')
})
emitter1.on('some', () => {
// 监听 some 事件
console.log('some event is occured 2')
})
// 触发 some 事件
emitter1.emit('some')
const EventEmitter = require('events').EventEmitter
// 任何构造函数都可以继承 EventEmitter 的方法 on emit
class Dog extends EventEmitter {
constructor(name) {
super()
this.name = name
}
}
var simon = new Dog('simon')
simon.on('bark', function () {
console.log(this.name, ' barked')
})
setInterval(() => {
simon.emit('bark')
}, 500)
class PubSub {
constructor() {
this.messages = {};
this.listeners = {};
}
//
publish(type, content) {
const existContent = this.messages[type];
if (!existContent) {
this.messages[type] = [];
}
this.messages[type].push(content);
}
//
subscribe(type, cb) {
const existListener = this.listeners[type];
if (!existListener) {
this.listeners[type] = [];
}
this.listeners[type].push(cb);
}
//
notify(type) {
const messages = this.messages[type];
const subscribers = this.listeners[type] || [];
subscribers.forEach((cb, index) => cb(messages[index]));
}
}
class Publisher {
constructor(name, context) {
this.name = name;
this.context = context;
}
publish(type, content) {
this.context.publish(type, content);
}
}
class Subscriber {
constructor(name, context) {
this.name = name;
this.context = context;
}
subscribe(type, cb) {
this.context.subscribe(type, cb);
}
}
const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';
const pubsub = new PubSub();
const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');
const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {
console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {
console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {
console.log('subscriberC received', res)
});
pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);
// subscriberA received we are young
// subscriberC received the silicon valley
// subscriberB received a brief history of time
# 迭代器模式
顺序访问一个有序集合,使用者无需知道集合内部结构
场景 jquery-each es6-iterator
class Iterator {
constructor(conatiner) {
this.list = conatiner.list
this.index = 0
}
next() {
if (this.hasNext()) {
return this.list[this.index++]
}
return null
}
hasNext() {
if (this.index >= this.list.length) {
return false
}
return true
}
}
class Container {
constructor(list) {
this.list = list
}
getIterator() {
return new Iterator(this)
}
}
// 测试代码
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while(iterator.hasNext()) {
console.log(iterator.next())
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>jquery each</p>
<p>jquery each</p>
<p>jquery each</p>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var arr = [1, 2, 3]
var nodeList = document.getElementsByTagName('p')
var $p = $('p')
// 要对这三个变量进行遍历,需要写三个遍历方法
// 第一
arr.forEach(function (item) {
console.log(item)
})
// 第二
var i, length = nodeList.length
for (i = 0; i < length; i++) {
console.log(nodeList[i])
}
// 第三
$p.each(function (key, p) {
console.log(key, p)
})
// 如何能写出一个方法来遍历这三个对象呢
function each(data) {
var $data = $(data)
$data.each(function (key, p) {
console.log(key, p)
})
}
each(arr)
each(nodeList)
each($p)
</script>
</body>
</html>
ES6 Iterator是什么?
- Array Map Set String TypedArray arguments NodeList Generator
- 以上数据类型,都有[Sysbol.iterator]属性
- 属性值是函数,执行函数返回一个迭代器
- for...of的出现
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 200)
function each(data) {
生成遍历器
let iterator = data[Symbol.iterator]()
console.log(iterator.next()) // 有数据时返回 {value: 1, done: false}
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next()) // 没有数据时返回 {value: undefined, done: true}
let item = {done: false}
while (!item.done) {
item = iterator.next()
if (!item.done) {
console.log(item.value)
}
}
each(arr)
each(nodeList)
each(m)
for...of是[Symbol.iterator]语法糖
# 状态模式
一个对象有状态变化,每次状态变化都会触发一个逻辑,不能总用if...else控制
场景 有限状态机 promise
class State {
constructor(color) {
this.color = color
}
handle(context) {
console.log(`turn to ${this.color} light`)
context.setState(this)
}
}
class Context {
constructor() {
this.state = null
}
setState(state) {
this.state = state
}
getState() {
return this.state
}
}
// 测试代码
let context = new Context()
let green = new State('green')
let yellow = new State('yellow')
let red = new State('red')
// 绿灯亮了
green.handle(context)
console.log(context.getState())
// 黄灯亮了
yellow.handle(context)
console.log(context.getState())
// 红灯亮了
red.handle(context)
console.log(context.getState())
javascript-state-machine (opens new window)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>有限状态机</p>
<button id="btn"></button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="./03-javascript-state-machine.js"></script>
<script>
//var StateMachine = require('javascript-state-machine');模块化下载的引入,
// 状态机模型
var fsm = new StateMachine({
init: '收藏', // 初始状态,待收藏
transitions: [
{
name: 'doStore',
from: '收藏',
to: '取消收藏'
},
{
name: 'deleteStore',
from: '取消收藏',
to: '收藏'
}
],
methods: {
// 执行收藏
onDoStore: function () {
alert('收藏成功')
updateText()
},
// 取消收藏
onDeleteStore: function () {
alert('已取消收藏')
updateText()
}
}
})
var $btn = $('#btn')
// 点击事件
$btn.click(function () {
if (fsm.is('收藏')) {
fsm.doStore(1)
} else {
fsm.deleteStore()
}
})
// 更新文案
function updateText() {
$btn.text(fsm.state)
}
// 初始化文案
updateText()
</script>
</body>
</html>
# 其他设计模式
# 原型模式
clone自己,生成一个新对象(new一个对象开销大)
// 一个原型 对象
let prototype = {
getName: function () {
return this.first + " " + this.last;
},
say: function () {
alert("hello")
}
}
// 基于原型创建x
let x = Object.create(prototype);
x.first = "A";
x.last = "B";
alert(x.getName());
x.say();
// 基于原型创建y
let y = Object.create(prototype);
y.first = "C";
y.last = "D";
alert(y.getName());
y.say();
# 桥接模式
用于抽象画与实现化解耦
要画这样一幅图 黄色圆圈 红色圆圈 黄色三角 红色三角 这样四个图形 代码实现如果混合在一起一般是这个样子的
class ColorShape {
yellowCircle() {
console.log("yellow circle")
}
redCircle() {
console.log("red circle")
}
yellowTriangle() {
console.log("yellow triangle")
}
redTriangle() {
console.log("red triangle")
}
}
// 测试
let cs = new ColorShape();
cs.yellowCircle()
cs.redCircle()
cs.yellowTriangle()
cs.redTriangle()
但是从前端模式来说,我们一般是分离开的,大概是这样的画图是画图,填充颜色是填充颜色,比较适合一些复杂性比较大的业务。
class Color {
constructor(name){
this.name = name;
}
}
class Shape {
constructor(name, color){
this.name = name;
this.color = color;
}
draw(){
console.log(`${this.color.name} ${this.name}`)
}
}
// 测试代码
let red = new Color("red");
let yellow = new Color("yellow");
let circle = new Shape("circle", red);
circle.draw();
let triangle = new Shape("triangle", yellow);
triangle.draw();
# 组合模式
生成树结构,表示整体和部分关系,让整体和部分都有操作方式一致,结构也一致(如虚拟dom的格式)
{
tag: "div",
attr: {
id: "div1",
className: "container"
},
children: [
{
tag: "p",
attr: {},
children: ["123"]
},
{
tag: "p",
attr: {},
children: ["456"]
},
]
}
# 享元模式
共享内存(主要考虑内存,而非效率),相同的数据,共享使用【js基本不需要使用】
场景:如代理模式的addeventListener某种意义上也是一种享元模式,在父节点上绑定事件,可以给无数给子节点共享,节省内存。
# 策略模式
不同策略分开处理,避免大量if...eles switch等【js用的不多】
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,将算法的使用与算法的实现分离开来。
策略模式分为两部分,第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类
/** 一大堆的if-else **/
//level 评级 basicBonus 基础奖金
const computeBonus(level, basicBonus) = () => {
if(level === 'A') {
return basicBonus * 1;
} else if(level === 'B') {
return basicBonus * 2;
} else if(level === 'C') {
return basicBonus * 3;
} else if(level === 'D') {
return basicBonus * 4;
}
}
computeBonus('A', 1000);//1000
/** 策略模式改写 **/
//定义策略类
// js 天然特性的策略模式
const strategies = {
'A': function(basicBonus) {
return basicBonus * 1;
},
'B': function(basicBonus) {
return basicBonus * 2;
},
'C': function(basicBonus) {
return basicBonus * 3;
},
'D': function(basicBonus) {
return basicBonus * 4;
},
}
//使用环境类
const computeBonus = (level, basicBonus) {
return strategies[level](basicBonus);
}
computeBouns('A', 1000);//1000
场景:
//未使用策略模式
class User {
constructor (type) {
this.type = type;
}
buy() {
if(this.type === "ordinary") {
console.log("普通用户购买");
} else if(this.type === "member") {
console.log("会员用户购买");
}else if(this.type === "VIP") {
console.log("VIP用户购买");
}
}
}
// 测试代码
let u1 = new User('ordinary')
u1.buy()
let u2 = new User('member')
u1.buy()
let u3 = new User('VIP')
u1.buy()
//使用策略模式
class OridnaryUser {
buy() {
console.log("普通用户购买")
}
}
class MemberUser {
buy() {
console.log("会员用户购买")
}
}
class VipUser {
buy() {
console.log("VIP用户购买")
}
}
let u1 = new OridnaryUser();
let u2 = new MemberUser();
let u3 = new VipUser();
减少ifelse判断,减少嵌套层级
A. 简单策略模式
import pic from '../pic'
......
export enum IDrawingType {
/**
* 图片
*/
pic = 'pic',
/**
* 图形
*/
wsp = 'wsp',
/**
* 组合图形
*/
wgp = 'wgp'
}
const METHODS = {
[IDrawingType.pic]: {
[IDrawingDisplayType.inline]: pic,
[IDrawingDisplayType.positioned]: picPositioned
},
[IDrawingType.wsp]: {
[IDrawingDisplayType.inline]: wsp,
[IDrawingDisplayType.positioned]: wspPositioned
},
[IDrawingType.wgp]: {
[IDrawingDisplayType.inline]: wgp,
[IDrawingDisplayType.positioned]: wgp
}
}
const effectDrawing = METHODS[drawingType as IDrawingType][
isInline ? IDrawingDisplayType.inline : IDrawingDisplayType.positioned
B. 复杂策略模式
interface ICaculateStrategy {
excute(element: IElementLayer<ElementType>): number
}
abstract class AbstractStrategy implements ICaculateStrategy {
abstract excute(element: IElementLayer<ElementType>): number
}
export enum ElementType {
TextChar, // 文本
Br // 分页符
}
/**
计算普通文本的宽度
*/
class CaculateTextWidth extends AbstractStrategy {
excute(element: IElementLayer<ElementType>) {
return element.width
}
}
/**
计算分页符宽度
*/
class CalculateBr extends AbstractStrategy {
excute(element: IElementLayer<ElementType>): number {
return zoomWidth(20)
}
}
/**
通过工厂模式创建对应的实例
@param K
@returns
*/
// T extends () => any 返回的类型
// T 就是拥有这个类型
const InstanceFactory = <T extends new () => AbstractStrategy>(K: T) => {
return new K()
}
const CaculateStrategyMap: Record<ElementType, ICaculateStrategy> = {
[ElementType.TextChar]: InstanceFactory(CaculateTextWidth),
[ElementType.Br]: InstanceFactory(CalculateBr)
}
const calculateXWidth = (element: IElementLayer<ElementType>) => {
return CaculateStrategyMap[element.type].excute(element)
}
export default calculateXWidth
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
// T extends new (...args: any) => any 首先给T加了个约束 必须满足new (...args: any) => any 也就是说T必须是构造函数类型
// T extends new (...args: infer P) => any ? P : never
// T若满足new (...args: any) => any 则返回所有入参的类型, 否则返回never
# 模板方法模式
某些特定的方法封装在一起,像模板一样使用
class Action {
handle() {
this.handle1();
this.handle2();
this.handle3();
}
handle1() {
console.log(1)
}
handle2() {
console.log(2)
}
handle3() {
console.log(3)
}
}
let a=new Action()
a.handle()
# 职责链模式
- 一步操作可能分为多个职责角色来完成
- 把这些角色都分开,然后用一个链串起来
- 将发起者和各个处理者进行隔离
场景 js中的链式操作 jquery promise
class Action {
constructor(name) {
this.name = name;
this.nextAction = null;
}
setNextAction(action) {
this.nextAction = action;
}
handle() {
console.log(`${this.name} 审批`)
if(this.nextAction !== null){
this.nextAction.handle();
}
}
}
// 测试
let a1 = new Action("组长");
let a2 = new Action("经理");
let a3 = new Action("总监");
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();
// 组长 审批
// 经理 审批
// 总监 审批
# 命令模式
- 执行命令时,发布者和执行者分开
- 中间加入命令对象,作为中转站
// 接收者
class Receiver {
exec() {
console.log("执行");
}
}
// 命令者
class Command {
constructor(receiver){
this.receiver = receiver;
}
// 触发
cmd() {
console.log("执行命令")
this.receiver.exec();
}
}
// 触发者
class Invoker {
constructor(command){
this.command = command;
}
// 触发
invoke() {
console.log("开始")
this.command.cmd();
}
}
// 测试
// 士兵
let solider = new Receiver();
// 小号手
let trumpeter = new Command(solider);
// 将军
let general = new Invoker(trumpeter);
general.invoke();
// 开始
// 执行命令
// 执行
应用:网页富文本编辑器操作,浏览器封装了一个命令对象
// execCommand 浏览器封装富文本操作,复制,加粗选中文字等
document.execCommand("bold")
document.execCommand("undo")
# 备忘录模式
- 随时记录一个对象的状态变化
- 随时可以恢复之前的某个状态(如撤销功能)
- 常见工具(编辑器)
// 状态备忘
class Memento {
constructor(content){
this.content = content;
}
getContent() {
return this.content;
}
}
// 备忘列表
class CareTaker {
constructor(){
this.list = []
}
add(memento){
this.list.push(memento)
}
get(index){
return this.list[index]
}
}
// 编辑器
class Editor {
constructor (){
this.content = null;
}
setContent(content){
this.content = content;
}
getContent(){
return this.content;
}
// 保存
saveContentToMemento(){
return new Memento(this.content);
}
// 恢复
getContentFromMemento(memento){
this.content = memento.getContent();
}
}
// 测试代码
let editor = new Editor();
let careTaker = new CareTaker();
editor.setContent("111");
editor.setContent('222')
careTaker.add(editor.saveContentToMemento()); // 存储备忘录
editor.setContent("333");
careTaker.add(editor.saveContentToMemento());
editor.setContent("444");
console.log(editor.getContent());
editor.getContentFromMemento(careTaker.get(1));
console.log(editor.getContent());
editor.getContentFromMemento(careTaker.get(0));
console.log(editor.getContent());
# 中介者模式
/*js设计模式——中介者模式*/
class A {
constructor() {
this.number = 0;
}
setNumber(num, m) {
this.number = num;
if (m) {
m.setB();
}
}
}
class B {
constructor() {
this.number = 0;
}
setNumber(num, m) {
this.number = num;
if (m) {
m.setA();
}
}
}
// 中介者
class MediatorDome {
constructor(a, b) {
this.a = a;
this.b = b;
}
setB() {
let number = this.a.number;
this.b.setNumber(number * 10);
}
setA() {
let number = this.b.number;
this.a.setNumber(number / 10);
}
}
// 测试
let a = new A();
let b = new B();
let m = new MediatorDome(a, b);
a.setNumber(100, m);//100 1000
console.log(a.number, b.number);
b.setNumber(2000, m);//200 2000
console.log(a.number, b.number);
# 访问者模式
将数据操作和数据结构进行分离
# 解释器模式
描述语言语法如何定义,如何解释和编译
# 常用的设计模式综合应用
做一个购物车功能
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>前端设计模式</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
//index
import App from './demo/App.js'
let app = new App('app')
app.init()
//app.js
import $ from 'jquery'
import ShoppingCart from './ShoppingCart/ShoppingCart.js'
import List from './List/List.js'
export default class App {
constructor(id) {
this.$el = $(`#${id}`)
}
// 初始化购物车
initShoppingCart() {
let shoppingCart = new ShoppingCart(this)
shoppingCart.init()
}
// 初始化商品列表
initList() {
let list = new List(this)
list.init()
}
init() {
this.initShoppingCart()
this.initList()
}
}
//shoppingcat.js
import $ from 'jquery'
import getCart from './GetCart.js'
export default class ShoppingCart {
constructor(app) {
this.app = app
this.$el = $('<div>').css({
'padding-bottom': '10px',
'border-bottom': '1px solid #ccc'
})
this.cart = getCart()
}
// 显示购物车内容
showCart() {
alert(this.cart.getList())
}
// 初始化按钮
initBtn() {
let $btn = $('<button>购物车</button>')
$btn.click(() => {
this.showCart()
})
this.$el.append($btn)
}
// 渲染
render() {
this.app.$el.append(this.$el)
}
init() {
this.initBtn()
this.render()
}
}
//getcart.js
class Cart {
constructor() {
this.list = []
}
add(data) {
this.list.push(data)
}
del(id) {
this.list = this.list.filter(item => {
if (item.id === id) {
return false
}
return true
})
}
getList() {
return this.list.map(item => {
return item.name
}).join('\n')
}
}
// 返回单例
let getCart = (function () {
let cart
return function () {
if (!cart) {
cart = new Cart();
}
return cart
}
})()
export default getCart
//list.js
import $ from 'jquery'
import createItem from '../Item/CreateItem.js'
import { GET_LIST } from '../config/config.js'
export default class List {
constructor(app) {
this.app = app
this.$el = $('<div>')
}
// 获取数据
loadData() {
// 使用 fetch (低版本浏览器可使用 https://github.com/github/fetch 兼容)
// 返回 promise
return fetch(GET_LIST).then(result => {
return result.json()
})
}
// 生成列表
initItemList(data) {
data.map(itemData => {
let item = createItem(this, itemData)
item.init()
return item
})
}
// 渲染
render() {
this.app.$el.append(this.$el)
}
init() {
this.loadData().then(data => {
this.initItemList(data)
}).then(() => {
// 最后再一起渲染 DOM ,以避免重复渲染的性能问题
this.render()
})
}
}
//createItem
import Item from './Item.js'
function createDiscount(item) {
// 用代理做折扣显示
return new Proxy(item, {
get: function (target, key, receiver) {
if (key === 'name') {
return `${target[key]}【折扣】`
}
if (key === 'price') {
return target[key] * 0.8
}
return target[key]
}
})
}
// 工厂函数
export default function (list, itemData) {
if (itemData.discount) {
itemData = createDiscount(itemData)
}
return new Item(list, itemData)
}
//item
import $ from 'jquery'
import StateMachine from 'javascript-state-machine'
import { log } from '../util/log.js'
import getCart from '../ShoppingCart/GetCart.js'
export default class Item {
constructor(list, data) {
this.list = list
this.data = data
this.$el = $('<div>')
this.cart = getCart()
}
initContent() {
let $el = this.$el
let data = this.data
$el.append($(`<p>名称:${data.name}</p>`))
$el.append($(`<p>价格:${data.price}</p>`))
}
initBtn() {
let $el = this.$el
let $btn = $('<button>')
// 状态管理
let _this = this
let fsm = new StateMachine({
init: '加入购物车',
transitions: [
{
name: 'addToCart',
from: '加入购物车',
to: '从购物车删除'
},
{
name: 'deleteFromCart',
from: '从购物车删除',
to: '加入购物车'
}
],
methods: {
// 加入购物车
onAddToCart: function () {
_this.addToCartHandle()
updateText()
},
// 删除
onDeleteFromCart: function () {
_this.deleteFromCartHandle()
updateText()
}
}
})
function updateText() {
$btn.text(fsm.state)
}
$btn.click(() => {
if (fsm.is('加入购物车')) {
fsm.addToCart()
} else {
fsm.deleteFromCart()
}
})
updateText()
$el.append($btn)
}
// 加入购物车
@log('add')
addToCartHandle() {
this.cart.add(this.data)
}
// 从购物车删除
@log('del')
deleteFromCartHandle() {
this.cart.del(this.data.id)
}
render() {
this.list.$el.append(this.$el)
}
init() {
this.initContent()
this.initBtn()
this.render()
}
}
//logjs
export function log(type) {
return function (target, name, descriptor) {
console.log(descriptor,1)
var oldValue = descriptor.value;
descriptor.value = function() {
// 此处统一上报日志
console.log(`日志上报 ${type}`);
console.log(oldValue,2)
// 执行原有方法
return oldValue.apply(this, arguments);
};
return descriptor;
}
}