第一篇
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设计模式 张容铭》