0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

js设计模式(上)

修罗 2020-10-06 1431 0条评论 JS

首页 / 正文

js设计模式

面向对象编程

面向对象是一种程序设计思想,与之相对的编程思想叫做面向过程。

比如我们想要用代码描绘这样一个场景,有一只叫做张三的猫,吃了一个苹果,又吃了一条鱼,然后又有一只叫做李四的猫,吃了一根香蕉。

  • 面向过程
function zhangsanEatApple(){}
function zhangsanEatFish(){}
function lisiEatBanana(){}

zhangsanEatApple()
zhangsanEatFish()
lisiEatBanana()
  • 面向对象
function Cat(name){
    this.name = name
}
Cat.prototype.eat = function(something){}

var zhangsan = new Cat('zhangsan')
var lisi = new Cat('lisi')

zhangsan.eat('apple');
zhangsan.eat('fish');
lisi.eat('banana');

工厂模式

工厂模式是一种比较简单的设计模式,主要使用来创建对象用的

批量生产对象

function createPerson(name){
    // 创建对象
    var o = {};
    o.name = name;
    o.getName = function(){
        console.log(this.name)
    }
    // 返回对象
    return o;
}

把构造放在工厂的原型上

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name)
}

function Car(model){
    this.model = model;
}
Car.prototype.getModel = function(){
    console.log(this.model)
}

function create(type, param){
    // 后面的构造函数原型,是不是存在前面这个对象的原型链里
    if(this instanceof create){
        return new this[type](param);
    }
    // 直接调用create函数的情况
    return new create(type, param)
}
create.prototype = {
    person: Person,
    car: Car
}

var person1 = new create('person', 'zhang san')
var car1 = create('car', 'Benz')
person1.getName();

建造者模式

  • 建造者模式注重创建对象的细节,使用这种模式创建出的复杂对象或者复合对象结构会非常清晰。

比如说一家招聘中介公司,现在有一批纸质简历或者简历数据信息,为了做成统一的电子版简历,我们需要将这些信息进行整理,我们如何去做呢?

var data = [
    {
        name: 'zhang san',
        age: 23,
        work: 'engineer'
    },
    {
        name: 'li si',
        age: 33,
        work: 'teacher'
    },
    {
        name: 'wang wu',
        age: 22,
        work: 'xxx'
    }
]

方法一:定义一个函数,对数据处理

缺点:可读性差,全部挤在一起,结构性差

function Candidate(param){
    var _candidate = {}
    _candidate.name = param.name
    _candidate.age = param.age
    
    _candidate.firstName = _candidate.name.split(' ')[0]
    _candidate.secondName = _candidate.name.split(' ')[1]
    
    _candidate.work = {}
    
    switch(param.work){
        case 'engineer':
            _candidate.work.name = '工程师'
            _candidate.work.description = '热爱编程'
            break
        case 'teacher':
            _candidate.work.name = '老师'
            _candidate.work.description = '灵魂工程师'
            break
        default:
            _candidate.work.name = param.work
            _candidate.work.description = '无'
    }
    
    // 修改工作名
    _candidate.work.changeWork = function(newWork){
        // work对象的name
        this.name = newWork
    }
    // 修改工作描述
    _candidate.work.changeDes = function(des){
        this.description = res
    }
    return _candidate
}

// 调用函数
var candidateArr = []
for(let i = 0;i < data.length; i++){
    candidateArr[i] = Candidate(data[i])
}
console.log(candidateArr[0])

方法二:建造者模式

function Candidate(param){
    // 对类抽象和封装,代码更清晰
    var _candidate = new Person(param)
    
    _candidate.name = new Person(param)
    _candidate.work = new CreateWork(param.work)
    
    return _candidate
}
function Person(param){
    this.name = param.name
    this.age = param.age
}
function CreateName(name){
    this.wholeName = name
    this.firstName = name.split(' ')[0]
    this.secondName = name.split(' ')[1]
}
function CreateWork(work){
    switch(work){
        case 'engineer':
            this.name = '工程师'
            this.description = '热爱编程'
            break
        case 'teacher':
            this.name = '老师'
            this.description = '灵魂工程师'
            break
        default:
            this.name = param.work
            this.description = '无'
    }
}
CreateWork.prototype.changeWork = function(work){
    this.name = work
}
CreateWork.prototype.changeDes = function(des){
    this.description = des
}

// 使用
var candidateArr = []
for(let i = 0;i < data.length; i++){
    candidateArr[i] = Candidate(data[i])
}
console.log(candidateArr[0])

单例模式

单例模式就是希望创建对象的时候,我们无论创建多少次对象都指向同一个

一、使用一个变量缓存创建的对象

1601904658537.png

二、闭包

1601904900528.png

装饰器模式

装饰器模式,希望在不改变原对象的基础上,通过对其拓展功能和属性来实现更复杂的逻辑

例:4s在卖一种车,价格为10万元,如果用户需要在此基础上加装一些配置则需要加钱,比如加热座椅需要2万元,电动后视镜需要0.8王元等等。

普通写法

1601905296085.png

装饰器写法,没有破坏原来的构造函数

1601905453881.png

组合模式

​ 将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

应用

虚拟DOM

Vue、React用render函数渲染虚拟DOM。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        div{
            border:1px solid red;
        }
        p{
            border:1px solid blue;
        }
    </style>
</head>
<body>
<div id="root"></div>
<script>
    let vnode = {
        type: 'div',
        attrs:{
            id: 'div2',
            className: 'container'
        },
        children:[
            {
                type: 'p',
                attrs: {},
                children: ['123']
            },
            {
                type: 'p',
                attrs: {},
                children: ['456']
            }
        ]
    }
    function render(vnode, parent){
        let element;
        if(typeof vnode == 'object'){
            let { type, attrs, children = [] } = vnode;
            element = document.createElement(type);
            for(let attr in attrs){
                if(attr == 'className'){
                    element.setAttribute('class', attrs[attr]);
                }else{
                    element.setAttribute(attr, attrs[attr]);
                }
            }
            // 递归调用
            for(let child of children){
                render(child, element);
            }
        }else{
            element = document.createTextNode(vnode);
        }
        // 将构造的元素追加到第二个参数里面
        parent.appendChild(element);
    }
    render(vnode, document.getElementById('root'));
</script>
</body>
</html>

宏命令

  宏命令对象包含了一组具体的子命令对象,不管是宏命令对象,还是子命令对象,都有一个execute方法负责执行命令。

let closeDoorCommand = {
    execute: function(){
        console.log( '关门' )
    }
}
let openPcCommand = {
    execute: function(){
        console.log( '开电脑' )
    }
}
let openQQCommand = {
    execute: function(){
        console.log( '登录QQ' )
    }
}
let MacroCommand = function(){
    return {
        commandsList: [],
        add: function( command ){
            this.commandsList.push( command )
        },
        execute: function(){
            for ( let i = 0, command; command = this.commandsList[ i++ ]; ){
                command.execute()
            }
        }
    }
}
let macroCommand = MacroCommand()
macroCommand.add( closeDoorCommand )
macroCommand.add( openPcCommand )
macroCommand.add( openQQCommand )
macroCommand.execute()

宏命令中包含了一组子命令,它们组成了一个树形结构,这里是一棵结构非常简单的树。

1601954999407.png

  其中,marcoCommand被称为组合对象,closeDoorCommand、openPcCommand、openQQCommand都是子对象。在macroCommand的execute方法里,并不执行真正的操作,而是遍历它所包含的子对象,把真正的execute请求委托给这些子对象。macroCommand表现得像一个命令,但它实际上只是一组真正命令的“代理”。并非真正的代理,虽然结构上相似,但macroCommand只负责传递请求给子对象,它的目的不在于控制对子对象的访问。
 组合模式将对象组合成树形结构,以表示“部分——整体”的层次结构。除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
 1、表示树形结构。组合模式的优点:提供了一种遍历树形结构的方案,通过调用组合对象的execute方法,程序会递归调用组合对象下面的子对象的execute方法。组合模式可以非常方便地描述对象部分——整体层次结构。
 2、利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。

扫描文件夹

  文件夹和文件之间的关系,非常适合用组合模式来描述。文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组合成一棵树,组合模式在文件夹的应用中有以下两层好处。

 1. 组合模式让Ctrl+V、Ctrl+C成为了一个统一的操作。例如,在移动硬盘里找到了一些电子书,想把它们复制到F盘中的学习资料文件夹。在复制这些电子书的时候,并不需要考虑这批文件的类型,不管它们是单独的电子书还是被放在了文件夹中。
 2. 用杀毒软件扫描该文件夹时,往往不会关心里面有多少文件和子文件夹,组合模式使得只需要操作最外层的文件夹进行扫描。

   现在来编写代码,首先分别定义好文件夹Folder和文件File这两个类。见如下代码:
let Folder = function( name ){
    this.name = name
    this.files = []
}
Folder.prototype.add = function( file ){
    this.files.push( file )
}
Folder.prototype.scan = function(){
    console.log( '开始扫描文件夹: ' + this.name )
    for ( let i = 0, file, files = this.files; file = files[ i++ ]; ){
        file.scan()
    }
}
let File = function( name ){
    this.name = name
}
File.prototype.add = function(){
    throw new Error( '文件下面不能再添加文件' )
}
File.prototype.scan = function(){
    console.log( '开始扫描文件: ' + this.name )
}

  接下来创建一些文件夹和文件对象,并且让它们组合成一棵树,这棵树就是F盘里的现有文件目录结构。

let folder = new Folder( '学习资料' )
let folder1 = new Folder( 'JavaScript' )
let folder2 = new Folder ( 'jQuery' )
let file1 = new File( 'JavaScript 设计模式与开发实践' )
let file2 = new File( '精通jQuery' )
let file3 = new File( '重构与模式' )
folder1.add( file1 )
folder2.add( file2 )
folder.add( folder1 )
folder.add( folder2 )
folder.add( file3 )

现在的需求是把移动硬盘里的文件和文件夹都复制到这棵树中,假设已经得到了这些文件对象:

let folder3 = new Folder( 'Nodejs' )
let file4 = new File( '深入浅出Node.js' )
folder3.add( file4 )
let file5 = new File( 'JavaScript 语言精髓与编程实践' )

接下来就是把这些文件都添加到原有的树中:

folder.add( folder3 )
folder.add( file5 )

  通过这个例子,再次看到客户是如何同等对待组合对象和基本对象的。在添加一批文件的操作过程中,客户不用分辨它们到底是文件还是文件夹。新增加的文件和文件夹能够很容易地添加到原来的树结构中,和树里已有的对象一起工作。改变了树的结构,增加了新的数据,却不用修改任何一句原有的代码,这是符合开放——封闭原则的
 运用了组合模式之后,扫描整个文件夹的操作也是轻而易举的,只需要操作树的最顶端对象:

folder.scan()

总结

  组合模式如果运用得当,可以大大简化客户的代码。一般来说,组合模式适用于以下这两种情况。

 1. 表示对象的部分——整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分——整体结构。特别是在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放——封闭原则。
 2. 客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和子对象的区别,客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是子对象,也就不用写一堆if、else语句来分别处理它们。组合对象和子对象会各自做自己正确的事情,这是组合模式最重要的能力。
然而,组合模式并不是完美的,它可能会产生一个这样的系统:系统中的每个对象看起来都与其他对象差不多。它们的区别只有在运行的时候会才会显现出来,这会使代码难以理解。此外,如果通过组合模式创建了太多的对象,那么这些对象可能会让系统负担不起。

此部分原文:https://www.jianshu.com/nb/32302644

观察者模式

观察者模式又叫发布订阅模式或者消息模式。是具体模式中非常著名也是非常重要的一种模式,这种模式一般会定义一个主体和众多个个体,这里主体可以想象为一个消息中心,里面有各种各样的消息,当未来消息中心发布某条消息的时候,订阅过他的个体就会得到通知。

1、创建发布订阅闭包

var msgCenter = (function(){
    /** { carInfo: [person1的carInfo函数, person2的carInfo函数, person3的carInfo函数] }
        { personInfo: [person1的personInfo函数, person2的personInfo函数] }
    */
    var _msg = {}
    return {
        // 用于订阅一个消息
        // 参数:消息类型:'carInfo',订阅函数
        register: function(type, fn){
            if(_msg[type]){
                _msg[type].push(fn)
            }else{
                _msg[type] = [fn]
            }
        },
        // 用于发布消息
        // 参数: 消息类型:'carInfo',消息内容:{ info: '新款汽车上市' }
        fire: function(type, args){
            if(!_msg[type]) return
            var event = {
                type,
                args: args || {}
            }
            for(var i = 0; i < _msg[type].length; i++){
                _msg[type][i](event)
            }
        },
        // 用于取消订阅
        cancel: function(type, fu){
            if(!_msg[type]) return
            for(var i = 0; i < _msg[type].length; i++){
                if(_msg[type][i] === fn){
                    _msg[type].splice(i, 1)
                    break
                }                  
            }
        }
    }
})()

2、封装person发布订阅构造函数

function Person(){
    // 查看已订阅消息(函数)
    this.alreadyRegister = {}
}
Person.prototype.register = function(type, fn){
    if(this.alreadyRegister[type]){
        console.log('您已订阅此消息!不要重复订阅了')
    }else{
        // 注册订阅
        msgCenter.register(type, fn)
        this.alreadyRegister[type] = fn
    }  
}
Person.prototype.cancel = function(type){
    // 取消订阅
    msgCenter.cancel(type, this.alreadyRegister[type])
    delete this.alreadyRegister[type]
}

测试

var person1 = new Person()
var person2 = new Person()
var person3 = new Person()
// 订阅
person1.register('carInfo', function(e){
    console.log('person1得到了关于' + e.type + '的消息,消息内容是:' + e.args.info)
})

person1.register('personInfo', function(e){
    console.log('person1得到了关于' + e.type + '的消息,消息内容是:' + e.args.info)
})

person2.register('personInfo', function(e){
    console.log('person2得到了关于' + e.type + '的消息,消息内容是:' + e.args.info)
})

person2.register('carInfo', function(e){
    console.log('person2得到了关于' + e.type + '的消息,消息内容是:' + e.args.info)
})

person3.register('carInfo', function(e){
    console.log('person3得到了关于' + e.type + '的消息,消息内容是:' + e.args.info)
})
// 发布
msgCenter.fire('carInfo', { info: '新款汽车上市' })
msgCenter.fire('personInfo', { info: 'helloworld!' })

效果

1601997380989.png

策略模式

策略模式是一种相对简单的设计模式,主要用在有多种状态或者策略需要进行选择的时候,将所有选择封装在一起,只给外部暴露出必要的接口。

例:我们有一个表单,需要验证表单项,如果验证不通过就返回一个提示文案。这里我们就可以使用策略模式的思路进行封装。

1601998037925.png

测试

1601998485060.png

评论(0)


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis