前端开发:JS中闭包的使用详解

简介: 在前端开发的时候,JavaScript的变量可以是局部变量或全局变量,当需要使用局部私有变量的时候,就涉及到闭包相关的内容。关于JS中闭包的使用是一个非常重要的知识点,也是JS中的一个难点,不管是在实际开发过程中的时候,尤其是在高级应用中必须依靠闭包来操作实现;还是在求职面试的时候,都是必备知识点。那么本篇博文就来分享一下关于JS中闭包的相关知识,记录一下,方便查阅使用。

前言

在前端开发的时候,JavaScript的变量可以是局部变量或全局变量,当需要使用局部私有变量的时候,就涉及到闭包相关的内容。关于JS中闭包的使用是一个非常重要的知识点,也是JS中的一个难点,不管是在实际开发过程中的时候,尤其是在高级应用中必须依靠闭包来操作实现;还是在求职面试的时候,都是必备知识点。那么本篇博文就来分享一下关于JS中闭包的相关知识,记录一下,方便查阅使用。

什么是闭包

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

通俗的说,闭包就是能够读取其他函数内部变量的函数。由于在 JavasSript 语言中,只有函数内部的子函数才能读取局部变量,所以闭包可以简单理解成"定义在一个函数内部的函数"。在本质上看,闭包是将函数内部和函数外部连接起来的桥梁。

作用域

变量的作用域分为:全局变量和局部变量(私有变量)。JS中变量作用域有点特殊,那是因为在函数内部可以直接获取到全局变量,但是在函数外面无法获取函数内部的局部变量(私有变量。在Javascript中,每一个函数体对应于一个作用域,这有点像原型链,而且JS中的变量是遵从作用域链规则,这就是 JavasSript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之,则不成立。

在访问一个变量的时候,会先访问当前作用域内是否包含有定义的该变量,若没有,就会在该作用域外的作用域内继续寻找是否包含有该变量,依此类推,直到寻找到全局变量;若全局变量中依然没有定义该变量,那么就会返回undefined。

注意:在函数内部声明变量的时候,必须要使用 var 命令,若不使用var,实际上声明的是一个全局的变量。

闭包作用

  1. 为的是可以读取函数内部的变量;
  2. 让函数体内定义的变量的值始终保持在内存中;
  3. 访问函数内部变量、保持函数在环境中一直存在,在调用结束后不被垃圾回收机制回收处理;
  4. 模拟出可以实现面向对象,使得不同对象拥有独立的成员和状态,达到互不干涉的目的。

私有方法

在编程语言里面,如Java语言是支持将方法声明为私有的,即它这些私有方法只能被同一个类中的其它方法所调用。但是JavaScript语言没有这种原生的支持,但开发者可以使用闭包来模拟私有的方法。

私有方法不仅有利于限制对代码的访问;而且还提供了能够管理全局命名空间的功能,避免非核心方法污染了代码中的公共接口部分内容。

闭包的优点

  1. 使用起来非常灵活和方便,方便调用上下文的局部变量;
  2. 加强程序的封装特性,可以对变量起到保护作用;
  3. 保护函数内部的变量的安全性,防止被环境污染;
  4. 在内存中,维持一个变量 ;
  5. 可以保持逻辑连续性,当闭包作为另一个函数调用的参数的时候,可以避免脱离当前逻辑而另行编写其他额外逻辑。

闭包的缺点

  1. 由于闭包会使得函数中的变量都被保存在内存中,使得内存消耗很大,是常存内存,会增加内存的使用量,若使用不当,很容易引起内存泄露;
  2. 内存空间浪费对问题,这个内存浪费不仅因为它常驻内存,更重要的是对闭包的使用不当会造成无效内存的产生;
  3. 闭包会在父函数外部,改变父函数内部变量的值,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定不要随便改变父函数内部变量的值,否则会引起严重的问题。
  4. 由于闭包自己的局部活动对象和依赖到的活动对象都会被包含在它自身的作用域链里面,所以它的体量往往是比普通函数大很多,性能消耗大;

闭包的特性

  1. 闭包一般是函数嵌套函数;
  2. 闭包内部函数可以访问外部函数的变量;
  3. 闭包的参数和变量在使用后不会被垃圾回收机制所回收。

使用闭包的注意点

  1. 由于闭包会使得函数中的变量都被保存在内存中,会使得内存消耗很大,所以不能滥用闭包,否则会造成页面的性能问题,在IE中可能导致内存泄露。解决方法:在退出函数前,把不使用的局部变量全部删除释放即可。
  2. 闭包会在父函数的外部,改变父函数内部变量的值。所以,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,此时一定要千万小心,不要随便改变父函数内部变量的值。

示例代码

1、从外部读取函数内部的局部变量

需要获取到函数内部的局部变量,只有通过变通的方法才能实现,那就是在函数内部再定义一个函数,具体如下所示:

function fun1(){
    var num=111;
    function fun2(){
        alert(num); // 输出结果为:111
    }
}

1.png

上述代码中,函数 fun2被包括在函数fun1内部,此时fun1内部的所有局部变量,对fun2都是可见的;但是反过来就不行,fun2内部的局部变量,相对 fun1是不可见的。

2、使变量一直保存在内存之中,不被垃圾回收机制所回收

具体如下所示:

var changed;
function includ() {
    var fruit = ‘苹果’
    function food() {
        console.log(‘红的’ + fruit)
    }
    changed = function () {
        fruit =  ‘樱桃’
    }
    return food;
}
var result = includ()
result()  //输出结果:红的苹果
changed()
result()  //输出结果:红的樱桃

2.png

上述代码中,includ()执行之后,fruit变量能一直被访问到,原因就是result引用了includ()内部的food() 函数,food()函数又引用了fruit变量,所以它一直不被垃圾回收机制所回收。

3、模拟实现私有变量

具体如下所示:

function calculate(start){
  var num = start;
   return{
    increase: function(){
      num++;
    },
    get: function(){
      return num;
    }
  }
}
var result = calculate(4);
result.increase();
result.get();  //输出结果:5

3.png

上述代码中,calculate()函数返回两个闭包:increase()和get(),该两个函数都维持着对外部作用域 calculate ()的引用,所以总是可以访问此作用域内定义的变量num。

拓展:作用域规则

  1. 大括号{}不产生一个作用域,定义函数才会产生一个函数作用域;
  2. 函数在执行的过程中,先从函数自身内部找变量;
  3. 若函数自身内部找不到变量,才会再从创建当前函数所在的作用域里去找变量, 依此往上查找变量。

总结

1、闭包是可以访问另一个函数作用域中的变量的函数,创建闭包的最常方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。
2、闭包的缺点之一就是常驻内存,会增大内存使用量,若使用不当就很容易引起内存泄露。
3、闭包不适用场景:返回闭包的函数是个体积比较大的函数,所以对函数体积要求小的情景下不适合使用闭包。
4、闭包是JavaScript语言的经典特点之一,应用闭包场合主要是为了设计私有的方法和变量。
5、在做框架的时候闭包的运用更普遍,有些方法和属性只在运算逻辑过程中使用,不想让外部修改这些属性,所以就可以设计一个闭包来只提供方法获取,闭包的典型框架非jquery莫属了。
6、不用纠结到底在什么情况下才算闭包,其实在写下的每一个函数都可以算作闭包,即便是全局函数在访问函数外部的全局变量时,也属于闭包的体现。

最后

通过上面介绍的JS中闭包的使用详解,常用闭包的相关知识点通过本文内容就可涵盖,在Vue.js开发中的使用就游刃有余了,这也是在开发过程中必用的功能,尤其是对于初中级开发者来说,更应该掌握这些情况的使用,还有在前端面试求职的时候,闭包的使用以及相关知识点也是必考内容,所以一定要完全掌握闭包相关的内容,这里不再赘述。

相关文章
|
1月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
1月前
|
JavaScript 前端开发 Java
springboot解决js前端跨域问题,javascript跨域问题解决
本文介绍了如何在Spring Boot项目中编写Filter过滤器以处理跨域问题,并通过一个示例展示了使用JavaScript进行跨域请求的方法。首先,在Spring Boot应用中添加一个实现了`Filter`接口的类,设置响应头允许所有来源的跨域请求。接着,通过一个简单的HTML页面和jQuery发送AJAX请求到指定URL,验证跨域请求是否成功。文中还提供了请求成功的响应数据样例及请求效果截图。
springboot解决js前端跨域问题,javascript跨域问题解决
|
1月前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
47 5
|
1月前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
74 1
|
1月前
|
JSON 前端开发 JavaScript
聊聊 Go 语言中的 JSON 序列化与 js 前端交互类型失真问题
在Web开发中,后端与前端的数据交换常使用JSON格式,但JavaScript的数字类型仅能安全处理-2^53到2^53间的整数,超出此范围会导致精度丢失。本文通过Go语言的`encoding/json`包,介绍如何通过将大整数以字符串形式序列化和反序列化,有效解决这一问题,确保前后端数据交换的准确性。
41 4
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
129 1
|
1月前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
45 0
|
1月前
|
存储 缓存 自然语言处理
掌握JavaScript闭包,提升代码质量与性能
掌握JavaScript闭包,提升代码质量与性能
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包(Closures)
深入理解JavaScript中的闭包(Closures)
|
1月前
|
存储 自然语言处理 JavaScript
深入理解JavaScript的闭包(Closures)
深入理解JavaScript的闭包(Closures)
30 0