【JavaScript】难点2-闭包

闭包:是指有权访问另一个函数作用域中的变量的函数。创建闭包的方式,就是在一个函数内部创建两一个函数。—《JavaScript高级程序设计》
闭包有权访问包含函数内部的所有变量,原理如下:
在后台执行环境中,闭包的作用域链包含着自己的作用域、包含函数内部的作用域和全局作用域。通常,函数的作用域及所有变量都会在函数执行结束后被销毁,但是,当函数返回了一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。
照这句话理解的话,闭包就是一个嵌套函数嘛!嵌套函数对包含它的函数的变量当然可以访问,这是没有问题的。

面试的时候经常会被问到闭包有关的问题,什么是闭包?为什么使用闭包?最近就把闭包仔细的研究了一下,现在总结如下:

1、什么是闭包

function a(){  
    var name="vicky"; 
    console.log(name); 
} 
a();//vicky
以上是一段非常简单的代码,当函数执行结束之后,它就会从内存中释放,里面声明的局部变量也将在内存中被释放,自然就无法被访问。
function a(){  
    var name="vicky";  
    function show() {  
        console.log(name);  
    }  
    return show;  
}  
var func = a();  
func();//vicky

以上代码就形成了一个典型的闭包,函数a()执行之后,在它内部声明的变量name依然可以使用。
这么两段代码,当然还是很晦涩。先回顾一下作用域链

作用域链:如果我们把作用域简单的分个级的话,假设全局作用域作为第一级,其中定义的函数体内部作用域作为第二级,在第二级作用域内嵌套定义的函数体内部作用域作为第三级,….等等,传统意义上,第一级不能访问第二级的变量(这种变量叫做局部变量),第二级不能访问第三级,…,而反过来是可以的,这就是作用域链。本级作用域内找不到再到上一级找,直至第一级全局。

闭包:而闭包这种机制可以在第一级作用域中通过第三级作用域引用到第二级作用域中的变量,方法就是在第二级作用域向第一级作用域返回拥有第三级作用域的函数引用。 这个引用才是关键,因为这个引用的存在,相关的第三作用域与第二作用域都成了这个引用运行的上下文,迫使垃圾回收机制不能回收这条链上所占用的资源。而如果没有这个引用,则跟一般函数一样,函数运行完资源就会被回收。

        那么问题来了,到底什么是闭包?闭包是指函数中的嵌套函数还是指被第一级引用了的嵌套函数?还是都是?还是说闭包并不是嵌套函数而是嵌套函数被第一级作用域引用时所形成的这种机制?

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

解释一下以上的代码:
        在a执行完并返回后,闭包使得Javascript的垃圾回收机制不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。由于闭包的存在使得函数a返回后,a中的 i 始终存在,这样每次执行c(),i都是自加1后alert出i的值。
        那么,如果a不返回函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。

再来看《JavaScript高级程序设计》中的这段代码:

function createFunctions(){
  var result = new Array();
  for (var i=0; i < 10; i++){
    result[i] = function(){
      return i;
    };
  }
  return result;
}
var funcs = createFunctions();
for (var i=0; i < funcs.length; i++){
  console.log(funcs[i]());
}

我们都以为,会输出0~9,但是实际上却会输出10个10。为什么呢?现在来解释一下上面的代码,整段代码翻译一下,其实就是下面的过程:

var result = new Array();
result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
...
result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
i = 10;
funcs = result;
result = null; 
console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10
console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10
...
console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10

明白了没,函数带()才是执行函数! 单纯的一句 var f = function() { alert(‘Hi’); }; 是不会弹窗的,后面接一句 f(); 才会执行函数内部的代码。所以,就会输出10个10.而解决办法就是利用闭包,

function createFunctions(){
  var result = new Array();
  for (var i=0; i < 10; i++){
    result[i] = function(num){
        return function () {
            return num;
        }
    }(i);
  }
  return result;
}
var funcs = createFunctions();
for (var i=0; i < funcs.length; i++){
    console.log(funcs[i]());
}

说了这么多,闭包到底是什么,下面做一下总结:
闭包是一个概念,它描述了函数执行完毕内存释放后,依然内存驻留的一个现象,只要把握这个核心概念,闭包就不难理解了。
实际上是就是闭包延长变量的生命周期。通常函数的作用域即变量会在函数执行结束后被销毁,但当函数返回一个闭包,只要闭包不被释放,整条作用域链都会占用内存。

2、闭包的用途

闭包特点:
1、一个是可以读取函数内部的变量
2、另一个就是让这些变量的值始终保持在内存中,即延长变量的生命周期。(因为外部函数执行完毕后,内部函数的执行依赖于外部环境中的变量,所以垃圾回收机制不会将外部函数的内存回收)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
用途:
1、使用闭包可以在JavaScript中模仿块级作用域
2、还可以用于在对象中创建私有变量。可以利用闭包来实现特权方法(构造函数模式,原型模式,模块模式,增强的模块模式)

(1)、模仿块级作用域

用作块级作用域(也称私有作用域)的匿名函数的语法如下:

(function () {
    //这里是块级作用域
})();

以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,但是注意,下面的代码将会出错:

function () {
//这里是块级作用域
}();

**原因是:**JavaScript将function关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号。但是函数表达式后面可以跟圆括号,要将函数声明转换成函数表达式,只要像上面第一段代码那样,给它加个圆括号就行。

无论什么地方,只要临时需要一些变量,就可以使用私有作用域,如:

function outputNumbers (count) {
    (function () {
        for (var i = 0;i < count; i++){
            alert(i);
        }
    })();
    alert(i);//导致一个错误
}

在上面的代码中,for循环外面包含了一个私有作用域。因此,在匿名函数中定义的任何变量,都会在执行结束时销毁。所以,i只能在私有作用域中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是个闭包,它能够访问其外部函数的变量。

私有作用域经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

(2) 私有变量

任何在函数内部定义的变量,都可以认为是私有变量,因为在函数外部无法访问这些变量。
有权访问私有变量和私有函数的公有方法成为特权方法。

创建特权方法的两种方式:
1)在构造函数中定义特权方法

function MyObject() { 
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction () { 
        return false;
    }
    //特权方法,特权方法作为闭包,有权访问在构造函数中定义的私有变量和私有函数
    this.publicMethod = function (){
        privateVariable++;
        return privateFunction ();
    }
}

2)静态私有变量
通过在私有作用域中定义私有变量或函数,也可以创建特权方法,基本模式如下:

(function () { 
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction () { 
        return false;
    }
    //构造函数,这里没有使用函数声明的形式而是使用了函数表达式,因为函数声明只能创建局部函数。
    MyObject = function () { 
    };
    //特权方法
    MyObject.prototype.publicMethod = function () { 
        privateVariable ++;
        return privateFunction ();
    }

})
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值