理解 JavaScript 闭包,从这里开始

以下为本人学习所记,若有不对,望指出。

1. 变量的作用域

根据作用域的不同,JavaScript 中的变量可分为两种:全局变量局部变量

其中,函数内部可以直接读取全局变量

var a = 1;
function fn1() {
    console.log(a);
}
fn1(); // 1

但是,函数外部不能读取函数内的局部变量

function fn2() {
    var b = 2;
}
console.log(b); // ReferenceError: b is not defined

定义局部变量时要注意,要使用关键字varconstlet声明,若像下面这样声明,相当于window.b = 2,声明了一个全局变量!

function fn2() {
    b = 2; // 不要这样声明,这样相当于声明了全局变量
}
fn2();
console.log(b); // 2

2. 什么是闭包

以下摘自MDN:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

我们看下面这个例子,在函数内部,再定义一个函数,并将其作为返回值,返回一个函数:

function fn1() {
    let a = 1;
    return function fn2() {
        console.log(a);
    };
}
// 因为fn1函数有返回值,用result接收
const result = fn1();
result(); // 1

JavaScript中的函数会形成闭包。 闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内任何局部变量。在本例中,result是执行fn1时创建的fn2函数实例的引用。fn2的实例维持了一个对它的词法环境(变量a存在于其中)的引用。因此,当result被调用时,变量a仍然可用,其值就被传递到console.log中。

下面看一个makeAdder函数:

function makeAdder(x) {
    return function (y) {
        return x + y;
    };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2)); // 7
console.log(add10(2)); // 12

add5add10都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在add5的环境中,x为 5。而在add10中,x则为 10。

3. 闭包的作用

1. 在函数外部访问到函数内部变量

从上述例可看出,闭包可以在函数外部访问到函数内部的变量,这是其第一个作用。

2. 使变量留存在内存中

闭包另一个作用,使已经运行结束的函数上下文中的变量对象继续留在内存中。看下面一段 demo:

function fn1() {
    var a = 1;
    return function fn2() {
        console.log(a++);
    };
}
var result = fn1();
result(); // 1
result(); // 2
result(); // 3

a是函数fn1中的局部变量,a的值在函数fn2中改变,fn2每执行一次,a+1

而上述代码执行 3 次result()后,分别输出了a的值,a不断地+1。这说明函数fn1中的变量a一直保存在内存中,并没有在函数fn1调用后被清除。

因为函数fn2被赋给了一个全局变量,因此fn2会一直在内存中,而fn2的存在依赖于fn1,所以fn1也一直保存在内存中,并不会调用后被垃圾回收机制清除。

3. 模拟私有方法

JavaScript不支持将方法声明为私有,但可以用闭包来模拟私有方法。下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数变量

const counter = function () {
    let privateCounter = 0;

    return {
        add() {
            privateCounter++;
        },
        min() {
            privateCounter--;
        },
        value() {
            return privateCounter;
        },
    };
};

const counter1 = counter();
const counter2 = counter();

counter1.add();
counter1.add();
counter1.add();

counter2.min();

console.log(counter1.value()); // 3
console.log(counter2.value()); // -1

两个计数器counter1counter1都保持各自的独立性。每个闭包都是引用自己作用域内的变量privateCounter,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。另外,在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。

4. 闭包的副作用

1. 常驻内存

闭包会使得函数中的变量都被保存在内存中,内存消耗很大,不能滥用闭包,否则会造成严重的性能问题,在IE浏览器中可能由于循环引用而导致内存泄露

解决方法:在退出函数之前,将不使用的局部变量全部清除。

2. 误改函数内部的值

不要随便改变父函数内部变量的值。


参考文章

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火星飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值