闭包(*)

前言

今天好好的研究了一下闭包,顺便写一下自己的个人理解。

什么是闭包?

闭包是指有权访问另一个函数作用域中的变量的函数。闭包有如下特点:

  • 函数嵌套函数;
  • 函数外部可以读取内部的参数和变量;
  • 参数和变量不会被垃圾回收机制回收;

常见的闭包创建方式:在一个函数内部创建另一个函数,详见下面的例子:

function Foo(){
	var typeName = 'coffee';
	return function(){
		console.log(typeName);
	}
}
var func = Foo();
func();   // "coffee"

上面的例子中,按道理,typeName是函数Foo的内部变量,在函数执行完毕后,就会被js的垃圾回收机制,回收释放,但为什么我们还可以访问到typeName呢?那就是因为我们使用了闭包,所以闭包的用途之一:可以读取到函数内部的变量。

闭包究竟是如何做到这一点的呢?

        我们能访问到某个变量,那是因为该变量包含在我们可访问的作用域链上。在上面的例子中,我们先定义了Foo()函数,然后又在全局的作用域中调用了它。当我们第一次调用Foo()函数时,会创建一个包含this、arguments以及其他命名参数的值来初始化函数的活动对象。然而在函数内部定义的匿名函数,当它返回时,他的作用域链会被初始化为包含Foo函数的活动变量和全局变量对象。更为重要的是,Foo函数执行完毕后,其执行环境的作用域链会被销毁,但它的活动对象不会被销毁,因为匿名函数仍然在引用这个活动变量,直到匿名函数被销毁后,Foo函数的活动对象才会被销毁。

副作用

        基于作用域链的机制,使用闭包也会存在副作用,即闭包只能取得包含函数中的任何变量的最后一个值(尤其是在使用循环语句的时候特别要注意)因为闭包保存的整个变量对象,而不是某个特殊时刻的变量。详见下面例子

function createFun(){
	var result = [];
	for(var i=0;i<10;i++){
		result[i] = function () {
			return i;
		}
	}
	return result;
}
var test = createFun(); 
test[0](); // 10

上面的例子,从表面上看每个函数都会返回自己的索引值,即位置0的函数返回0,位置1的函数返回1,但实际每个函数都返回10。因为每个函数的作用域链中都保存着createFun()函数的活动变量,所以他们引用的都是同一个变量 i 。在函数createFun()执行完毕后,变量 i 的值为10,此时每个函数都引用着保存变量 i 的同一个活动变量,所以每个函数内部 i 的值都是10。

我们可创建另一个立即执行的匿名函数,利用闭包的另一作用:将函数内部的变量始终保存在内存中。使得强制返回每一时刻的变量的值。

function createFun(){
	var result = [];
	for(var i=0;i<10;i++){
		result[i] = function (num) {
			return function(){
				return num;
			};
		}(i);
	}
	return result;
}
createFun()[0]();   // 0

在上面的版本中,我们没有直接把闭包赋值给 result 数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给result 数组。而匿名函数有一个参数num,也就是最终要返回的值,在调用匿名函数的时候,我们将传入变量 i,由于函数参数是按值传递的,所以会将变量 i 的当前值赋值给参数 num。而在这个匿名函数内部,有创建并返回了一个可以访问num的闭包。这样一来,result 数组中的每个函数都有自己 num 变量的副本,因此可以返回不同的数值。

当然因为闭包在使用的时候会访问函数的内部作用域,导致函数的内部变量被引用,从而无法被GC机制回收,占用的内存也比普通函数要多,所以闭包不在使用时,要及时释放,将引用内层函数对象的变量赋值为null。

 

 

以上只是我个人的粗浅理解,如有不对,欢迎指正。详细的大家看可参看MDN上的文档,下附链接地址:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

#### JavaScript核心概念解析与简答题考点 --- ##### **一、变量声明机制** **概念内容** - `var`存在变量提升(声明提升至作用域顶部)和函数级作用域 - `let/const`具有块级作用域,存在暂时性死区(TDZ) - `const`声明必须初始化且不可重新赋值(对象属性可变) **简答题考点** - 对比`var`、`let`、`const`的特性差异 - 解释变量提升现象及实际影响 - 分析块级作用域对循环计数器的影响 ```javascript for (var i=0; i<3; i++) { setTimeout(() => console.log(i), 0); // 输出3次3 } ``` --- ##### **二、数据类型与内存管理** **概念内容** - 基本类型(栈内存):`Number`、`String`、`Boolean`等 - 引用类型(堆内存):`Object`、`Array`、`Function` - 类型检测方法:`typeof`、`instanceof`、`Object.prototype.toString` **简答题考点** - 解释基本类型与引用类型的存储差异 - 深拷贝与浅拷贝的实现原理及区别 - 为什么`typeof null`返回`"object"`(历史遗留问题) --- ##### **三、作用域与闭包** **概念内容** - 词法作用域(静态作用域)决定变量访问权限 - 闭包是函数及其引用环境的组合,可访问外部函数变量 - 常见应用:模块化、数据封装、防抖节流 **简答题考点** - 解释闭包的内存泄漏风险及解决方案 - 举例说明闭包在实践中的典型应用场景 - 分析以下代码输出: ```javascript function createFunc() { let name = "test"; return function() { console.log(name) }; } const func = createFunc(); func(); // 输
最新发布
04-28
<think>好的,用户现在需要关于JavaScript核心概念的解析,包括变量声明机制、数据类型与内存管理、作用域与闭包,还要简答题考点和代码示例。首先,我得回顾之前提供的引用内容,看看哪些信息可以直接应用。 用户提到的引用[1]详细介绍了var的特性,比如函数作用域、变量提升,还有例子说明。引用[2]强调了var、let、const的区别和作用域的重要性,以及闭包。引用[3]提到了eval中的变量提升,这可能是一个考点,但实际应用中可能较少。引用[4]涉及数据类型和弱类型特性。用户上一次的问题是关于考试考点完成度的评估,现在转向具体的JS核心概念,可能是在准备考试或面试,需要结构化的知识点总结。 首先,我需要整理变量声明部分,对比var、let、const的作用域、提升、重复声明等差异。然后数据类型方面,基本类型和引用类型的区别,内存管理的栈和堆。作用域部分要区分全局、函数、块级,闭包的定义和应用场景。简答题考点需要列出常见问题,比如变量提升的现象、闭包的实际应用,以及如何避免内存泄漏。 代码示例方面,需要展示变量提升的例子,比如使用var在声明前访问得到undefined,而let会报错。闭包可以举计数器或私有变量实现的例子。数据类型转换的例子,比如==和===的区别,以及类型转换的陷阱。 还要注意用户之前的回答结构,分点明确,有示例代码和公式(如果有的话)。需要确保LaTeX格式正确,行内公式用$,独立公式用$$。例如,内存地址的引用可以用$ref1$这样的形式。 可能需要检查是否有遗漏的核心概念,比如事件循环或原型链,但根据用户当前的问题,主要集中在变量、数据类型、作用域和闭包,所以暂时不需要涉及其他部分。需要确保每个部分都有代码示例,帮助理解理论。 最后,简答题考点需要预测常见问题,比如“let和const的区别”、“闭包的应用场景”等,并给出简要回答要点。这样用户复习时可以直接参考这些考点。</think>### JavaScript核心概念解析与简答题考点 --- #### **一、变量声明机制** **1. `var`、`let`、`const`对比** | 特性 | `var` | `let` | `const` | |---------------|------------------------|------------------------|------------------------| | 作用域 | 函数作用域或全局作用域 | 块级作用域 | 块级作用域 | | 变量提升 | 提升并初始化为`undefined` | 提升但未初始化(TDZ) | 提升但未初始化(TDZ) | | 重复声明 | 允许 | 禁止 | 禁止 | | 重新赋值 | 允许 | 允许 | 禁止(基本类型) | **代码示例**: ```javascript // var 变量提升 console.log(a); // undefined var a = 10; // let 的暂时性死区(TDZ) console.log(b); // ReferenceError let b = 20; // const 不可重新赋值 const c = 30; c = 40; // TypeError ``` **简答考点**: - **变量提升现象**:`var`声明的变量在作用域顶部被初始化为`undefined`,而`let/const`存在暂时性死区(TDZ),访问会报错[^1]。 - **块级作用域**:`let/const`的块级作用域如何避免循环中的闭包陷阱(例如`for`循环中使用`var`的经典问题)。 --- #### **二、数据类型与内存管理** **1. 基本类型 vs 引用类型** | 类型 | 存储方式 | 示例 | |--------------|----------|----------------------| | **基本类型** | 栈内存 | `number`, `string`, `boolean`, `null`, `undefined`, `Symbol` | | **引用类型** | 堆内存 | `object`, `array`, `function` | **内存管理示例**: ```javascript let num1 = 10; let num2 = num1; // 值拷贝(栈内存) num2 = 20; console.log(num1); // 10 let arr1 = [1, 2]; let arr2 = arr1; // 引用拷贝(堆内存地址) arr2.push(3); console.log(arr1); // [1, 2, 3] ``` **简答考点**: - **浅拷贝与深拷贝**:如何通过`JSON.parse(JSON.stringify(obj))`或递归实现深拷贝。 - **类型检测**:`typeof`与`instanceof`的局限性(例如`typeof null`返回`"object"`)。 --- #### **三、作用域与闭包** **1. 作用域链与闭包原理** - **作用域类型**: - **全局作用域**:`window`对象(浏览器环境)。 - **函数作用域**:由函数定义形成独立作用域。 - **块级作用域**(ES6+):`{}`包裹的代码块(`if`、`for`等)。 **闭包示例**: ```javascript function createCounter() { let count = 0; return function() { count++; return count; }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2 ``` **简答考点**: - **闭包应用场景**:模块化开发、私有变量模拟、函数柯里化。 - **内存泄漏风险**:未释放的闭包可能导致内存占用过高(例如未销毁的DOM事件监听)。 --- #### **四、简答题考点总结** 1. **变量提升的实际影响**:为什么推荐使用`let/const`代替`var`? - 避免变量污染和不可预测的`undefined`值,块级作用域更符合直觉[^2]。 2. **基本类型与引用类型赋值差异**: - 基本类型通过值传递,引用类型通过地址传递,修改引用类型会影响所有引用该地址的变量[^4]。 3. **闭包与作用域链的关系**: - 闭包通过保留外部函数的作用域链,实现对自由变量的持久访问,即使外部函数已执行完毕。 --- #### **代码示例分析** **题目**:以下代码输出什么? ```javascript for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // 输出:3, 3, 3 ``` **解析**: - `var`声明的`i`为全局作用域,循环结束后`i=3`,所有定时器回调共享同一变量。 **修复方案**: ```javascript for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // 输出:0, 1, 2 ``` - `let`为块级作用域,每次循环生成独立变量副本。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值