0

0

0

修罗

站点介绍

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

js设计模式(上)

修罗 2020-10-06 1611 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)


最新评论

  • https://www.studyglobus.com/author/carroll34a5552/

    Ценники на вывоз мусора сейчас космос, любые лайфхаки по экономии в тему.

  • Richardgrold

    1xbet onlayn kazino platforması ilə vaxtınızı daha səmərəli və maraqlı keçirin. Saytın canlı kazino bölməsi istifadəçilərə fərqli bir dünya təklif edir. Burada canlı dilerli kazino oyunlarının hər bir növünü sınaqdan keçirə bilərsiniz. Krupyelərin peşəkarlığı oyunun dinamikasını və marağını artırır. Müntəzəm olaraq yenilənən oyun siyahısı cansıxıcılığa yol vermir. Hesabınızı artırmaq üçün onlarla fərqli və etibarlı üsul mövcuddur. Qazanılan pullar cəmi bir neçə dəqiqə ərzində şəxsi hesabınıza köçürülür. Aktiv fəaliyyətinizə görə fərdi bonuslar və hədiyyələr qazana bilərsiniz. Dəstək xidməti həm çat, həm də e-poçt vasitəsilə kömək göstərir. 1xbet ailəsinə qoşulun və böyük uduşlar üçün ilk addımı atın. onlayn kazino

  • 砂岩白丘

    我一直梦想, 看到你们相册那样的地方。真棒。

  • 城市地標

    我喜欢, 这里分享真实经验。你的项目 就是 属于这里的。请继续。

  • 宗教地標

    我热爱这样的想法, 去那么多国家。真的很鼓舞。

  • 觀景高地

    能感受到热爱。继续保持 感受。

  • 最佳季節

    我关注这样的资源, 写得很实在。你的网站 就是 属于这里的。加油。

  • 1

    1

  • 1

    1

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

    1

日历

2026年07月

   1234
567891011
12131415161718
19202122232425
262728293031 

文章目录

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