# JS中的作用域和闭包(一) ### JS中的作用域和闭包(一) JavaScript中的闭包和作用域是非常重要的概念,它们是函数式编程(Functional Program)的基础,并且在开发中经常被使用。 **作用域**: 作用域是JavaScript中定义变量的区域。JavaScript使用词法作用域,也就是静态作用域。这意味着函数的作用域是在函数定义时就确定下来了,而不是在函数调用时。 在JavaScript中,变量有全局作用域和局部作用域之分。在函数内部声明的变量,如果没有使用var关键字进行声明,则自动成为全局变量,即使在函数外部也可以访问。而在函数内部使用var关键字声明的变量则只能在函数内部访问,称为局部变量。 **闭包**: 闭包是指函数可以访问其外部作用域中的变量,即使函数在外部作用域之外被调用也可以访问这些变量。具体来说,当函数内部定义了一个函数,并且这个函数可以访问外部函数的变量时,就形成了闭包。 闭包可以用来创建私有变量,也就是外部作用域无法访问的变量。这种变量在函数外部是不可见的,但在函数内部可以被访问。闭包还可以用来保留函数执行时的状态,使得函数可以“记住”之前的状态,从而实现一些高级的功能,例如柯里化、函数记忆等。 需要注意的是,闭包可能会导致内存泄漏的问题,因为闭包中引用的外部变量在函数执行完毕后并不会被释放,除非手动解除对闭包的引用。所以在使用闭包时需要注意内存管理的问题。 总之,作用域和闭包是JavaScript中非常重要的概念,对于理解函数式编程和高级JavaScript开发都至关重要。 ### JS的词法作用域 词法作用域(lexical scoping)是指在代码编写阶段确定的变量作用域,与代码执行的上下文无关。在 JavaScript 中,函数作用域和块级作用域都是词法作用域的一种体现。 在词法作用域中,变量的作用域是由函数定义时嵌套的代码块决定的。也就是说,当一个函数被定义时,它的作用域链就被创建了,作用域链是由当前环境和外部环境的一系列变量对象组成的。 JavaScript 引擎在执行代码时,通过查找作用域链上的变量对象,找到对应的变量。当在一个函数内部访问一个变量时,如果在该函数内部找不到该变量,则会在函数定义时的作用域链上继续查找,直到找到该变量或者查找到全局作用域。 词法作用域的一个重要特性是,函数可以访问在它定义时可见的所有变量,而不仅仅是在它执行时可见的变量。这就是闭包的基础。 闭包是指一个函数可以访问它的外部作用域的变量,即使这个函数在外部作用域已经执行完毕,它仍然可以访问外部作用域中的变量。闭包的实现依赖于词法作用域,在一个函数内部定义另一个函数,内部函数可以访问外部函数的变量,形成了一个闭包。 由于闭包可以访问外部作用域中的变量,所以可以实现许多有趣的功能,比如函数柯里化、函数记忆化等。但同时,闭包也可能导致内存泄漏的问题,因为闭包会持有外部作用域中的变量,如果外部作用域中的变量占用了大量内存,就可能导致内存泄漏。 ```javascript function outer() { const message = 'Hello, '; function inner(name) { console.log(message + name); } return inner; } const sayHello = outer(); sayHello('John'); // 输出 "Hello, John" ``` 在这个例子中,outer函数定义了一个变量 message 和一个内部函数 inner。inner函数可以访问其外部函数 outer 的变量 message,因为 inner函数在 outer 函数的作用域内被定义,所以它可以访问到在 outer 中声明的变量。当 outer 函数被调用并返回 inner 函数时,inner 函数可以继续访问 outer 函数的变量 message,因为 inner 函数形成了一个闭包,可以访问其外部函数的作用域。最后,sayHello('John') 调用了 inner 函数,输出了 "Hello, John"。 ### JS的函数作用域 函数作用域也被称为局部作用域,是指在函数内部声明的变量只能在该函数内部访问,而在函数外部无法访问这些变量。函数作用域可以帮助我们避免变量污染和冲突。 以下是一个使用函数作用域的例子: ```javascript function foo() { var x = 1; // 在函数内部声明变量x console.log(x); // 输出1 } foo(); // 调用函数foo console.log(x); // 报错,x未定义 ``` 在上面的例子中,变量x是在函数foo内部声明的,因此只能在函数内部访问,函数外部无法访问。当我们在函数外部尝试访问变量x时,会得到一个“未定义”的错误。 函数作用域可以帮助我们封装代码并避免命名冲突。在实际开发中,我们通常会将一些常用的代码封装成函数,以便在需要的时候调用。这些函数中的变量只在函数内部可见,不会对外部造成影响。