搜索
首页web前端js教程JavaScript中的闭包
JavaScript中的闭包Mar 07, 2018 pm 02:08 PM
javascriptjs闭包

闭包就那么一两句话谁都能背出来。可是闭包偏偏就是那种初学者十次面试八次可能会遇到,答不上来就是送命题、答得出来也不加分题。为了不让我们前端开发从入门到放弃,我还是来谈谈我认为的 JS 里面的闭包。

闭包是什么

闭包创建一个词法作用域,这个作用域里面的变量被引用之后可以在这个词法作用域外面被自由访问,是一个函数和声明该函数的词法环境的组合

还有一种说法,闭包就是引用了自由变量的函数,这个自由变量与函数一同存在,即使脱离了创建它的环境。所以你常看到的说闭包就是绑定了上下文环境的函数,也大概是这个意思。只要想明白了,就觉得其实是很简单的一个东西,并没有多么高深。

在下面的介绍中,我还是比较偏向闭包是一个函数和声明该函数的词法环境的组合这种解释,所以也会基于这种解释去阐述。

闭包其实是计算机科学里面的一个概念,并不是JS里面独有的。闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。(别问我 Scheme 是什么,问了我也不知道,这段是从维基上抄的。)之后,闭包被广泛使用于函数式编程语言

JS里面的闭包

现在,我就发大绝招了,徒手撸一个闭包。

function sayHello(name) {
  let str = `Hello,${name}`;
  function say() {
    console.log(str);
  }
  return say;
}

let myHello = sayHello('abby');
myHello(); // Hello,abby

上面这段代码,其实就形成了一个闭包,其中在 sayHello 这个函数里面定义的函数 say 和其声明它的词法环境就形成了一个闭包,因为它引用了sayHello 里面定义的一个变量 str,并且将 say 这个函数 return 了出去,这样在 sayHello 这个函数的外面也能访问它里面定义的变量 str,就好像 say 这个函数和这个变量绑定了一样。

看到这里可能会疑问为什么在外部还能访问到这个变量呢,因为在有些语言中,一般认为函数的局部变量只在函数的执行期间可访问。说到这里又不得不说到执行环境,不太了解的朋友可能先去看我这篇文章:你不知道的执行上下文。其实当执行到let myHello = sayHello('abby');这段代码的时候按理会销毁掉 sayHello()的执行环境,但是这里却没有,原因是因为 sayHello() 返回的是一个函数,这个函数里面的 str 引用了外部的变量 str,如果销毁了就找不到了,因此 sayHello() 这个函数的执行环境会一直在内存中,所以会有闭包会增加内存开销balabala之类的。

其实说到这里,闭包就应该是说完了的,但是可能有好多东西看的都是一脸懵,那么我们就继续来讲几个例子吧!

举个例子

例子1:闭包并不是一定需要 return 某个函数

虽然常见的闭包都是 return 出来一个函数,但是闭包并不一定非要 return,return 出一个函数只是为了能在作用域范围之外访问一个变量,我们用另一种方式也能做到,比如:

let say;
function sayHello(name) {
  let str = `Hello,${name}`;
  say = function() {
    console.log(str);
  }
}
let myHello = sayHello('abby');
say(); // Hello,abby

在这个例子里面,say和声明它的词法环境其实也形成了一个闭包,在它的作用域里面持有了 sayHello 这个函数里面定义的 str 变量的引用,因此也能在 str 变量定义的作用域之外访问它。只要弄清楚闭包的本质即可。

但是在 JS 里面,最常用的形成闭包的方式便是在一个函数里面嵌套另一个函数,另一个函数持有父作用域里面定义的变量。

 例子2:同一个调用函数生成同一个闭包环境,在里面声明的所有函数同时具有这个环境里面自由变量的引用。

这句话说起来很绕,其实我给个很简单的例子就可以了。

let get, up, down
function setUp() {
  let number = 20
  get = function() {
    console.log(number);
  }
  up = function() {
    number += 3
  }
  down = function() {
    number -=2;
  }
}
setUp();
get(); // 20
up();
down();
get(); // 21

在这个例子里面,我们用setUp这个函数生成了一个闭包环境,在这个环境里面的三个函数共享了这个环境里面的 number 变量的引用,因此都可以对 number 进行操作。

例子3:每一个调用函数都会创建不同的闭包环境。

还是给一个很简单的例子。

function newClosure() {
  let array = [1, 2];
  return function(num) {
    array.push(num);
    console.log(`array:${array}`);
  }
}
let myClosure = newClosure();
let yourClosure = newClosure();
myClosure(3); // array:1,2,3
yourClosure(4); // array:1,2,4
myClosure(5); // array:1,2,3,5

上面这个例子里面, myClosure 和 yourClosure 的赋值语句,也就是 newClosure 这个函数被调用了两次,因此创建了两个不同的闭包环境,因此里面的变量是互不影响的。

例子4:在循环里面创建闭包

function newClosure() {
  for(var i = 0; i < 5; i++) {
    setTimeout(function() {
      console.log(i);
      })
  }
}
newClosure(); // 5个5

打印的结果大家也知道是5个5,因为 setTimeout 里面的函数保持对 i 的引用,在setTimeout的回调函数被执行的时候这个循环早已经执行完成,这里我之前在另一篇文章里面做过更深入的介绍:深入浅出Javascript事件循环机制(上)。

这里我要说的是我们如何才能得到我们想要的01234,在这里有两种做法。

一种是 创建一个新的闭包对象,这样每个闭包对象里面的变量就互不影响。例如下面的代码种每次 log(i)都会创建不同的闭包对象,所有的回调函数不会指向同一个环境。

function log(i) {
  return function() {
    console.log(i);
  }
}
function newClosure() {
  for(var i = 0; i < 5; i++) {
    setTimeout(log(i));
  }
}
newClosure(); // 0 1 2 3 4

另一种做法就是使用自执行函数,外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。写法如下:

function newClosure() {
  for(var i = 0; i < 5; i++) {
    (function(e) {
      setTimeout(function() {
        console.log(e);
      })
    })(i)  
  }
}
newClosure(); // 0 1 2 3 4

看看,写这么多,多累是不是,还是let省事,所以赶紧拥抱 es6 吧。。。

好了,这次是真的结束了,我所理解的闭包大概就是这样了,如果理解有所偏差,欢迎指出,谁当初不是从颗白菜做起的呢,学习前端的小伙伴们可以看看哦!

关于闭包:

js中作用域与函数闭包实例讲解

以上是JavaScript中的闭包的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
es6数组怎么去掉重复并且重新排序es6数组怎么去掉重复并且重新排序May 05, 2022 pm 07:08 PM

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

JavaScript的Symbol类型、隐藏属性及全局注册表详解JavaScript的Symbol类型、隐藏属性及全局注册表详解Jun 02, 2022 am 11:50 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

原来利用纯CSS也能实现文字轮播与图片轮播!原来利用纯CSS也能实现文字轮播与图片轮播!Jun 10, 2022 pm 01:00 PM

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

JavaScript对象的构造函数和new操作符(实例详解)JavaScript对象的构造函数和new操作符(实例详解)May 10, 2022 pm 06:16 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

javascript怎么移除元素点击事件javascript怎么移除元素点击事件Apr 11, 2022 pm 04:51 PM

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

JavaScript面向对象详细解析之属性描述符JavaScript面向对象详细解析之属性描述符May 27, 2022 pm 05:29 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

foreach是es6里的吗foreach是es6里的吗May 05, 2022 pm 05:59 PM

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。

整理总结JavaScript常见的BOM操作整理总结JavaScript常见的BOM操作Jun 01, 2022 am 11:43 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具