在 JavaScript 编程中,理解闭包和作用域是至关重要的。这两个概念在不同的执行环境中,如浏览器和 Node.js,可能会有所不同。本文将深入探讨这两个概念,并解释它们在浏览器和 Node.js 中的不同之处。
闭包
根据 MDN 的定义,闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
说人话就是函数内部使用了外部的变量就会产生闭包
请看接下来的例子思考
为什么以上段代码在nodejs的环境下产生了闭包?
为什么同样的代码在浏览器环境下没有产生闭包?
以上代码是浏览器正常产生的闭包
作用域
我们首先了解一下什么是作用域,在 JavaScript 中,作用域是一个非常重要的概念,它决定了变量、函数和对象的可访问性。作用域可以是全局的,也可以是局部的,还可以是块级的。根据 MDN 的定义,JavaScript 有两种类型的作用域:全局作用域和局部作用域。一个变量如果在函数外部声明,或者没有使用 var
、let
或 const
关键字就赋值,那么它就是全局变量,有全局作用域。如果一个变量在函数内部用 var
、let
或 const
关键字声明,那么它就是局部变量,有局部作用域。
浏览器中的作用域
在浏览器中,全局作用域是 window
对象。这意味着在全局作用域中定义的所有变量和函数都会成为 window
对象的属性和方法。在浏览器中,作用域链的顺序是:局部作用域 -> 全局作用域。
javascript
console.log(this); // window
console.log(this === window); // true
var name1 = "global name1";
const name2 = "global name2";
console.log(window.name1); // "global name1"
console.log(window.name2); // undefined
function testFunc() {
console.log(this); // window
}
testFunc();
new Function('console.log(this)')(); // window
在这段代码中,首先我们打印出 this
,在全局作用域中,this
指向 window
对象。然后我们声明了两个变量 name1
和 name2
,其中 name1
使用 var
关键字声明,name2
使用 const
关键字声明。在浏览器中,使用 var
关键字在全局作用域中声明的变量会成为 window
对象的属性,而使用 const
或 let
关键字声明的则不会。然后我们定义了一个函数 testFunc
,在这个函数内部,this
也指向 window
对象。最后,我们使用 new Function
创建了一个新的函数并立即执行,这个新函数的 this
也指向 window
对象。
Node.js 中的作用域
然而,在 Node.js 中,情况就有些不同了。Node.js 不仅有全局作用域,还有模块作用域。每个 Node.js 文件(或称为模块)都有自己的作用域,这就意味着在一个文件中定义的变量和函数在其他文件中是不可见的,除非它们被明确地导出和引入。在 Node.js 中,作用域链的顺序是:局部作用域 -> 模块作用域 -> 全局作用域。
javascript
console.log(this); // {}
function testFunc() {
console.log(this); // global
}
testFunc();
(function() {
"use strict";
console.log(this); // undefined
})();
var name1 = "global name1";
console.log(this.name1); // undefined
console.log(this === module.exports); // true
new Function('console.log(this)')(); // global
在 Node.js 中,this
在全局作用域中不再指向全局对象,而是指向当前模块的 exports
对象(如果你没有导出任何内容,那么它就是一个空对象)。在函数内部,this
指向全局对象。在严格模式下的函数内部,this
是 undefined
。在 Node.js 中,使用 var
关键字在全局作用域中声明的变量不会成为全局对象的属性。最后,我们使用 new Function
创建了一个新的函数并立即执行,这个新函数的 this
指向全局对象。
结论
在全局作用域中,虽然函数可能引用了外部变量,但这并不会产生我们通常理解的闭包,因为全局变量对所有代码都是可见的,没有被封装或隐藏起来。而在函数或模块作用域中,如果一个函数引用了外部变量,那么这个函数就形成了一个闭包,因为它封装了对外部变量的访问,使得这个变量即使在函数执行完毕后,仍然不会被垃圾回收机制回收。希望这篇文章能帮助你更好地理解这些概念