JavaScript 中的作用域、闭包
介绍
在本文中,我们将学习Scope、Closure 和Hoisting。
当我们构建一个实时应用程序时,我们将使用很多函数和很多变量,而这些变量将被多个函数使用。如果一个变量值将被另一个函数覆盖怎么办。
在 C# 等其他语言中,java 作用域是特定于块的,但在 JavaScript 中作用域是特定于函数的,函数内部定义的变量在局部作用域内,而在函数外部定义的变量在全局作用域内。
在 Javascript 语言中,有两种类型的作用域。
全局范围
让我们了解一下全局作用域。
在所有函数之外声明的任何变量,独立地是全局作用域的一部分。
var x = "cinnabonsticks";
function dessert() {
console.log(x); // prints "cinnabonsticks"
}
dessert();
本地范围
任何在函数内部独立声明的变量都有自己的作用域,并且不受作为全局作用域一部分的变量值的影响。同名变量可能存在于函数内部和外部,请始终记住,存在于函数内部的该变量的作用域是独立的,不受外部该变量值的影响。
var x = "cinnabonsticks";
function dessert() {
var x = "cinnabonchips";
console.log(x); // prints "cinnabonchips"
}
dessert();
块作用域
让我们来看看块作用域的工作原理。
我们已经在 Javascript 中学习了使用“var”关键字创建变量。还有另一种使用关键字“let”创建变量的方法。
当我们使用 let 关键字创建一个变量时,该语句声明了一个块作用域局部变量。“let”关键字使我们能够为给定的作用域/函数/块只为变量声明和赋值。分配给它的值不再能在块外访问。
“let”关键字已在较新版本的 JavaScript 中引入,最终在较旧版本中不存在。
var x = "cinnabonsticks";
function dessert() {
if (true) {
let x = "cinnabonchips";
console.log(x); // prints "cinnabonchips" because in this scope, x has value "doughnut"
}
console.log(x); // prints "cinnabonsticks" because "let" keyword uses block scoping. x assumes its original value which is "cinnabonsticks"
}
dessert();
处理作用域时要记住的最重要的事情是使用 var 和 let 关键字,每当我们使用 let 关键字时,作用域就会变成一个块,并且声明的那个变量的值只在该块内有效。
我们来看例子,
var a = 1;
function seven() {
if (true) {
var a = 7;
}
console.log(a);
}
当你调用函数 Seven() 时,你会发现它打印了一个 7 的值。在解析一个变量时,JavaScript 从最里面的作用域开始向外搜索。因此,当遇到 console.log(a) 时,编译器会从最内层的作用域开始向外搜索。在最里面的作用域(强制的“if”块)中,a 的值为 7。因此,7 被打印在控制台上。
让我们使用 let 关键字来尝试一下,
var a = 1;
function seven() {
if (true) {
let a = 7;
}
console.log(a);
}
正如我们了解到的,关键字“let”始终具有块作用域,这意味着在“if”块之外,“let a = 7”语句不再有效。因此,当您在此块外打印“a”的值时,它会打印 1!
全局和本地范围
让我们一起了解 Global 和 Local Scope。看一下例子,
var x = 1;
function foo() {
console.log(x);
var x = 2;
console.log(x);
};
foo();
想想输出。它是 1 和 2,还是未定义和 2?
这将打印输出 undefined 和 2 而不是 1 和 2,因为 JavaScript 总是将用 var 声明的变量声明移动到作用域的顶部,让我们创建一个解释器如何执行上述代码的代码。
var x = 1;
function() {
var x;
console.log(x);
x = 2;
console.log(x);
};
上面的例子展示了解释器如何在 JavaScript 中执行它。
JavaScript 如何总是将使用 var 声明的变量声明移动到作用域的顶部。这个概念叫做 Hoisting。
关闭
闭包是 JavaScript 中最重要的话题。
让我们试着理解什么是闭包以及它们是如何工作的。
看一段代码片段,
function greet(fname) {
var greeting = "Welcome! " + fname;
var message = function() {
console.log(greeting);
}
return message;
}
var sayHello = greet("Gurpreet");
sayHello();
在上述函数“greet(fname)”内的代码片段中,变量消息存储了一个打印变量“greeting”的函数。请注意,在整个函数“greet”中,变量“greeting”具有局部作用域。这意味着理想情况下,变量“greeting”不应该存在于函数greet() 之外。
现在,看看“greet”中的最后一个 return 语句。它返回“message”,它是一个变量,其中存储了一个匿名函数。
在函数之外,变量“sayHello”保存了函数greet(“Gurpreet”)返回的值。现在,您会认为一旦在 greet(fname) 函数中执行了“return message”语句,就不能访问局部变量“greeting”。
在调用 sayHello() 函数时,它打印出“Welcome! Gurpreet”并且表现得好像局部变量“greeting = Welcome! Gurpreet”仍然存在于函数“greet”之外!这是因为我们称之为闭包的概念。
上面的代码因为匿名函数 function() { console.log(greeting); 而有一个闭包。} 在另一个函数中声明,greet(fname)。
在 JavaScript 中,每当你在一个已经存在的函数中声明另一个函数时,你就是在创建一个闭包。一旦你在另一个函数中声明了一个函数,那么即使在内部函数返回之后,外部函数的局部变量和参数仍然可以被内部函数访问。这可以在这里看到,因为我们在从 greet(fname) 返回之后调用了函数 sayHello()。sayHello() 实际上引用了变量“greeting”,它是函数greet(fname) 的局部变量。
让我们看一个简单的例子,看下面的代码,
var x = "cinnabonStick";
function dessert() {
var x = "cinnabonchips";
function print() {
console.log(x); // prints "cinnabonchips"
};
print();
};
dessert();
这也是另一个简单的关闭案例。当我们在最后一行调用“dessert()”函数时,变量 x 的原始值是“cinnabonsticks”。现在我们看到另一个函数“print()”嵌套在“dessert()”函数中。
在“dessert()”函数内部,变量x的值被修改为“cinnabonchips”,即使在“print()”函数在“dessert()”函数内部结束/返回之后,它仍然记得x 是“dessert()”函数中的“cinnabonchips”。
具体来说,print() 函数可以引用desert() 函数中声明的“x”并记住它的值,因为
- 闭包的一般思想是,只要您在另一个函数中使用一个函数,就会创建闭包。此外,JavaScript 中的内部函数 Closure 保留其局部变量的状态以及它在父函数中引用的变量的副本。
- 在闭包时,内部函数(或本例中的 print())可以引用并记住其父函数(或本例中的 Desert())中声明的变量和参数的值
- 因此,这就是为什么我们可以调用 console.log(x),并将值“cinnabonchips”打印到控制台
Hoisting
在这个过程中,变量和函数声明实际上被移到了代码的顶部。在 JavaScript 中,将在处理任何其他代码之前处理用“var”声明的变量。
function printAge() {
age = 19;
console.log("My age is " + age);
var age;
}
即使变量“age”的声明出现在使用变量“age”之后,也能正常工作。在变量提升中,JavaScript 会解释上面的代码如下,
常见问题FAQ
- 程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
- 请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!