深入浅出JavaScript闭包

文章目录

  • 一、闭包是什么?
  • 二、闭包的使用场景
    • 创建私有变量
  • 三、注意事项


一、闭包是什么?

引用官方解释:闭包是一个函数以及其捆绑的周边环境状态(词法环境)的引用的组合,emmm…有些晦涩,那么应该怎么理解这句话呢,来看一个典型的闭包例子:

function makeFunc() {
    const name = 'Jerry'   // 创建局部变量:name
    function getName() {    // 创建内部函数getName
        console.log('name:', name);    
    }
    return getName;		// 返回内部函数
}
const myFunc = makeFunc();	// 接收返回值
const name = 'Tom';     // 创建全局变量:name
myFunc();

以上我们定义了一个函数makeFunc,在其内部又定义了一个局部变量name内部函数getName,并将内部函数当作返回值返回。当我们执行函数makeFunc()时,我们用一个变量myFunc接收该返回值,并执行myFunc(),问此时会输出局部变量name:Jerry,还是全局变量name:Tom

输出:name: Jerry

分析:按照正常情况,局部变量name仅存在于函数的执行期间,当函数makeFunc执行完毕后,其执行上下文理应被销毁,局部变量name无法再被访问,而执行结果显然是不符合预期的。

原因:内部函数getName,该函数创建在父函数makeFunc的执行上下文中,此时我们称父函数makeFunc的执行上下文为内部函数getName的词法环境,而根据上文例子中,即便内部函数的词法环境被销毁后(父函数执行完毕后),内部函数getName依旧保存对创建时所在词法环境的引用(即保存了局部变量name),以至于可以输出局部变量name。因此在上文例子中:内部函数getName与其保存的其创建时所在词法环境的引用,称作闭包。现在回过头去重新认识一下官方的解释就通得多了。

二、闭包的使用场景

创建私有变量

在Java语言中,是允许将属性和方法声明为私有的,表现为它们只能被同一个类中的其它方法所调用。而在JavaScript并无原生支持,但是我们可以通过闭包,来创建私有变量。

举个栗子:

let Counter = (function () {
    let privateCount = 0;
    return {
        showCount: function () {
            console.log(privateCount);
        },
        addCount: function () {
            privateCount++;
        }
    }
})();
Counter.addCount();
Counter.addCount();
Counter.addCount();
Counter.showCount();    // 3
console.log(Counter.privateCount);  // 函数已立即执行,执行上下文销毁,输出undefined

解析::该例中,我们在一个匿名的立即执行函数内创建了一个词法环境,其中包含一个私有变量privateCount并返回两个公共函数showCount与addCount共享该词法环境,通过结果我们可以得出:私有变量privateCount无法在匿名函数外部直接访问,必须通过匿名函数返回的两个公共函数访问,从而达到了模拟私有变量的效果。

除此之外:我们可以不采用匿名函数的方式(只会创建一个实例),可以将函数立即执行后,其返回值存储在不同的变量当中,创建多个闭包

let Counter = function () {
    let privateCount = 0;
    return {
        showCount: function () {
            console.log(privateCount);
        },
        addCount: function () {
            privateCount++;
        }
    }
};
let count_1 = Counter();
let count_2 = Counter();
count_1.addCount();
count_1.addCount();
count_1.showCount();    // 2
count_2.addCount();
count_2.showCount();    // 1

有此例我们可以看出,每个闭包都是各自独立的,都只引用自己词法环境中的privateCount,在一个闭包内对改变其保存的词法环境的引用,不会影响到另外一个闭包中保存的此法环境的引用,也就是在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。

三、注意事项

通过上面的例子就可以得知,闭包的好处有,他可以使变量始终保存在内存中直到被销毁为止。另一个好处是,他可以创建私有属性或者方法,避免变量被全局变量污染

注意:闭包是能使变量常驻在内存中,这个是他的优点,但如果滥用闭包的话这个就变成了他的缺点。因为如果大量使用闭包存储变量,那么就会增加内存的消耗

因此:在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中,因为每次创建对象时,构造器部定义方法都要被重写一次。

如下栗子:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };
  this.getMessage = function() {
    return this.message;
  };
}

在该例中,我们并没有利用到闭包的好处,因此可以避免使用闭包

修改如下:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

你可能感兴趣的:(javascript,开发语言,前端)