• 直播开始:'云榨汁机'诞生记--聊聊JavaScript中的'业务建模'


    闭包是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份。

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

  • 相关阅读:
    PHP多进程模拟多客户端并发访问远程mysql数据库进行网络压力测试
    不安装证书如何通过模拟器+Fiddler抓包APP的HTTPS请求?
    Fiddler+模拟器+APP抓包HTTPS 为什么有时候抓不到?
    什么是宽表?什么是窄表?宽表和窄表的区别、优点、缺点以及各自的用处
    php-fpm的配置详解
    网站千万级访问量优化服务器负载方案
    理解正向代理&反向代理
    字符串数组中查找固定字符串
    paddingBottom和layout_marginBottom的区别
    android线性布局
  • 原文地址:https://www.cnblogs.com/alai88/p/5544522.html
Copyright © 2020-2023  润新知