3. 特性
2024/10/3大约 3 分钟
3. 特性
3.1. 闭包
- 闭包:一个函数对周围状态的引用捆绑在一起,内层函数可以访问外层函数的作用域,本质是内部函数+引用的外层函数变量
- 原理:内部函数使用了外部函数中部分变量的引用,产生独特的闭包作用域
closure。如果内部函数被返回给外界,这些变量的生命周期延长,闭包保留了外层作用域 - 作用:
创建模块,私有内部变量,使用内部函数修改和获取内部变量,避免全局污染,已可被类取代,但这种写法更方便
function counter() { let count = 0; return function () { return ++count; } } let c = counter(); console.log(c()); console.log(c());只暴露接口方法,隐藏细节实现,比如函数计数器等
创建函数工厂,创建带有固定行为的函数
function makeCounter(m) { return (x) => x**m }延迟执行,按需计算
function delay(a, b) { return () => a + b } let f = delay(1, 2) console.log(f())保证异步函数执行时也能获取到当前上下文
- 限制:
由于函数会保留,引用到的变量使用的堆空间不会被垃圾回收,如果闭包的函数很多可能造成内存泄漏
如果是异步执行,此时的全局变量会随之后的执行而改变,因此如果是循环执行,得到的变量是相同的,这是因为
js代码的执行是有优先级的,主程序优先级最高,会先执行,而异步任务会在之后执行,此时得到的var变量是同一个(var变量不会受到块级作用域限制),解决方法:- 使用let,let声明的变量在每轮次会重新绑定
- 使用函数包裹,让异步执行获取函数的变量
for (var i = 0; i < 10; i++) { // 输出都是 10 setTimeout(()=>console.log(i), 100) // 输出 0 1 2 3 4 5 6 7 8 9 (function(j){ setTimeout(()=>console.log(j), 100) })(i) } for (let i = 0; i < 10; i++) { // 输出 0 1 2 3 4 5 6 7 8 9 setTimeout(()=>console.log(i), 100) }
3.2. 变量提升
在
js中有奇怪的现象,下列声明会提升到当前作用域的最前面var声明的变量(包括函数),但初始化不会提升,得到undefined- 直接使用
function name() {}进行声明的函数会提升,在开头就可以使用 var变量声明提升到在声明函数之前
这些情况会提升,在
js词法环境中会被创建,但存在暂时性死区TDZ,暂时无法访问ReferenceError: Cannot access ... before initializationclass类声明let和const声明的变量import导入ES模块- 函数的变量列表未按列表顺序创建并使用默认值,比如
foo(x = y, y = 2)
匿名函数,会根据指定变量的声明规则决定在之前是
TDZ还是undefined变量提升是由于
js引擎解析文件时,会预先在词法环境创建变量,也就是变量提升- 早期时仅有
var和函数,当时希望这样能够方便灵活地调用这些变量和函数,并没有考虑过提前使用可能会造成声明不清晰和混乱 - 而在之后的
es6语法新增的let和const都明确声明初始化,减少变量提升带来的混乱
- 早期时仅有
