前言

函数和作用域啥的我们前面已经了解了,现在就要学习闭包了,这是一个挺晦涩的知识点,初学者可能会感觉不好理解,但是高手都不不以为然了,高手就给我提点意见吧,我和新手一起来学习什么是闭包。

例子

先不说定义,先看一个题,看看大家能得出正确的结果不,

function test(){
var arr = [];
for(var i = 0;i<10;i++){
arr[i] = function(){
return i;
}
}
return arr;
} var fns = test();
console.log(fns[9]()); // 值是多少?
console.log(fns[0]());//值是多少?

结果就是

10
10

你做对了吗?

什么是闭包

我们知道,javascript中的变量作用域分为全局变量和局部变量,全局的变量我们在什么地方都可以使用,但是局部变量就不是这样的了,我们只能在该变量的作用域中得到,换句话说就是我们在函数的内部可以使用函数外部的变量,但是我们在函数的外部却不能使用函数内部定义的局部变量,但是在实际中我们就是想要在函数的外部使用函数内部定义的变量那该怎么办呢?例子来了

function test(){
var inner = 10;
}
alert(inner);//error?咋办

咋办呢?我们知道,在内部我们可以访问到这个变量,我们还知道有一个操作符return可以返回想要的值,那我就在内部定义一个函数来访问这个变量,然后在返回这个函数不就行了,实践一下

function test(){
var inner = 10;
function inFun(){
alert(inner);//
};
return inFun;
}
var outter = test();
outter();//10;

我们做到了,为自己鼓鼓掌,有时候我们就该不断鼓励自己一下,不要给自己太大的压力,我们不是富二代,在不鼓励一下自己怎么能成为富二代他爹呢。

这就是闭包了,官方没有给出闭包一个完整的准确的定义,民间流传的是在一个函数内定义一个函数,并且这个内部函数可以在外面访问,这时候就形成了闭包。看看上面函数的结构,一个函数返回了一个内部函数,我们知道在正常情况下,一个函数执行结束之后,里面的变量会被释放,也就是说,在test()这句执行之后,里面的inner应该被释放了才对,但是我们发现,outter()时我们拿到了inner的值,这就是闭包的特性:如果闭包中使用了局部的变量,那么这个变量会一直贮存在内存中,闭包会一直保持这个值,一直到外部的函数没有被引用为止,看例子

function closure(){
var num = 0;
function add(){
console.log(++num);
}
return add;
}
var test1 = closure();//形成一个闭包,保持着自己的一个num变量
test1 ();//
test1 ();//
var test2 = closure();//又一个闭包,保持了一个自己的num变量
test2 ();//
test2 ();//

好玩不?这就是闭包的神奇的地方,也是让身为初学者的我们感到彷徨的地方,相信我,我会让你们理解明白的。要想释放num占用的内存,就该这样

test1 = null;
test2 = null;

简单解析下这个例子:在执行 var test1 = closure()时,由于closure()返回到是一个函数,这里就相当于test1变量指向了一个函数add,但是这个add函数有自己的作用域和活动对象,都存在了test1中,执行test1()时,会寻找num变量,由于闭包存储了该变量就可以直接取到,并且自加1,再一次执行test1()时会继续在test1执行的add函数的执行环境和作用域中查找,发现num为1了,就找到了这个num;在执行var test2 = closure()时,会重新创建一个闭包,重新存储执行环境和活动对象,所以这是和第一次完全没有关系的。

闭包的机制

  1. 函数也是对象,有[[scope]]属性(只能通过JavaScript引擎访问),指向函数定义时的执行环境上下文。

  2. 假如A是全局的函数,B是A的内部函数。执行A函数时,当前执行环境的上下文指向一个作用域链。作用域链的第一个对象是当前函数的活动对象(this、参数、局部变量),第二个对象是全局window。

  3. 当执行代码运行到B定义地方, 设置函数B的[[scope]]属性指向执行环境的上下文作用域链。

  4. 执行A函数完毕后,若内部函数B的引用没外暴,A函数活动对象将被Js垃圾回收处理;反之,则维持,形成闭包。

  5. 调用函数B时,JavaScript引擎将当前执行环境入栈,生成新的执行环境,新的执行环境的上下文指向一个作用域链,由当前活动对象+函数B的[[scope]]组成,链的第一个对象是当前函数的活动对象(this、参数、局部变量组成),第二个活动对象是A函数产生的,第三个window。

  6. B函数里面访问一个变量,要进行标志符解析(JavaScript原型也有标识符解析),它从当前上下文指向的作用域链的第一个对象开始查找,找不到就查找第二个对象,直到找到相关值就立即返回,如果还没找到,报undefined错误。

  7. 当有关A函数的外暴的内部引用全部被消除时,A的活动对象才被销毁。

这段是其他的地方的,就是说了执行环境和作用域的理解闭包怎么维持变量的。

闭包的应用

一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,这既是函数也是弊端。我们可以利用闭包封装一些私有的属性,例如

var factorial = (function () {
var cache = [];
return function (num) {
if (!cache[num]) {
if (num == 0) {
cache[num] = 1;
}
cache[num] = num * factorial(num - 1);
}
return cache[num];
}
})();

封装了一个内部私有的属性来缓存结果。

下面流行的模块模式,它允许你模拟公共,私有以及特权成员

var Module = (function(){
var privateProperty = 'foo'; function privateMethod(args){
//do something
} return { publicProperty: "", publicMethod: function(args){
//do something
}, privilegedMethod: function(args){
privateMethod(args);
}
}
})();

另一个类型的闭包叫做立即执行函数表达式,是一个在window上下文中自我调用的匿名函数:

(function(window){

    var a = 'foo';

    function private(){
// do something
} window.Module = { public: function(){
// do something
}
}; })(this);

闭包的弊端

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

解释例子

回到开始的例子,这是闭包的经典的例子,这个和其他的例子和有些不一样,我们分析一下,这里用了一个数组,其实这里我们执行一次var fns = test(),形成了10个闭包,数组的每一个项存了一个闭包,这与其他的例子是不一样的,其他的例子是函数执行一次形成了一个闭包,所以这个10个闭包的初始的执行环境是一样的,每一个闭包使用了i这个变量,这个变量在函数var fns = test()执行之后变为了退出循环的那个i的值10,JavaScript是解释型的语言,所以在执行数组中的闭包的时,会找到此时i的值10;看看arr的结果

现在想怎样解决这个问题呢?我们想想,这10个闭包形成时的执行环境和活动对象是一样的,现在考虑的就是要在初始时就不一样,我们知道函数的作用域是一层一层的,那我们就需要在这之间家一层作用域,这层作用域要有不同的i的值,我们想到了自执行匿名函数,(funciton(){})(),我们把i的值穿进去,按值传参就是相当于复制了一份变量嘛,在(funciton(){})()外部的作用域中的i的值的改变不会改变内部的i的值,试一下

function test(){
var arr = [];
for(var i = 0;i<10;i++){
(function(i){ arr[i] = function(){return i;}})(i);
}
return arr;
} var fns = test();
console.log(fns[9]()); // 值是9
console.log(fns[0]());//值是0

当然也可以这样

function test(){
var arr = [];
for(var i = 0;i<10;i++){ arr[i] = (function(i){return function(){return i}})(i);
}
return arr;
} var fns = test();
console.log(fns[9]()); // 值是9
console.log(fns[0]());//值是0

这两个的实质都是在闭包形成之前,给每一个闭包包上一层作用域,在这个作用域中传一个参数,是每一个闭包上一级的作用域中都有不同的i。当然还有其他的办法这里不说了。

小结

闭包的应用场景挺多的,在模块化编程中很重要的,有些地方说函数也是闭包,还是那就话,概念不重要,理解会用才是最现实的。

【javascript基础】8、闭包的更多相关文章

  1. javaScript基础之闭包

    不管是Jquery还是EXTJS,现代的js框架中大量应用了js的一些特性,比如:匿名函数,闭包等等,导致源代码非常难以阅读. 不过要想真正的使用好前台的UI技术,还是需要去深入的理解这些概念.   ...

  2. Vue之JavaScript基础(闭包与原型链)

    闭包 定义:能够访问另一个函数作用域的变量的函数. 作用:可以通过闭包,设计私有变量及方法 实例: function outer() { var a = '变量1' var inner = funct ...

  3. JavaScript基础

    JavaScript基础 JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的处 ...

  4. 一步步学习javascript基础篇(0):开篇索引

    索引: 一步步学习javascript基础篇(1):基本概念 一步步学习javascript基础篇(2):作用域和作用域链 一步步学习javascript基础篇(3):Object.Function等 ...

  5. 前端之JavaScript基础

    前端之JavaScript基础 本节内容 JS概述 JS基础语法 JS循环控制 ECMA对象 BOM对象 DOM对象 1. JS概述 1.1. javascript历史 1992年Nombas开发出C ...

  6. Javascript基础回顾 之(二) 作用域

    本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Javascript的一些基本原理普遍存在这里或者 ...

  7. 深入理解javascript原型和闭包系列

    从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...

  8. 深入理解javascript原型和闭包(1)——一切都是对象

    “一切都是对象”这句话的重点在于如何去理解“对象”这个概念. ——当然,也不是所有的都是对象,值类型就不是对象. 首先咱们还是先看看javascript中一个常用的函数——typeof().typeo ...

  9. 深入理解javascript原型和闭包(2)——函数和对象的关系

    上文(理解javascript原型和作用域系列(1)——一切都是对象)已经提到,函数就是对象的一种,因为通过instanceof函数可以判断. var fn = function () { }; co ...

  10. 深入理解javascript原型和闭包(3)——prototype原型

    既typeof之后的另一位老朋友! prototype也是我们的老朋友,即使不了解的人,也应该都听过它的大名.如果它还是您的新朋友,我估计您也是javascript的新朋友. 在咱们的第一节(深入理解 ...

随机推荐

  1. iOS开发拓展篇—UIDynamic(简单介绍)

    iOS开发拓展篇—UIDynamic(简单介绍) 一.简单介绍 1.什么是UIDynamic UIDynamic是从iOS 7开始引入的一种新技术,隶属于UIKit框架 可以认为是一种物理引擎,能模拟 ...

  2. 拓扑编号 vijos1790

    题意就是拓扑排序,要求1的序号尽可能小,然后2的序号尽可能小,3,4... 一开始很容易想到直接贪心,每次选一个入度为0的点,如果有多个,就选编号最小的那个,但是很容易找到反例. 看了下题解,应该是反 ...

  3. centos 防火墙配置

    转载:http://www.cnblogs.com/bugs/p/3587915.html 1.安装iptables防火墙 怎么知道系统是否安装了iptables?执行iptables -V,如果显示 ...

  4. Rhel6-pacemaker+drbd配置文档

    系统环境: rhel6 x86_64 iptables and selinux disabled 主机: 192.168.122.119 server19.example.com 192.168.12 ...

  5. python mysql 更新和插入数据无效

    注意,在删除和增加后必须执行conn.commit()才有效,否则操作无效.

  6. 设置ubuntu12.04桌面版开机进入命令行模式

    1)命令:sudo gedit /etc/default/grub 找到GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" 将"quite ...

  7. HDU 4768 (二分区间---涨姿势)

    题意:告诉n组A,B,C,按照A + k * C生成等差数列,问这n组数列中哪个数字出现了奇数次以及出现了几次,题目保证最多只会出现一个这种数字. 分析:读完题并没有思路,后来知道是二分区间,枚举是哪 ...

  8. VC++ 文件系统

    using namespace System; using namespace System::IO; void ShowHelpMsg(){ Console::WriteLine(L"本程 ...

  9. LInux内核分析——计算机是如何工作的进行

    万子惠 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 " 实 ...

  10. sed详细分析

    [一.简单描述] sed命令类似命令行的文本编辑器,以行为单位(见注1).除非带命令i(in-place)否则源文件内容并不会被更新.   [二.使用] [2.1.使用方式] 存在两种使用方式: 1. ...