JavaScript中经常使用的设想形式

本文已同步到Github JavaScript中常见的设想形式,如果觉得写的还能够,就给个小星星吧,迎接star和珍藏。

近来拜读了曾探大神的《JavaScript设想形式与开辟实践》,真是醍醐灌顶,如同买通任督二脉的觉得,让我对JavaScript的明白加深了许多。

本文中关于种种设想形式定义都是援用书中的,部份援用自百度百科已标出。别的,本文中所举例子大多是书中的,自已做了一些修正和补充,用ES6(书中都是ES5的体式格局)的体式格局完成,以加深本身对“类”的明白,并非本身来解说设想形式,主如果做一些笔记以随意马虎本身事后温习与加深明白,同时也愿望把书中典范的例子整顿出来和人人分享,配合讨论和提高。

一提起设想形式,置信人人都邑脱口而出,23种设想形式,五大设想准绳。这里就不说了,怎样我功力远远不够啊。下面把我整顿出的经常运用JavaScript设想形式按范例做个表格整顿。本文较长,如果浏览起来不随意马虎,可链接到我的github中,零丁检察每一种设想形式。先整顿这些,后续会继续补充,感兴趣的同砚能够关注。

形式分类称号
建立型工场形式
单例形式
原型形式
构造型适配器形式
代办形式
行动型战略形式
迭代器形式
观察者形式(宣布-定阅形式)
敕令形式
状况形式

建立型形式

工场形式

工场形式中,我们在建立对象时不会对客户端暴露建立逻辑,而且是经由过程运用一个配合的接口来指向新建立的对象,用工场要领替代new操纵的一种形式。

class Creator {
    create(name) {
        return new Animal(name)
    }
}

class Animal {
    constructor(name) {
        this.name = name
    }
}

var creator = new Creator()

var duck = creator.create('Duck')
console.log(duck.name) // Duck

var chicken = creator.create('Chicken') 
console.log(chicken.name) // Chicken

小结:

  1. 组织函数和建立者星散,对new操纵举行封装
  2. 相符开放封闭准绳

单例形式

《JavaScript中经常使用的设想形式》

举一个书中登录框的例子,代码以下:

<!DOCTYPE html>
<html lang="en">

<body>
    <button id="btn">登录</button>
</body>
<script>
    class Login {
        createLayout() {
            var oDiv = document.createElement('div')
            oDiv.innerHTML = '我是登录框'
            document.body.appendChild(oDiv)
            oDiv.style.display = 'none'
            return oDiv
        }
    }

    class Single {
        getSingle(fn) {
            var result;
            return function() {
                return result || (result = fn.apply(this, arguments))
            }
        }
    }

    var oBtn = document.getElementById('btn')
    var single = new Single()
    var login = new Login()

    // 由于闭包,createLoginLayer对result的援用,所以当single.getSingle函数实行完今后,内存中并不会烧毁result。

    // 当第二次今后点击按钮,依据createLoginLayer函数的作用域链中已包含了result,所以直接返回result

    // 讲猎取单例和建立登录框的要领解耦,相符开放封闭准绳
    var createLoginLayer = single.getSingle(login.createLayout)
    oBtn.onclick = function() {
        var layout = createLoginLayer()
        layout.style.display = 'block'
    }
</script>

</html>

小结:

1.单例形式的重要头脑就是,实例如果已建立,则直接返回

function creatSingleton() {
    var obj = null
    // 实比方已建立过,直接返回
    if (!obj) {
        obj = xxx
    }
    return obj
}

2.相符开放封闭准绳

原型形式

用原型实例指定建立对象的品种,而且经由过程拷贝这些原型建立新的对象。–
百度百科

在JavaScript中,完成原型形式是在ECMAScript5中,提出的Object.create要领,运用现有的对象来供应新建立的对象的__proto__。

var prototype = {
    name: 'Jack',
    getName: function() {
        return this.name
    }
}

var obj = Object.create(prototype, {
    job: {
        value: 'IT'
    }
})

console.log(obj.getName())  // Jack
console.log(obj.job)  // IT
console.log(obj.__proto__ === prototype)  //true

更多关于prototype的学问能够看我之前的JavaScript中的面向对象、原型、原型链、继续,下面列一下关于prototype的一些运用要领

1. 要领继续

var Parent = function() {}
Parent.prototype.show = function() {}
var Child = function() {}

// Child继续Parent的一切原型要领
Child.prototype = new Parent()

2. 一切函数默许继续Object

var Foo = function() {}
console.log(Foo.prototype.__proto__ === Object.prototype) // true

3. Object.create

var proto = {a: 1}
var propertiesObject = {
    b: {
        value: 2
    }
}
var obj = Object.create(proto, propertiesObject)
console.log(obj.__proto__ === proto)  // true

4. isPrototypeOf

prototypeObj是不是在obj的原型链上

prototypeObj.isPrototypeOf(obj)

5. instanceof

contructor.prototype是不是出现在obj的原型链上

obj instanceof contructor

6. getPrototypeOf

Object.getPrototypeOf(obj) 要领返回指定对象obj的原型(内部[[Prototype]]属性的值)

Object.getPrototypeOf(obj)

7. setPrototypeOf

设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null

var obj = {}
var prototypeObj = {}
Object.setPrototypeOf(obj, prototypeObj)
console.log(obj.__proto__ === prototypeObj)  // true

构造型形式

适配器形式

《JavaScript中经常使用的设想形式》

举一个书中衬着舆图的例子

class GooleMap {
    show() {
        console.log('衬着谷歌舆图')
    }
}

class BaiduMap {
    show() {
        console.log('衬着百度舆图')
    }
}

function render(map) {
    if (map.show instanceof Function) {
        map.show()
    }
}

render(new GooleMap())  // 衬着谷歌舆图
render(new BaiduMap())  // 衬着百度舆图

然则如果BaiduMap类的原型要领不叫show,而是叫display,这时刻就能够运用适配器形式了,由于我们不能随意马虎的转变第三方的内容。在BaiduMap的基础上封装一层,对外暴露show要领。

class GooleMap {
    show() {
        console.log('衬着谷歌舆图')
    }
}

class BaiduMap {
    display() {
        console.log('衬着百度舆图')
    }
}


// 定义适配器类, 对BaiduMap类举行封装
class BaiduMapAdapter {
    show() {
        var baiduMap = new BaiduMap()
        return baiduMap.display() 
    }
}

function render(map) {
    if (map.show instanceof Function) {
        map.show()
    }
}

render(new GooleMap())         // 衬着谷歌舆图
render(new BaiduMapAdapter())  // 衬着百度舆图

小结:

  1. 适配器形式重要处理两个接口之间不婚配的题目,不会转变原有的接口,而是由一个对象对另一个对象的包装。
  2. 适配器形式相符开放封闭准绳

代办形式

《JavaScript中经常使用的设想形式》

本文举一个运用代办对象加载图片的例子来明白代办形式,当收集不好的时刻,图片的加载须要一段时间,这就会发生空缺,影响用户体验,这时刻我们可在图片真正加载完之前,运用一张loading占位图片,等图片真正加载完再给图片设置src属性。

class MyImage {
    constructor() {
        this.img = new Image()
        document.body.appendChild(this.img)
    }
    setSrc(src) {
        this.img.src = src
    }
}

class ProxyImage {
    constructor() {
        this.proxyImage = new Image()
    }

    setSrc(src) {
        let myImageObj = new MyImage()
        myImageObj.img.src = 'file://xxx.png'  //为本舆图片url
        this.proxyImage.src = src
        this.proxyImage.onload = function() {
            myImageObj.img.src = src
        }
    }
}

var proxyImage = new ProxyImage()
proxyImage.setSrc('http://xxx.png') //服务器资本url

本例中,本体类中有本身的setSrc要领,如果有一天收集速率已不须要预加载了,我们能够直接运用本体对象的setSrc要领,,而且不须要修改本体类的代码,而且能够删除代办类。

// 照旧能够满足需求
var myImage = new MyImage()
myImage.setSrc('http://qiniu.sunzhaoye.com/CORS.png')

小结:

  1. 代办形式相符开放封闭准绳
  2. 本体对象和代办对象具有雷同的要领,在用户看来并不知道要求的本体对象照样代办对象。

行动型形式

战略形式

定义一系列的算法,把它们一个个封装起来,并使它们能够替代

var fnA = function(val) {
    return val * 1
}

var fnB = function(val) {
    return val * 2
}

var fnC = function (val) {
    return val * 3
}


var calculate = function(fn, val) {
    return fn(val)
}

console.log(calculate(fnA, 100))// 100
console.log(calculate(fnB, 100))// 200
console.log(calculate(fnC, 100))// 300

迭代器形式

《JavaScript中经常使用的设想形式》

直接上代码, 完成一个简朴的迭代器

class Creater {
    constructor(list) {
        this.list = list
    }

    // 建立一个迭代器,也叫遍历器
    createIterator() {
        return new Iterator(this)
    }
}

class Iterator {
    constructor(creater) {
        this.list = creater.list
        this.index = 0
    }

    // 推断是不是遍历完数据
    isDone() {
        if (this.index >= this.list.length) {
            return true
        }
        return false
    }

    next() {
        return this.list[this.index++]
    }

}

var arr = [1, 2, 3, 4]

var creater = new Creater(arr)
var iterator = creater.createIterator()
console.log(iterator.list)  // [1, 2, 3, 4]
while (!iterator.isDone()) {
    console.log(iterator.next()) 
    // 1
    // 2
    // 3
    // 4
}

ES6中的迭代器:

JavaScript中的有序数据鸠合包含:

  • Array
  • Map
  • Set
  • String
  • typeArray
  • arguments
  • NodeList

注重: Object不是有序数据鸠合

以上有序数据鸠合都布置了Symbol.iterator属性,属性值为一个函数,实行这个函数,返回一个迭代器,迭代器布置了next要领,挪用迭代器的next要领能够按递次接见子元素

以数组为例测试一下,在浏览器掌握台中打印测试以下:
《JavaScript中经常使用的设想形式》

var arr = [1, 2, 3, 4]

var iterator = arr[Symbol.iterator]()

console.log(iterator.next())  // {value: 1, done: false}
console.log(iterator.next())  // {value: 2, done: false}
console.log(iterator.next())  // {value: 3, done: false}
console.log(iterator.next())  // {value: 4, done: false}
console.log(iterator.next())  // {value: undefined, done: true}

小结:

  1. JavaScript中的有序数据鸠合有Array,Map,Set,String,typeArray,arguments,NodeList,不包含Object
  2. 任何布置了[Symbol.iterator]接口的数据都能够运用for…of轮回遍历
  3. 迭代器形式使目的对象和迭代器对象星散,相符开放封闭准绳

观察者形式(定阅-宣布形式)

《JavaScript中经常使用的设想形式》

先完成一个简朴的宣布-定阅形式,代码以下:

class Event {
    constructor() {
        this.eventTypeObj = {}
    }
    on(eventType, fn) {
        if (!this.eventTypeObj[eventType]) {
            // 根据差别的定阅事宜范例,存储差别的定阅回调
            this.eventTypeObj[eventType] = []
        }
        this.eventTypeObj[eventType].push(fn)
    }
    emit() {
        // 能够明白为arguments借用shift要领
        var eventType = Array.prototype.shift.call(arguments)
        var eventList = this.eventTypeObj[eventType]
        for (var i = 0; i < eventList.length; i++) {
            eventList[i].apply(eventList[i], arguments)
        }
    }
    remove(eventType, fn) {
        // 如果运用remove要领,fn为函数称号,不能是匿名函数
        var eventTypeList = this.eventTypeObj[eventType]
        if (!eventTypeList) {
            // 如果没有被人定阅改事宜,直接返回
            return false
        }
        if (!fn) {
            // 如果没有传入作废定阅的回调函数,则改定阅范例的事宜悉数作废
            eventTypeList && (eventTypeList.length = 0)
        } else {
            for (var i = 0; i < eventTypeList.length; i++) {
                if (eventTypeList[i] === fn) {
                    eventTypeList.splice(i, 1)
                    // 删除今后,i--保证下轮轮回不会遗漏没有被遍历到的函数名
                    i--;
                }
            }
        }
    }
}
var handleFn = function(data) {
    console.log(data)
}
var event = new Event()
event.on('click', handleFn)
event.emit('click', '1')   // 1
event.remove('click', handleFn)
event.emit('click', '2')  // 不打印

以上代码能够满足先定阅后宣布,然则如果先宣布音讯,后定阅就不满足了。这时刻我们能够轻微修正一下即可满足先宣布后定阅,在宣布音讯时,把事宜缓存起来,等有定阅者时再实行。代码以下:

class Event {
    constructor() {
        this.eventTypeObj = {}
        this.cacheObj = {}
    }
    on(eventType, fn) {
        if (!this.eventTypeObj[eventType]) {
            // 根据差别的定阅事宜范例,存储差别的定阅回调
            this.eventTypeObj[eventType] = []
        }
        this.eventTypeObj[eventType].push(fn)

        // 如果是先宣布,则在定阅者定阅后,则依据宣布后缓存的事宜范例和参数,实行定阅者的回调
        if (this.cacheObj[eventType]) {
            var cacheList = this.cacheObj[eventType]
            for (var i = 0; i < cacheList.length; i++) {
                cacheList[i]()
            }
        }
    }
    emit() {
        // 能够明白为arguments借用shift要领
        var eventType = Array.prototype.shift.call(arguments)
        var args = arguments
        var that = this

        function cache() {
            if (that.eventTypeObj[eventType]) {
                var eventList = that.eventTypeObj[eventType]
                for (var i = 0; i < eventList.length; i++) {
                    eventList[i].apply(eventList[i], args)
                }
            }
        }
        if (!this.cacheObj[eventType]) {
            this.cacheObj[eventType] = []
        }

        // 如果先定阅,则直接定阅后宣布
        cache(args)

        // 如果先宣布后定阅,则把宣布的事宜范例与参数保留起来,比及有定阅后实行定阅
        this.cacheObj[eventType].push(cache)
    }
}

小结:

  1. 宣布定阅形式能够使代码解耦,满足开放封闭准绳
  2. 当过量的运用宣布定阅形式,如果定阅音讯一向都没有触发,则定阅者一向保留在内存中。

敕令形式

《JavaScript中经常使用的设想形式》
–百度百科

在敕令的宣布者和接收者之间,定义一个敕令对象,敕令对象暴露出一个一致的接口给敕令的宣布者,而敕令的宣布者没必要去管接收者是怎样实行敕令的,做到敕令宣布者和接收者的解耦。

举一个如果页面中有3个按钮,给差别按钮增添差别功用的例子,代码以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>cmd-demo</title>
</head>
<body>
    <div>
        <button id="btn1">按钮1</button>
        <button id="btn2">按钮2</button>
        <button id="btn3">按钮3</button>
    </div>
    <script>
        var btn1 = document.getElementById('btn1')
        var btn2 = document.getElementById('btn2')
        var btn3 = document.getElementById('btn3')

        // 定义一个敕令宣布者(实行者)的类
        class Executor {
            setCommand(btn, command) {
                btn.onclick = function() {
                    command.execute()
                }
            }
        }

        // 定义一个敕令接收者
        class Menu {
            refresh() {
                console.log('革新菜单')
            }

            addSubMenu() {
                console.log('增添子菜单')
            }
        }

        // 定义一个革新菜单的敕令对象的类
        class RefreshMenu {
            constructor(receiver) {
                // 敕令对象与接收者关联
                this.receiver = receiver
            }

            // 暴露出一致的接口给敕令宣布者Executor
            execute() {
                this.receiver.refresh()
            }
        }

        // 定义一个增添子菜单的敕令对象的类
        class AddSubMenu {
            constructor(receiver) {
                // 敕令对象与接收者关联
                this.receiver = receiver
            }
            // 暴露出一致的接口给敕令宣布者Executor
            execute() {
                this.receiver.addSubMenu()
            }
        }

        var menu = new Menu()
        var executor = new Executor()

        var refreshMenu = new RefreshMenu(menu)
        // 给按钮1增添革新功用
        executor.setCommand(btn1, refreshMenu)

        var addSubMenu = new AddSubMenu(menu)
        // 给按钮2增添增添子菜单功用
        executor.setCommand(btn2, addSubMenu)

        // 如果想给按钮3增添删除菜单的功用,就继续增添删除菜单的敕令对象和接收者的详细删除要领,而没必要修正敕令对象
    </script>
</body>
</html>

状况形式

《JavaScript中经常使用的设想形式》

举一个关于开关掌握电灯的例子,电灯只要一个开关,第一次按下翻开弱光,第二次按下翻开强光,第三次按下封闭。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>state-demo</title>
</head>

<body>
    <button id="btn">开关</button>
    <script>
        // 定义一个封闭状况的类   
        class OffLightState {
            constructor(light) {
                this.light = light
            }
            // 每一个类都须要这个要领,在差别状况下按都须要触发这个要领
            pressBtn() {
                this.light.setState(this.light.weekLightState)
                console.log('开启弱光')
            }
        }

        // 定义一个弱光状况的类   
        class WeekLightState {
            constructor(light) {
                this.light = light
            }
            pressBtn() {
                this.light.setState(this.light.strongLightState)
                console.log('开启强光')
            }
        }

        // 定义一个强光状况的类
        class StrongLightState {
            constructor(light) {
                this.light = light
            }
            pressBtn() {
                this.light.setState(this.light.offLightState)
                console.log('封闭电灯')
            }
        }

        class Light {
            constructor() {
                this.offLightState = new OffLightState(this)
                this.weekLightState = new WeekLightState(this)
                this.strongLightState = new StrongLightState(this)
                this.currentState = null
            }
            setState(newState) {
                this.currentState = newState
            }
            init() {
                this.currentState = this.offLightState
            }
        }

        let light = new Light()
        light.init()
        var btn = document.getElementById('btn')
        btn.onclick = function() {
            light.currentState.pressBtn()
        }
    </script>
</body>

</html>

如果这时刻须要增添一个超强光,则只需增添一个超强光的类,并增添pressBtn要领,转变强光状况下,点击开关须要把状况更改成超强光,超强光状况下,点击开关把状况改成封闭即可,其他代码都不须要修改。

class StrongLightState {
    constructor(light) {
        this.light = light
    }
    pressBtn() {
        this.light.setState(this.light.superLightState)
        console.log('开启超强光')
    }
}

class SuperLightState {
    constructor(light) {
        this.light = light
    }
    pressBtn() {
        this.light.setState(this.light.offLightState)
        console.log('封闭电灯')
    }
}

class Light {
    constructor() {
        this.offLightState = new OffLightState(this)
        this.weekLightState = new WeekLightState(this)
        this.strongLightState = new StrongLightState(this)
        this.superLightState = new SuperLightState(this)
        this.currentState = null
    }
    setState(newState) {
        this.currentState = newState
    }
    init() {
        this.currentState = this.offLightState
    }
}

小结:

  1. 经由过程定义差别的状况类,依据状况的转变而转变对象的行动,二没必要把大批的逻辑都写在被操纵对象的类中,而且轻易增添新的状况
  2. 相符开放封闭准绳

终究到最后可,用时多日地浏览与明白,并纪录与整顿笔记,现在整顿出10中JavaScript中常见的设想形式,后续会对笔记继续整顿,然后加以补充。由于笔者功力比较浅,若有题目,还望人人多多斧正,感谢。

参考文章:

JavaScript设想形式与开辟实践
深切明白JavaScript系列/设想形式–汤姆大叔的博客
设想形式–菜鸟教程
JavaScript 中常见设想形式整顿
ES6入门–阮一峰

    原文作者:令狐寻欢
    原文地址: https://segmentfault.com/a/1190000017787537
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞