LOADING

MiniKano的小窝


 

JavaScript设计模式

整完了vue3之后忽然发现好像没有系统性学习过设计模式这方面的知识,所以就有了这篇文章 :鹿乃_OK:

构造器模式(Constructor Pattern)

这个模式在面向对象的语言中很常见,但咱是在JavaScript的基础上进行解释的,所以还是得翻出来讲一遍

先直接举个例子吧:

//很臃肿
let person1 = {
    name: 'kano',
    age: 11
}
let person2 = {
    name: 'kano1',
    age: 18
}
let person3 = {
    name: 'kano1',
    age: 18
}
//构造器模式-commonJs
function Person(name, age) {
    this.name = name
    this.age = age
}
//构造器模式-ES6+
class Person1 {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}
let kano = new Person('kano', 18)
let kano1 = new Person1('kano1', 19)

简而言之就是将对象生成的方式交给构造函数给我们解决,这样可以大大提高代码的复用率

原型模式(Prototype Pattern)

原型模式也算是Javascript中特有的,作用就是将功能单一的方法挂载到原型对象中,以达到节省实例化后多余内存占用的问题

//commonJs
function Person(name, age) {
    this.name = name
    this.age = age
    //这个方法功能单一重复,可以剥离出来,可以减少内存占用
    // this.say() {
    //     console.log(this.name + '在说话');
    // }
}
Person.prototype.say = function () {
    console.log(this.name + '在说话');
}
let p = new Person('kano', 18)
p.say()

//ES6+
class Person1 {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    say() {
        console.log(this.name + '在说话');
    }
}
//es6会自动将方法挂载到原型对象上,无需手动操作(点赞

Tab栏小例子

demo下载地址:https://kanokano.cn/wp-content/uploads/2023/02/02.html

//tab导航栏的应用
function Tabs(selector, type) {
    this.selector = document.querySelector(selector)
    this.headerItems = this.selector.querySelectorAll('.header li')
    this.boxItems = this.selector.querySelectorAll('.box li')
    this.type = type
    console.log(this.headerItems, this.boxItems, this.type);
    this.change()
}
Tabs.prototype.change = function () {
    // 注意要用let,var的作用域不严格
    for (let i = 0; i < this.headerItems.length; i++) {
        // 注意箭头函数的指向
        this.headerItems[i].addEventListener(this.type, () => {
            //removeAll
            for (let m = 0; m < this.headerItems.length; m++) {
                this.headerItems[m].classList.remove('active')
                this.boxItems[m].classList.remove('active')
            }
            this.headerItems[i].classList.add('active')
            this.boxItems[i].classList.add('active')
        }, false)
    }
}
let tab1 = new Tabs('.container1', 'click')
let tab2 = new Tabs('.container2', 'mouseover')

工厂模式(Factory Pattern)

由一个工厂对象决定创建某一种产品对象类的实例,就叫做工厂模式
主要是用来创建同一类对象用

举个实际开发中的例子:如果要开发一个后台管理系统的前端项目,我们通常会遇到用户角色处理的问题,不同角色的用户,在侧面板显示的功能也不一样,我们可以使用工厂模式的方法编写代码

class User {
    constructor(role, pages) {
        this.role = role
        this.pages = pages
    }
    static UserFactory(role) {
        switch (role) {
            case "superadmin":
                return new User('superadmin', ['home', 'user-manage', 'right-manage', 'news-manage'])
                break
            case "admin":
                return new User('admin', ['home', 'user-manage', 'news-manage'])
                break
            case "editor":
                return new User('editor', ['home', 'user-manage'])
                break
            default:
                throw new Error('参数不正确')
        }
    }
}
let superAdmin = User.UserFactory('superadmin')
let admin = User.UserFactory('admin')
let editor = User.UserFactory('editor')
console.log(superAdmin);
console.log(admin);
console.log(editor);

抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式并不会直接产生实例,而是用于对产品类集合的创建
一般在比较大的工程项目中使用
和一般的工厂模式相比,抽象工厂模式能够描述一些功能类似但是实现方式不一样的方法,并且把他们抽象出来进行批量产出

class User {
    constructor(name) {
        this.name = name
    }
    welcome() {
        console.log('欢迎:', this.name);
    }
    // 在Typescript中可以使用abstract关键字,js中并不可以
    showData() {
        throw new Error('抽象方法不允许直接调用')
    }
}
class Editor extends User {
    constructor(name) {
        super(name)
        this.role = 'editor'
        this.pages = ['home', 'user-manage', 'right-manage', 'news-manage']
    }
    showData() {
        console.log('Editor show');
    }
}
class Admin extends User {
    constructor(name) {
        super(name)
        this.role = 'admnin'
        this.pages = ['home', 'user-manage', 'news-manage']
    }
    showData() {
        console.log('admnin show');
    }
}
function getAbstractUserFactory(role) {
    switch (role) {
        case 'admin':
            return Admin
        case 'editor':
            return Editor
        default:
            throw new Error('参数不正确')
    }
}
//返回一个类
let adminClass = getAbstractUserFactory('admin')
let admin = new adminClass('kano')
console.log(admin);

建造者模式(Builder Pattern)

建造者模式 属于创建型模式的一种,提供一种创建复杂对象的方式。它将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式 是一步一步的创建一个复杂的对象,它允许用户只通过指定复杂的对象的类型和内容就可以构建它们,用户不需要指定内部的具体构造细节。

class Navbar {
    init() {
        console.log('navbar初始化');
    }
    getData() {
        console.log('navbar-getData');
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve('resolved-Navbar-getData')
            }, 1000);
        })
    }
    render() {
        console.log('navbar-render')
    }
}
class List {
    init() {
        console.log('List初始化');
    }
    getData() {
        console.log('List-getData');
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve('resolved-List-getData')
            }, 1000);
        })
    }
    render() {
        console.log('List-render')
    }
}
// 建造者
class Operator {
    static async startBuild(builder) {
        await builder.init()
        await builder.getData()
        await builder.render()
    }
}
const navbar = new Navbar()
const list = new List()
Operator.startBuild(navbar)
Operator.startBuild(list)

建造者模式可以将一个复杂对象的构建层和表示层互相分离,建造者模式关心的是创建这个对象的整个过程,甚至是创建对象的具体细节

单例模式(Singleton pattern)

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

总结起来就是两点:

  1. 保证一个类仅有一个实例,并提供一个访问它的全局访问点
  2. 主要解决一个全局使用的类频繁地创建和销毁,占用内存
//commonJs
let Singleton1 = (function () {
    //闭包
    let instance
    function User(name, age) {
        this.name = name
        this.age = age
    }
    return function (name, age) {
        if (!instance) {
            //创建实例
            instance = new User(name, age)
        }
        return instance
    }
})()
//true
console.log(new Singleton1('kano', 11) === new Singleton1('kano1', 12));

//es6
class Singleton {
    constructor(name, age) {
        if (!Singleton.instance) {
            this.name = name
            this.age = age
            Singleton.instance = this
        }
        // 构造器里返回的是对象实例,那new的时候接收的就是返回的实例
        // 构造器里返回的是其他的值,默认返回构造器所在的类实例
        return Singleton.instance
    }
}
//true
//创建过一次就第二次就不会再执行实例化操作了,节省很多内存
console.log(new Singleton('kano', 11) === new Singleton('kano1', 12));

装饰器模式(Decorator Pattern)

装饰器模式能够非常好的对已有方法的功能进行拓展,这样就可以在不更改原有的代码,对其他的业务产生影响,方便我们在较少的改动下对软件的功能进行拓展(低耦合,高内聚)

装饰器模式示例

在JavaScript中,我们可以通过在function的原型对象中挂载before和after方法的方式,进行装饰器模式的实现

// 装饰器模式
Function.prototype.before = function (beforeFn) {
    let _this = this
    return function () {
        //先进行前置函数调用
        beforeFn.apply(this, arguments)
        //再执行原来的函数
        return _this.apply(this, arguments)
    }
}
Function.prototype.after = function (afterFn) {
    let _this = this
    return function () {
        //先执行原来的函数
        let result = _this.apply(this, arguments)
        //再进行后置函数调用
        afterFn.apply(this, arguments)
        return result
    }
}

使用装饰器

这里before返回的也是一个function,因为我们在Function原型对象上挂载了before和after
这样就可以实现链式调用

function test() {
    console.log('主任务');
}
let Fun = test.before(() => {
    console.log('我是before注入的函数');
}).after(() => {
    console.log('我是after注入的函数');
})
Fun()

装饰器模式在实际中的应用

例如Ajax,如果我们想在一部分请求中挂入JWT Token,我们就可以使用装饰器模式很方便的挂载Token
Axios 的拦截器也是是利用了装饰器模式

//假如这是原装的ajax
function ajax(url, method, params) {
    console.log(url, method, params);
}
//这是带token前置的ajax
let ajax1 = ajax.before((url, method, params) => {
    params.token = '1dsfkjhsfj';
})
//带token
ajax1('/api', 'post', {
    name: 'kano'
})
//不带token
ajax('/api', 'post', {
    name: 'kano'
})

适配器模式(Adapter Pattern)

将一个类的接口转换成用户希望的另一个接口,这就适配器模式
适配器模式让那些接口不兼容的类可以一起工作

举一个很常见的例子:

// 适配器模式,举一个小例子
class AC220 {
    use220() {
        console.log('使用220v交流电');
    }
}
class AC120 {
    use120() {
        console.log('使用120v交流电');
    }
}
//封装一个交流电源适配器,去适配220v电压
class JP2CH_Adapter extends AC120 {
    constructor() {
        super()
    }
    use220() {
        // 变压操作
        this.use120()
    }
}

//在中国,只有220v电压
function useElec(elec) {
    elec.use220()
}
//使用适配转换器
useElec(new JP2CH_Adapter())
//正常插电
useElec(new AC220())

如你所见,适配器模式的原理其实就是生活中常见的“适配器”运作方式
在实际编程中,如果有两套不一样的接口需要交替使用,就可以给其中一个接口定制一个适配器,这样就可以使用相同的API来操作两套不同的接口集了

策略模式(Strategy Pattern)

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,而且如果以后有改动,也不会影响使用算法的用户;策略模式对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理,总之这个模式的扩展性非常好

这个模式在前后端分离开发中还是用的比较多的,下面直接用一个例子来解释:

<ul id="mylist"></ul>
<script>
        //后端返回来的数据
        let list = [{
            title: '好耶!',
            type: 1
        }, {
            title: '前方高能',
            type: 2
        }, {
            title: '坏',
            type: 3
        }, {
            title: '开口跪',
            type: 1
        }]
//后端返回回来的规则
let status = {
    '1': {
        title: '已通过',
        color: 'red'
    },
    '2': {
        title: '审核中',
        color: 'pink'
    },
    '3': {
        title: '已驳回',
        color: 'skyblue'
    }
}
//前端渲染
mylist.innerHTML = list.map(item => {
    return `
    <li>
         <div>${item.title}</div>
         <div style="background:${status[item.type].color}">${status[item.type].title}</div>
    </li>
            `
}).join('')
</script>

代理模式(Proxy Pattern)

代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。
代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。

ES6中就有Proxy,完整地实现了代理模式

let obj = {}
let proxy = new Proxy(obj,{
    get(target,key){
        console.log('get',target[key])
        return target[key]
    },
    set(target,key,value){
        console.log('set',target,key,value)
        if(key === 'data'){
            box.innerHTML = value
        }
        target[key] = value
    }
})

观察者模式

观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者
一旦观察目标的状态发生改变,所有的观察者都将得到通知。

优势:目标和观察者的功能耦合度降低,可以专注于自身的功能逻辑,观察者被动接受更新,时间上可以解耦,实时接受目标者的更新状态

缺点:观察者模式虽然实现了对象的依赖关系的低耦合,但却不能对事件通知进行功能细分管理,比如,我想筛选一个特定的事件信息,观察者模式就不好实现,因为观察者模式是“牵一发而动全身”的全局模式,而且观察者模式实现的前提是目标和观察者要互相知晓对方的存在,才能建立联系

当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
观察者模式的运作方式类似于vue中的watch

class Subject {
    constructor() {
        this.observers = []
    }
    add(observer) {
        this.observers.push(observer)
    }
    off(observer) {
        this.observers = this.observers.filter(item => item != observer)
    }
    notify() {
        this.observers.forEach(item => {
            item.update()
        })
    }
}
//观察者
class Observer {
    constructor(name) {
        this.name = name
    }
    update() {
        //这里可以处理核心业务
        console.log('update', this.name);
    }
}

const subject = new Subject()
const observer1 = new Observer('kano')
const observer2 = new Observer('kano1')
subject.add(observer1)
subject.add(observer2)
// 发送通知
subject.off(observer1)
subject.notify()

实际运用(面包屑导航)

观察者模式在实际运用也是比较多的,比如后台管理系统的菜单栏和面包屑之间的通信,面包屑就可以作为一个Observer观察者,只要用户点击了菜单栏,菜单栏就会通知观察者,观察者就会做出相应的改变

<style>
    body {
        margin: 0;
    }

    .box {
        display: flex;
        width: 500px;
        height: 500px;
        background-color: pink;
        overflow: hidden;
    }

    .menu ul {
        list-style: none;
        padding-left: 0;
    }

    .menu ul li {
        width: 100px;
        text-align: center;
        background-color: #fff;
        margin: 10px;
    }
</style>
<body>
    <header class="header">

    </header>
    <div class="box">
        <div class="menu">
            <ul class="left">
                <li>首页</li>
                <li>用户</li>
                <li>系统</li>
            </ul>
        </div>
        <div class="bread">
        </div>
    </div>
    <script>
        class Subject {
            constructor() {
                this.observers = []
            }
            add(observer) {
                this.observers.push(observer)
            }
            off(observer) {
                this.observers = this.observers.filter(item => item != observer)
            }
            notify(text) {
                this.observers.forEach(item => {
                    item.update(text)
                })
            }
        }
        //观察者
        class Observer {
            constructor(name) {
                this.element = document.querySelector(name)
                this.items = []
            }
            update(text) {
                if (this.items.includes(text)) {
                    let index = this.items.indexOf(text)
                    this.items.splice(index, this.items.length - index)
                }
                this.items.push(text)
                this.element.innerHTML = this.items.join('=>')
            }
        }
        const subject = new Subject()

        //bread和head作为观察者
        const observer = new Observer('.bread')
        const observer2 = new Observer('.header')

        subject.add(observer)
        subject.add(observer2)

        let ol = document.querySelectorAll('.left li')
        for (let i = 0; i < ol.length; i++) {
            ol[i].onclick = function (e) {
                subject.notify(e.target.innerText)
            }
        }
    </script>
</body>

发布订阅模式

"发布订阅模式是常用的一种观察者的模式的实现,并且从解耦和重用角度来看,优于典型的观察者模式。"

发布订阅模式区别于观察者模式。在发布订阅模式中订阅者和发布者不需要互相知道对方的身份,且发布订阅模式可以进行事件的筛选,也就是可以自主的选择要订阅的消息

因为之前有写过发布订阅模式的代码,所以这次使用之前写好的Typescript进行演示吧(逃

//发布订阅模式演示

//接口规范
interface Eventt {
    on: (name: string, fn: Function) => void,
    emit: (name: string, ...args: Array<any>) => void,
    off: (name: string, fn: Function) => void,
    once: (name: string, fn: Function) => void,
}
interface List {
    [key: string]: Array<Function>
}

//订阅模式类实现
class Dispatch implements Eventt {
    list: List
    constructor() {
        this.list = {}
    }
    //发布消息
    on(name: string, fn: Function) {
        const callback = this.list[name] || []
        callback.push(fn)
        this.list[name] = callback
        console.log(this.list);
    }
    //订阅消息
    emit(name: string, ...args: Array<any>) {
        let eventName = this.list[name]
        if (eventName) {
            eventName.forEach((fn: Function) => {
                fn.apply(this, args)
            })
        } else {
            console.error('没有找到订阅');
        }
    }
    //取消订阅
    off(name: string, fn: Function) {
        let eventName = this.list[name]
        if (eventName && fn) {
            //寻找函数所在下标
            let index = eventName.findIndex(fns => fns === fn)
            eventName.splice(index, 1)
        } else {
            console.error('没有找到订阅');
        }
    }
    //单次订阅
    once(name: string, fn: Function) {
        // 包裹一层函数
        let tempfn = (...args: Array<any>) => {
            //调用完之后删除
            fn.apply(this, args)
            this.off(name, tempfn)
        }
        this.on(name, tempfn)
    }
}

const o = new Dispatch()

o.on('post', (...args: Array<any>) => {
    console.log(args, 1);
})
o.on('post', (...args: Array<any>) => {
    console.log(args, 2);
})
let fn = (...args: Array<any>) => {
    console.log(args, 3);
}
o.on('post', fn)
// o.off('post', fn)
o.once('post1', fn)
o.emit('post1', 1, 'kano', { ddd: true })

模块模式(Module Pattern)

模块化模式最初被定义为在传统软件工程中为类提供私有和公共封装的一种方法
能够使一个单独的对象拥有公共、私有的方法和变量,从而屏蔽来自全局作用域的特殊部分,这可以减少我们的函数名在页面中其他脚本区域内定义的函数名冲突的可能性

1.闭包

 //闭包
let test = (() => {
    let kano = 'kano'
    return {
        get() {
            return kano
        },
        set(val) {
            kano = val
        }
    }
})()
console.log(test.get());
test.set('111')
console.log(test.get());

这里提一句,ES11已支持私有属性,写法如下:

// JS中私有属性 就是#开头的 
class Person{
    #name = 'kano'
}
console.log(new Person().#name);//无法访问

2.模块化

这个就是老演员了,ES6的模块化也可以隔离属性和变量,在这里就用默认导出做演示吧

//1.js
export default {
    name: 'module',
    test() {
        return 1
    }
}

//2.js
import module1 from '1.js'
console.log(module1);

module模式使用了闭包封装"私有"状态和组织。它提供了一种包装混合公有/私有方法和变量的方式,防止起泄露至全局作用域,并与别的开发人员的接口发生冲突。通过该模式,只需要返回一个公有的API,而其他的一切则都维持在私有闭包里。

桥接模式(Interface Pattern)

桥接模式:将抽象的部分与他实现的部分分离,使他们都可以独立地变化
使用场景:一个类存在两个或者多个独立变化的维度,且这两个维度都需要进行扩展

其实就是接口(interface)模式(

优点:把抽象和部分和实现部分隔离开,有助于独立地管理各个组成部分

缺点:每次使用一个桥接元素都需要多一次函数调用,这样会对程序的性能造成负面影响

可以用一个Animation库的例子来解释:

//动画类型具体实现
const Animations = {
    bounce: {
        show(ele) {
            console.log(ele, "弹跳显示");
        },
        hide(ele) {
            console.log(ele, "弹跳隐藏");
        }
    },
    slide: {
        show(ele) {
            console.log(ele, "滑动显示");
        },
        hide(ele) {
            console.log(ele, "滑动隐藏");
        }
    },
    rotate: {
        show(ele) {
            console.log(ele, "旋转显示");
        },
        hide(ele) {
            console.log(ele, "旋转隐藏");
        }
    }
}

//弹出框类型
function Toast(ele, animation) {
    this.ele = ele
    this.animation = animation
}
Toast.prototype.show = function (ele) {
    //抽象
    this.animation.show(ele)
}
Toast.prototype.hide = function (ele) {
    //抽象
    this.animation.hide(ele)
}

let tt = new Toast('div1',Animations.bounce)
tt.show('div')
setTimeout(()=>{
    tt.hide('div')
},1000) 

上述Animations就是弹出框动画的具体实现,弹出框类型里的弹出动画是抽象的,这样的代码更清晰有条理,假如需要开发一个动画\其他库,将来在适配新功能和重构的时候会节省很多代码量

组合模式(Composite Pattern)

组合模式在对象间形成树形结构
组合模式中基本对象和组合对象被一致对待;
无须关心对象有多少层,调用时只需在根部进行调用;

它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

这种模式在树形菜单生成和控制方面比较常用

//文件夹
function Folder(folder) {
    this.folder = folder
    this.list = []
}
Folder.prototype.add = function (res) {
    this.list.push(res)
}
Folder.prototype.scan = function (deep = "    ") {
    console.log(deep + '扫描文件夹:', this.folder);
    for (let i = 0; i < this.list.length; i++) {
        this.list[i].scan(deep + "    ")
    }
}
//文件
function File(file) {
    this.file = file
    this.list = []
}
File.prototype.add = function (res) {
    this.list.push(res)
}
File.prototype.scan = function (deep = "    ") {
    console.log(deep + '扫描文件:', this.file);
    for (let i = 0; i < this.list.length; i++) {
        this.list[i].scan(deep + "    ")
    }
}
//文件夹
let rootFolder = new Folder('rootFolder')
let htmlFolder = new Folder('htmlFolder')
let cssFolder = new Folder('cssFolder')
let jsFolder = new Folder('jsFolder')
let jsFolderInter = new Folder('jsFolderInter')
//文件
let html1 = new File('html1')
let html2 = new File('html2')
let css = new File('css')
let js = new File('js')

rootFolder.add(htmlFolder)
rootFolder.add(cssFolder)
rootFolder.add(jsFolder)
jsFolder.add(jsFolderInter)

htmlFolder.add(html1)
htmlFolder.add(html2)
cssFolder.add(css)
jsFolder.add(js)
jsFolderInter.add(js)
jsFolderInter.add(html1)

rootFolder.scan()
//输出结果:
// 扫描文件夹: rootFolder 
// 扫描文件夹: htmlFolder
//     扫描文件: html1
//     扫描文件: html2
// 扫描文件夹: cssFolder
//     扫描文件: css
// 扫描文件夹: jsFolder
//     扫描文件夹: jsFolderInter
//         扫描文件: js
//         扫描文件: html1
//     扫描文件: js

命令模式

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。需要一种松耦合的方式来设计程序,使得发送者和接收者能够消除彼此之间的耦合关系。

命令模式由三种角色构成:

  1. 发布者invoker(发出命令,调用命令对象,不知道如何执行,和谁一起执行)
  2. 接收者receiver(提供对应接口处理请求,不知道谁发起的请求)
  3. 命令对象command(接受命令,调用接收者对应接口处理发布者的请求)
class Receiver {
    //接受类
    execute(){
        console.log('接收者执行请求');
    }
}
class Command{
    constructor(receiver){
        this.receiver = receiver
    }
    //命令类
    execute(){
        console.log('命令对象=>接收者如何处理');
        this.receiver.execute()
    }
}
class Invoker{
    //发布类
    constructor(command){
        this.command = command
    }
    order(){
        console.log('发布请求');
        this.command.execute()
    }
}
const order = new Command(new Receiver())
const client = new Invoker(order)
client.order()

模板方法模式(Template method pattern)

模板方法模式由两部分组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方达的执行顺序。子类通过继承这个抽象类,也继承了整个算结构,并且可以选择重写父类的方法。(类似面向对象语言中的抽象类)

我觉得Vue2.x的options API写法就是使用了模板方法模式(虽然没有去看过v2的源码

//抽象类的es5写法
let Container = function (params = {}) {
    let F = function () {
    }
    F.prototype.init = function () {
        this.getData()
        this.render()
    }
    F.prototype.getData = params.getData || function () {
        throw new Error('抽象方法需要实例化')
    }
    F.prototype.render = params.render || function () {
        throw new Error('抽象方法需要实例化')
    }
    return F
}

let C1 = Container({
    getData() {
        console.log('getData');
    },
    render() {
        console.log('render');
    }
})
let c = new C1()
c.init()

模板方法模式时一种典型的通过封装变化提高系统扩展性的设计模式。运用了模板方法模式的程序中,子类方法种类和执行顺序都是不变的,但是子类的方法具体实现则是可变的。父类是个模板,子类可以添加,就增加了不同的功能。

迭代器模式(Iterator Pattern)

迭代器模式是指的提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑里面分离出来,在使用迭代器模式后,即使不关心对象的内部构造也可以按顺序访问其中每一个元素

  1. 为便利不同数据结构的“集合”提供统一的接口
  2. 能便利访问‘集合’数据中的项,不关心项的数据结构

下面演示了一个简单的迭代器模式实现:

let keyEach = function (arr, callback) {
    for (let i = 0; i < arr.length; i++) {
        callback(i, arr[i])
    }
}
//外部调用
keyEach([11, 222, 333, 444, 555], function (index, value) {
    console.log([index, value]);
})

在ES6以后,新增了一个Symbol.iterator迭代器,Array Map set String arguments NodeList 均可使用for of遍历
但是普通对象不可以使用for of遍历
有些JavaScript组件中的一些对象的嵌套层数很深,如果想取里面的值遍历出来,会很麻烦,手动实现的迭代器可以比较完美地解决这个问题(

文档: Symbol.iteratorSymbol

//让不支持迭代器的对象支持for of遍历
let obj = {
    code: 200,
    name: 'ok',
    list: ["111", '222', '33333'],
    [Symbol.iterator]: function () {
        let index = 0;
        return {
            next: () => {
                if (index < this.list.length) {
                    return {
                        value: this.list[index++],
                        done: false
                    }
                } else {
                    return {
                        value: undefined,
                        done: true
                    }
                }
            }
        }
    }
}
for (let key of obj) {
    console.log(key);
}

职责链模式(Chain of Responsibility Pattern)

使多个对象都有机会处理请求,从而避免了请求的发送者和多个接收者直接的耦合关系,将这些接收者连接成一条链,顺着这条链传递请求,直到能找到处理请求的对象
JavaScript中的原型链也是职责链模式的实现

优点:

  1. 符合单一职责,使每个方法中都只有一个职责
  2. 符合开放封闭原则,在需求增加的时候也可以很方便的扩充新的责任
  3. 使用的时候不需要知道谁才是真正的处理方法,减少了大量的分支语句

缺点:

  1. 团队成员需要最责任链存在共识,否则当看到一个方法返回一个next的时候可能会感到困惑
  2. 出错时候不好排查原因,因为责任链中任何一个步骤出错,都需要从链头一一排查,增加维护难度
submit.onclick = () => {
    checks.check()
}
//职责链测试
const checkEmpty = () => {
    if (passwd.value == '') {
        console.log('不能为空');
        return 'err'
    } else {
        return 'ok'
    }
}
const checkNum = () => {
    if (Number.isNaN(+passwd.value)) {
        console.log('请输入数字!');
        return 'err'
    } else {
        return 'ok'
    }
}
const checkLength = () => {
    if (passwd.value.length < 6) {
        console.log('输入必须大于六位!');
        return 'err'
    } else {
        return 'ok'
    }
}

class Chain {
    constructor(fn) {
        this.checkRule = fn
        this.nextRule = null
    }
    addRule(nextRule) {
        this.nextRule = new Chain(nextRule)
        //return出去,构成链式调用
        return this.nextRule
    }
    check() {
        this.checkRule() == 'ok' ? this.nextRule.check() : false
    }
}

const checks = new Chain(checkEmpty)
checks.addRule(checkNum).addRule(checkLength).addRule(() => 'end')
点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注