自己对javascript闭包的了解

目录

正文

前言: 在这篇文章里,我将对那些在各种有关闭包的资料中频繁出现,但却又千篇一律,且暧昧模糊得让人难以理解的表述,做一次自己的解读。或者说是对“红宝书”的《函数表达式/闭包》的那一章节所写的简洁短小的描述,做一些自己的注解,仅供抛砖引玉

好,看到文章标题,你就应该知道我下文的画风是怎样的了,嘿嘿嘿...

回到顶部

闭包的概念

首先要搞懂的就是闭包的概念: 闭包是能够访问另一个函数作用域中变量的函数(这个“另外一个函数”,通常指的是包含闭包函数的外部函数), 例如:

function outerFunction () {
var a = 1
return function () {
    console.log(a);
 }
}
 
var innerFunction = outerFunction();
innerFunction();

在这个例子里:负责打印a的匿名函数被包裹在外部函数outerFunction里面,且访问了外部函数outerFunction作用域里的变量a,所以从定义上说,它是一个闭包。

我在标题上说过我要讲故事的对吧,但...  在听故事前,你需要先看以完下两个方面的知识:

1. 谈谈函数执行环境,作用域链以及变量对象

2. 闭包和函数柯里化

回到顶部

谈谈函数执行环境,作用域链以及变量对象

(作用域和执行环境其实是同一个概念,我下面的介绍主要会以后者为名)

首先我想让大家理解的是:  函数执行环境,作用域链以及变量对象的相互关系以及各自作用

先引用一下《javaScript高级语言程序》中的两段原话:

1. "当某个函数被调用时,会创建一个执行环境 (execution context)及相应的作用域链(scope Chain)"   — —第178页    7.2  闭包

2. "每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中"   — — 第73页   4.2  执行环境及其作用域

 

这是我在“红宝书”上所能找到的最关键的一句话,但看完后,我。。。。一脸懵逼!!!! 现在我知道了函数被调用的时候就会连带产生和这个函数息息相关的三个东东:

执行环境(execution context),作用域链(scope Chain)以及变量对象(variable object),但这三者们具体是什么关系呢?

后来我看了汤姆大叔的文章,顿时豁然开朗: (文末有相关链接)

下面贴出他写的伪代码:

ExecutionContext = {
    variableObject: { .... },
    this: thisValue,
    Scope: [ // Scope chain
      // 所有变量对象的列表
    ]
};

所以说,关于三者,更准确的描述或许是这样的: 在函数调用的时候,会创建一个函数的执行环境,这个执行环境有一个与之对应的变量对象和作用域链。

嗯,这下三者的关系应该就比较明朗了吧(虽然好像也并没有什么卵用。。)

所以说,下面我要介绍的是变量对象和作用域链的作用。

变量对象的作用:

每个函数的变量对象保存了它所拥有的数据,以供函数内部访问和调用,这些数据包括:(位于执行环境内部的)

1.声明变量

2.声明函数

3.接收参数

虽然我们编写的代码无法访问到这个对象,但解析器还处理数据的时候会在后台使用它

例如:

function foo (arg) {
    var variable = ’我是变量‘;
    function innerFoo () {
         alert("我是彭湖湾")
    }
}
foo(‘我是参数‘);

这个时候执行环境对应的变量对象就变成了这样:

ExecutionContext = {
    variableObject: {
      variable:’我是变量‘
      innerFoo: [对函数声明innerFoo的引用]
      arg: ‘我是参数‘
    },
    this: thisValue,
    Scope: [ // Scope chain
      // 所有变量对象的列表
    ]
};

作用域链的作用

通过作用域链,函数能够访问来自它上层作用域(执行环境)中的变量

先看一个例子

function foo () {
    var a = 1;
    function innerFoo () {
        console.log(a)
    }
    innerFoo();
}
foo(); // 打印  1

在这里,变量a并不是innerFoo作用域(执行环境)内声明的变量呀,为什么能够取到它外部函数foo作用域内的变量呢? 这就是作用域链的作用啦,现在的执行环境用汤姆大叔的伪代码描述是这样的:

InnerFoo函数的执行环境:

InnerFooExecutionContext = {
    variableObject: {
    },
    this: thisValue,
    Scope: [ // Scope chain
       innerFooExecutionContext. variableObject,  // innerFoo的变量对象
       FooExecutionContext.variableObject,  // Foo的变量对象
       globalContext.variableObject   // 全局执行环境window的变量对象
    ]
};

Foo函数的执行环境:

FooExecutionContext = {
    variableObject: {
       a: 1
    },
    this: thisValue,
    Scope: [ // Scope chain
         FooExecutionContext.variableObject,  // Foo的变量对象
         globalContext.variableObject   // 全局执行环境window的变量对象
    ]
};

你可以看到,作用域链其实就是个从当前函数的变量对象开始,从里到外取出所有变量对象,组成的一个列表。通过这个作用域链列表,就可以实现对上层作用域的访问。

innerFoo在自己的执行环境的变量对象中没有找到 a 的变量声明, 它感到很苦恼,但转念一想: 诶! 我可以向上层函数执行环境的变量对象(variableObject)中找嘛! 于是乎沿着作用域链( Scope chain)攀爬,往上找变量a,幸运的是,在父函数Foo的变量对象,它找到了自己需要的变量a

“啊! 找到a了! 它的值是1”

如果今天innerFoo恰逢水逆,没有在Foo的变量对象中找到a呢? 那么它会沿着作用域链继续向上“攀爬‘,直到它到达全局执行环境window(global)

回到顶部

闭包和函数柯里化

闭包和函数柯里化在定义一个函数的时候,可能会使用到多层嵌套的闭包,这种用法,叫做“柯里化”。 而闭包柯里化有两大作用:参数累加和延迟调用

例子:

function foo (a) {
     return function (b) {
       return function (c) {
            console.log(a + b + c);
       }
     }
}foo(‘我‘)(‘叫‘)(‘彭湖湾‘); // 打印 我叫彭湖湾

从这里,我们可以很直观地看出闭包柯里化的时候参数累加的作用

我们把上面那个例子改变一下:

function foo (a) {
    return function (b) {
       return function (c) {
           console.log(a + b + c);
       }
    }
}
 
var foo1 = foo(‘我‘);
var foo2 = foo1(‘叫‘);
foo2(‘彭湖湾‘); // 打印 我叫彭湖湾

可以看到,最内层的闭包在外层函数foo和foo1调用的时候都没有调用,直到最后得到foo2并调用foo2()的时候,这个最内层的闭包才得到执行, 这也是闭包的一大特性——延迟执行

好,如果你看完了以上两个方面的内容,那接下来就可以听我将故事啦。

回到顶部

闭包造成的额外的内存占用  (注意我说的不是“内存泄漏”!)

函数的变量对象一般在函数调用结束后被销毁(它的“任务”已经完成了,可以被垃圾回收了)

但闭包的情况却不同

function foo (a) {
    return function () {
        console.log(a)
    }
}
 
var foo1 = foo(1);
var foo2 = foo(2);
var foo3 = foo(3);
foo1();  // 输出1
foo2();  // 输出2
foo3();  // 输出3

实际上,foo函数调用结束后, foo函数的变量对象并不会被立即销毁,而是只有当取得foo函数闭包的值的foo1, foo2, foo3调用结束, 这三个函数的变量对象和作用域链被销毁后, foo函数才算“完成任务”,这时,它才能被销毁。

所以说,闭包会造成额外的内存占用(注意这种内存占用是有必要的,和内存泄漏不同!!)

如果你不是很明白。看看我下面这个故事:

故事: 有这么一个差异化明显的班级,班级成员由一个学霸和一堆学渣组成,在某次监管很宽松的测验中(老师不在) , 为了其他人能够不去教导处喝茶,非常老好人的学霸用10分钟做完了试卷后,把卷子给全班同学抄, 弘扬了中华民族一贯以来的团结和谐,共同奋斗的精神。。。。

这个外层函数,就是那个学霸; 

里面的闭包,就是那些学渣;

闭包所引用的外层函数的变量,就是学霸递给学渣们的试卷!!!!!

问:

学霸10分钟就做完了试卷,那为什么他一整节课都忙的满头大汗???(为什么外层函数的变量对象在外层函数调用完毕之后没有立即销毁???)

答案

因为他要忙着给其他同学们传递他做好的试卷,又因为他是个老好人,所以只有最后一个同学做完试卷后,这位善良“负责”的学霸才能休息 呀!!!!!!!(因为闭包通过作用域链还保留着对这个外部函数的变量对象的引用,所以外部函数并不能立即得到销毁)

回到顶部

闭包只能取得包含函数的最后一个值

让我们来看看《红宝书》闭包那一章节中的一个典型例子:

function createArray() {
   var arr = new Array();
   for (var i = 0; i < 10; i++) {
      arr[i] = function () {
         return i;
      }
    }
    return arr;
}
var funcs = createArray();
for (var i = 0; i < funcs.length; i++) {
     document.write(funcs[i]() + "<br />");
}

实际上,最后输出的不是1,2,3,4,5,6,7 。。10,而是全部都是10,为什么? 因为:

1. 这几个函数都保留着对同一个外部函数的变量对象的引用

2. 因为闭包函数“延迟调用”的特性,而关键变量值i的获取是在闭包函数调用(f也即uncs[i]())的时候才从外部函数的变量对象中获取,而这个时候,外部函数早就完成for循环使 i =10了 !!!

时间: 2024-10-11 08:44:13

自己对javascript闭包的了解的相关文章

深入理解javascript闭包

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. Js代码 var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外部自然无法读取函数内的局部变量. Js代码 function

JavaScript 闭包

1.词法作用域: 简单地说子集能访问父级的变量, 说人话就是变量拿来就用不用传入 2.函数局部变量: 在函数体中以var 声明变量的为局部变量 + 函数传入的参数, 直接写变量名声明的变量是全局变量 3.局部变量生存期: 局部变量在函数函数的执行期间可用,  一旦执行过后,局部变量将不再可用 4.延长局部变量生存期: 现在问题来了,我想要延长局部变量的生存期,怎么办.(因为调用函数不仅仅是为了return, 有时候还需要保存函数中的状态, 或者实现类等等) 5.使用全局变量不好吗: 不好.有时函

javascript—闭包

javascript 闭包就是在另一个作用域中保存了一份它从上一级函数或作用域取得的变量(键值对0), 而这些键值对是不会随上一级函数的执行完成而销毁. function a(){ var i=0; function b(){ alert(++i); } return b; } var c=a(); c(); 在执行完var c=a()后,变量c实际上是指向了函数b,b中用到了变量i, 再执行c()后就会弹出一个窗口显示i的值(第一次为1).这段代码其实就创建了一个闭包. 为什么?因为函数a外的

JavaScript 闭包究竟是什么

JavaScript 闭包究竟是什么 1.简单的例子 首先从一个经典错误谈起,页面上有若干个div, 我们想给它们绑定一个onclick方法,于是有了下面的代码 <div id="divTest"> <span>0</span> <span>1</span> <span>2</span> <span>3</span> </div> <div id="d

全面理解Javascript闭包和闭包的几种写法及用途

一.什么是闭包和闭包的几种写法和用法                                                       1.什么是闭包 闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.闭包的特点: 1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态. 2. 一个闭包就是当一个函数返回时,一个没有释放资源的栈区. 简单的说,Javascript允许使用内部函数---即函数定义和函数表

javascript闭包的简单理解

奖Javascript闭包前,先给大家讲个小故事 故事背景:刘备和曹操煮酒的时间段,故事可能有点小差异,刘备为曹操手下,关羽为间谍. 咳咳 曹操很想知道手下新来的刘备整天鼓捣啥: 曹操就问刘备,玄德你整天鼓捣啥: 刘备想,我靠这怎么能告诉你,劳资岂不是找死,打个马虎眼就过去了,曹操什么也没问出来: 刘备回到家里,跟关羽说,二弟,哼,曹操那个傻吊,哼! 关羽晚上也回到家里,跟老婆说,哼,大哥那个傻吊,哼!我这就去告诉丞相: 曹操也不能罢休,就去问关羽,关羽正要去告密,就一五一十  ¥%…@?%%:

javascript 闭包学习

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. Js代码 var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外部自然无法读取函数内的局部变量. Js代码 function

javascript闭包详解(内容为转载的,觉得不错就分享一下)

一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. Js代码 var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外部自然无法读取函数内的局部变量. Js代码 function f1(){ var n=999; } alert(n); // error 这里有一个地方需要注意,函数

转 全面理解Javascript闭包和闭包的几种写法及用途

转自:http://www.cnblogs.com/yunfeifei/p/4019504.html 好久没有写博客了,过了一个十一长假都变懒了,今天总算是恢复状态了.好了,进入正题,今天来说一说javascript里面的闭包吧!本篇博客主要讲一些实用的东西,主要将闭包的写法.用法和用途. 一.什么是闭包和闭包的几种写法和用法                                                       1.什么是闭包 闭包,官方对闭包的解释是:一个拥有许多变量和绑

javascript闭包的理解-韩烨

javascript闭包是javascript的难点,很多人对js闭包不是很理解,我对js闭包一开始也是云里雾里,我刚刚进兴安得力的时候,做的转正试题中就有一个对闭包理解的题目.如何理解javascript的闭包呢?下面我们一起来学习一下: 闭包的含义和理解 通俗地讲,JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性,请看下面这个例子: var generateClosure = function() { var count = 0; var get