Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JavaScript闭包

JavaScript闭包

原创
作者头像
WindRunnerMax
修改于 2020-10-21 02:12:18
修改于 2020-10-21 02:12:18
1.1K0
举报
文章被收录于专栏:Czy‘s BlogCzy‘s Blog

JavaScript闭包

函数和对其词法环境lexical environment的引用捆绑在一起构成闭包,也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

定义闭包

为了定义一个闭包,首先需要一个函数来套一个匿名函数。闭包是需要使用局部变量的,定义使用全局变量就失去了使用闭包的意义,最外层定义的函数可实现局部作用域从而定义局部变量,函数外部无法直接访问内部定义的变量。

代码语言:txt
AI代码解释
复制
function student(){
    var name = "Ming";
    var sayMyName = function(){ // sayMyName作为内部函数,有权访问父级函数作用域student中的变量
        console.log(name);
    }
    console.dir(sayMyName); // ... [[Scopes]]: Scopes[2] 0: Closure (student) {name: "Ming"} 1: Global ...
    return sayMyName; // return是为了让外部能访问闭包,挂载到window对象也可以 
}
var stu = student(); 
stu(); // Ming

可以看到定义在函数内部的name变量并没有被销毁,我们仍然可以在外部使用函数访问这个局部变量,使用闭包,可以把局部变量驻留在内存中,从而避免使用全局变量。全局变量污染会导致应用程序不可预测性,每个模块都可调用必将引来灾难。

词法环境

闭包共享相同的函数定义,但是保存了不同的词法环境lexical environment

代码语言:txt
AI代码解释
复制
function student(name){
    var sayMyName = function(){
        console.log(name);
    }
    return sayMyName;
}
var stu1 = student("Ming"); 
var stu2 = student("Yang"); 
stu1(); // Ming
stu2(); // Yang

模拟私有方法

在面向对象的语言中,例如JavaPHP等,都是支持定义私有成员的,即只有类内部能够访问,而无法被外部类访问。JavaScript并未原生支持定义私有成员,但是可以使用闭包来模拟实现,私有方法不仅仅有利于限制对代码的访问,还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

代码语言:txt
AI代码解释
复制
function student(){
    var HP = 100;
    var addHP = function(){
        return ++HP;
    }
    var decHP = function(){
        return --HP;
    }
    return {
        addHP,
        decHP
    };
}
var stu = student();
console.log(stu.HP); // undefined 不允许直接访问
console.log(stu.addHP()); // 101
console.log(stu.decHP()); // 100

回调机制

Js的闭包为回调机制提供了支持,无论函数是否立马被调用,这个闭包都不会被释放。而且在Js里,无论把callback函数作为参数传递给其他函数,或者作为返回值返回,以便于之后调用,都是合法的。

代码语言:txt
AI代码解释
复制
function localContext(){
    var localVal = 1;
    var callback = function(){
        console.log(localVal); // ... [[Scopes]]: Scopes[2] 0: Closure (localContext) {localVal: 1} 1: Global ...
    }
    console.dir(callback);
    setTimeout(callback, 1000); // 1
}
localContext();

在本例中,callback函数与其词法环境构成了闭包,其词法环境中存在的变量localVal = 1在函数callback作为回调函数传递时并没有被立即释放,而可以在回调执行时继续使用,这就是闭包为回调机制提供了支持。

循环创建闭包

ECMAScript 2015引入let关键字之前,只有函数作用域和全局作用域,函数作用域中又可以继续嵌套函数作用域,在for并未具备局部作用域,于是有一个常见的闭包创建问题。

代码语言:txt
AI代码解释
复制
function counter(){
    var arr = [];
    for(var i = 0 ; i < 3 ; ++i){
        arr[i] = function(){
            return i;
        }
    }
    return arr;
}

var coun = counter();
for(var i = 0 ; i < 3 ; ++i){
    console.log(coun[i]()); // 3 3 3
}

可以看到运行输出是3 3 3,而并不是期望的0 1 2,原因是这三个闭包在循环中被创建的时候,共享了同一个词法作用域,这个作用域由于存在一个ivar声明,由于变量提升,具有函数作用域,当执行闭包函数的时候,由于循环早已执行完毕,i已经被赋值为3,所以打印为3 3 3

匿名函数新建函数作用域来解决

代码语言:txt
AI代码解释
复制
function counter(){
    var arr = [];
    for(var i = 0 ; i < 3 ; ++i){
        (function(i){
            arr[i] = function(){
                return i;
            }
        })(i);
    }
    return arr;
}

var coun = counter();
for(var i = 0 ; i < 3 ; ++i){
    console.log(coun[i]()); // 0 1 2
}

使用let关键字

代码语言:txt
AI代码解释
复制
function counter(){
    var arr = [];
    for(let i = 0 ; i < 3 ; ++i){
        arr[i] = function(){
            return i;
        }
    }
    return arr;
}

var coun = counter();
for(var i = 0 ; i < 3 ; ++i){
    console.log(coun[i]()); // 0 1 2
}

内存泄漏

内存泄露是指你用不到(访问不到)的变量,依然占据着内存空间,不能被再次利用起来。

闭包引用的变量应该是需要使用的,不应该属于内存泄漏,但是在IE8浏览器中JScript.dll引擎使用会出现一些问题,造成内存泄漏。

对于各种引擎闭包内存回收具体的表现参阅 这篇文章

性能考量

如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次。

参考

代码语言:txt
AI代码解释
复制
https://zhuanlan.zhihu.com/p/22486908
https://www.cnblogs.com/Renyi-Fan/p/11590231.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
定义闭包
函数和对其词法环境lexical environment的引用捆绑在一起构成闭包,也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
WindRunnerMax
2022/05/06
2630
详解JavaScript中闭包(Closure)概念
闭包(Closure)是 JavaScript 中一个非常重要的概念,它允许函数访问其词法作用域(lexical scope)中的变量,即使这个函数在其词法作用域之外执行。理解闭包有助于编写更高效、模块化的代码,并且在处理异步操作、回调函数和数据封装时非常有用。
jack.yang
2025/04/05
780
Javascript 闭包学习
说到函数,就不可避免的涉及到作用域问题,JS中把变量作用域分为全局和局部,所有函数均可访问全局变量,局部变量在函数体内使用,其他函数不能调用,并且函数执行完成会被清理。
ranky
2019/04/29
5520
JavaScript 核心特性之《闭包》
其实闭包这个话题一直也是面试高频题,我在面试当中有 80% 的时候面试官会问我闭包的特性以及实际的应用场景。闭包也确实是 JavaScript 中的核心特性,在实际当中可以说你一直在使用闭包,只不过你并不知道这个是闭包。
离殊
2022/04/01
2850
《你不知道的JavaScript》:闭包与局部作用域
闭包:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
前端_AWhile
2022/05/10
5320
你为什么学不好闭包
因为,你没有遇到一个好老师,告诉你闭包的本质。本质的东西往往都是通俗易懂的,你只需要花费几分钟,就能彻底消化你自己独立摸索了几年都掌握不好的一个小知识。
用户6901603
2024/06/07
1210
你为什么学不好闭包
《现代Javascript高级教程》Javascript执行上下文与闭包
JavaScript中的闭包源于计算机科学中的一种理论概念,称为“λ演算”(Lambda Calculus)。λ演算是计算机科学的基础之一,1930年由Alonzo Church提出,它是一种用于描述计算过程的数学抽象模型,也是函数式编程语言的基础。
linwu
2023/07/27
1940
javascript中的闭包这一篇就够了
两段代码,在第二段代码中,函数A内的匿名函数可以访问到函数A中的局部变量这就是闭包的基本使用。
coder_koala
2019/07/30
6590
一篇文章把你带入到JavaScript中的闭包与高级函数
在JavaScript中,函数是一等公民。JavaScript是一门面向对象的编程语言,但是同时也有很多函数式编程的特性,如Lambda表达式,闭包,高阶函数等,函数式编程时一种编程范式。
达达前端
2019/11/19
8120
一篇文章把你带入到JavaScript中的闭包与高级函数
Javascript 闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
超级大帅比
2021/09/05
4300
JavaScript——作用域和闭包
本文着重于对 JavaScript 中的作用域和闭包机制进行剖析和说明,闭包本质上也是作用域的一种类型,因为在 JavaScript 里非常重要,所以我们把它在标题里单独列出来。
Html5知典
2019/11/26
7480
JavaScript进阶教程(5)-一文让你搞懂作用域链和闭包
在JS中变量可以分为局部变量和全局变量,对于变量不熟悉的可以看一下我这篇文章:搞懂JavaScript全局变量与局部变量,看这篇文章就够了 作用域就是变量的使用范围,分为局部作用域和全局作用域,局部变量的使用范围为局部作用域,全局变量的使用范围是全局作用域。在 ECMAScript 2015 引入let 关键字之前,js中没有块级作用域---即在JS中一对花括号({})中定义的变量,依然可以在花括号外面使用。
AlbertYang
2020/09/16
3310
JavaScript进阶教程(5)-一文让你搞懂作用域链和闭包
JavaScript闭包
init() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 init() 里的内部函数,并且仅在 init() 函数体内可用。displayName() 没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 init() 中声明的变量 name 。
闲花手札
2021/11/15
6310
什么是JavaScript 的闭包???
Javascript的闭包是指一个函数与周围状态(词法环境)的引用捆绑在一起(封闭)的组合,在JavaScript中,每次创建函数时,都会同时创建闭包。闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰,即形成一个不销毁的栈环境。
AlbertYang
2020/09/08
1.2K0
说说JavaScript闭包
JavaScript 闭包难点剖析一、作用域基本介绍ES6之前只有全局作用域与函数作用域两种,ES6出现之后,新增了块级作用域1.全局作用域在JavaScript中,全局变量是挂载在window对象下的变量,所以在网页中的任何位置你都可以使用并且访问到这个全局变量当我们定义很多全局变量的时候,会容易引起变量命名的冲突,所以在定义变量的时候应该注意作用域的问题var globalName = 'global'function getName() { console.log(globalName) // gl
hellocoder2029
2022/12/07
2300
let与const
ES2015(ES6)新增加了两个重要的JavaScript关键字: let和const。
WindRunnerMax
2020/08/27
5650
JavaScript中的闭包(closure)
在JavaScript中,当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。简单说,闭包就是能够读取其他函数内部变量的函数。
刘亦枫
2020/03/19
1.2K0
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包在很多现代编程语言中都存在。常见支持闭包的语言有 JavaScript、Python、Ruby、Swift、Kotlin、Scala 等。本文将着重讲在JavaScript中闭包的常见用法及实操意义。
watermelo37
2025/01/22
1530
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
JavaScript之作用域和闭包
一、作用域 作用域共有两种主要的工作模型:第一种是最为普遍的,被大多数编程语言所采用的词法作用域,另外一种叫作动态作用域; JavaScript所采用的作用域模式是词法作用域。 1.词法作用域 词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找。 JavaScript 中有两个机制可以“欺骗”词法作用域: eval(..):可以对一段包含一个或多个声明的“代码”字符串进行演算,并借此来修
keyWords
2018/09/19
7130
浅学前端:JavaScript篇(一)
要表示真正的整数,需要用 bigint,数字的结尾用 n 表示它是一个 bigint 类型
传说之下的花儿
2023/11/08
2490
浅学前端:JavaScript篇(一)
相关推荐
定义闭包
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档