一、前言

对于 JavaScript 来说,闭包是一个非常强大的特征。但对于刚开始接触的初学者来说它又似乎是特别高深的。今天我们一起来揭开闭包的神秘面纱。闭包这一块也有很多的文章介绍过了,今天我就浅谈一下自己对闭包的的一些理解,希望能提供一点鄙陋的见解帮助到正在学习的朋友。该文章中能使用口语化的我将尽量使用口语化的叙述方式,希望能让读者更好理解,毕竟文章写出来宗旨就是要让人读懂。文章难免有不足之处还希望帮忙指出。

二、Javascript 的作用域链

在了解闭包之前,我们先来看看几个准备知识。

  1. 变量的作用域

    首先,什么是作用域?域,区域。简单来理解就是一个变量能被访问的范围(区域)。换言之就是这个变量能起作用的区域。按这个标准来划分我们将变量分为 全局变量局部变量 两种

    以定义的方式来区分有以下特点:

    定义在函数内部的变量是局部变量,定义在函数外部的变量是全局变量。(这个并不只是 Javascript 语言的特点)局部变量在函数内部能被访问,在函数外部不能被直接访问,所以局部变量就是从定义它的地方开始到函数结束的位置结束。当然这里有个细节--变量声明提升。等下我们用一小段代码提一下变量声明提升是什么。我们先来看看局部变量和全局变量的代码

    var a = 0;
    
    function testFunc(){
    var b = 1;
    console.log('-------------函数内输出-------------');
    console.log(a);//
    console.log(b);//
    } //调用函数
    testFunc(); console.log('-------------函数外输出-------------');
    console.log(a);//
    console.log(b);//Uncaught ReferenceError: b is not defined

    执行以上代码结果如下图所示

    在代码的最后一行抛出了 b 未定义的异常.也就是说我们在函数外部访问不到在函数内部定义的局部变量。但是第六行代码的正常输出,可见在函数内部我们是可以访问到在函数外部定义的全局变量 a

    变量声明提升

    相信如果学过 C 语言的话,应该会很熟悉一句话 "先声明后使用"。就是说一个变量或者函数在使用它之前必须是要先找得到这个变量或函数的声明的。例如:

    //C 语言正确写法
    int a = ;
    printf(a); //错误写法,下面代码没办法通过标准编译(直接报异常)
    printf(a);
    int a = ;

    我们再来看看 Javascript 代码

    var a = 0;
    console.log(a);//输出结果 0

    上面这种普通写法我们不探讨,重点看下面的这段代码

    console.log(a);//输出结果 undefined
    var a = "hello";
    console.log(a);//输出结果 hello

    运行结果如下

    上面这个例子就恰好说明了变量声明提升的特点,我们在没有声明变量 a 之前就直接访问变量a 输出结果为 undefined 而并不是直接报异常。所以最直观的感觉是变量的声明被提升到使用之前了。实质上代码如下:

    var a;//声明被提升到这里
    console.log(a);//输出结果 undefined
    a = "hello";
    console.log(a);//输出结果 hello

    小结一下

    • 函数内部定义的变量是局部变量,函数外部定义的变量是全局变量。
    • 局部变量不能被外界直接访问,全局变量可以在函数内被访问。
    • 变量声明提升
  2. 嵌套函数的作用域特点

    搞清楚上面的小结部分我们缕一缕思路继续探讨另一个话题,javascript 中的嵌套函数,我们先上一段代码:

    function A(param){
    var vara = 1;
    function B(){
    var varb = 2;
    console.log("----Function B----------")
    console.log(vara);//函数B中访问A函数中定义的变量
    console.log(param);//A函数中传进来的变量
    console.log(varb);//访问自身函数内定义的变量
    }
    B();
    console.log("----Function A----------")
    console.log(vara);//访问自身函数内定义的变量
    console.log(param);//A函数中传进来的变量
    console.log(varb);//访问B函数中定义的变量--异常
    }
    A("hello");

    运行结果如下:

    由此可见嵌套函数(B)可以继承容器函数(A)的参数和变量,但是嵌套函数(B)中的变量对于他的容器函数来说却是B私有的,也就是说 A 无法访问 B 中定义的变量。换句话说,B 函数形成了一个相对独立的环境(空间)使得它自身的变量只能由它自己来访问,但是 A 函数里的变量 B 也可以访问,这里嵌套函数 B 就形成了一个闭包。有一句话很适合 B 来说 “你的就是我的,我的还是我的”

    从语法上看是函数 A 包含着函数 B,但是从作用域上来看是函数 B 的作用域包含着函数 A 的作用域,关系如下图所示:

    假设:函数 B 下面又包含了函数 C。此时函数 C 为函数 B 的嵌套函数,函数 B 为函数 C 的容器函数。对于C来说也具有刚刚讲过的 “你的就是我的,我的还是我的” 的特点。以此类推层层嵌套的话就形成了一条链条, 作用域按此规律也形成了 Javascript 中的作用域链。

三、闭包的特点

我们先来总结上面提到的两点

  • 嵌套在容器函数(A)内部的嵌套函数(B)只能在容器函数(A)内被访问
  • 嵌套函数(B)继承了容器函数(A)的变量,但是 B 函数中的变量只有它自己能访问,也就是嵌套函数(B)的作用域包含容器函数(A)的作用域。
闭包之保存变量

我们还是先上一段代码

function A(a){
function B(b){
return a + b;
}
return B;
}
var C = A(1);
var result = C(2);
console.log(result);//输出结果 3

函数 B 形成了一个闭包,A 函数调用之后返回函数 B 的引用。执行 C 之后发现结果等于3,这也就说明了我们调用 A 的时候 传进去的参数 1 没有被销毁,而是被保存起来了,这就是闭包保存变量的特点。

有保存就有销毁那我们被闭包保存的变量在什么时候销毁?答案是当 B 没有再被引用的时候,就会被销毁.

闭包的注意点--命名冲突

我们还是先上一段代码

function A(){
var num = 6;//外部的名为num 的变量
function B(num){
return num;//当做参数传进来的num 变量,命名冲突发生在这
}
return B;
}
var result = A()(10);
console.log(result);//输出结果10

上述代码的执行结果

通过上面的代码我们能看到有一个容器函数内的名为 num 的变量以及一个嵌套函数内同样名为 num 的变量。这样的执行代码结果以嵌套函数内的变量优先。可能这里说成就近原则更容易记得住。这个就是闭包在实际应用中应该注意的一点。

四、闭包在开发中的应用。

关于闭包在开发中的使用,最多的体现应该还是在 Javascript 插件的开发上面。使用闭包可以避免变量污染。也就是说你在闭包中使用的变量名称不会影响到其他地方同样名称,换个角度来讲,我将我嵌套函数内部的变量给保护起来了,外部没办法随便修改我内部定义的变了。也就是虽然名字一样但是你是你我是我。代码体现如下:

function A(){
function B(num){
var c = 10;//内部变量 c
return num + c;
}
return B;
} var c = 20;//外部变量c
var result = A()(c);
console.log(c);//20
console.log(result)//30

以上特点应用在插件开发中就可以很好的保护了插件本身,避免了外界的串改,保证了插件的稳定。

简单的插件

初步代码

//编写插件代码
var plugin = (function(){
   var _sayhi = function(str = '你好啊!'){
console.log(str);
}
   return {
SayHi : _sayhi
}
})(); //使用插件 plugin('hello'); plugin();

上面代码闭包部分我就不在累述了,我们来看看新出现的一种语法--自调用匿名函数:

(function{
//code
})();

实际作用是创建了一个匿名函数,并在创建后立即执行一次。作用等价于下面的代码,唯一的区别就是下面的函数不是匿名的。

//创建
var func = function(){
//code
}
//调用
func();

当然,我们编写插件不可能只提供一个API给外部使用,如何返回多个API,我们这里使用字面量形式返回。改进之后的代码如下

//编写插件代码
var plugin = (function(){
var _sayhi = function(str = '你好啊!'){
console.log(str);
}
var _sayhello = function(){
console.log("这个API能做很牛逼的事情");
}
return {
SayHi : _sayhi,
SayHello : _sayhello
}
})(); //通过插件提供的API使用插件
plugin.SayHi('hello');
plugin.SayHello();

执行结果

五、后语

今天对于闭包的看法暂时先写到这了,秉承着学以致用的原则,下两篇文章我将介绍 javascript 插件的几种开发形式,以及实践--开发一个原生的 Javascript 插件。

六、补充

技术因交流而进步,很感激各位提供意见的博友。写文章的目的就是为了给正在学习阶段的朋友一些参考,当然我最怕给的是误导。针对博友指出的东西我在这里补充一下,希望不会打乱你们之前对闭包的理解。所以请将上述知识点理解为“从实践角度理解的闭包”,其实关于 JS 闭包的概念我觉得可以从广义和狭义(实践)上来理解,既然有朋友提到了我们就继续延伸一下。先来看看《JavaScript高级程序设计》第三版 中对闭包的定义“闭包是定义在一个外部函数内部,并且能够访问(存取)外部函数中自由变量的函数”,按这样的定义那么久必须是在嵌套函数的前提下才能形成闭包。我们上文所讲的例子都是基于这一种。

我们再来看下另一个闭包的定义

“闭包,是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落”

感觉挺抽象,没关系。我们来看下下面代码

var a = 10;
function A(){
console.log(a);//10(读的能力)
a = 20;//(写的能力)
console.log(a);//20
}
A();

通过代码我们从新来看这一段定义闭包的文字

“闭包,是指语法域位于某个特定的区域(A 函数),具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量(上述的变量 a)值能力的段落”

那么这样子定义的话,Javascript 中的所有函数都有这样的能力。所以也就有了这样的说法:
Javascript 中的函数都是一个闭包。

关于闭包,本文先更新到这里。其实闭包涉及的基础知识比较多,有些许基础(比如执行上下文,变量对象以及funarg 等)知识点本文还没有提及,后续文章会有所补充。

限于笔者技术,文章观点难免有不当之处,希望发现问题的朋友帮忙指正,笔者将会及时更新。也请转载的朋友注明文章出处并附上原文链接,以便读者能及时获取到文章更新后的内容,以免误导读者。笔者力求避免写些晦涩难懂的文章(虽然也有人说这样显得高逼格,专业),尽量使用简单的用词和例子来帮助理解。如果表达上有好的建议的话也希望朋友们在评论处指出。

本文为作者原创,转载请注明出处! Cboyce

浅析 JavaScript 中的闭包(Closures)的更多相关文章

  1. [译]Javascript中的闭包(closures)

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  2. 浅析 JavaScript 中的闭包(-------------------------------------------)

    一.前言 对于 JavaScript 来说,闭包是一个非常强大的特征.但对于刚开始接触的初学者来说它又似乎是特别高深的.今天我们一起来揭开闭包的神秘面纱.闭包这一块也有很多的文章介绍过了,今天我就浅谈 ...

  3. 浅析 JavaScript 中的 函数 currying 柯里化

    原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字 ...

  4. 浅析 JavaScript 中的 函数 uncurrying 反柯里化

    柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是 ...

  5. 让你分分钟学会Javascript中的闭包

    Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...

  6. 难道这就是JavaScript中的"闭包"

    其实对于JavaScript中的"闭包"还没真正理解,这次在实际Coding中似乎遇到了"闭包"的问题,仅此摘录,以待深究. 表现为jQuery的post方法回 ...

  7. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  8. javascript中的闭包解析

    学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去 ...

  9. JavaScript中的闭包理解

    原创文章,转载请注明:JavaScript中的闭包理解  By Lucio.Yang 1.JavaScript闭包 在小学期开发项目的时候,用node.js开发了服务器,过程中遇到了node.js的第 ...

随机推荐

  1. GLSL学习_高斯滤波

    使用RenderMonkey: 从自带的sobel修改的. vertex: uniform float height; varying vec4 vTexCoords00; varying vec4 ...

  2. mysql关于列转行的想法,以及列求乘集

    mysql列转行可以通过concat,先分组然后连接. show VARIABLES like '%group%' select @@group_concat_max_len SELECT GROUP ...

  3. Btn要记得对状态进行设置

    self.catBtn = [UIButtonbuttonWithType:UIButtonTypeSystem]; self.catBtn.backgroundColor = [UIColorred ...

  4. 通过文件流stream下载文件

    public ActionResult ShowLocalizedXML(int id) { string orderName = ""; string xmlString = G ...

  5. [转] 接触C# 反射 2

    逆心 原文 反射基础,2013-4. 反射用于在程序运行过程中,获取类里面的信息或发现程序集并运行的一个过程.通过反射可以获得.dll和.exe后缀的程序集里面的信息.使用反射可以看到一个程序集内部的 ...

  6. 使用 XML 实现 REST 式的 SOA

    什么是 SOA? 如果公司有大量应用程序,这些程序供不同部门的承担不同责任的职员使用,那么就适合使用面向服务体系结构(Service Oriented Architecture,SOA).这些应用程序 ...

  7. 【C++】统计代码覆盖率(二)

    嗷嗷嗷!!!好激动,我好蠢.不过最后还是解决了.呜呜呜 有些都是东一块西一块查的,如果有侵权欢迎私信我,我注明出处. 一 gcov&CMake 昨天试了下测试代码和被测代码都是c++的情况,直 ...

  8. 安卓 开发 The connection to adb is down, and a severe error has occured.

    The connection to adb is down, and a severe error has occured.问题解决 其原因就是其他进程占用了  ADB的端口,所以无法启动 遇到问题描 ...

  9. 小数点输出精度控制问题 .xml

    pre{ line-height:1; color:#9f1d66; background-color:#d2d2d2; font-size:16px;}.sysFunc{color:#5d57ff; ...

  10. STL六大组件之——迭代器这个东西

    迭代器:除了在其它语言中司空见惯的下标法访问容器元素之外,C++语言提供了一种全新的方法——迭代器(iterator)来访问容器的元素.迭代器其实类似于引用,指向容器中某一元素.换个方式来说,容器就是 ...