Javascript闭包

对闭包的概念不是很清楚,于是查几篇博客。其中有几篇我觉得对我帮助非常大。

  1. 阮一峰老师的博客——初步理解
  2. 伪架构师的博客——专业理解
  3. 一像素的博客——最需要的理解,高级写法

在此摘录一些对自己有用的内容,作为自己的学习笔记。

定义

在《JavaScript高级程序设计(第3版)》中文版中[3],具体描述在第7章函数表达式第7.2节(页码为第178页):闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

先上一个例子看一下:

function f1() {
    var n = 999;

    function f2() {
        return n;
    }

    return f2;
}


var F2 = f1()
console.log(F2())  //  999

var F2 = f1()当调用f1()时这时候不是返回一个值,而是返回的一个函数,所以现在要把F2当成是一个函数,而F2()即为f2(),f1()外部是不能访问n,但是f2()是可以访问n的。因此达到了访问n的目的。

再看一段代码:

function startAt(CAPTURED) {

    function incrementBy(i) {

        return CAPTURED + i;

    }

    return incrementBy;

}

var startAt1 = startAt(1)
var startAt10 = startAt(10)

console.log(startAt1(1))  // 2
console.log(startAt10(2))  // 12

我们注意到,因为startAt返回一个函数,所以变量startAt10 和 startAt100 是属于函数类型。运行startAt1(1)会返回2,运行startAt10(2)会返回12。尽管startAt1和startAt10都关联到同一个函数incrementBy,相关的环境却不同,调用闭包会将CAPTURED与不同值的不同变量绑定,因此执行函数会得到不同的结果。
其实有些类似于类,但是和类的区别还不是很懂,以后研究了在做补充吧。

闭包的用途

在了解闭包的作用之前,我们先了解一下 Javascript 中的 GC 机制:

在 Javascript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收,否则这个对象一直会保存在内存中。

function A() {
    var count = 0;
    function B() {
       count ++;
       console.log(count);
    }
    return B;
}
var C = A();
C();// 1
C();// 2
C();// 3

count 是函数A 中的一个变量,它的值在函数B 中被改变,函数 B 每执行一次,count 的值就在原来的基础上累加 1 。因此,函数A中的 count 变量会一直保存在内存中。

当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。

闭包的高级写法

上面的写法其实是最原始的写法,而在实际应用中,会将闭包和匿名函数联系在一起使用。下面就是一个闭包常用的写法:

(function (document) {
    var viewport;
    var obj = {
        init: function(id) {
           viewport = document.querySelector('#' + id);
        },
        addChild: function(child) {
            viewport.appendChild(child);
        },
        removeChild: function(child) {
            viewport.removeChild(child);
        }
    }
    window.jView = obj;
})(document);

这个组件的作用是:初始化一个容器,然后可以给这个容器添加子容器,也可以移除一个容器。

功能很简单,但这里涉及到了另外一个概念:立即执行函数。 简单了解一下就行,需要重点理解的是这种写法是如何实现闭包功能的。

可以将上面的代码拆分成两部分:(function(){}) 和 () 。

第1个() 是一个表达式,而这个表达式本身是一个匿名函数,所以在这个表达式后面加 () 就表示执行这个匿名函数。
因此这段代码执行执行过程可以分解如下:

var f = function(document) {
    var viewport;
    var obj = {
        init: function(id) {
            viewport = document.querySelector('#' + id);
        },
        addChild: function(child) {
            viewport.appendChild(child);
        },
        removeChild: function(child) {
            viewport.removeChild(child);
        }
    }
    window.jView = obj;
};
f(document);

在这段代码中似乎看到了闭包的影子,但 f 中没有任何返回值,似乎不具备闭包的条件,注意这句代码:window.jView = obj;obj 是在函数 f 中定义的一个对象,这个对象中定义了一系列方法, 执行window.jView = obj 就是在 window 全局对象定义了一个变量 jView,并将这个变量指向 obj 对象,即全局变量 jView 引用了 obj . 而 obj 对象中的函数又引用了函数 f 中的变量 viewport ,因此函数 f 中的 viewport 不会被 GC 回收,viewport 会一直保存到内存中,所以这种写法满足了闭包的条件。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

关于自由变量(free variable)和名称绑定(name binding)

“自由变量与闭包的关系是,自由变量闭合于闭包的创建。闭包背后的基本原理是,如果一个函数包含内部函数,那么它们都可以看到其中声明的变量;这些变量被称为‘自由’变量。然而,这些变量可以被内部函数捕获,从高阶函数中return实现‘越狱’,以供以后使用。唯一需要注意的是,捕获函数必须在外部函数内定义。函数内没有任何局部声明之前(既不是被传入,也不是局部声明)使用的变量就是被捕获的变量。”

名称绑定(name binding) 在计算机程序语言中[6],名称绑定就是将实体(entities- data and/code)与标识符(identifiers)相关联的过程。我们将一个标识符绑定到一个对象上叫作引用(reference)该对象。机器语言没有内置的标识符记法(notion),但是名称与对象(name-object)绑定做为一个服务和记法(notation)被程序语言实现并且被程序员所使用。绑定与作用域的概念紧密相关,因为作用域从词法上,在计算机编码中定义了何名称在何种可能执行的路径下于何处绑定何具体对象。
关于名称绑定可以结合理解一下值传递和引用传递。

简单理解(基本)

闭包是一个函数

细化(来自阮一峰博客)
闭包是定义在一个函数内部,并且能够读取其他函数内部变量的函数。

明确(来自《JavaScript高级程序设计》第三版)
闭包是定义在一个外部函数内部,并且能够访问(存取)外部函数中自由变量的函数。

广义抽象(来自Mozilla与维基百科)
闭包是一个抽象的环境记录,它包含狭义的闭包函数以及在创建该函数时,每个自由变量及其值或名称绑定存储区直接的一个映射

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值