js 设计模式

266 阅读5分钟

前端常用设计模式总结 和 练习

面向对象

类 - 模板

class People {
    constructor(name, age) {
        this.name = name;
        this.age = age
    }
    eat() {
        console.log(`${ this.name } eat something`)
    }
    read() {
        console.log(`${ this.name } reading book`)
    }
}

let zhangsan = new People('zhangsan', 20);
zhangsan.eat()

let wang = new People('wang', 19);
wang.read()

三要素

一 、继承 子类继承父类
// 子类 继承 父类
class Student extends People {
    constructor(name, age, number) {
        super(name, age)
        this.number = number
    }
    study() {
        console.log(`${ this.name } can study`)
    }
}

// 实例 
let xiaohong = new Student('xiaohong', 12, 234)
xiaohong.study()
xiaohong.rend()
xiaohong.eat()
二 、封装 数据的权限和保密
* 减少耦合,不该外露的不外露
* 利于数据、接口的权限管理
* ES6 目前不支持 一般认为 _ 开头的属性是private的
// puiblic 完全开放
// protected 对子类开放
// private 对自己开发
// typescript 版本
class People {
    age
    name
    protected weight // 受保护属性
    constructor(name, age) {
        this.name = name
        this.age = age
        this.weight = 120
    }
    eat() {
        console.log(`${ this.name } eat something`)
    }
    read() {
        console.log(`${ this.name } reading book`)
    }
}

class Student extends People {
    number
    private girlfriend // 自身才能访问
    constructor(name, age, number) {
        super(name, age)
        this.number = number
        this.girlfriend = 'xiaobai'
    }
    study() {
        console.log(`${ this.name } can study`)
    }
    getWeight() {
        console.log(`${ this.weight }`)
    }
}
三 、多态 同一接口不同实现
* 同一个接口 不同表现
* JS 应用极少
* 需要结合java等语言的接口、重写、重载等功能

配置开发环境

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    mode: 'development',
    entry: {
        index: path.join(__dirname, './src/01/index.js'),
        test: path.join(__dirname, './src/02/index.js')
    },
    output: {
        path: __dirname,
        filename: './js/[name].js'
    },
    devServer: {
        contentBase: './',
        compress: true,
        port: 8888,
        host: 'localhost',
        open: true
    },
    module: {
        rules: [
            {
                test: /\.js?$/,
                exclude: /(node_modules)/,
                loader: 'babel-loader'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'public/index.html')
        })
    ]
}

// .babelrc
{
	"presets" ["es2015", "latest"],
    "plugins": ["transform-decorators-legacy"]
}

常用设计模式

工厂、单例、适配器、装饰器、代理、外观、观察者


console.log('hello world')

// 沉默是金 - 让每个程序都是过滤器

class MyPromise {
    constructor() {}
    then() {}
    resolve() {}
    reject() {}
}

function loadimg (src) {
    let promise = new Promise((resolve, reject) =>{
        let img = document.createElement('img')
        img.onload(()=> {
            resolve(img)
        })
        img.onerror(() => {
            reject('error')
        })
        img.src = src
    })
    return promise
}

console.log(new MyPromise())

// 工厂模式 将 new 单独封装 遇见 new 时 要考虑是否需要封装
// 举例 去店里购买面包 

class Product {
    constructor(name) {
        this.name = name
    }
    init() {
        console.log('init')
    }
    fn1() {
        console.log('fn1')
    }
    fn2() {}
}
class Creator {
    create(name) {
        return new Product(name)
    }
}
let creator = new Creator()
let prod = creator.create(name);
prod.init()


// 单例模式 登陆框 购物车 有且只有一个实例 用 java 来进行演示一下
// 以下则是 js 的实现 暂时可能说zhi 只能按照 文档的约束 的形式
class SingleObject {
    login(userName, password) {}
}
// 通过闭包 去存储
SingleObject.getInstance = (function(){
    let instance
    return function () {
        if(!instance) {
            instance = new SingleObject()
        }
        return instance
    }
})()

let obj1 = SingleObject.getInstance()
let obj2 = SingleObject.getInstance()
console.log('obj1 === obj2 ', obj1 === obj2)

// 单例模式的实例
// 1. 使用 Jquery 时,只有一个 $ 
// 2. 模拟登陆框

class LoginForm {
    constructor() {
        this.state = 'hide'
    }
    show() {
        if (this.state === 'hide') {
            this.state = 'show'
            console.log('show login')
        } else {
            console.log('already show')
        }
    }
    hide() {
        if (this.state === 'show') {
            this.state = 'hide'
            console.log('login hide')
        } else {
            console.log('already hide')
        }
    }
}

LoginForm.getInstance = (function() {
    let instance 
    return function() {
        if (!instance) {
            instance = new LoginForm()
        }
        return instance
    }
})()

// 测试
let login1 = LoginForm.getInstance()
let login2 = LoginForm.getInstance()
console.log('login1 === login2', login1 === login2)

// 其他的场景 购物车的
// 其他场景 vuex 和 redux 中的 store 数据需要共享 
// 验证 : 符合单一职责,只实例化一个对象

// 适配器 模式
// 1. 旧借口格式和使用者不兼容 中间加一个适配转换借口
class Adatee {
    specificRequest() {
        return '德国标准插头'
    }
}
class Target {
    constructor() {
        this.adatee = new Adatee()
    }
    request() {
        let info = this.adatee.specificRequest()
        return `${info} - 转换器 - 中国标准插头`
    }
}

let target = new Target()
console.log(target.request())

//  场景 自己封装 ajax    老借口的 形式 ==>   $.ajax() 不兼容的情况下 尽量用一层适配器模式
//  vue computed  ===> 如果你使用 的数据 并不合适的话  可以使用 computed 进行转换 这样的话,也是一种适配器的模式
// 验证: 将旧借口 和 新接口 进行来分离 符合开放封闭原则

// 装饰器模式   为对象添加新功能 不改变其原有的结构和功能  手机壳 --- 很明显就是一个体现 增加了保护套,指环扣,同时并没有影响手机的本身功能
class Circle {
    draw() {
        console.log('华画圆')
    }
}
class Decorator {
    constructor(circle) {
        this.circle = circle
    }
    draw() {
        this.circle.draw()
        this.setBorderRed(this.circle)
    }
    setBorderRed() {
        console.log('draw red border')
    }
}

//  测试
let circle = new Circle()
circle.draw()

let decorator = new Decorator(circle)
decorator.draw()

// ES7 装饰器的语法  core-decorators 
// 配置环境 安装插件   babel-plugin-transform-decorators-legacy 在babelrc文件中 plugins 添加 ‘transform-decorators-legacy’
// 装饰 类  

@mydDecorator
class A {}
// 等同于
class As {}
// A = decorator(A) || A

function mydDecorator(target) {

}

// 装饰 方法

// 测试插件是否可用
@testDec
class Demo {}

function testDec (target) {
    target.isDev = true
}
console.log('test decorator', Demo.isDev)

// 测试成功 

// 带参数的

function testParam(isDev) {
    return function(target) {
        target.isDev = isDev
    }
}

@testParam('aaa')
class Demmo {}

console.log('测试带参数 ', Demmo.isDev)

// 拓展
function mixins(...list) {
    return function(target) {
        Object.assign(target.prototype, ...list)
    }
}

const Foo = {
    foo () {
        console.log('foo')
    }
}

@mixins(Foo) 
class HaClass {
}
let obj = new HaClass()
obj.foo()

// ------
function readyonly(target, name, descriptor) {
    descriptor.writable = false
    return descriptor
}

// 装饰方法
class Person {
    constructor() {
        this.first = 'Q'
        this.last = 'M'
    }
    @readyonly
    name() {
        return `${this.first} ${this.last}`
    }

    
}

let mp = new Person()
console.log(mp.name())

// Log
function log (target, name, descriptor) {
    let oldValue = descriptor.value
    descriptor.value = function () {
        console.log(`Calling ${name} with`, arguments)
        return oldValue.apply(this, arguments)
    }
    return descriptor
}

class MyMath {
    @log
    add(a,b) {
        return a + b
    }
}
const math = new MyMath()
const resu = math.add(2, 4)
console.log('resukt - ', resu)

// core-decorators 第三方开源的库 npm i core-decorators --save 可以去尝试使用一下

// { deprecate } 废弃掉的

// 验证 将现有的对象 和 装饰器进行分离 两者独立存在  符合开放封闭原则


// 代理模式 使用者无法访问目标对象  中间加代理 通过代理做授权和控制

class RealImg {
    constructor (filename) {
        this.filename = filename
        this.loadFileFromDisk()
    }
    loadFileFromDisk() {}
    display() {
        console.log(`${this.filename}`)
    }
}

class ProxyImg {
    constructor(filename) {
        this.realImg = new RealImg(filename)
    }
    display() {
        this.realImg.display()
    }
}

// test 
let proxyImg = new ProxyImg('my name')
proxyImg.display()

//  网页 事件代理  $.proxy 做代理 ES6 做代理
let Star = {
    name: '张翰',
    age: 25,
    phone: 13234234948
}
let agent = new Proxy(Star, {
    get: function(target, key) {
        if (key === 'phone') {
            return '10010'
        }
        if (key === 'price') {
            return '15万'
        }
        return target[key]
    },
    set: function(target, key, value) {
        if (key === 'customPrice') {
            if (value < 100000) {
                throw new Error('报价太低')
            } else {
                target[key] = value
                return true
            }

        }
    }
})

// 经济人报价  最后验证 --> 代理类和目标类分离 隔离开目标类和使用者


// 对比 适配器和代理模式 

//  -------> 适配器模式 提供不同的接口 目标类你可以使用 但是不合适

//  -------> 代理模式 提供一样的接口  目标类你无权使用 能显示原有的功能,但是经过类限制或者阉割之后的

//  -------> 装饰器模式 拓展功能,原油的功能不变且能直接使用

// 外观模式 // $.juqery 的 $.ajax()

// 为子系统中的一组接口 提供一个高层接口  让使用者使用 这个高层接口
/**
 * 
 *              client 用户端
 *                  |
 *                  |
 *              Facade 集成
 *               /   |   \
 *              /    |    \
 *             A     B     C   子系统
 * 
 *   不符合单一职责 不符合开发封闭原则 要谨慎使用 不可滥用
 * 
 */

//  观察者模式  篇幅 比较大
/**
 * 发布订阅 订报纸 订牛奶
 * 一对多  一对N
 * 发布订阅的模式  点咖啡  --- 坐等 咖啡就可
 */

 /**
  * Observer { name: String ; subject: Subject  ; update() } 观察者
  * 
  * Subject { observers: Array ; state: init ; getState(): init ; setState(state) ; attach(observer) ; notifyAllObservers() } 主题
  * 主题的状态变化了 我将会把状态 通知到 观察者
  */

  class Subject {
      constructor() {
          this.state = 0
          this.observers = []
      }
      getState() {
          return this.state
      }
      setState(state) {
          this.state = state
          this.notifyAllObservers()
      }
      notifyAllObservers() {
          this.observers.forEach(observer => {
              observer.update()
          }) 
      }
      attach(observer) {
          this.observers.push(observer)
      }
  }
  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()}`)
      }
  }

//   test 一个主题 三个观察者
let sub = new Subject();
let observer1 = new Observer('01', sub)
let observer2 = new Observer('02', sub)
let observer3 = new Observer('03', sub)

sub.setState(1)
sub.setState(2)

/**
 * 场景 
 * 网页事件绑定 等待被点击
 * <div id="btn1">嗲集</div>
 * <script>
 *  $('#btn1').click(function() { console.log(1) })
 *  $('#btn1').click(function() { console.log(2) })
 * </script>
 * 
 * Promise
 * var result = loadImg(src)
 * result.then(() => {}).then(() =>{})  添加监听 Promise 的状态变化
 * 
 * Jquery callbacks 属于 jquery 内部的实现
 * 
 * var calbacks = $.Callbacks() 主注意大小写
 * callbacks.add(function(info) {
 *  console.log(info)
 * })
 * 
 * callbacks.fire('gigogo')
 * callbacks.fire('fire')
 * 
 * nodejs 自定义事件
 * 
 * EventEmiter 
 * 
 * nodejs 中 http请求
 * 
 * request.on('data', function(chunk) {
 *  data += chunk
 * })
 * 
 * 父子进程通信
 * var cp = require('child_process')
 * var n = cp.fork('./sub.js')
 * n.on('message', function(m) {
 *  console.log('aaaa', m)
 * })
 * n.send({hello: 'world'})
 * 
 * process.on('message' function(m) {
 *  console.log('Child got message:' + m)
 * })
 * 
 * process.send({foo: 'bar'})
 * 
 * 
 * vue react 组件生命周期 机制
 * 
 * render() {} 就相当于 回调
 * componentDidMount() {}
 * 
 * vue watch
 * 
 * watch: {
 *  firstName: function() {
 *      return this.name + '_aaa'
 *  }
 * }
 */

//  验证: 主题和观察者分离  不是主动触发而是被动监听 两者解耦合  符合开发封闭原则 大部分不会直接去自己实现一个发布订阅


迭代器模式和状态机模式

	import StateMachine from 'javascript-state-machine';
import $ from 'jquery';
// 迭代器模式
// 特点 : 1. 顺序访问一个集合  2. 使用者无需知道集合内部的结构(数据被封装过)
// 案例:$.each array.forEach 

function each(data) {
    var $data = $(data) // 生成迭代器
    $data.each(function(key, val) {
        console.log(key, val)
    })
}

// 使用者不需要知道集合的内部结构

/**
 * 
 * Container { list: Array  getIterator(): Iterator }
 * 
 * Iterator { list: Array index:int next():int hasNext():boolean } 
 * 
 * next() ---> 数据遍历
 * 
 * hasNext() ----> 是否有下一个节点
 * 
 */

 class Continer {
     constructor(list) {
         this.list = list
     }
    //  生成遍历器
    getIterator(){
        return new Iterator(this)
    }
 }

 class Iterator {
     constructor (container) {
        this.list = container.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
     }
 }

//  test 
var arr = [1,2,3,4,5,6]
let containerr = new Continer(arr)
let iterator = containerr.getIterator()

while(iterator.hasNext()) {
    console.log(iterator.next())
}

// 使用场景 

/**
 * 
 * 1. jQuery each
 * 
 * 2. ES6 Iterator 为何存在 实现了迭代器模式 有序集合的类型已经有很多了,统一一种方式去遍历 
 *                         Array Map Set String TypedArray arguments NodeList
 *                         Object 不是有序集合
 * 
 *    以上的数据类型都有 [Symbol.iterator]属性
 *    属性值是函数,执行函数返回一个迭代器 next()方法顺序迭代子元素
 *    Array.prototype[Symbol.iterator]
 *    Array.prototype[Symbol.iterator]() -> Array Iterator {} 
 *    Array.prototype[Symbol.iterator]().next() -> { value: undefined, done: true }
 *    并不是没一个人都会去使用 [Symbol.iterator] 所以 会有了 for ... of 他其实也是用的 iterator 去进行的迭代
 * 
 * 3. 
 * 
 */

 function myEach(data) {
     if (data[Symbol.iterator]) {
        var it = data[Symbol.iterator]()
        let item = {done: false}
        while(!item.done) {
            item = it.next()
            console.log(item.value)
        }
        // 必须有 [Symbol.iterator]
        // for (let item of data) {
        //     console.log(item)
        // }
     } else {
         throw new Error('该数据不存在迭代器')
     }
 }
 var aArr = [1,2,3,4,5,6]
 myEach(aArr)

 let maps = new Map()
 maps.set('a', 120)
 maps.set('b', 190)

 myEach(maps)

/**
 * Iterator 的价值不限于上述几种类型
 * 还有 Generator 
 * function* helloWorld() {
 *  yield 'hello';
 *  yield 'hello';
 *  return 'ending'
 * }
 * var wph = helloWorld();
 * wph.next();
 * wph.next();
 * wph.next();
 * wph.next();
 */

//  总结: 迭代器对象和目标对象分离  迭代器将使用者隔离开  符合开放封闭原则

// 状态模式  
/**
 * 
 * 一个对象有状态变化
 * 
 * 每次状态变化都触发一个逻辑
 * 
 * 不能总是 if else 控制
 * 
 * 示例: 红绿灯变化
 * 
 * State { color  handle(context) }
 * 
 * Context { state  getState(): state setState(state):void }
 * 
 */
// 红灯 绿灯 黄灯
class State {
    constructor(color) {
        this.color = color
    }
    handle(context) {
        console.log('turn to ' + this.color)
        context.setState(this)
    }
}
// 主体
class Context {
    constructor(){
        this.state = null
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
    }
}

// test 
let context = new Context()

let red = new State('red')
let green = new State('green')
let yellow = new State('yellow')

// 绿灯亮
green.handle(context)
context.getState()

// 红灯亮
red.handle(context)
context.getState()

// 黄灯亮
yellow.handle(context)
context.getState()

/**
 * 
 * 有限状态机 javascript-state-machine 库
 * 有限个状态、以及在这些状态之间变化  -- 交通灯
 * 
 * 简单的 Promise 实现
 * 
 */
let fsm = new StateMachine({
    init: '收藏',
    transitions: [
        {
            name: 'doStore',
            from: '收藏',
            to: '取消收藏'
        },
        {
            name: 'delStore',
            from: '取消收藏',
            to: '收藏'
        },
    ],
    methods:{
        onDoStore: function() {
            updateText()
        },
        onDelStore: function() {
            updateText()
        }
    }
})

let btn = $('#btn')
btn.click(function() {
    if (fsm.is('收藏')) {
        fsm.doStore()
    } else {
        fsm.delStore()
    }
})

function updateText() {
    btn.text(fsm.state)
}
// init text
updateText()

// 简单的 Promise

/**
 * 三种状态 pending fullfilled rejected z状态不可逆
 */
var mfsm = new StateMachine({
    init: 'pending',
    transitions: [
        {
            name: 'resolve',
            from: 'pending',
            to: 'fullfilled'
        },
        {
            name:'reject',
            from: 'pending',
            to:'rejected'
        }
    ],
    methods: {
        onResolve: function(state, data) {
            data.successList.forEach(fn => fn())
        },
        onReject: function( state, data) {
            data.failList.forEach(fn => fn())
        }
    }
})

 class TPromise {
     constructor(fn) {
         this.successList = []
         this.failList = []
         fn(() => {
            mfsm.resolve(this)
         },() => {
            mfsm.reject(this)
         })
     }
     then(successFn,failFn) {
         this.successList.push(successFn)
         this.failList.push(failFn)
     }
 }

//  test
function loadImage(src) {
    const prom = new TPromise(function(resolve,reject) {
        let img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject()
        }
        img.src = src
    })
    return prom
}
let srcs = 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1593106255,4245861836&fm=26&gp=0.jpg'
let resss = loadImage(srcs)
resss.then(function() {
    // success
    console.log('ok')
}, function() {
    //fail
    console.log('fail')
})

// 状态变化