珠峰培训

设计模式

作者:

2015-11-23 15:52:04

133

一.this

  跟静态语言大相近庭的是,Javascript中this总是指向一个对象,而具体指向哪个对象,是在运行时根据执行环境(也就是被压栈的闭包函数)动态绑定的,而非其他语言中声明时的环境。

二.this指向

  1. 作为对象方法调用
  2. 作为普通函数调用
  3. 构造器调用
  4. call,apply调用
  5. this丢失

三.this指向示例

        //1.作为对象方法调用
        var obj={
            a:1,
            getA:function(){
                console.log(this==obj);   //返回true
                console.log(this.a);      //指向调用对象:obj
            }
        };

        obj.getA();
        //2.作为普通函数调用
        window.name='globalname';
        var getName=(function(name){
            //use strict"                 //严格模式下,this指向undefined
            var name='private';
            name='newgolbalname';         //name不会影响全局,因为这是一个闭包
            console.log(name);            //输出newgolbalname
            console.log(this.name);       //指向window
        })(window.name);

        console.log(name);                //输出globalname
        //3.作为构造器调用
        var MyClass=function(){
            this.name='sven';              //this指向new返回对象:myClass
            return {                       //如果构造器不显示返回数据,或者返回的不是对象,则返回this,否则返回对象
                name:'anne'
            }
        };

        var myClass=new MyClass();
        console.log(myClass.name);
        //4.作为call,apply调用
        var obj1={
            a:'obj1',
            getA:function(){
                console.log(this.a);      //指向调用call参数:obj2
            }
        };

        var obj2={
            a:'obj2'
        };

        obj1.getA.call(obj2);
        //5.this丢失
        var obj3={
            myName:'sven',
            getName:function(){
                console.log(this.myName);
            }
        };

        obj3.getName();

        var getName2=obj3.getName;        //把函数给了getName2,直接调用,this会指向window,因为这只是普通调用函数的方式(函数赋值只会赋值函数体的字符串)
        getName2();

四.this指向实例

我们是否觉得document.getElementById过长,试图封装简短函数代替它?比如prototype.js中

        var getId=function(id){
            return document.getElementById(id)
        };

我们也许思考过,为什么不用更简单的方式:

        var getById=document.getElementById;

我们不妨花一分钟时间,再浏览器中看看效果(注意加粗倾斜部分)。

document.getElementById("unfavorite-btn")
<li id=​"unfavorite-btn" data-toggle=​"tooltip" data-placement=​"top" data-original-title=​"取消收藏" data-url=​"/​course/​36/​unfavorite" style=​"display:​none;​">​…​</li>​
var getId=document.getElementById
undefined
getId("unfavorite-btn")

VM1163:2 Uncaught TypeError: Illegal invocation(…)

 机智的你一定想到了,这发生了this丢失。在document.getElementById函数内部,this指向document(作为document对象方法调用)。而我们getId的函数体内this指向window,导致了这个错误,那么如何修改呢?

        document.getElementById=(function(func){
            return function(){
                return func.apply(document,arguments)
            };
        })(document.getElementById);

        var getById=document.getElementById;
        window.onload=function(){
            alert(getById("test").id);
        };

 我们重写了document.getElementById函数,让这个函数执行的时候,this永远指向document(注意:apply(document))。我们的getId也就是

        function(){
            return func.apply(document,arguments)
        };

当getId执行时候,会执行document.getElementById.apply(document,arguments)这段代码,我们把id作为参数传入到这个函数,并且将获得到的元素return,所以调用时候,只需要getId("domId")即可得到元素。

一.判断区操作索引

  特点:省去了索引操作区代码。此方法在{}内无法获得正确索引。
        var list=[1,2,3];
        for(var i=0,item;item=list[i++];){
            console.log("正序:"+item);
        }


二.迭代删除方法

  特点:如果不需要再操作数据源,可以选择此方法,会删除掉了数组内容。
        var list=[1,2,3];
        for(;item=list[0];){
            list.shift();
            console.log("正序:"+item);
        }

三.倒叙排列

  利用i==0返回false原理实现倒叙排列
        var list=[1,2,3],i=list.length;
        while(i){
            i--;
            console.log("倒序:"+list[i]);
        }
  利用i==0返回false原理实现倒叙排列
        var list=[1,2,3],i=list.length;
        for(;i--;){
            console.log("倒序:"+list[i]);
        }

      

MarkdownPad Document

独自走向长坂坡,蘑菇不罗嗦,直接上代码。

1.apply最大的作用之一就是函数借调,修改this指向和传递参数

   /*
    * 计算函数
    * params:要计算的数值
    * */
   var calculate={
       sum:function(){              //求和
           var sum=0;
           for(var i= 0,item;item=arguments[i++];){
               sum+=item;
           }
           return sum;
       },                           //求积
       mult:function(){
           var sum=1;
           for(var i= 0,item;item=arguments[i++];){
               sum*=item;
           }
           return sum;
       }
   };

   /*
   * 计算数组函数,根据传入计算方式计算结果
   * params:计算方式
   * */
   Array.prototype.mathOperate=(function(){
       var cache={};                                                        //这里对已经计算的值进行缓存
       return function(){
           var operate=[].shift.call(arguments);
           var vals=[].join.call(this);
           var key=operate.concat(vals);
           if(cache[key]){
               return cache[key];
           }else{
               return cache[key]=calculate[operate].apply(arguments,this);   //我们借调了其他对象的函数
           }
       }
   })();

   alert([1,2,3,4].mathOperate("sum"));
   alert([1,2,3,4].mathOperate("mult"));
   alert([1,2,3,4,5].mathOperate("sum"));
   alert([1,2,3,4,5].mathOperate("mult"));
   alert([1,2,3,4].mathOperate("sum"));                                      //第二次计算,从缓存中读取
   alert([1,2,3,4].mathOperate("mult"));                                     //第二次计算,从缓存中读取

2.apply可以用来调用高阶函数。

以下是个单例调用函数的例子,第一次调用函数,执行函数,以后每次调用,获得第一次执行函数的返回值,以后我们会在单例模式中再次遇见它。

   var getSingleFn=function(fn){
       var ret;
       return function(){               //利用apply把参数传递给高阶函数
           return ret||(ret=fn.apply(this,arguments));
       }
   };

   var getScript=getSingleFn(function(){
       var script=document.createElement("script"),options=[].shift.call(arguments);
       options.src?script.src=options.src:script.innerHTML=options.html;  //高阶函数可以直接获得getSinleFn传递过来的参数
       return script;
   });

   var script1=getScript({
       html:"alert('hello')"
   });
   var script2=getScript({
       html:"alert('world')"                     //从缓存中读取,此处代码不会生效
   });

   alert("脚本是否相等:"+(script1==script2));     //第二次计算,从缓存中读取

   window.onload=function(){
       document.getElementsByTagName("head")[0].appendChild(script1);
   }

3.我们再来玩转复杂点的高阶函数,让我们看看如何用一个高阶函数的参数传递到另一个高阶函数内部。

   Function.prototype.before=function(beforeFn){
        var self=this;                                       //这个this指向func对象
        return function(){
            if(typeof beforeFn=='function'){
                beforeFn.apply(self,arguments);              //把参数传递给before高阶函数beforeFn
                self.apply(self,arguments);
            }
            return self;
        }
    };

    Function.prototype.after=function(afterFn){
        var self=this;                                      //这个this指向before函数的返回值
        return function(){                                  //当我们调用func时,这个函数开始执行
            if(typeof afterFn=='function'){
                //加了判断为了防止直接调用after,没有执行before函数,context为空,并且this指向window。
                var context=self.apply(this===window?self:this,arguments);                  //before函数的返回值开始执行,我们把参数(这里是1,2,3)传递给before函数的返回值
                afterFn.apply(!context||context===window?self:context,arguments);           //最后执行after函数
            }
        }
    };

    var func=function(){
        console.log("函数开始执行,this:"+this,"arguments:"+[].join.call(arguments,","));
    };

    func=func.before(function(){
        console.log("before开始执行,this:"+this,"arguments:"+[].join.call(arguments,","));
    }).after(function(){
        console.log("after开始执行,this:"+this,"arguments:"+[].join.call(arguments,","));
    });
    func(1,2,3);

4.apply其他的用途:数据借调,函数借用其他数据源对象数据。

    var data={
       list:[
           {id:1,name:'test1'},
           {id:2,name:'test2'}
       ]
    };

    var ListBox=(function(){
       var drawCount=0;
       return function(){
           var self=this,args=arguments,list=data.list;
           list.forEach(function(item){
               var options=args[0];
               options["count"]=drawCount;
               //这里把this指向data,并且修改了传入参数,带上了绘制次数
               drawCount=args.callee.draw.drawListItem.apply(item,[options]);
           }.bind(this));
       }
    })();


    ListBox.draw={
        drawListItem:function(options){
            console.log("绘制项:"+this.id+",绘制内容"+this.name+"绘制次数:"+(++options.count)+"是否可见:"+options.isVisible);
            return options.count;
        }
    };

    ListBox({isVisible:true});

apply的用法还是非常广泛的,这里仅仅列举了一小部分,学好这些,你就可以正大光明去装x了。