欢迎光临散文网 会员登陆 & 注册

JavaScript 闭包

2020-04-11 17:25 作者:一枚猿  | 我要投稿

什么是JavaScript 闭包

函数和对其周围状态的引用捆绑在一起构成闭包。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。


作用域

请看下面的代码


function init(name){    // 内部函数,因此就形成了一个闭包    function show(name){        console.log('name',name);    }    show(name);}
init('爱上学院');


最后页面效果如下:

init() 创建了一个局部变量 name 和一个名为 show() 的函数。show() 是定义在 init() 里的内部函数,并且仅在 init() 函数体内可用。请注意,show() 没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 show() 可以使用父函数 init() 中声明的变量 name 。

运行该代码后发现, show() 函数内的 第4行语句成功执行了。这个例子描述了分析器如何在函数嵌套的情况下解析变量名。词法一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。


下面我们来看第二个例子:

function sum(x) {    return function sum1(y) {        return x + y;    }}
document.write(sum(2)(5));

执行上面代码,最后会在页面显示“7”,如下图:


实用的闭包

闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。

在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。


我们来看下面一个例子:点击按钮改变文字的字体大小。代码(只展示js代码)和页面效果如下:

上面的代码,是我们通常的写法。下面我们就采用闭包的形式来写。

采用闭包形式改写的代码如下:

function changeFontSize(fontSize) {    return function () {        document.getElementById("msg").style.fontSize = `${fontSize}px`;    }}const changeFontSize18 = changeFontSize(18);const changeFontSize24 = changeFontSize(24);const changeFontSize32 = changeFontSize(32);document.getElementById("btn18").onclick = changeFontSize18;document.getElementById("btn24").onclick = changeFontSize24;document.getElementById("btn32").onclick = changeFontSize32;


私有方法

编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。

而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。


下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为模块模式

const Count = (function () {    // 定一个变量    let count = 0;
   // 定一个调整count的方法,该方法只能在这里使用    function change(val) {        count += val;    }
   return {        add: function (step) {            change(step);        },        get: function () {            return count;        }    }})();// 获取count的值console.log(Count.get());// 0// count增加1Count.add(1)// count增加20Count.add(20)// 获取count的值console.log(Count.get());// 21// count值减小3Count.add(-3)// 获取count的值console.log(Count.get());// 18


在之前的示例中,每个闭包都有它自己的语法环境;而这次我们只创建了一个语法环境,为两个函数所共享:Count.add和 Count.get。

该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 count 的变量和名为 change 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的两个公共函数访问。

这两个公共函数是共享同一个环境的闭包。多亏 JavaScript 的语法作用域,它们都可以访问 count 变量和 change 函数。


下面我们来把上面的代码做一下微调:创建两个Count。


const Count = function () {    // 定一个变量    let count = 0;
   // 定一个调整count的方法,该方法只能在这里使用    function change(val) {        count += val;    }
   return {        add: function (step) {            change(step);        },        get: function () {            return count;        }    }};const Count1 = Count();const Count2 = Count();
// 获取Count1里面的count值console.log("Count1里面的count:", Count1.get());// 获取Count2里面的count值console.log("Count2里面的count:", Count2.get());// Count1里面的count增加1Count1.add(1)console.log("==========Count1里面的count增加1================");// 获取Count1里面的count值console.log("Count1里面的count:", Count1.get());// 获取Count2里面的count值console.log("Count2里面的count:", Count2.get());// Count2里面的count增加1Count2.add(13)console.log("==========Count2里面的count增加13================");// 获取Count1里面的count值console.log("Count1里面的count:", Count1.get());// 获取Count2里面的count值console.log("Count2里面的count:", Count2.get());


运行结果如下:


我们会发现两个计数器 Count1 和 Count2 ,每个闭包都是引用自己词法作用域内的变量 count 。

每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。






JavaScript 闭包的评论 (共 条)

分享到微博请遵守国家法律