一步一步 的理解设计模式

 

第一篇

1,灵活的设计你的代码

假如有这样一个需求,需要你写一个注册登录页,要求验证邮箱,验证密码,验证手机号。最没有思考的实现方式:

function checkPhone(params) {
    
}
function checkMail(params) {
    
}
function checkPassword(params) {
    
}

功能划分清晰,一目了然,但是有个问题,你创建了很多全局变量,一个团队中,这样做就很危险,很难避免不会有人跟你的这些函数重名。尽管重名的情况一直都存在,我们要做的仅仅是尽量的减少全局变量的创建。于是乎,可以这样来实现:



const checkObj = {
     checkPhone(params) {
    
    },
    checkMail(params) {
        
    },
    checkPassword(params) {
        
    }
}

// 或者这样

function checkObjFun(params) {}
checkObjFun.checkPhone = {}
checkObjFun.checkMail = {}
checkObjFun.checkPassword = {}

嗯,很好的避免的大量全局对象的创建,但是还有一个问题,如果其他人需要使用你的方法,就只能通过checkObj.checkPhone()的方式去调用。恰好其他人的验证密码的方式跟你的有所不同,就会比较麻烦,如果他直接修改的checkPassword方法,所有使用这个方法的位置都会被影响到。可以用一个对象的复制来避免上面的问题:


const checkObj = function(){
    return {
        checkPhone(params) {
    
        },
        checkMail(params) {
            
        },
        checkPassword(params) {
            
        }
    }
    
}

a,b使用者可以对两个对象进行任意的修改,两者不会相互影响。

const a = checkObj()
const b = checkObj()

console.log(a === b) // false

类也可以做到:

function CheckObj(params) {
    this.checkMail={}
    this.checkPassword={}
    this.checkPhone={}
}
const a = new CheckObj()
const b = new CheckObj()

console.log(a === b) // false

但是这么做成的消费是很奢侈的,每次实例化都需要重新创建一次方法函数,实际上,是可以做到不那么浪费的:


function CheckObj(params) {
}
CheckObj.prototype.checkMail = function() {}
CheckObj.prototype.checkPassword = function() {
    console.log(1)
}
CheckObj.prototype.checkPhone =  function (){}

const a = new CheckObj()
const b = new CheckObj()


console.log(a === b) // falsec

无论实例化多少次,都只会创建一次功能函数。每个实例还可以单独定制自己的方法,比如:b修改了checkPassword方法:

b.checkPassword = function(){
    console.log(2)
}
a.checkPassword() // 1
b.checkPassword() // 2

console.log(a === b) // false

只要不去修改原型上的方法,就不会相互影响。

代码还可以更简约一些,调用方法的时候可以实现链式调用:

function CheckObj(params) {
}
CheckObj.prototype.checkMail = function() {return this}
CheckObj.prototype.checkPassword = function() {
    console.log(1)
    return this
}
CheckObj.prototype.checkPhone =  function (){return this}



const a = new CheckObj()
a.checkMail().checkPassword().checkPhone()

2,面向对象编程

面向对象就是将你的需求抽象为一个对象,然后针对这个对象分析其特征(属性)和动作(方法)。特点就是封装。

创建对象的方法有很多种,上一章已经明确列出,复用性和可维护性比较好的就是构造函数的方式创建对象。但是如果有人调用的时候不是通过new,那么就有可能造成内存泄漏:

function Person() {
  this.id = "麦乐"
}
Person();
console.log(id) // 麦乐 局部泄露到了全局变量

所以采用这种方式创建对象的时候,要使用安全的方式来创建:

function Person() {
  if(this instanceof Person) {
    this.id = "麦乐"
  } else {
    return new Person()
  }
}
Person();
console.log(id) // id is not defined

真实的开发过程种,肯定不会只需要一个对象,或者一类对象,可能需要很多,这些对象之间还会有一些共有的属性和方法,这里就要用到继承了:详细继承相关可以查看 构造函数--继承

下面介绍4种比较类似的模式:

3,发布-订阅者模式 & 观察者模式 & 中介者模式 & 代理模式

发布-订阅者模式

定义了一种依赖关系,解决了主体对象与观察者之间的功能耦合。

这本书中把发布-订阅模式和观察者模式看着了一种,我们暂且分开考虑。订阅消息就是注册一条消息,发布消息就是触发这条消息执行。

个人觉得下面的这张图最能代码发布-订阅者模式,发布者把数据传递到发布-订阅者对象,通过这个对象再将数据传递给所有的订阅者。这么做的好处是,任何一个模块都可以订阅这些消息,也就是说任何一个模块都可以轻松的拿到这些消息。

比如说一个新闻评论列表需求,有评论模块,消息模块,添加评论模块。删除评论,评论模块会删除一条评论,消息模块的消息数量会减少;各个模块有自己的功能,又和其他模块有一定的交互,相互影响,如果想要每个模块独立在自己的闭包内,似乎有点困难。发布-订阅者模式就可以降低三个模块之间的耦合,实现独立开发就变得容易了。

一条评论被删除,需要执行两个动作,一个是删除评论,一个是消息数量减去1,就可以把这两个动作看做订阅者,删除评论的操作就是消息的发布者,触发这两个订阅者。同理,留言功能也是一样。代码就可以这样来实现了。

先实现一个观察者:

var Observer = (function(){
    var message = {};
    return {
        // 注册消息
        regist(type, fn) {
            if(!message[type]) message[type] = [fn]
            else message[type].push(fn)
        },
        //发布消息
        fire(type, args){
            var registList = message[type]
            if(!registList || !registList.length) return;
            var len = registList.length;
            var events = {
                type,
                args: args || {}
            }
            for(var i = 0; i < len; i ++) {
                registList[i].call(this, events)
            }
            
            
        },
        //移除消息
        remove(type, fn){
            var registList = message[type]
            if(!registList || !registList.length) return;
            var index = registList.indexOf(fn);
            index >= 0 && registList.splice(index, 1)
        }
    }
})()

添加评论:订阅者也是发布者

(function (){
   function addMsg(e) {
       var text = e.args.text,
       ul = $('msg'),
       li = document.createElement('li'),
       span = document.createElement('li');
       li.innerHTML = text;
       // 删除按钮
       span.onclick = function() {
            ul.removeChild();
            // 发布删除留言消息
            Observer.fire('removeCommentMessage', {
                num: -1
            })
       }
       //添加删除按钮
       li.appendChild(span);
       ul.appendChild(li)
   }
   Observer.regist('addCommentMessage', addMsg)

})()

 

改变消息:订阅者

(function (){
    function changeMes(e) {
        var num = e.args.num;
        $('meg_num').innerHTML += num;
    }
  
    Observer.regist('addCommentMessage', changeMes);
    Observer.regist('removeCommentMessage', changeMes);
})()

 

添加评论:发布消息

(function (){
    $('submit').onclick = function() {
        var text = $('user_input');
        if(text.value) return;
        // 发布一条评论消息
        Observer.fire('addCommentMessage', {
            text: text.value,
            num: 1
        })
    }
})()

订阅消息者有多个的时候,或者模块间耦合比较大的时候,都可以使用这种模式来实现。

中介者模式

平时我们大概能记住10个朋友的电话、30家餐馆的位置。在程序里,也许一个对象会和其他10个对象打交道,所以它会保持10个对象的引用。当程序的规模增大,对象会越来越多,它们之间的关系也越来越复杂,难免会形成网状的交叉引用。当我们改变或删除其中一个对象的时候,很可能需要通知所有引用到它的对象。这样一来,就像在心脏旁边拆掉一根毛细血管一般,即使一点很小的修改也必须小心翼翼,如图所示。(借用他人示例图)

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系,如图所示

看了很多个例子,感觉还是下面这个更好理解一些:

var mediator = (function () {
    var topics = {}

	// 订阅一个 topic,提供一个回调函数,一旦 topic 被广播就执行该回调
    var subscribe = function(topic, fn) {
        if (!topics[topic]) {
            topics[topic] = []
        }
        topics[topic].push({
            context: this,
            callback: fn
        })

        return this
    }

	// 发布/广播事件到程序的剩余部分
    var publish = function(topic) {
        var args

        if (!topics[topic]) {
            return false
        }

        args = Array.prototype.slice.call(arguments, 1)

        for (var i = 0, l = topics[topic].length; i < l; i++) {
            var subscription = topics[topic][i]
            subscription.callback.apply(subscription.context, args)
        }
        return this
    }

    return {
        publish: publish,
        subscribe: subscribe
    }
})()
// 小A加入了前端群,如果群里有人说话,自己就会收到消息
mediator.subscribe('fe-group', function(data) {
    console.log(data)
})

// 小B也加入了前端群,如果群里有人说话,自己就会收到消息
mediator.subscribe('fe-group', function(data) {
    console.log(data)
})

// 这个时候小B在群里给小A发送了一条信息
// 在群里的人都会收到这条信息
// 打印两次 {message: 'hello A', from: 'B', to: 'A'}
mediator.publish('fe-group', {message: 'hello A', from: 'B', to: 'A'})

在这里面沟通是单向的,订阅者只会订阅消息,不会发布消息。

中介者模式(Mediator)是用来降低多个对象和类之间的通信复杂性。这种模式提供一个中介类,该类通常处理不同类的通信,并支持松耦合,使代码易于维护。中介者模式属于行为模式。

看起来和发布-订阅者模式很像,与观察者模式相比,虽然两者都是通过消息的收发机制来降低模块或者对象间的耦合,但是观察者模式中的订阅者是双向的,即可以是订阅者,也可以是发布者;中介者模式中的订阅者只能是订阅者。

代理模式

由于一个对象无法直接引用另外一个对象,所以需要通过代理对象在这两个对象间起到中介的作用。

什么情况下没有办法访问另外一个对象呢?前端开发中,我们经常会遇到跨域问题,受同源策略的限制,没有办法直接请求不同源的数据,但是实际需求中常常需要拿不同源的数据,这种情况下就需要借助于中间代理来实现。

解决跨域问题的方法有很多,这里就不一一例举了。

观察者模式

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。

下面这个例子可以帮助更好的理解观察者模式:

    //有一家猎人工会,其中每个猎人都具有发布任务(publish),订阅任务(subscribe)的功能
	//他们都有一个订阅列表来记录谁订阅了自己
	//定义一个猎人类
	//包括姓名,级别,订阅列表
	function Hunter(name, level){
		this.name = name
		this.level = level
		this.list = []
	}
	Hunter.prototype.publish = function (money){
		console.log(this.level + '猎人' + this.name + '寻求帮助')
	    this.list.forEach(function(item, index){
	    	item(money)
	    })
	}
	Hunter.prototype.subscribe = function (targrt, fn){
		console.log(this.level + '猎人' + this.name + '订阅了' + targrt.name)
	    targrt.list.push(fn)
	}
	
	//猎人工会走来了几个猎人
	let hunterMing = new Hunter('小明', '黄金')
	let hunterJin = new Hunter('小金', '白银')
	let hunterZhang = new Hunter('小张', '黄金')
	let hunterPeter = new Hunter('Peter', '青铜')
	
	//Peter等级较低,可能需要帮助,所以小明,小金,小张都订阅了Peter
	hunterMing.subscribe(hunterPeter, function(money){
		console.log('小明表示:' + (money > 200 ? '' : '暂时很忙,不能') + '给予帮助')
	})
	hunterJin.subscribe(hunterPeter, function(){
		console.log('小金表示:给予帮助')
	})
	hunterZhang.subscribe(hunterPeter, function(){
		console.log('小金表示:给予帮助')
	})
	
	//Peter遇到困难,赏金198寻求帮助
	hunterPeter.publish(198)
	
	//猎人们(观察者)关联他们感兴趣的猎人(目标对象),如Peter,当Peter有困难时,会自动通知给他们(观察者)

对比发布-订阅模式和观察者模式

在Observer模式中,Observers知道Subject,同时Subject还保留了Observers的记录。然而,在发布者/订阅者中,发布者和订阅者不需要彼此了解。他们只是在消息队列或代理的帮助下进行通信。

其实我不知道发布订阅模式是不是观察者模式,就像我不知道辨别模式的关键是设计意图还是设计结构(理念),虽然《JavaScript设计模式与开发实践》一书中说了分辨模式的关键是意图而不是结构。

如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。

4 状态模式 & 策略模式

这两种模式都可以用来优化代码分支,侧重点有所不同。

状态模式

当一个对象的状态发生改变时,会引起行为的改变。

看定义会觉得很难理解,其实这种模式就是为了解决代码种臃肿的分支判断问题的,将每个分支转化为一种独立的状态,方便每个状态的管理,又不至于每次都遍历所有分支。

基本结构:

 
function strategy(){
  var strategyObj = {
      add: function(a,b ) {
          return a + b
      },
      minus: function(a,b ) {
          return a - b
      },
      division: function(a,b ) {
          return a / b
      },
  }
  function show(type, a ,b ) {
    strategyObj[type] && strategyObj[type](a,b)
  }
 
  return {
    show: show
  }
}

策略模式

将定义的一组算法封装起来,使其之间可以相互替换。封装的算法有一定的独立性,不会随客户端的变化而变化。

相对来说好理解一些,就是一个算法工厂。也是一种优化分支判断语句的方式,基本的结构:

var inputStrategy = function() {
  var strategy = {
    notS: function(value) {
      return /\s+/.test(value) ? '请不要输入空格': ''
    },
    number: function(value) {
      return /^[0-9]+/.test(value) ? '' : '请输入数字'
    },
    phone: function(value) {
      return /^[0-9]+/.test(value) ? '' : '请输入电话号码'
    }
  }
 
  return {
    check: function(type, value) {
      return strategy[type] ? strategy[type](value) : '没有该类型的检测方法'
    },
    addStrategy: function(type, fun) {
      strategy[type] = fun
    }
 
  }
}

状态模式和策略模式都可以用来优化代码分支,是代码变得高效可维护。策略模式侧重于算法,是一个算法工厂。

5 简单工厂 & 工厂方法 & 抽象工厂

举个例子:

大众汽车公司想必大家都不陌生,它旗下也有不少汽车品牌。大众汽车公司就好比一个汽车工厂,负责生产和销售汽车。它可以为客户提供一个客户需要的汽车。但是,如果客户需要的汽车大众公司目前还没有,但是公司想要盈利,就必须为此而设计汽车,在这种情况下,大众公司就要新添加一种汽车,同时要修改公司内部的生产环境(也就是工厂类的代码)。这就是简单工厂模式的运行情况。简单而言,就是工厂类(汽车公司)什么都要干,要修改必须大动干戈。因而一定程度上违背了开闭原则。

而工厂方法模式则不一样,大众汽车公司不在总公司生产汽车,而是成立分公司,收购别的公司,成立具有针对性的汽车工厂专门生产对应的汽车。若客户的大量需求得不到满足,则总公司就另外成立新的二级公司(新品牌汽车的工厂)生产汽车,从而在不修改具体工厂的情况下引进新的产品。正如大众集团的收购一样。

简单工厂 和 工厂方法的区别

简单工厂

举个例子,比如说体育商店卖体育器材,里面有很多体育用品,及其相关的介绍。当你来到商店买篮球的时候,你只需要告诉店员需要什么颜色,价位,他会帮你找到你想要的。

如果我像下面这样写代码:

function Football() {
  this.info = '很喜欢足球'
}
Football.prototype = {
  getPrice() {},
  getSize(){}
}

function Basketball() {
  this.info = '篮球我最爱'
}
Basketball.prototype = {
  getPrice() {},
  getSize(){}
}

function Tennis() {
  this.info = '每年都有网球比赛'
}
Tennis.prototype = {
  getPrice() {},
  getSize(){}
}

很清晰的分出了三种运动工具,但是使用不太方便,需要我自己去找对应的运动类,查看信息,找到适合自己的。如果创建一个工厂方法,我就不用关心内部怎么实现的,只需要传递给工厂函数信息,它会帮我找到我想要的,这就是简单工厂方法模式:

function popFactory(type) {
  switch(type) {
    case 'basketball':
      return new Basketball()
    case 'football':
      return new Football()
    case 'tennis':
      return new Tennis()
  }
}

但是这样也有一个问题,如果说我需要增加一种运动器材,我需要修改两个地方,增加一个工具类,增加一个case 条件。使用工厂方法模式,就只需要修改一个地方就可以了

工厂方法模式

(function(root){
    
    function pop(type, content, color) {
        if(this instanceof pop) {
            return new this[type](content,color)
        } else {
            return new pop(type, content, color)
        }
    }
    pop.prototype.info = function() {
 
    }
    pop.prototype.confirm = function() {
        
    }
    pop.prototype.cancel = function() {
        
    }
    root.pop = pop
})(window)
 
var arr = [{type: 'info', content: '好的方式', color: 'red'}, {type: 'confirm', content: '好的方式', color: 'red'}]
arr.forEach((item) => {
    pop(item.type, item.content, item.color)
})

抽象工厂模式

function Car() {}
Car.prototype = {
  getPrice() {
    return new Error('抽象方法不能调用')
  },
  getBrand() {
    return new Error('抽象方法不能调用')
  }
}

上面声明了一个小汽车类,原型上定义了两个方法,却没有实际的作用,调用会报错。在一些大型的应用中,总会有一些子类去继承父类,这些父类经常会定义一些必要的方法却没有具体的实现,一旦子类创建了对象,子类就需要重写这些方法,不然会报错。

6 桥接模式 & 享元模式

桥接模式

在系统沿着多维度变化时,在不改变业务逻辑的情况下达到解耦的目的。比如事件与业务逻辑解耦。

页面种的导航常常会有一些特效,滑出和滑入外观有所改变,需要给每个事件绑定相应的事件:

var span = document.getElementById('spans');
span.onmouseenter = function() {
  this.style.color = 'red';
  this.style.background = '#ddd'
}
span.onmouseout = function() {
  this.style.color = 'blue'
  this.style.background = '#f5f5f5'

}

像这样的特效如果比较多的话,就会产生大量臃肿的代码。可以使用桥接模式来简化代码:

var span = document.getElementById('spans');
span.onmouseenter = function() {
  changeColor(this, 'red', '#ddd')
 
}
span.onmouseout = function() {
  changeColor(this, 'blue', '#f5f5f5')

}
function changeColor(dom, color, bg) {
  dom.style.color = color;
  dom.style.background = bg;
}

享元模式

运用共享技术有效的支持大量的细粒度的对象,避免对象间拥有相同内容造成多余开销。

假设有个内衣工厂,目前的产品有50种男式内衣和50种女士内衣,为了推销产品,工厂决定生产一些塑料模特来穿上他们的内衣拍成广告照片。正常情况下需要50个男模特和50个女模特,然后让他们每人分别穿上一件内衣来拍照。不使用享元模式的情况下,在程序里也许会这样写:

var Model = function( sex, underwear){
    this.sex = sex;
    this.underwear= underwear;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
    var maleModel = new Model( 'male', 'underwear' + i );
    maleModel.takePhoto();
};
for ( var j = 1; j <= 50; j++ ){
    var femaleModel= new Model( 'female', 'underwear' + j );
    femaleModel.takePhoto();
};

要得到一张照片,每次都需要传入sex和underwear参数,如上所述,现在一共有50种男内衣和50种女内衣,所以一共会产生100个对象。如果将来生产了10000种内衣,那这个程序可能会因为存在如此多的对象已经提前崩溃

  下面来考虑一下如何优化这个场景。虽然有100种内衣,但很显然并不需要50个男模特和50个女模特。其实男模特和女模特各自有一个就足够了,他们可以分别穿上不同的内衣来拍照

  现在来改写一下代码,既然只需要区别男女模特,那先把underwear参数从构造函数中移除,构造函数只接收sex参数:

var Model = function( sex ){
    this.sex = sex;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};

 分别创建一个男模特对象和一个女模特对象:

var maleModel = new Model( 'male' ),
femaleModel = new Model( 'female' );

  给男模特依次穿上所有的男装,并进行拍照:

for ( var i = 1; i <= 50; i++ ){
    maleModel.underwear = 'underwear' + i;
    maleModel.takePhoto();
};

  同样,给女模特依次穿上所有的女装,并进行拍照:

for ( var j = 1; j <= 50; j++ ){
    femaleModel.underwear = 'underwear' + j;
    femaleModel.takePhoto();
};

  可以看到,改进之后的代码,只需要两个对象便完成了同样的功能

 享元模式使用的目的是为了提高程序的执行效率与系统的性能。有时系统内存在大量对象,会造成大量的内存占用,这个时候使用享元模式来降低内存消耗是很有必要的。

7 访问者模式 & 参与者模式

这两种模式的外形很像,意思却是差别很大。

访问者模式

针对对对象结构中的元素,定义在不改变对象的前提下访问结构中的元素的新方法。

访问者模式的思想就是我们在不改变操作对象的同时,为它添加新的操作方法,来实现对操作对象的访问。

// 访问者模式:数组方法封装
var Visitor = (function() {
    return {
        splice: function() {
            var args = Array.prototype.splice.call(arguments, 1);
            return Array.prototype.splice.apply(arguments[0], args);
        },
        push: function() {
            var len = arguments[0].length || 0;
            var args = this.splice(arguments, 1);
            arguments[0].length = len + arguments.length - 1;
            return Array.prototype.push.apply(arguments[0], args);
        },
        pop: function() {
            return Array.prototype.pop.apply(arguments[0]);
        }
    }
})();

var a = new Object();
Visitor.push(a,1,2,3,4);
Visitor.push(a,4,5,6);
Visitor.pop(a);
Visitor.splice(a,2);

通过上面的访问者对象,我们可以为任何对象添加slice,push,pop方法,而不需要改变原来的对象。

访问者模式的适用场景
1、假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
2、假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。

参与者模式

在特定的作用域中执行给定的函数,并将参数原封不动的传递。

用下面的函数绑定事件,可以兼任不同的浏览器,但是会存在一个问题,没有办法传递数据。

let A = { event: {} }

A.event.on = function(dom, type, fn) {
    if(dom.addEventListener) {
        dom.addEventListener(type, fn, false)
    }else if(dom.attachEvent) {
        dom.attachEvent('on' + type, fn)
    }else{
        dom['on'+ type] = fn 
    }
}

解决办法是在回调里面传递参数: 

// 难点 addEventListener 不能传入data
// 解决 在回调函数里面做文章

A.event.on = function(dom, type, fn, data) {
    if(dom.addEventListener) {
        dom.addEventListener(type, function(e){
            fn.call(dom, e, data)
        })
    }
}

但是这样又带来了新的问题,添加事件的回调函数没有办法移除了, 借助于bind来解决:



// 新问题: 添加的事件回调函数不能移除了
// 解决: bind apply改变this  apply 小demo


function bind(fn, context) {
    return function() {
        return fn.apply(context, arguments)
    }
}

var demoObj = {
    title: '这是一个demo',
}
function demoFn() {
    console.log(this.title)
}
var bindFn = bind(demoFn, demoObj)

bindFn() // 这是一个例子



var btn = document.getElementsByTagName('button')[0];

var p = document.getElementsByTagName('p')[0]

//改造
function demoFn() {
    console.log(arguments, this)
}

var bindFn = bind(demoFn)

btn.addEventListener('click', bindFn)    // [MouseEvent] Window {external: Object, chrome: Object, document: document, demoObj: Object, btn: button…}

bindFn = bind(demoFn, btn)

btn.addEventListener('click', bindFn)    // [MouseEvent] <button>​click me​</button>​


// 有些高级浏览器有提供bind函数 实现如下

var bindFn = demoFn.bind(demoObj)

/**
 * 函数柯里化: 对函数的参数分割, 类似于多态
 */

function curry(fn) {
    //缓存数据slice方法

    var slice = [].slice

    var args = slice.call(arguments, 1);

    return function() {
        var addArgs = slice.call(arguments),
            allArgs = args.concat(addArgs)

        return fn.apply(null, allArgs)
    }
}

function add(num1, num2) {
    return num1 + num2
}
function add5(num) {
    return 5+num
}

//用curry实现两种加法 函数的创建过程在curry里实现了
let add7= curry(add, 7, 8)
let add58 = curry(add, 5)


//重写bind
function bind(fn, context) {
    var slice = Array.prototype.slice,
    args = slice.call(arguments, 2)

    return function() {
        var addArgs = slice.call(arguments),
            allArgs = addArgs.concat(args);

        return fn.apply(context, allArgs)
    }
}


// 测试

var demoData1 = {
        text: '这是第一组数据'
    },
    demoData2 = {
        text: '第二个数据'
    }

bindFn = bind(demoFn, btn, demoData1) 

btn.addEventListener('click', bindFn)    // [MouseEvent, Object<demoData1>]  <button>​click me​</button>​

其它设计模式

参考:JavaScript设计模式

参考资料:《JavaScript设计模式 张容铭》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值