设计模式
最近在复习,记录一下复习心得(再emo也要努力咯)
本次复习点
- 发布订阅模式 + 观察者模式
- 它们的场景:自定义事件,Vue watch ,DOM 事件绑定
- 装饰器模式
- TS 语法 Decorator
@
- TS 语法 Decorator
- 迭代器
- ES 语法 generator yield
- 代理模式
- ES 语法 Proxy Reflect
发布订阅模式 + 观察者模式
发布订阅模式
和观察者模式
就让我想起了很久以前点外卖的时候的日子。
观察者模式
那时候外卖平台还没有兴起,我们点外卖就要靠电话,如果不知道我想吃的那个店家的电话号码那我可就犯难了;店家也需要知道我的送货地址还有电话号码,这样我们双方都知道对方的具体消息的就是观察者模式
。
js
class Publisher {
// 发布者机制
constructor() {
this.subscribers = [];
this.state = '遥遥领先!'
}
addsubscribers(subscriber) {
// 添加订阅者名单
this.subscribers.push(subscriber)
}
// 移除订阅者名单
removesubscribers(subscriber) {
for (let i in this.subscribers) {
const item = this.subscribers[i]
if (item.id === subscriber.id) {
this.subscribers.splice(i, 1);
break
}
}
}
notifysubscriber() {
// 状态更新,发布消息
this.subscribers.forEach(item => {
item.update(this.state);
})
}
}
class Subscriber {
// 订阅者机制
constructor(name, id) {
this.name = name
this.id = id
}
update(state) { // 订阅者根据发布的消息做出的举动
console.log(`${this.name}手机出问题了:${state}`)
}
}
发布订阅模式
在外卖2.0时代,外卖就轻松了,我们只需要在平台上点单就好,商家也不需要知道我们的消息,我们也不需要知道商家的电话号码轻轻松松,这样我们双方各司其职会有对应的中间商给我们处理事件的就是发布订阅模式
。
js
/**
* 发布订阅模式
*/
class EventEmitter {
constructor() {
this.subs = Object.create(null)
}
// 事件订阅
// 就是类似用户订阅了公众号的工作
on(name, fn){
if (!Reflect.has(this.subs, name)) {
this.subs[name] = []
}
this.subs[name].push(fn)
}
// 事件通知
// 就是类似要发文了开始通知用户了
emit(name, params) {
if (!Reflect.has(this.subs, name)) {
console.warn('没有该事件')
return
}
this.subs[name].forEach(element => {
element(params)
});
}
}
装饰器模式
JavaScript装饰器模式是一种常用的设计模式,它可以让你在不改变原有代码的情况下,动态地给对象添加新的功能。
例子:
js
function add(a,b) {
return a + b
}
我们定义了一个add
函数,计算两个参数相加的结果!但是这里有个问题,用户的输入输出都是不可限制的,很有可能给我们来一个add('a', 1)
|| add(1, 'c')
,当然也可以这样子
js
function add(a,b) {
if(typeof a !== "number" || typeof b !== "number") {
console.warn('输入不对')
return
}
return a + b
}
但是这样不够灵活,后期不止需要+
还需要-
、*
、\
呢,当然也有聪明的小伙伴说了,把校验的方法提取出来,作为一个公共函数就好了!这里就引入了装饰器了。
js
function decorator(fn) {
return function (...arg) {
for (let item of arg) {
if (typeof item !== "number") {
console.warn('输入不对')
return
}
}
return fn(...arg)
}
}
最后,我们使用装饰器函数来装饰我们的计算函数:
js
const calculate = decorator(add);
console.log(calculate(1,2)) // 3
console.log(calculate(31,2)) // 33
console.log(calculate('1',2)) // 输入不对
这里其实有点像柯里化函数的概念了;也顺便写写
js
function curry(fn) {
return function curryFn(...arg) {
if (fn.length > arg.length) {
return function (...other) {
return curryFn(...arg, ...other)
}
}
return fn(...arg)
}
}
装饰器模式 TS版本
当然TS中也有更帅气的装饰器模式,具体文档
解释
装饰器 是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
准备环境
我们先准备一个TS的基本环境。创建一个新的文件夹。
npm i typescript --save-dev
- 安装ts依赖
npm i ts-node --save-dev
- 一个在node中写ts的工具包
npx tsc --init
- 初始化一个ts项目
- 打开
tsconfig.json
- 修改 "experimentalDecorators": true, 属性,因为装饰器属于一个实验性的属性
npx ts-node xxxx.ts
- 来编译执行写的ts文件
举例:方法装饰器
这次我们还是用加法为例,做一个缓存函数,记录每次调用加法后的值,减少遍历次数
js
class Maths {
cunt = 0
constructor(cunt: number) {
this.cunt = cunt
}
@log()
add(num: number) {
let result = 0;
for (let i = 0; i < num; i++) {
result += i;
}
return result + this.cunt;
}
}
这次我们想要记录每次调用add
方法的参数,方便我们之后查看
js
const log = () =>{
const map = new Map()
return (target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) => {
// 记录一下原方法 之后调用
let method = descriptor.value;
// 旧方法改造
descriptor.value = function (num: number) {
if(map.has(num)) {
return map.get(num)
}
const result = method.apply(this, [num]);
map.set(num, result)
return result
}
}
}
多个装饰器
在TypeScript里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:
- 由上至下依次对装饰器表达式求值。
- 求值的结果会被当作函数,由下至上依次调用。
js
function f() {
console.log("f(): evaluated");
return function () {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function () {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
在控制台里会打印出如下结果:
js
f(): evaluated
g(): evaluated
g(): called
f(): called
当然还有:类装饰器、访问器装饰器、属性装饰器、参数装饰器,这里就不一一赘述了
迭代器
- 迭代器对象本质上,就是一个指针对象。通过指针对象的next(), 用来移动指针。
- 迭代器协议:对象必须提供一个next(),执行该方法要么返回迭代的下一项,要么就引起Stopiteration异常,以终止迭代。
- 每调用一次next ()方法,都会返回一个对象,都会返回数据结构的当前成员的信息。
- 这个对象有 value 和 done 两个属性
- value属性返回当前位置的成员
- done属性是一个布尔值,表示遍历是否结束,即是否有必要再调用一次next ()
- 对于遍历器来说,value:undefined和done:false属性都是可以省略的
实现Iterator接口的原生对象
原生具备Iterator接口的数据结构有:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
简单实现一下
js
const makeIterator = () => {
let index = 0
return {
next: () => {
return index < 10 ? { value: `value${index++}`, done: false } : { value: undefined, done: true }
},
return() {//return方法标识该迭代器可关闭,return是可选的,
console.log('如果迭代期间使用break continue return thorw则会触发该函数')
return {
done: true
}
}
}
}
const a1 = {}
a1[Symbol.iterator] = makeIterator
let num = 0
for (const i of a1) {
console.log(i, 'obj')
num++
if (num > 4) {
break
}
}
最终输出
js
value0 obj
value1 obj
value2 obj
value3 obj
value4 obj
如果迭代期间使用break continue return thorw则会触发该函数
代理模式
经典表现就是vue的响应式数据
js
var user = {
name: "zht",
dept: "部门一"
}
let proxy_user = new Proxy(user,{
get(target,prop){
console.log(target +'和' + prop +'处理业务')
return target[prop]
},
set(target,prop,value){
console.log(target )
console.log('变化' + prop + '属性值变化值' + value )
target[prop] = value
}
})
proxy_user.name = "kaimi";
console.log(proxy_user.name);
console.log(user.name);
JavaScript 中Reflect使用
Reflect 是一个内置的对象,提供了一组有用的方法,用于操作对象和函数。它提供了一种与 Proxy 对象交互的方法,使开发人员可以使用相同的方法来处理对象和函数,同时提供更多的操作和控制选项。
联合使用
js
var user = {
name: "zht",
dept: "部门一"
}
let proxy_user = new Proxy(user,{
get(target,prop,receiver){
console.log(receiver) //添加收器的内容
return Reflect.get(target,prop,receiver)
},
})
console.log(proxy_user.name)