闭包是JavaScript中的一个重要特性,在之前的博文中,我们说闭包是一个'看似简单,其实很有内涵'的特性。当我们用JavaScript来实现相对复杂的业务建模时,我们可以如何利用'闭包'这个特性呢?JavaScript中的'原型继承',又可以解决业务建模中的哪些问题呢?今天我们就通过一家'榨汁机工厂'生产设计'榨汁机'的故事,来聊一聊'闭包'和'原型继承'在业务建模中的作用。
现在直播开始:

1》 工厂默认选用A型刀头方案制造榨汁机

例子当中我们主要涉及到2个函数:
1.榨汁机的生产工厂(JuiceMachineFactory)。
2.生产榨汁机的方案(createJuiceMachineGuide)。
在JuiceMachineFactory中,通过向createJuiceMachineGuide传入参数,可以获得能生产特定刀头的榨汁机的方法_createJuiceMachine,而通过执行_createJuiceMachine,则可以生产出特定刀头的榨汁机。对外表现出来的逻辑就是:JuiceMachineFactory可以确定生产出来的榨汁机所使用的刀头,并且能够生产榨汁机。
用代码表示如下:

var JuiceMachineFactory = function(){
var newFactory = {};
var _cutterFn = function(){
//榨汁机工厂当前使用的榨汁方法,相当于定义了一个特定的刀头
console.log( '正在使用:A型刀头。' );
}; //获得一种制造榨汁机的方法
var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); //制造了一台榨汁机
newFactory.createMachine = function(){
return _createJuiceMachine();
} return newFactory; }; var createJuiceMachineGuide = function( cutterFn ){
return function(){
var newMachine = {};
var _cutterInMachine = cutterFn ; //用传入的'刀头'作为制造榨汁机的原件 newMachine.makeJuice = function(){
//开始榨汁
console.log( '##开始榨汁:' );
//调用传入的私有变量
_cutterInMachine();
} return newMachine ;
}
}
//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
正在使用:A型刀头。

【分析】
1. 和我们预期的一样,我们通过传入_cutterFn来获得一个'采用刀头A来进行榨汁'的榨汁机制造方案,并且用这个方案制造了一台榨汁机。
2. 在使用新造的这台榨汁机进行榨汁的时候,确实是使用了刀头A的方法。
>> 通俗地讲,工厂制造这台榨汁机时,选用了哪种'制造方案',决定了生产出来的榨汁机的特性

2》工厂选用了B型刀头方案制造榨汁机
经过一段时间的发展,市场人员反馈说,现在用户都希望使用B型的刀头,我们得修改制造一下制造榨汁机的方案了。但是,从目前来看,我们并没有预留'修改榨汁机刀头'的接口,所以需要调整一下工厂,使它能够方便选择采用哪种刀头方案,调整后的代码如下:

var JuiceMachineFactory = function(){
var newFactory = {};
var _cutterFn = function(){
//榨汁机工厂当前使用的榨汁方法,相当于定义了一个特定的刀头
console.log( '正在使用:A型刀头。' );
}; //获得一种制造榨汁机的方法
var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); //制造了一台榨汁机
newFactory.createMachine = function(){
return _createJuiceMachine();
}
//修改新的制造榨汁机的方案
newFactory.setCutterFn = function( new_fn ){
_cutterFn = new_fn ;
return true;
} return newFactory; }; var createJuiceMachineGuide = function( cutterFn ){
return function(){
var newMachine = {};
var _cutterInMachine = cutterFn ; //用传入的'刀头'作为制造榨汁机的原件 newMachine.makeJuice = function(){
//开始榨汁
console.log( '##开始榨汁:' );
//调用传入的私有变量
_cutterInMachine();
} return newMachine ;
}
}
//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.makeJuice( ); //设置一下工厂的制造方案
factory.setCutterFn( function(){
console.log( '正在使用:B型刀头。' );
});
//用新的方案再制造一台榨汁机
var machine_B = factory.createMachine();
machine_B.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
正在使用:A型刀头。
##开始榨汁:
正在使用:A型刀头。

【分析】
1. 采用默认的工艺,制造出来的榨汁机,采用的是 A型刀头。
2. 后面通过setCutterFn( ) 调整了制造方案,生产出来的榨汁机,按理,应该是采用“B型刀头”了啊!为什么还是A型刀头?!

这个问题引出了JavaScript的一个知识点:JavaScript中的对象的赋值,是一种'引用'传递。
我们分析一下整个过程。

1. 下面的这段代码执行之后,变量_cutterFn引用了一个'函数对象',我们给这个函数对象一个编号:F_007。

var _cutterFn = function(){
  //榨汁机工厂当前使用的榨汁方法,相当于定义了一个特定的刀头
  console.log( '正在使用:A型刀头。' );
};

示意图如下:

2.  执行下面的这段代码之后,相当于变量_cutterFn 和 _cutterInMachine 都指向了 F007 这个函数对象。

var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); 

示意图如下:

3. 当我们向用新的方案来制造B型刀头榨汁机,在前面的代码中,我们用了setMakeMachineMethod方法,代码如下:

//设置一下工厂的制造方案
factory.setMakeMachineMethod( function(){
console.log( '正在使用:B型刀头。' );
});

此时各个变量所指的对象如下所示:

因为只是赋值仅仅是变量之间的'引用'传递,所以,执行完毕之后,变量_cutterFn 和 _cutterInMachine 并没有必然的联系!也就是说,_cutterFn 指向了别的地方(代号为F008的新对象),_cutterInMachine 并不会随之改变,除非再来一次赋值。

在我们的例子,我们要改变的其实不是_cutterFn所指的对象,我们应该改变 _cutterInMachine 所指的对象。也就是要重新'赋一次'值,生成一个新的方案_createJuiceMachine。调整后的代码如下:

var JuiceMachineFactory = function(){
var newFactory = {};
var _cutterFn = function(){
console.log( '正在使用:A型刀头。' );
}; //获得一种制造榨汁机的方法
var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); //制造了一台榨汁机
newFactory.createMachine = function(){
return _createJuiceMachine();
}
//修改新的制造榨汁机的方案
newFactory.setMakeMachineMethod = function( new_fn ){
_cutterFn = new_fn ;
//关键是修改下面的内容
_createJuiceMachine = createJuiceMachineGuide( _cutterFn );
return true;
} return newFactory; }; var createJuiceMachineGuide = function( cutterFn ){
return function(){
var newMachine = {};
var _cutterInMachine = cutterFn ; //用传入的'刀头'作为制造榨汁机的原件 newMachine.makeJuice = function(){
//开始榨汁
console.log( '##开始榨汁:' );
//调用传入的私有变量
_cutterInMachine();
} return newMachine ;
}
}
//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.makeJuice( ); //设置一下工厂的制造方案
factory.setMakeMachineMethod( function(){
console.log( '正在使用:B型刀头。' );
});
//用新的方案再制造一台榨汁机
var machine_B = factory.createMachine();
machine_B.makeJuice( ); //再看一下原来制造的那台machine_A,是否还是使用了A型刀头?
//答案是还是A型刀头,因为每执行一次createJuiceMachineGuide,其实是新增一个闭包!
//也就是说,
//制造machine_A的那套方案,并没有消失,它的榨汁的方法,还是指向F007那个函数对象。
//而制造machine_B的那套方案,榨汁方法指向了F008这个函数对象。
machine_A.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
正在使用:A型刀头。
##开始榨汁:
正在使用:B型刀头。
##开始榨汁:
正在使用:A型刀头。

这回就正确了!
在生成对象machine_A和machine_B的时候,实际上我们调用了两次createJuiceMachineGuide,生成了两个独立的'运行时环境',而在这两次调用的过程中,运行时环境中的_cutterInMachine私有变量所指向的对象是不一样的。
整个过程示意如下:

【小节】
1. JavaScript中的赋值、参数传递都是'引用'传递。
    "这个好理解不?",
    "好理解。"
2. 执行一次'闭包'函数,会生成一个独立的'运行时环境'。
   "这个好理解不?"
   "好理解哈,相当于返回一个对象,这个对象中的函数可以访问'闭包'函数中的私有成员。"
把它们放到一起综合起来理解,就可以很好的解释"工厂可以决定生产出来的榨汁机采用哪种'刀头'了。"

2》给榨汁机添加'水果'

截止目前为止,我们在'榨汁机'这个模型中,其实还仅仅定义了'榨汁方法',并且确定,采用哪种榨汁方法(选用哪种刀头),是在工厂设置'制造方案'是决定的。但是,完整的榨汁过程,应该是有'水果'的,并且,'水果'应该是可以替换的。

用代码描述如下:

var JuiceMachineFactory = function(){
var newFactory = {};
var _cutterFn = function(){
console.log( '正在使用:A型刀头。' );
}; //获得一种制造榨汁机的方法
var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); //制造了一台榨汁机
newFactory.createMachine = function(){
return _createJuiceMachine();
}
//修改新的制造榨汁机的方案
newFactory.setMakeMachineMethod = function( new_fn ){
_cutterFn = new_fn ;
//关键是修改下面的内容
_createJuiceMachine = createJuiceMachineGuide( _cutterFn );
return true;
} return newFactory; }; var createJuiceMachineGuide = function( cutterFn ){
return function(){
var newMachine = {};
var _cutterInMachine = cutterFn ; //用传入的'刀头'作为制造榨汁机的原件
var _fruit = '橙子' ; //默认是橙子汁

newMachine.selectFruit = function( new_fruit ){
_fruit = new_fruit ;
} newMachine.makeJuice = function( ){
//开始榨汁
console.log( '##开始榨汁:' );
console.log( '使用水果<' +_fruit+'>' );
//调用传入的私有变量
_cutterInMachine();
} return newMachine ;
}
}
//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.makeJuice( );
//选择另外的水果
machine_A.selectFruit( '西瓜' );
//再次榨水果
machine_A.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
使用水果<橙子>
正在使用:A型刀头。
##开始榨汁:
使用水果<西瓜>
正在使用:A型刀头。

【分析】
与我们预期的一样:默认情况下,我们使用<橙子>来榨果汁,因为我们也开放了操纵_fruit的接口,所以,我们也可以使用<西瓜>来榨果汁。

【小节】
到目前为止,我们掌握了以下两个情况的处理:
1. 在工厂中可以根据需要调整制造方案:

我们通过传入参数的方式,结合闭包的特性,更换_createJuiceMachine的值,用另外一个'闭包'的运行结果代替之前的'闭包运行结果'。
由于两个运行结果中的_cutterInMachine的值不同,相当于产生了两套不同的制造榨汁机的方法。
>>
回到举例的场景中:制造榨汁机的工厂,可以决定生产出来的榨汁机采用哪种'刀头'。
对于JavaScript本身,由于'高阶函数'的特点,相当于通过传入参数,可以定制'闭包'函数本身。
举例中:
         createJuiceMachineGuide:制造'闭包'函数的函数。
         _createJuiceMachine:就是执行createJuiceMachineGuide之后返回的'闭包'函数。
         var machine_A = factory.createMachine();:machine_A,执行_createJuiceMachine之后的返回的一个'闭包'。
相关知识点:闭包,高阶函数特性,JavaScript赋值操作的本质。

2. 在榨汁时,可以设定榨汁采用的'水果'。
这相当于给了我们一个从'业务模型'认识'闭包'函数的好例子:
a. 运行'闭包'函数,会产生一个'对象'(或函数定义)。这个'对象'中,原来定义在'闭包'函数中的局部变量和参数,成为这个'对象'的私有成员。而这个'对象'中的方法,则成为'对象'的对外接口(公共函数),这些对外接口可以访问'对象'的私有成员。当然,一般不会在返回的'对象'中直接设置'属性'成员,因为那样是'违背'封装性的原则的。
b. 设计一个'闭包'函数的时候:
1> 如果某个成员是业务对象的属性(例如:_fruit),那么,就把它声明为'闭包'函数的局部变量或通过参数传入。
2> 如果某个函数并不想开放给别人使用(例如:_cutterInMachine),那么也把它声明为'闭包'函数的局部变量或通过参数传入。
3> 业务对象对外呈现出来的功能(例如:makeJuice),则把它声明为该对象的'成员'函数。
4> '闭包'函数中的'私有成员',可以通过对外开放出来的公共函数(makeJuice和selectFruit)更改。
讨论到这里,借助'闭包'和'高阶函数'的特性,我们似乎已经能够实现应用中的'业务'建模。我们定义好一个'闭包函数'之后,就可以'产生满足我们业务要求'的对象,并且,这些对象具有很好的'封装性'。
到现在为止,我们还没有用到this,没有this的日子,似乎也是很美好的。

3》给榨汁机增加云端系统

现在的社会,当我们讲到家电设备时,我们往往会说‘智能家电’,取名称时喜欢带一个'云'字,例如:云冰箱,云电视。其实质就是,每个家电背后都有一个'云端'系统,通过'云端'系统,我们可以'远程'与我们的家电通信。比如,还没有下班,我们知道今天下午有一场AC米兰的球赛。这时候,借助'云端'系统,我们可以通过手机遥控家里的'云电视',让它把AC米兰的球赛先录下来,这样,我们下班回到家就可以观看。
现在,我们要给榨汁机也增加这样的'云端'系统,这样,我们就可以在下班回家的路上远程操控榨汁机,一回到家,就能喝到'新鲜'的果汁。比起那些只有'定时'功能的家电,'云家电'还是要方便很多,比如:你如果发现路上堵车了,就可以晚点启动'榨汁作业'。如果老板告诉你晚上要加一下班,你就可以不启动'榨汁作业'。
好了,现在我们就给榨汁机增加一个'云控制系统',然后增加一个'远程启动'的功能。

var JuiceMachineFactory = function(){
var newFactory = {};
var _cutterFn = function(){
console.log( '正在使用:A型刀头。' );
}; //获得一种制造榨汁机的方法
var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); //制造了一台榨汁机
newFactory.createMachine = function(){
return _createJuiceMachine();
}
//修改新的制造榨汁机的方案
newFactory.setMakeMachineMethod = function( new_fn ){
_cutterFn = new_fn ;
//关键是修改下面的内容
_createJuiceMachine = createJuiceMachineGuide( _cutterFn );
return true;
} return newFactory; }; var createJuiceMachineGuide = function( cutterFn ){
return function(){
var newMachine = {};
var _cutterInMachine = cutterFn ; //用传入的'刀头'作为制造榨汁机的原件
var _fruit = '橙子' ; //默认是橙子汁 var _cloudCenter = {
remoteStart:function(){
//这里模拟执行远程启动时,云端控制'榨汁机'开始榨汁
console.log( '^^云中心收到指令,马上控制榨汁机开始榨汁^^' );
newMachine.makeJuice();
return ;
}
} newMachine.selectFruit = function( new_fruit ){
_fruit = new_fruit ;
} newMachine.makeJuice = function( ){
//开始榨汁
console.log( '##开始榨汁:' );
console.log( '使用水果<' +_fruit+'>' );
//调用传入的私有变量
_cutterInMachine();
} //增加远程启动榨汁机的功能
newMachine.remoteStart = function(){
//模拟这个功能要借助云端中心执行
_cloudCenter.remoteStart();
} return newMachine ;
}
} //先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.remoteStart( );

运行代码之后输出如下结果:

^^云中心收到指令,马上控制榨汁机开始榨汁^^
##开始榨汁:
使用水果<橙子>
正在使用:A型刀头。

【分析】
嘿嘿,看来增加一个云端的系统也是很简单的嘛。没错,这样的设计目前来看是符合我们的预期的。但是,我们是一个'榨汁机'的工厂,这就意味着我们不是'仅仅'生产一台'榨汁机',我们可能会生产上千台,上万台这样的榨汁机。这样,我们再次从'空间'分配的角度来剖析整个榨汁机的过程。
运行下面的代码:

var  machine_A = factory.createMachine();

相当于执行了_createJuiceMachine这样的闭包函数,我们知道:"每执行一次'闭包'函数,JavaScript引擎会为闭包函数中的'局部变量'分配一次空间。"
执行1次'闭包'函数factory.createMachine()之后和执行n次'闭包'函数之后的花费空间示意:

假设_cloudCenter这个局部变量占据了很大的空间,我们用深颜色标识出来。回到业务场景,搭建一个'云中心'需要大量的资金投入,但是,我们现在的搞法就相当于:
"为生产出来的每一台榨汁机,都搭建一个专属的云中心。"
显然,这是不合理的。

有同学可能会说,对于_cloudCenter这个局部变量,我们没有必要在'闭包'函数的函数体内专门全新生成一个对象,可以引用'外部'的一个唯一对象,这样就可以节省大量的空间。

这种方式确实也是一种解决方案,在之前的'地址选择控件开发'这篇博文中,针对全国地址信息模型这个比较大的对象,我们就是采用这种方式。

我们再仔细审视一下_createJuiceMachine这个闭包函数,除了_fruit之外,似乎其他的方法也都很_cloudCenter一样,没有必要每生成一个对象,就全新打造一份!
综合上面的分析,我们可以使用JavaScript的'原型继承'特性,来完成我们的业务目标。
我们先来看一下,优化之前,machine_A, machine_B......这些对象的'原型链'关系。

如果基于'原型继承'来实现我们的业务目标,那么,我们希望我们的'原型链'关系会变成如下所示:

有一个BaseMachine,具有我们'期望'的功能。新创建的machine_A, machine_B都'原型继承'自BaseMachine。

下面,我们用代码来实现这样的业务:

var JuiceMachineFactory = function(){
var newFactory = {};
var _cutterFn = function(){
console.log( '正在使用:A型刀头。' );
}; //获得一种制造榨汁机的方法
var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); //先制造一个'原型对象'
var _base_machine = _createJuiceMachine(); //制造了一台榨汁机
newFactory.createMachine = function(){
//基于'原型'对象来创建新的榨汁机
var new_machine = Object.create( _base_machine );
new_machine.setDefaultFruit();
return new_machine;
}
return newFactory; }; var createJuiceMachineGuide = function( cutterFn ){
return function(){
var newMachine = {};
var _cutterInMachine = cutterFn ; //用传入的'刀头'作为制造榨汁机的原件 var _fruit = '橙子' ; //默认是橙子汁 var _cloudCenter = {
remoteStart:function(){
//这里模拟执行远程启动时,云端控制'榨汁机'开始榨汁
console.log( '^^云中心收到指令,马上控制榨汁机开始榨汁^^' );
//注意:因为在makeJuice中使用了this.fruit,
//所以,这里也要用this来调用,否则,makeJuice中的this所指对象就不正确!
this.makeJuice();
return ;
}
} newMachine.selectFruit = function( new_fruit ){
this.fruit = new_fruit ;
} //看一下当前使用的苹果
newMachine.showFruit = function( ){
console.log( '>>正在使用的水果是:' + this.fruit );
return ;
} newMachine.setDefaultFruit = function(){
this.fruit = _fruit; //设置新的对象使用默认的水果
} newMachine.makeJuice = function( ){
//开始榨汁
console.log( '##开始榨汁:' );
console.log( '使用水果<' +this.fruit+'>' );
//调用传入的私有变量
_cutterInMachine();
} //增加远程启动榨汁机的功能
newMachine.remoteStart = function(){
//模拟这个功能要'借助'云端中心执行
//相当于要'借用'_cloudCenter.remoteStart这个函数,借用函数怎么搞,用apply哈
_cloudCenter.remoteStart.apply( this , arguments );
}
return newMachine ;
}
} //先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var machine_A = factory.createMachine();
//设置一下水果的值
machine_A.selectFruit( '苹果' );
//查看一下当前所使用的水果
machine_A.showFruit( );
//使用这台榨汁机
machine_A.remoteStart( ); //再全新生成一个对象machine_B
//
var machine_B = factory.createMachine();
machine_B.makeJuice( );

运行代码之后输出如下结果:

>>正在使用的水果是:苹果
^^云中心收到指令,马上控制榨汁机开始榨汁^^
##开始榨汁:
使用水果<苹果>
正在使用:A型刀头。
##开始榨汁:
使用水果<橙子>
正在使用:A型刀头。

【分析】
就是为了使用'原型继承'这个特性,我们引入了this,为了达到各种预期的效果,我们对各个函数都进行了调整。
1. 工厂中,生成machine对象的方式,调整成:先生成一个原型对象(_base_machine),然后基于这个原型对象创建全新的对象
2. 基于原型对象创建的全新对象,自己是没有任何'成员属性'的。
所以,我们通过调用new_machine.setDefaultFruit();来为新创建的对象添加自己的成员属性fruit,而它的值就是原型对象(_base_machine)中闭包空间中的值_fruit,这样符合我们的业务预期。
3. 将原型对象中,对外公开方法中_fruit的地方,都换成了this.fruit,因为我们希望,当我们使用原型对象_base_machine中的对外公开方法时,其中的fruit应该指代全新创建的对象的fruit成员,而不是生成_base_machine时,闭包中存在的_fruit。
4. 原型对象的对外公开方法(remoteStart),其实调用的不是原型对象本身拥有的方法,而是'借用'了'第三方对象'_cloudCenter中的方法。如果不做调整,直接调用_cloudCenter.remoteStart();那么,函数体的所指代的this就会指向了_cloudCenter。如何向别的对象'借用'方法,我们在前面的博客'闲聊JS中的apply和call'中有比较详细的介绍,所以,我们采用如下的方式调用:

_cloudCenter.remoteStart.apply( this , arguments );

这样,就确保了当你执行new_machine.remoteStart()时,_cloudCenter.remoteStart函数体中的this也是指代new_machine,与我们的业务预期相符。

经过一番周折,我们终于利用JavaScript的'原型继承'特性,实现了我们的'业务目标'。JavaScript的函数调用特性,使得JavaScript更具有'弹性',当然,也使实现逻辑变得更加复杂。当你'迫不得已'要在函数体中使用this的时候,你一定要想清楚,到时候真正调用这个函数的对象会是谁,它具有什么样的成员属性。
回顾整个过程,下面这个语句,其实是用到了一定的随意性,或者说是JavaScript语言的'弹性'。

newMachine.setDefaultFruit = function(){
this.fruit = _fruit; //设置新的对象使用默认的水果
}

如果不存在,那么就给对象新增一个fruit成员属性。不过问题不大,我们把这个随意性控制在selectFruit这个方法当中了。现在再回到第2个场景"工厂选用了B型刀头方案制造榨汁机",因为我们使用了'原型继承',要换一种榨汁方式,就需要修改原型对象中'运行时环境'中的_cutterInMachine变量。到底要如何修改呢?就留作练习吧。

【总结】

今天我们以'榨汁机工厂'生产特定功能的'榨汁机'为场景,讲解了如何用JavaScript的'闭包'特性进行'业务建模'。首先我们采用了更改'_createJuiceMachine'的方式达到更换榨汁方式的目的。后来,我们给生产的'榨汁机'增加了'云'功能。当增加了'云'功能之后,发现从'空间利用'的角度看,原来的生产'榨汁机'的方式存在一些问题。最后,我们利用了JavaScript的'原型继承'特性,解决了'空间利用'问题。'榨汁机'只是我们讲故事的道具,真正想表达的是JavaScript的各种特性。
下次如果有时间,我们一起聊聊JavaScript社区中的设计模式。这里先举一个小故事。
有一天,老板对小明说,“小明啊,周末我们超市苹果打特价,6.98一斤,你画一张海报宣传一下。”于是,小明设计了下面的一张海报:

老板说,“设计得不错,果然身手不凡哈!这样,你再画1000份,明天周五给你放一天假,你去东门路口把画好的1000份宣传单发了。”
"再画1000份?!"
现在已经是周四下午5点,你觉得小明是会拿起画笔赶紧开始画呢?还是拿起画笔赶紧开始画呢?当然不会啦,小明肯定会选择拿着之前画好的宣传海报,去扫描复印1000份。

生活中会遇到许多的'迫不得已',所以,我们要学会聪明做事,善待自己。只有聪明地做事,才能使用户满意,老板开心,自己也不闹心。而对于我们写代码的人来说,了解一些'设计模式',也许能使自己做到:聪明地做事情,优雅地写代码,从容面对各种'迫不得已'。
感谢诸位捧场,谢谢。

直播开始:'云榨汁机'诞生记--聊聊JavaScript中的'业务建模'的更多相关文章

  1. 面试说:聊聊JavaScript中的数据类型

    前言 请讲下 JavaScript 中的数据类型? 前端面试中,估计大家都被这么问过. 答:Javascript 中的数据类型包括原始类型和引用类型.其中原始类型包括 null.undefined.b ...

  2. 聊聊Javascript中的AOP编程

    Duck punch 我们先不谈AOP编程,先从duck punch编程谈起. 如果你去wikipedia中查找duck punch,你查阅到的应该是monkey patch这个词条.根据解释,Mon ...

  3. 来聊聊JavaScript中的防抖和节流

    目录 JavaScript防抖和节流 问题还原 防抖 什么是防抖 使用场景 节流 什么是节流 使用场景 JavaScript防抖和节流 问题还原 我们先来通过代码把常见的问题还原: <html& ...

  4. 聊聊javascript中的面向对象

    面向对象这个东西一直晕晕乎乎的,正好这段时间没有活,可以好好整理整理了! 1.什么是对象? 其实这个说起来一切东西都是对象 2.目前我们使用对象的时候,使用的是两种设计模式杂糅起来的 分别是原型模式和 ...

  5. 【跟着子迟品 underscore】JavaScript 中如何判断两个元素是否 "相同"

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  6. JavaScript中的正则表达式(终结篇)

    JavaScript中的正则表达式(终结篇) 在之前的几篇文章中,我们了解了正则表达式的基本语法,但那些语法不是针对于某一个特定语言的.这篇博文我们将通过下面几个部分来了解正则表达式在JavaScri ...

  7. javascript中的原型继承

    在Javascript面向对象编程中,原型继承不仅是一个重点也是一个不容易掌握的点.在本文中,我们将对Javascript中的原型继承进行一些探索. 基本形式 我们先来看下面一段代码: <cod ...

  8. 谈谈javascript 中的函数问题

    聊聊javascript中的函数 本文可作为李刚<疯狂htmlcssjavas讲义>的学习笔记 先说一个题外话 前几天在知乎上流传着一个对联  上联是雷锋推到雷峰塔 nnd 这是什么对联? ...

  9. JavaScript中的this指向规则

    首先,JavaScript的this指向问题并非传说中的那么难,不难的是机制并不复杂,而被认为不好理解的是逻辑关系和容易混淆的执行上下文.这篇博客也就会基于这两个不好理解的角度来展开,如要要严格的来对 ...

随机推荐

  1. Python中的注释(转)

    一.单行注释     单行注释以#开头,例如:     print 6 #输出6 二.多行注释     (Python的注释只有针对于单行的注释(用#),这是一种变通的方法)      多行注释用三引 ...

  2. Android记录一个setTextColor常见的一个bug

    今天写代码 一不小心就犯了个错误. 细致检查才发现,仅记录一下,防止各位同学犯相同的错误哦 代码例如以下: remote.setTextColor(summaryId, R.color.news_ha ...

  3. IFormatProvider,ICustomFormatter,IFormattable总结

    IFormatProvider中 public object GetFormat(Type formatType); 该方法主要用于获取一个 ICustomFormatter接口的实例 ICustom ...

  4. Linux 的启动流程

    转载:http://www.ruanyifeng.com/blog/2013/08/linux_boot_process.html 更多文档参见:http://pan.baidu.com/s/1hqo ...

  5. Sumdiv 等比数列求和

    Sumdiv Sumdiv Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 15364   Accepted: 3790 De ...

  6. MVC框架 - 捆绑

    捆绑和缩小是两个性能改进提高应用程序在请求负载时的技术.目前大多数的主流浏览器限制每个主机同时连接到六个数量.这意味着,在一个时间,所有的其他请求将被浏览器排队. 启用捆绑和缩小 为使捆绑和缩小MVC ...

  7. 单例模式——Singleton

    模式分类: 从目的来看: 1.创建型(Creational)模式:负责对象创建. 2.结构型(Structural)模式:处理类于对象间的组合. 3.行为型(Behavioral)模式:类与对象交互中 ...

  8. 新手留言薄asp.net MVC 学习(适合新手学习)

    以下是发布到IIS后的效果截图: 1)首页展示: 2)登录后台页面展示: 3)后台页面展示: 该项目源代码下载地址:http://files.cnblogs.com/files/f12-liugang ...

  9. uboot在s3c2440上的移植(1)

    一.移植环境 主  机:VMWare--Fedora 9 开发板:Mini2440--64MB Nand,Kernel:2.6.30.4 编译器:arm-linux-gcc-4.3.2.tgz u-b ...

  10. UIBootatrap:是由AngularJS UI团队编写的纯AngularJS实现的Bootstrap组件

    本文为翻译文档.原文是https://angular-ui.github.io/bootstrap/(需要FQ). 准备工作: 依赖关系:这个库中包含一组基于Bootstrap组件和CSS的原生Ang ...