JavaScript闭包 this 匿名函数 相关eval,apply,call 详解

首先说说我自己对于闭包的理解:闭包就是通过返回一个函数,可以通过这个函数访问局部变量(私有变量);(JavaScript高级程序语言一书中的解释是:闭包是指有权访问另一个函数作用域中的变量的函数。)

我是看了阮一峰的一篇关于闭包的博客(http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html)然后开始详细挖了挖关于闭包的各种情况例子。

1.函数内部可以访问函数外部变量,但是函数外部无法读取函数内部变量(函数内部变量需要var命令,否则变量为全局变量)。

2.闭包函数:通过一个函数A返回其闭包函数B,此闭包函数B能够访问该函数A的变量,但是函数A外不能访问,此时外界通过函数A返回的闭包函数B就能访问到A的局部变量了。此处说得有点晕;来个例子:

var f1=function(){
  var ans=100;
  var f2=function(a){
    sum=a+ans;
    alert(sum);
  }
  return f2;
}
var res=f1();
res(200);//300

3.闭包还有个用途就是能够让被包含函数的变量的值始终保存在内存中。

var f1=function(){
        var ans=100;
        nAdd=function(){
                ans+=300;;
        }
        var f2=function(a){
                sum=a+ans;
                alert(sum);
        }
        return f2;
}
var res=f1();
res(200);//300
nAdd();
res(200);//600

解析:res=f1();res得到f1返回的闭包函数f2。两次值不一样,第一次运行值为300;第二次值为600;即在第一次运行之后f1局部变量ans一Hi保存在内存中,并没有在f1调用之后被自动清除。因为f1是f2闭包函数的父函数,而f2被赋给了一个全局变量,从而使得f2始终在内存中不被清除,因为f2依赖于函数f1因此函数f1也不会在调用之后清除。

其中函数nAdd因为没有使用var命令所以nAdd为一个全局变量,同时也是一个匿名函数,这个匿名函数本身也是一个闭包,所以nAdd相当于setter,可以在函数外部对函数内部变量进行操作。

在阮一峰博客里的最后两道思考题:

var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());

第一个答案是the window;第二个答案是My Object。

解析:

  第一个的alert(object.getNameFunc()())其实就是相当于在全局执行这样一个函数alert(function(){return this.name;})此时的this是全局的this,this.name就是全局的name也就是"The Window";

  第二个函数里return的是that.name;而that在getNameFunc函数里面赋值为this,var that = this;此处的this所在函数getNameFunc被执行时的对象为object,this也就是object,所以this.name也就是My Object。

总结:

   JavaScript高级程序设计书中写到:this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,而当函数作为某个对象的方法调用时this等于这个对象。

   this永远指向函数被执行时的当前对象。

   this的指向是由它所在函数调用的上下文决定的,而不是由它所在函数定义的上下文决定的

  当一个函数作为函数而不是方法来调用的时候,this指向的是全局对象

  如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下); 如果嵌套函数作为方法调用,其this值指向调用它的对象。

  关于this可以看看 http://www.quirksmode.org/js/this.html      

          http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html

闭包作用:

    1.减少全局污染,因为闭包可以通过返回一个函数访问局部变量;

    2.实现面向对象中的对象,相当于c++中的对象的方法,使不同对象拥有独立的成员及状态,互不干涉;

    3.可以为函数引用设置延时;

    4.通过对象实例方法关联函数;

    5.包装相关的功能;

    6.解决循环绑定事件时候的总是响应最后一次的事件;

    7.能够让被包含函数的变量的值始终保存在内存中。

闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

 一篇关于闭包以及内存泄漏的说法:https://segmentfault.com/q/1010000000414875

 关于回调函数:http://blog.csdn.net/luoweifu/article/details/41466537

匿名函数:

1.最常规的一种

function addFunc(a,b){
        return a+b;
}

2.使用Function构造函数,把参数列表和函数体都作为字符串,很不方便,不建议使用。

var add=new Function('a','b','alert(a+b);')
add(1,2);//3

3.“=”号右边的函数就是一个匿名函数,函数创造完毕后将该函数赋给了变量add

var add=function(a,b){
        alert(a+b);
}
add(1,2);//3

4.第一个括号创建了一个匿名函数,第二个括号便是对它的调用并同时传入参数。

(function(a,b){
        alert(a+b);
})(1,2);//3
function timeClock(){
        var time=0;
        setTimeout(function(){
                alert(time+2000);
        },2000);
};
timeClock();

5.匿名函数最大的用途是创建闭包(这是JavaScript语言的特性之一),并且还可以构建命名空间,以减少全局变量的使用。

var out={};
(function(){
        var add=function(a,b){
                return (a+b);
        };
        function dis(a,b){
                return (a-b);
        };

        out.add=add;
        out.dis=dis;
})();
alert(out.add(1,2));//3

6.下面函数中one为局部变量,外部不可访问,但是函数里面的inner函数可访问,后面又将inner函数赋给了outer,所以三次调用outer会出现递增。

var outer = null;

(function(){
    var one = 1;
    function inner (){
        one += 1;
        alert(one);
    }
    outer = inner;
})();

outer();    //2
outer();    //3
outer();    //4

7.闭包允许内层函数引用父函数中的变量,但是该变量是最终值.

/**
 * <body>
 * <ul>
 *     <li>one</li>
 *     <li>two</li>
 *     <li>three</li>
 *     <li>one</li>
 * </ul>
 */

var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    lists[ i ].onclick = function(){
        alert(i);    
    };
}

结果总是弹出的4,并不是1,2,3,4;当onclick事件调用监听函数时,首先在匿名函数( function(){ alert(i); })内部查找是否定义了 i,结果是没有定义;因此它会向上查找,查找结果是已经定义了,并且i的值是4(循环后的i值);所以,最终每次弹出的都是4。

解决方法1:

var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    (function(index){
        lists[ index ].onclick = function(){
            alert(index);    
        };                    
    })(i);
}

解决方法二:

var lists = document.getElementsByTagName('li');
for(var i = 0, len = lists.length; i < len; i++){
    lists[ i ].$$index = i;    //通过在Dom元素上绑定$$index属性记录下标
    lists[ i ].onclick= function(){
        alert(this.$$index);    
    };
}

解决方法3:

function eventListener(list, index){
    list.onclick= function(){
        alert(index);
    };
}
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    eventListener(lists[ i ] , i);
}

下面贴一系列的例子:

function f1(){
    n=999;
nAdd=function(){
        n++;
        alert(n);
}
    return n;
  }
var result=f1();
alert(result);//999
nAdd();//1000
result=f1();
alert(result);//999
nAdd();//1000
result=f1();alert(result);//999   nAdd();第一句与第二句没有任何区别,nAdd是全局函数也是匿名闭包函数,每次调用它时里面的n的取值为f1函数的n值,
var result=f1();
alert(result);//999
nAdd();//1000
nAdd();//1001
function f1(){
    n=999;
nAdd=function(){
        n++;
        alert(n);
}
f2=function(){
        alert(n);
}
    return f2;
  }
var result=f1();
result();//999
nAdd();//1000
alert(n);//报错:n只存在于f1函数内,依旧为局部变量,外界不能调用n,只是对f1函数内局部变量有使用权的函数来讲一直存在,不会在第一次调用之后清除,如果第一次调用改变了值,/      //那第二次调用时为已经改变的值,不会再在f1里面重新声明 result();//1000 nAdd();//1001
function f1(){
test = 10;
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(this.n);
    }
    return f2;
  };
var n=1;
f1()();//1
alert(n);//1
function f1(){
test = 10;
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  };
var n=1;
f1()();//999
alert(n);//1
var w=100;
function f1(){
function f2(){
alert(this.w);
}
return f2;
}
f1()();//100
var w=100;
function f1(){
var w=101;
function f2(){
alert(this.w);
}
return f2;
}
f1()();//100
var w=100;
function f1(){
w=101;
function f2(){
alert(this.w);
}
return f2;
}
f1()();//101
var w=100;
function f1(){
this.w=102;
function f2(){
alert(this.w);
}
return f2;
}
f1()();//102
alert(w);//102
var w=100;
function f1(){
this.w=102;
function f2(){
alert(this.w);
}
return f2;
}
function f3(){
        var w= 0;
        var test=f1();
        test();
        alert(w);
}
f3();//102、0
alert(w);//102
var w=100;
function f1(){
        this.w=102;
        function f2(){
                alert(this.w);
        }
        return f2;
}
function f3(){
        var w= 0;
        var test=f1();
        test();
        this.w=103;
        alert(w);
}
function f4(){
    var w=104;
    f3();
    alert(w);
}
f4();//102 0 104 
alert(w);//103

 ====================================================================================================

2016-03-12补充

=====================================================================================================

http://www.cnblogs.com/justany/archive/2012/11/01/the_keyword_this_in_javascript.html
var name = "window";

var Bob = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var Tom = {
    name: "Tom",
    showName: function(){
        var fun = Bob.showName;
        fun();
    }
};

Tom.showName();  //window
var name = "Bob";  
 var nameObj ={  
     name : "Tom",  
     showName : function(){  
         alert(this.name);  
     },  
     waitShowName : function(){  
         !function(__callback){
            __callback();
        }(this.showName);  
     }  
 };  
 
 nameObj.waitShowName();  //Bob

在调用nameObj.waitShowName的时候运行匿名函数,将nameObj.showName作为回调函数传进这个匿名函数。匿名函数运行时候运行回调函数,当前匿名函数当前对象是window,所以在匿名函数中运行回调函数的时候回调函数的this指向了window。

eval

eval函数执行时的作用域是当前作用域,即等同于在该行将里面的代码填进去。下面的例子说明了这个问题:

var name = "window";

var Bob = {
    name: "Bob",
    showName: function(){
        eval("alert(this.name)");
    }
};

Bob.showName();    //Bob

apply和call

apply和call能够强制改变函数执行时的当前对象,让this指向其他对象。

var name = "window";
    
var someone = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var other = {
    name: "Tom"
};    

someone.showName.apply();    //window
someone.showName.apply(other);    //Tom

apply改变函数执行时的当前对象,当无参数时,当前对象为window,有参数时候为当前对象。

new关键字

new关键字后的构造函数中的this指向用该构造函数构造出来的新对象:

function Person(__name){
    this.name = __name;        //这个this指向用该构造函数构造的新对象,这个例子是Bob对象
}
Person.prototype.show = function(){
    alert(this.name);
}

var Bob = new Person("Bob");
Bob.show();        //Bob

思考题:

var fun = new Function("alert(this)");
fun();//[object window]
原文地址:https://www.cnblogs.com/Decmber/p/5256304.html