一. 闭包的概念

闭包是有权访问另一个函数作用域中的变量的函数

如下代码:根据变量作用域,函数outer中所有的局部变量对函数inner都是可见的。但是反过来不行,inner内部的局部变量对outer是不可见的。这就是JavaScript语言特有的链式作用域结构(chain scope),子对象会一级一级向上寻找所有父对象的变量。所以父对象的所有变量对子对象都是可见的,反之则不成立。

function outer() {
    var i = 100;
    function inner() {
        console.log(i);    //100
    }
    inner();
}
outer();

既然函数inner能读取函数outer的局部变量,那么只要将inner作为返回值,就可以直接在outer外部读取它内部的变量。

function outer() {
    var i = 100;
    function inner() {
        console.log(i);
    }
    return inner;
}
var res = outer();
res();   //

这个函数有两个特点:

1)函数inner嵌套在函数outer内部

2)函数outer返回函数inner

执行完var rs = outer()后,实际rs指向函数inner。这段代码其实就是一个闭包。也就是说当函数outer内的函数inner函数被函数outer外的一个变量引用的时候,就创建了一个闭包。

二. 闭包的作用

function outer() {
    var i = 100;
    function inner() {
        console.log(i++);
    }
    return inner;
}
var rs = outer();
rs();   //
rs();   //
rs();   //

上面的代码中,rs是闭包inner函数。rs共运行了3次,第一次100,第二次101,第三次102,这说明函数outer中的局部变量i一直保存在内存中,并没有在调用后清除。

闭包的作用:在outer执行完毕并返回后,闭包是JavaScript的垃圾回收机制(garbage collection)不会回收outer所占的内存,因为outer函数内部的inner函数执行要依赖outer中的变量。(另一种解释:outer是inner的父函数,inner被赋给了一个全局变量,导致inner会一直在内存中,而inner的存在依赖于outer,因此outer也一直存在于内存中,不会在调用结束后被垃圾回收机制(garbage collection)回收。)

由上述可得知:

1)闭包有权访问函数内部的所有变量;

2)当函数返回一个闭包时,这个函数的作用域会一直在内存中保存直到闭包不存在为止。

三. 闭包与变量

作用链机制,闭包允许内层函数引用父函数中的变量,但该变量是最终值

var f = function() {
    var rs = [];
    for(var i=0; i<10; i++) {
        rs[i] = function() {
            return i;
        }
    }
    return rs;
};
var fn = f();
for(var i=0; i<fn.length; i++) {
    console.log("函数fn(" + i + ")返回值是:" + fn[i]());
}

上述代码执行后,fn为一个数组,表面上看,每个函数执行后应该返回自己的索引值,但实际上,每个函数都返回10。fn数组中每个函数的作用域链上都保存着函数f的活动对象,它们引用的是同一变量i。当函数f返回后,i的值为10。

强制创建另一个闭包让函数的行为符合预期,如下代码所示。

var f = function() {
    var rs = [];
    for(var i=0; i<10; i++) {
        rs[i] = (function(num) {
            return function() {
                return num;
            }
        })(i);
    }
    return rs;
};
var fn = f();
for(var i=0; i<fn.length; i++) {
    console.log("函数fn(" + i + ")返回值是:" + fn[i]());
}

这个版本中,没有直接将闭包赋值给数组,而是定义了一个匿名函数,并将立即执行匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,在调用每个函数时,传入变量i,由于参数是按值传递的,因此将变量i复制给参数num。而在这个匿名函数内部又创建并返回了一个访问num的闭包,因此rs数组中每个函数包含自己的num变量,因此就可以返回不同的数值。

四. 闭包中this对象

var name = "Jack";
var o = {
    name: "Ting",
    getName: function() {
        return function() {
            return this.name;
        }
    }
};
console.log(o.getName()());   //Jack var name = "Jack";
var o = {
    name: "Ting",
    getName: function() {
        var self = this;
        return function() {
            return self.name;
        }
    }
};
console.log(o.getName()());   //Ting

五. 内存泄露

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

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

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

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

六. 模仿块级作用域

任何一对花括号中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,称之为块级作用域。

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

七. 闭包的应用

1)读取函数内部的变量

如上述例子中, 函数inner能够访问函数outer中的变量,将函数inner作为返回值,就能直接在outer外部读取它的变量。

2)在内存中维持变量

如上述例子中,由于闭包,函数outer一直存在于内存中,因此每次执行rs(),都会给i加1。

时间:2014-10-23

地点:合肥

引用:http://wlog.cn/javascript/javascript-closures.html

JavaScript 闭包系列一的更多相关文章

  1. JavaScript 闭包系列二(匿名函数及函数的闭包)

    一. 匿名函数 1. 函数的定义,可分为三种 1) 函数声明方式 function double(x) {     return 2*x; } 2)Function构造函数,把参数列表和函数体都作为字 ...

  2. 《深入理解javascript原型和闭包系列》 知识点整理(转)

    深入理解javascript原型和闭包系列 对原型和闭包等相关知识的讲解,由浅入深,通俗易懂,每个字都值得细细研究. 一.一切都是对象 1. typeof操作符输出6种类型:string boolea ...

  3. 《深入理解javascript原型和闭包系列》 知识点整理

    深入理解javascript原型和闭包系列 对原型和闭包等相关知识的讲解,由浅入深,通俗易懂,每个字都值得细细研究. 一.一切都是对象 1. typeof操作符输出6种类型:string boolea ...

  4. JavaScript进阶系列01,函数的声明,函数参数,函数闭包

    本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...

  5. 转:深入理解javascript原型和闭包系列

    转自:深入理解javascript原型和闭包系列 从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然 ...

  6. 深入理解javascript作用域系列第四篇——块作用域

    × 目录 [1]let [2]const [3]try 前面的话 尽管函数作用域是最常见的作用域单元,也是现行大多数javascript最普遍的设计方法,但其他类型的作用域单元也是存在的,并且通过使用 ...

  7. 什么是JavaScript闭包终极全解之一——基础概念

    本文转自:http://www.cnblogs.com/richaaaard/p/4755021.html 什么是JavaScript闭包终极全解之一——基础概念 “闭包是JavaScript的一大谜 ...

  8. 深入理解 JavaScript 异步系列(1)—— 什么是异步

    前言 2014年秋季写完了<深入理解javascript原型和闭包系列>,已经帮助过很多人走出了 js 原型.作用域.闭包的困惑,至今仍能经常受到好评的留言. 很早之前我就总结了JS三座大 ...

  9. 深入理解 JavaScript 异步系列(1)——基础

    前言 2014年秋季写完了<深入理解javascript原型和闭包系列>,已经帮助过很多人走出了 js 原型.作用域.闭包的困惑,至今仍能经常受到好评的留言. 很早之前我就总结了JS三座大 ...

随机推荐

  1. AspNet MVC中各种上下文理解

    0  前言 AspNet MVC中比较重要的上下文,有如下: 核心的上下文有HttpContext(请求上下文),ControllerContext(控制器上下文) 过滤器有关有五个的上下文Actio ...

  2. 利用CSS制作三角形

    在我们看到类似于这样的图片时: 我们一般都会想,哎,这还不简单,用一张图片就可以了. 的确,用图片可以很轻松地做到.不过我们接下来要讨论的是: 如何用css也作出这样的效果.   首先,我们来定义一个 ...

  3. iOS - Push 通知推送

    1.UserNotifications 通知是 App 用来和用户交流的一种方式,特别是当 App 并没有在前台运行的时候.通知,正如它的名称所强调的,被用作向用户'通知'一个事件,或者仅仅向用户提示 ...

  4. Nodejs报错集

    1.ReferenceError: userModule is not defined A:1>检查app.js文件中是否调用userModule所在的文件(const userModule=r ...

  5. C# 实现 Excel文件的数据导入

    前台 <asp:FileUpload ID="fuFile" runat="server" /> 后台 public string GetExcel ...

  6. Vim插件管理器Vundle使用

    参考地址:http://www.linuxidc.com/Linux/2012-12/75684.htm Vundle(Vim bundle) 是一个vim的插件管理器. 其Github地址为: ht ...

  7. Access使用join进行多个表联合查询的问题

    Access是支持三表或三表以上的join查询的,但是要加括号,如果不加的话,会报错,括号的作用是决定join的顺序.例如: SELECT *FROM (aa LEFT JOIN bb ON aa.a ...

  8. DOM操作方法的简单归纳

    (1)要在每个匹配的元素中插入新元素,使用: .append() .appendTo()   .prepend() .prependTo() (2)要在每个匹配的元素相邻的位置上插入新元素,使用: . ...

  9. 【原创】基于SVM作短期时间序列的预测

    [面试思路拓展] 对时间序列进行预测的方法有很多, 但如果只有几周的数据,而没有很多线性的趋势.各种实际的背景该如何去预测时间序列? 或许可以尝试下利用SVM去预测时间序列,那么如何提取预测的特征呢? ...

  10. 常用备份工具是mysql自带的mysqldump

    常用备份工具是mysql自带的mysqldump,mysqldump -u root -p密码 dbname >d:\test.sql ------------备份某个库mysqldump -u ...