作用域(Scope)
作用域是指程序中定义变量的区域,它决定了变量的可见性和生命周期.JavaScript中有以下几种作用域.
全局作用域
const globalVariable = '111'
function funA() {
console.log(globalVariable) // '111'
}
局部作用域
function funB() {
const localVariable = '222'
console.log(localVariable) // 222
}
console.log(localVariable) // Uncaught ReferenceError: localVariable is not defined
块级作用域(ES6引入)
if(true) {
let blockVariable = '333'
const constVariable = '444'
console.log(blockVariable, constVariable) // 333 444
}
console.log(blockVariable) // Uncaught ReferenceError: blockVariable is not defined
console.log(constVariable) // Uncaught ReferenceError: constVariable is not defined
作用域链
当访问一个变量的时候, JavaScript 引擎会从当前作用域开始查找. 如果没有找到,则会向上级作用域继续查找,直到全局作用域.
let a = 1
function outer() {
let b = 2
function inner() {
let c = 3
console.log(a + b + c)
}
inner()
}
outer() // 6
闭包(Closure)
闭包是指有权访问另一个函数作用域中的变量的函数.简单的说就是函数嵌套函数,内部函数可以访问外部函数的变量.
基本概念
function outer() {
let outVar = 1
function inner() {
return outVar
}
return inner
}
const func = outer()
func() // 1
闭包的特点
- 函数嵌套函数
- 内部函数可以引用外部函数的参数和变量
- 外部函数的变量不会被垃圾回收机制回收
闭包的应用场景
(1).创建私有变量
function counter() {
var count = 0
return {
increment: function() {
count++
return count
},
decrement: function() {
count--
return count
}
}
}
const myCounter = counter()
console.log(myCounter.increment()) // 1
console.log(myCounter.increment()) // 2
console.log(myCounter.decrement()) // 1
const copy = counter()
console.log(copy.increment()) // 1
console.log(copy.decrement()) // 0
模块化开发
const module = (function() {
const privateVariable = '私有变量'
let count = 1
function privateMethod() {
count++
console.log(privateVariable)
console.log(count)
}
return {
publicMethod: function() {
privateMethod()
}
}
})()
module.publicMethod() // 私有变量 2
module.publicMethod() // 私有变量 3
module.publicMethod() // 私有变量 4
module.publicMethod() // 私有变量 5
函数柯里化
function multiply(a) {
return function (b) {
return a * b
}
}
// 等价于
const mu = (a) => ((b) => a * b)
const double = mu(2)
console.log(double(5))
闭包的注意事项
(1)内存泄漏
闭包会导致外部函数的变量无法被垃圾回收,过度使用可能会导致内存泄露.
解决办法:
function outer() {
let largeData = new Array(10000).fill(0).map((_,idx) => idx + 1)
function inner() {
console.log(largeData)
}
return function() {
inner()
largeData = null // 解除引用
}
}
const func = outer()
func() // 会正常输出 而且读取完后会清空
func() // null
(2).循环中的闭包问题
for(var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
}, 10)
}
// 会输出10个10
// solution 1 : IIFE(Immediately Invoked Functions Expressions)
for(var i = 0; i < 10; i++) {
(function(j){
setTimeout(function() {
console.log(j)
}, 10)
})(i)
} // 0 1 2 3 4 5 6 7 8 9
// solution 2
for(let i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
}, 10)
} // 0 1 2 3 4 5 6 7 8 9
ES6中的块级作用域
ES6引入了 let 和 const, 提供了块级作用域:
{
let a = 1
const b = 2
var c = 3
}
console.log(a) // Uncaught ReferenceError: a is not defined
console.log(b) // Uncaught ReferenceError: b is not defined
console.log(c) // 3
暂时性死区(TDZ)
在块级作用域中, 使用 let / const 定义的变量在声明前不可访问.
console.log(a) // 1
var a = 1
console.log(b) // Uncaught ReferenceError: b is not defined
let b = 2
循环中的块级作用域
for(let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i) // 0, 1, 2
}, 100)
}
总结
- 作用域决定了变量的可见性和生命周期
- 闭包是JavaScript强大的特性之一, 可以实现私有变量,模块化等
- 合理使用闭包,注意内存管理
- ES6中的
let/const提供了块级作用域,解决了var的一些问题