前端必备知识:闭包的概念、作用与应用

简介: 前端必备知识:闭包的概念、作用与应用

闭包=能够读取其他函数内部变量的函数。在js中,可以将闭包理解成“函数中的函数“。


一、变量的作用域

要理解闭包,首先必须理解 Javascript 特殊的变量作用域

变量的作用域无非就是两种:全局变量和局部变量。

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

var n = 999;
function f1(){
  alert(n);
}
f1(); // 999

另一方面,在函数外部自然无法读取函数内的局部变量。

function f1(){    
  var n = 999;
}
alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。

如果不用的话,你实际上声明了一个全局变量!

function f1(){
  n = 999;
}
f1();
alert(n); // 999


二、如何从外部读取局部变量 ?
function f1() {
  var n = 999;
  function f2() {
    alert(n);
  }
  return f2;
}
var result = f1();
result(); // 999

既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!


三、闭包的概念

上一节代码中的 f2 函数,就是闭包。

我的理解是,闭包就是能够读取其他函数内部变量的函数。

由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 定义在一个函数内部的函数。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

四、闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

怎么来理解呢 ?请看下面的代码。

function f1() {
  var n = 999;
  nAdd = function () { n += 1 }
  function f2() {
    alert(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000

在这段代码中,result 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999,第二次的值是 1000。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除。


为什么会这样呢 ?

原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是

  • “nAdd=function(){ n+=1 }” 这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。
  • 其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。
五、使用闭包的注意点


由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。


闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。


六、闭包面试经典问题

问题:想每次点击对应目标时弹出对应的数字下标 0~4 ,但实际是无论点击哪个目标都会弹出数字 5。

function onMyLoad() {
  var arr = document.getElementsByTagName("p");
  for (var i = 0; i < arr.length; i++) {
    arr[i].onclick = function () {
      alert(i);
    }
  }
}


问题所在:arr 中的每一项的 onclick 均为一个函数实例(Function 对象),这个函数实例也产生了一个闭包域,这个闭包域引用了外部闭包域的变量,其 function scope 的 closure 对象有个名为 i 的引用,外部闭包域的私有变量内容发生变化,内部闭包域得到的值自然会发生改变。


解决办法一

解决思路:增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标),不过只限于基本类型(基本类型值传递,对象类型引用传递)。

//声明一个匿名函数,若传进来的是基本类型则为值传递,故不会对实参产生影响,
//该函数对象有一个本地私有变量 arg(形参) ,该函数的 function scope 的 closure 对象属性有两个引用,一个是 arr,一个是 i
//尽管引用 i 的值随外部改变 ,但本地私有变量(形参) arg 不会受影响,其值在一开始被调用的时候就决定了
for (var i = 0; i < arr.length; i++) {
  (function (arg) {
    arr[i].onclick = function () {
      // onclick 函数实例的 function scope 的 closure 对象属性有一个引用 arg,
      alert(arg);
      //只要 外部空间的 arg 不变,这里的引用值当然不会改变
    }
  })(i); //立刻执行该匿名函数,传递下标 i (实参)
}
解决办法二

解决思路:将事件绑定在新增的匿名函数返回的函数上,此时绑定的函数中的 function scope 中的 closure 对象的 引用 arg 是指向将其返回的匿名函数的私有变量 arg

for (var i = 0; i < arr.length; i++) {
  arr[i].onclick = (function (arg) {
    return function () {
      alert(arg);
    }
  })(i);
}
解决办法三

使用 ES6 新语法 let 关键字

for (var i = 0; i < arr.length; i++) {
  let j = i; // 创建一个块级变量
  arr[i].onclick = function () {
    alert(j);
  }
}
目录
相关文章
|
18天前
|
移动开发 缓存 前端开发
深入理解前端路由:原理、实现与应用
本书《深入理解前端路由:原理、实现与应用》全面解析了前端路由的核心概念、工作原理及其实现方法,结合实际案例探讨了其在现代Web应用中的广泛应用,适合前端开发者和相关技术人员阅读。
|
17天前
|
存储 前端开发 JavaScript
前端状态管理:Vuex 核心概念与实战
Vuex 是 Vue.js 应用程序的状态管理模式和库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。本教程将深入讲解 Vuex 的核心概念,如 State、Getter、Mutation 和 Action,并通过实战案例帮助开发者掌握在项目中有效使用 Vuex 的技巧。
|
24天前
|
自然语言处理 前端开发 JavaScript
深入理解前端中的 “this” 指针:从基础概念到复杂应用
本文全面解析前端开发中“this”指针的运用,从基本概念入手,逐步探讨其在不同场景下的表现与应用技巧,帮助开发者深入理解并灵活掌握“this”的使用。
|
24天前
|
存储 前端开发 JavaScript
前端中对象的深度应用与最佳实践
前端对象应用涉及在网页开发中使用JavaScript等技术创建和操作对象,以实现动态交互效果。通过定义属性和方法,对象可以封装数据和功能,提升代码的组织性和复用性,是现代Web开发的核心技术之一。
|
27天前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
39 5
|
25天前
|
JavaScript 前端开发 测试技术
构建高效可维护的前端应用
构建高效可维护的前端应用
|
25天前
|
编解码 监控 JavaScript
打造高效前端应用
打造高效前端应用
34 1
|
25天前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
63 1
|
2月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
177 2
|
2月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
48 0