JavaScript 中的作用域、闭包

作者 : 慕源网 本文共4025个字,预计阅读时间需要11分钟 发布时间: 2021-09-30 共249人阅读

JavaScript 中的作用域、闭包

介绍

在本文中,我们将学习ScopeClosure 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();

块作用域

让我们来看看块作用域的工作原理。

我们已经在 J​​avascript 中学习了使用“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 会解释上面的代码如下,

function printAge() {
    var age; // variable declarations are moved to the top of the code due to hoisting
    age = 19;
    console.log("My age is " + age);
}

在提升中最重要的一点是 JavaScript 只提升声明,而不是初始化。如果变量在使用后声明并初始化,则该值将是未定义的。

例如,

console.log(num); // Returns undefined
var num;
num = 6;

在上面的例子中,Javascript 将移动“var num;”这一行。由于变量提升,到上面的 console.log(num) 。更具体地说,Javascript 会将上述示例中的代码解释为:

var num;
console.log(num); // Returns undefined
num = 6;

因此,即使 num 被声明为一个变量,但尚未给 num 赋值,因此它返回“undefined”。

如果在使用后声明变量但事先初始化它,它将返回值,

num = 6;
console.log(num); // returns 6
var num;

这就是范围、关闭和提升的全部内容。


慕源网 » JavaScript 中的作用域、闭包

常见问题FAQ

程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!

发表评论

开通VIP 享更多特权,建议使用QQ登录