前端作用域&闭包

195 阅读3分钟

作用域(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引入了 letconst, 提供了块级作用域:

{
    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)
}

总结

  1. 作用域决定了变量的可见性和生命周期
  2. 闭包是JavaScript强大的特性之一, 可以实现私有变量,模块化等
  3. 合理使用闭包,注意内存管理
  4. ES6中的 let / const 提供了块级作用域,解决了 var 的一些问题