JS代码
function fn() { var num = 3; return function() { var n = 0; n++; num++; console.log('n=' + n); console.log('num=' + num); }}var fn1 = fn();fn1(); // 1 4console.log("------");fn1(); // 1 5
变量 num 在函数执行完毕后没有被销毁,当我第二次调用 fun1
的时候输出 num=5,而变量 n 每次执行时都会创建一个新的,导致两次调用输出都是 1,这是为什么?同样都是函数内部的变量
变量 num 在执行过程中是如何被保留下来的?
变量 num 和变量 n 之所以表现不同,关键在于他们所处的作用域和闭包的行为。
变量 num: 它定义在函数 fn 的作用域内。当你调用 fn() 时,它创建了一个闭包,这个闭包包含了对 num 的引用。根据闭包的工作原理,即使 fn 的执行上下文已经结束,num 依然被闭包内的匿名函数引用,因此它不会被垃圾回收机制回收。这就是为什么 num 的值在闭包函数(fn1)的连续调用之间被保留的原因。
变量 n: 它定义在闭包函数(匿名函数)的作用域内。每次调用 fn1 时,匿名函数都会创建一个新的执行上下文,其中包括一个新的 n 变量。因为 n 没有被外部的任何函数或闭包引用,它的生命周期仅限于闭包函数的单次调用。所以每次调用 fn1 的时候,n 都会重置为 0,然后增加到 1,然后在函数执行完毕后被销毁。
总结来说,num 能够在函数调用之间保留下来是因为它是通过闭包保持在内存中的,而 n 在每次调用结束后都会消失,因为它仅存在于函数调用的局部作用域中。
以上来自 GPT4
看你21年就开始写js代码了,24年在这钻牛角尖是吧?
fn函数根据你写的逻辑
你把fn函数想象成一个妈妈,执行它可以生很多小孩,
生小孩的时候这个小孩身高是3(var num = 3)
(调用一次)
都会从0(var n = 0)
开始存钱,每次存1块钱(n++)
(调用一次)
这个小孩长高1(num++)
可以看看MDN权威的文档,我觉得讲的很清楚:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
定义上来说闭包是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。闭包函数就是定义在一个函数里面的函数,这个函数(内部函数)可以访问到外部函数的作用域。回到题目中:
function fn() { var num = 3; return function() { //这个匿名函数就是闭包函数,它可以访问到外部函数fn()的作用域 var n = 0; n++; num++; console.log('n=' + n); console.log('num=' + num); }}
一个函数执行完后,垃圾回收器会回收已经没有被引用的对象实例
我们注意到
var fn1 = fn();
这个fn1是执行外部函数fn()时创建的匿名函数(内部函数)实例的引用,那么当外部函数fn()执行完后,这个引用的匿名函数(内部函数)对象实例并不会被回收,所以它的状态就一直被保持了下来,下次再调用fn()的时候引用指向的还是同一个匿名函数(内部函数)对象实例,对于外部函数里定义的
var num = 3;
被这个匿名函数引用了,所以同样也不会被回收,下次再执行fn()时是不会重新创建num这个变量的,还是对同一个num进行操作
对于内部的匿名函数里定义的
var n = 0;
在内部的匿名函数执行完后就被回收了,因为没有人对它持有引用,所以下次再执行fn()时会重新创建一个新的n并且初始化为0
所以闭包是有内存泄露的风险的,如果一个闭包函数引用外部函数的对象引用,而它自己本身一直被引用没有释放,那么外部函数的对象实例就不会被回收。
当一个函数可以通过某种方式访问其它函数内部变量时,闭包就形成了
你每次执行fn1
,里面的var n = 0
,都把n
初始化为0
了。
fn
其实也遵循这个规律,函数都这样。
但是,你的fn
在复赋值给fn1
之后,就没有再执行了。所以里面的num
没有被重新赋值,又由于闭包的作用让它不会被销毁(语言引擎特性),使得你在每次调用fn1
都可以访问到它累积起来的值。
回到开头的话,你的fun1
就有能力访问fn
内部的num
。也就形成了闭包。闭包的特性就使得num
不会被销毁(因为外面有函数可能会访问num
,不销毁才合理)。
function fn() { var num = 3; // 这里加一个console.log console.log('outer num', num); return function() { var n = 0; n++; num++; console.log('n=' + n); console.log('num=' + num); }}// 连续调用两次看看,num的表现和n是一样的fn()fn()
附一下阮一峰的闭包文章(阮一峰,我的hero!)
学习Javascript闭包(Closure)
在 JavaScript 中,函数内部定义的变量具有闭包。闭包是一种函数,它能够记住并访问其词法作用域,即使该函数在其词法作用域之外执行。这意味着,当函数返回另一个函数时,外部函数的作用域被封装在内部函数中,形成一个闭包。
在你的例子中,fn
函数返回了一个匿名函数,这个匿名函数创建了自己的作用域并引用了 num
变量。即使 fn
函数已经执行完毕,这个匿名函数仍然保持了对 num
变量的引用,因此 num
变量不会被垃圾回收。
每次你调用 fn1()
时,实际上是调用了匿名函数。这个匿名函数创建了自己的作用域,并且有自己的 n
变量。每次调用时,n
都会被初始化为 0,然后自增 1,同时 num
也自增 1。由于 n
是在匿名函数的作用域内定义的,所以每次调用时都会创建一个新的 n
变量。而 num
变量由于闭包的作用被保留了下来。
所以,第一次调用 fn1()
时,输出的是 n=1
和 num=4
。第二次调用时,由于 num
已经被闭包保留下来并自增了 1,所以输出的是 n=1
和 num=5
。
本文向大家介绍javascript闭包的理解,包括了javascript闭包的理解的使用技巧和注意事项,需要的朋友参考一下 1、首先我们要知道变量作用域链 变量的作用域分两种:全局变量和局部变量。没有定义到任何函数中的变量为全局变量,在函数中定义的变量为局部变量,注意在函数内部定义变量时一定要使用var关键字,不带var关键字的变量为全局变量。 javascript中每一段代码都有与之关联的作用域
本文向大家介绍彻底理解js面向对象之继承,包括了彻底理解js面向对象之继承的使用技巧和注意事项,需要的朋友参考一下 说道这个继承,了解object-oriented的朋友都知道,大多oo语言都有两种,一种是接口继承(只继承方法签名);一种是实现继承(继承实际的方法) 奈何js中没有签名,因而只有实现继承,而且靠的是原型链实现的。下面正式的说一说js中继承那点事儿 1、原型链 原型链:实现继承的主要
本文向大家介绍详解js闭包,包括了详解js闭包的使用技巧和注意事项,需要的朋友参考一下 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一
本文向大家介绍谈谈你对闭包的理解?相关面试题,主要包含被问及谈谈你对闭包的理解?时的应答技巧和注意事项,需要的朋友参考一下 说明: bar在foo函数的代码块中定义。我们称bar是foo的内部函数。 在bar的局部作用域中可以直接访问foo局部作用域中定义的m、n变量。 简单的说,这种内部函数可以使用外部函数变量的行为,就叫闭包。 闭包的意义与应用
本文向大家介绍Javascript的闭包详解,包括了Javascript的闭包详解的使用技巧和注意事项,需要的朋友参考一下 前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点 ECMAScript语言规范来使读
本文向大家介绍说一下你对闭包的理解?相关面试题,主要包含被问及说一下你对闭包的理解?时的应答技巧和注意事项,需要的朋友参考一下 参考回答: 一句话可以概括:闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。