论js闭包的重要性

很久没写博客了,今天发现了一个很有意思的问题,写下来分享一下

 

话不多说,贴前端代码:

 

 

<script type=”text/javascript”
src=”js/jquery-1.11.2.min.js”></script>

<script type=”text/javascript”>

    $(function(){

        $(“#btnky”).bind(‘click’,function(){

             //发送给服务端

             var postMoney=getMoney();

             alert(“发送给后台:”+postMoney);

           
 $.post(“to_json.html”,{postMoney:postMoney},function(data){

                 alert(data);

             });

        });

    })

    function getMoney(){

            return 1;

    }

</script>

</head>

<body>

<input type=”button” value=”发送后台” id=”btnky”>

</body>

 前端一个很简单的ajax提交代码,对不对?通过getMoney()函数
得到一个值,然后发送给后台,注意,该函数是不包含在$()代码块里面的

 

 后台代码:

 

 也是很简单的,只是响应用户的发送数据,代码如下:

 

 

@RequestMapping(“/to_json.html”)

@ResponseBody

public String to_Json(@RequestParam(“postMoney”) int postMoney){

    System.out.println(“前端发送的钱:”+postMoney);

    return “你给服务端发送的money是:”+postMoney;

}

OK,万事具备,页面走起!!

 

页面点击发送

 

 

 

看后台:

 

 

 

 

 

貌似没错,后台如愿得到我们要的数据

 

只是,如果有些捣蛋鬼喜欢捣蛋呢?比如我用火狐的firefox注入一个getMoney()方法

 

 

 

好吧,我注入了一个和页面上相同的函数getMoney(),居然返回100000.太坏了..

 

OK,让我们在点击发送后台按钮,看看是什么情况呢?

 

 

 

天呐…….居然真给变了…

 

好吧,再看看我们的服务端,是不是也会随波逐流呢?

 

 

 

我已经无语了,这别个捣蛋鬼岂不是能随便传送数据…

 

不然,如果页面修改一下呢?代码如下:

 

<script type=”text/javascript”>

    $(function(){

        $(“#btnky”).bind(‘click’,function(){

             //发送给服务端

             var postMoney=getMoney();

             alert(“发送给后台:”+postMoney);

           
 $.post(“to_json.html”,{postMoney:postMoney},function(data){

                 alert(data);

             });

        });

        function getMoney(){

            return 1;

        }

    })

     

</script>

</head>

<body>

<input type=”button” value=”发送后台” id=”btnky”>

</body>

 

很久没写博客了,今天发现了一个很有意思的问题,写下来分享一下
话不多说,贴前端代码: script type=text/javascript src=js/jque…

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。

闭包的理解

因为内部函数在被创建时,其作用域链对外部函数对应的变量对象存在一个引用,而JS采用引用计数的方法进行内存管理,所以当外部函数被执行完毕后,其对应的变量对象不会被回收,这样就发生了闭包,在外部函数执行完毕后,我们在内部函数中仍然可以访问外部函数作用域中的变量。
闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。

闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配,当在一个函数内定义另外一个函数就会产生闭包而当一个函数中的变量或者函数有权访问另一个函数作用域中的变量或者函数时就产生了闭包了

所谓的js闭包就是在函数的外部能访问到函数内部变量:

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。

要理解闭包,首先必须理解变量作用域。前面提到,JavaScript有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。
var n = 999;

嵌套的函数定义

我们只能通过变通的办法来访问函数的局部变量,一般来说,这个变通的办法就是在函数内部再定义一个函数,因为一个函数不仅可以访问全局变量,还可以访问它的外部函数(Outer
function)定义的局部变量,比如下面的代码:

var global_var = "I'm global";
function outer() {
    var local_var = "I'm local";
    return function inner() {
        console.log("local: " + local_var);
    }; 
}
outer()(); // 输出:local: I'm local

函数 inner
不仅有自己的内部作用域,还可以访问全局变量,也可以访问它外部的 outer
函数定义的所有局部变量。我们知道函数是 JavaScript
的一等公民,可以作为其他函数的参数或者作为函数的返回值,这里我们把 inner
函数返回,这样我们就通过变通的方法在 outer 函数外部访问了 outer
函数内部定义的局部变量。那这里的内部函数 inner 就构成了闭包。在
JavaScript 语言中,闭包的定义可以简化为嵌套定义在函数内部的函数。

instanceof和typeof都能用来判断一个变量是否为空或是什么类型的变量。typeof用以获取一个变量的类型,typeof一般只能返回如下几个结果:number,boolean,string,function,object,undefined。我们可以使用typeof来获取一个变量是否存在,如if(typeof
a!=”undefined”){},而不要去使用if(a)因为如果a不存在(未声明)则会出错,对于Array,Null等特殊对象使用typeof一律返回object,这正是typeof的局限性。

  function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage
collection)回收。
这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous
function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

function a() { 
 var i = 0; 
 function b() { 
  alert(++i); 
 } 
 return b;
}
var c = a();
c();

闭包特性:
(1)函数内再嵌套函数
(2)内部函数可以引用外层的参数和变量
(3)参数和变量不会被垃圾回收机制回收
li节点的onclick事件都能正确的弹出当前被点击的li索引
<ul>
<li> index = 0 </li>
<li> index = 1 </li>
<li> index = 2 </li>
<li> index = 3 </li>
</ul>
<script type=”text/javascript”>
var nodes = document.getElementsByTagName(‘li’);
for(i = 0;i<nodes.length;i+=1) {
nodes[i].onclick = function() {
console.log(i+1); //不使用闭包的话,值每次都是4
}(4);
}
</script>

function f1() {
console.log(n);
}
f1() // 999
上面代码中,函数f1可以读取全局变量n。

立即执行函数

立即执行函数能配合闭包保存状态。
像普通的函数传参一样,立即执行函数也能传参数。如果在函数内部再定义一个函数,而里面的那个函数能引用外部的变量和参数(闭包),利用这一点,我们能使用立即执行函数锁住变量保存状态。

// 并不会像你想象那样的执行,因为i的值没有被锁住
// 当我们点击链接的时候,其实for循环已经执行完了
// 于是在点击的时候i的值其实已经是elems.length了
var elems = document.getElementsByTagName( 'a' );

for ( var i = 0; i < elems.length; i++ ) {

  elems[ i ].addEventListener( 'click', function(e){
    e.preventDefault();
    alert( 'I am link #' + i );
  }, 'false' );

}


// 这次我们得到了想要的结果
// 因为在立即执行函数内部,i的值传给了lockedIndex,并且被锁在内存中
// 尽管for循环结束后i的值已经改变,但是立即执行函数内部lockedIndex的值并不会改变
var elems = document.getElementsByTagName( 'a' );

for ( var i = 0; i < elems.length; i++ ) {

  (function( lockedInIndex ){

    elems[ i ].addEventListener( 'click', function(e){
      e.preventDefault();
      alert( 'I am link #' + lockedInIndex );
    }, 'false' );

  })( i );

}

或者写成

但是,在函数外部无法读取函数内部声明的变量。

内存泄露

function assignHandler() {
    var el = document.getElementById('demo');
    el.onclick = function() {
        console.log(el.id);
    }
}
assignHandler();

以上代码创建了作为el元素事件处理程序的闭包,而这个闭包又创建了一个循环引用,只要匿名函数存在,el的引用数至少为1,因些它所占用的内存就永完不会被回收。

function assignHandler() {
    var el = document.getElementById('demo');
    var id = el.id;

    el.onclick = function() {
        console.log(id);
    }

    el = null;
}
assignHandler();

把变量el设置null能够解除DOM对象的引用,确保正常回收其占用内存。

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?

//问:三行a,b,c的输出分别是什么?
//这是一道非常典型的JS闭包问题。其中嵌套了三层fun函数,搞清楚每层fun的函数是那个fun函数尤为重要。
//答案: //a: undefined,0,0,0 //b: undefined,0,1,2 //c: undefined,0,1,1

JavaScript的执行上下文生成之后,会创建一个叫做变量对象的特殊对象(具体会在下一篇文章与执行上下文一起总结),JavaScript的基础数据类型往往都会保存在变量对象中。

严格意义上来说,变量对象也是存放于堆内存中,但是由于变量对象的特殊职能,我们在理解时仍然需要将其于堆内存区分开来。
基础数据类型都是一些简单的数据段,JavaScript中有5中基础数据类型,分别是Undefined、Null、Boolean、Number、String。基础数据类型都是按值访问,因为我们可以直接操作保存在变量中的实际的值。

function a() { 
 var i = 0; 
 return b() { 
  alert(++i); 
 } 
}
var c = a();
c();

function f1() {
var n = 999;
}

这样定义的a函数的i变量对于a的子函数b来说依然是有效的,然后通过c=a(),这样就将b函数赋值给了全局变量c,那么调用c()的时候也就访问到了a的内部函数;

console.log(n)
// Uncaught ReferenceError: n is not defined(
上面代码中,函数f1内部声明的变量n,函数外是无法读取的。

不过使用js闭包时虽然一定程度上访问了函数的内部变量,但如果访问完成后未注销掉变量会造成函数一直在内存中,影响性能,因此需要手动注销变量。

如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。

function f1() {
var n = 999;
function f2() {
  console.log(n); // 999
}
}
上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是JavaScript语言特有的”链式作用域”结构(chain
scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}

var result = f1();
result(); // 999
上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了。

闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。

function createIncrementor(start) {
return function () {
return start++;
};
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7
上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

为什么会这样呢?原因就在于inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

闭包的另一个用处,是封装对象的私有属性和私有方法。

function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}

return {
name: name,
getAge: getAge,
setAge: setAge
};
}

var p1 = Person(‘张三’);
p1.setAge(25);
p1.getAge() // 25
上面代码中,函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量。

注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图