# js设计模式

设计模式使代码编制真正工程化,设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。

# js面向对象

类和实例

  • 继承 子类继承父类 extends super
  • 封装 数据的权限和保密 public protected(对子类开放) private =>es暂不支持,可以用ts演示(2025年已经支持私有#)
    • 减少耦合,不该外露的不外露
    • 利于数据接口权限管理
  • 多态 统一接口不同实现
    • 保持子类开放性灵活性
    • 面向接口编程

# 为什么用面向对象

简单抽象 数据结构化

实际应用jquery

# UML图

统一建模语言 地址 (opens new window)

uml图也分很多类型,这里是其中的一项uml类图

加号代表public共有,减号代表private私有,#号代表protected受保护

泛化(表继承)和关联(表引用)

白色箭头表示了两个类间的关联关系;黑色箭头表示了依赖关系。

UML类图分析 (opens new window)

# 五大设计原则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

查阅文档 (opens new window)

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;
    }
}

参考 (opens new window)

最后更新: 6/29/2025, 2:59:57 PM