Javascript的作用域、作用域链以及闭包详解

yizhihongxing

Javascript的作用域、作用域链以及闭包详解

什么是作用域?

作用域是指代码中定义变量的区域,也是访问这些变量的规则。在Javascript中常见的作用域有全局作用域和函数作用域。

全局作用域

全局作用域是指定义在最外层的变量,在整个程序执行过程中都可以访问到。例如下面的代码:

var name = "Lucy";

function getName() {
    console.log(name);
}

getName();       // 输出 "Lucy"

name 变量是在全局作用域中定义的。在 getName() 函数中,我们可以直接访问到 name 变量,并将其输出。

函数作用域

函数作用域是指在函数内部定义的变量,只能在函数内部被访问。例如下面的代码:

function sayHello() {
    var message = "Hello, world!";
    console.log(message);
}

sayHello();       // 输出 "Hello, world!"

console.log(message);   // 报错,message未定义

message 变量是在 sayHello() 函数内部定义的。在函数外部访问 message 变量会报错,因为它只存在于函数作用域中。

作用域链

当Javascript代码运行时,变量的查找会按照作用域链的顺序进行。作用域链是多个作用域的集合,用于查找变量。

作用域链的顺序

在Javascript中,当代码运行到某个作用域时,会按照以下顺序查找变量:

  1. 查找当前作用域下是否有该变量。如果当前作用域中没有该变量则继续往外查找。
  2. 查找上一层作用域下是否有该变量。如果上一层作用域中没有该变量则继续往外查找。
  3. 直到找到全局作用域,如果全局作用域中还没有该变量,则返回 undefined。

例如下面的代码:

var name = "Lucy";

function getName() {
    var message = "My name is " + name;
    console.log(message);
}

getName();       // 输出 "My name is Lucy"

getName() 函数中,我们访问了全局作用域中的 name 变量。当查找 name 变量时,会首先在函数作用域中查找,发现没有该变量,则往上一层作用域(即全局作用域)查找,最终找到了 name 变量。

作用域链的生命周期

当函数执行完成时,其作用域链会被销毁,其中的变量也会随之被销毁。例如下面的代码:

function sayHello() {
    var name = "Lucy";
    console.log("Hello, " + name + "!");
}

sayHello();       // 输出 "Hello, Lucy!"
console.log(name);   // 报错,name未定义

sayHello() 函数中,我们定义了 name 变量,当函数执行完成后,该变量会被销毁。在函数外部访问 name 变量会报错。

什么是闭包?

闭包是指在函数内部创建一个新的作用域,使得函数内部的变量可以被函数外部访问。通过闭包,我们可以实现变量的隐藏和封装,同时增强函数的灵活性。

创建闭包

在Javascript中,创建闭包有两种方式:

1. 返回函数

通过在函数内部定义一个新的函数,并返回该函数,从而使用闭包。例如下面的代码:

function createCounter() {
    var count = 0;

    function counter() {
        count++;
        console.log(count);
    }

    return counter;
}

var counterA = createCounter();   // counterA 是一个闭包
counterA();       // 输出 1
counterA();       // 输出 2

var counterB = createCounter();   // counterB 是另一个闭包
counterB();       // 输出 1

createCounter() 函数中,我们定义了 count 变量和 counter() 函数,并返回了 counter() 函数。当我们调用 createCounter() 函数时,实际上是在创建一个闭包。闭包中包含了 count 变量及其对应的值,以及 counter() 函数的引用。

2. 函数内声明函数

通过在函数内部声明一个新的函数,从而使用闭包。例如下面的代码:

function sayHello(name) {
    function hello() {
        console.log("Hello, " + name + "!");
    }

    hello();
}

sayHello("Lucy");     // 输出 "Hello, Lucy!"

sayHello() 函数中,我们定义了 hello() 函数,并在函数内部执行了 hello() 函数。由于 hello() 函数是在 sayHello() 函数内部定义的,因此可以访问 sayHello() 函数中的 name 参数。

示例说明

示例1:作用域链查询变量

var name = "Lucy";

function hehe() {
    var name = "Jack";

    function haha() {
        console.log(name);
    }

    haha();
}

hehe();       // 输出 "Jack"

hehe() 函数中,定义了 name 变量并赋值 "Jack"。在 haha() 函数中,访问了 name 变量。由于 name 变量在函数作用域中已经定义,因此查找时会先在函数作用域中查找,找到了name变量的值"Jack"。

示例2:闭包应用

function createFullName(firstName) {
    return function(lastName) {
        console.log(firstName + " " + lastName);
    };
}

var fullNameA = createFullName("Lucy");   // fullNameA 是一个闭包,firstName = "Lucy"
fullNameA("Smith");       // 输出 "Lucy Smith"
fullNameA("Brown");       // 输出 "Lucy Brown"

var fullNameB = createFullName("Jack");   // fullNameB 是另一个闭包,firstName = "Jack"
fullNameB("Johnson");     // 输出 "Jack Johnson"

createFullName() 函数中,我们返回了一个匿名函数,并使用了参数 firstName。当我们调用 createFullName() 函数时,实际上是在创建一个闭包。闭包中包含了 firstName 参数及其对应的值,以及匿名函数的引用。

当我们调用 fullNameA("Smith") 时,实际上是在执行闭包中的匿名函数,并传入了参数 lastName = "Smith"。在匿名函数中,会将 firstNamelastName 拼接起来,并输出结果。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Javascript的作用域、作用域链以及闭包详解 - Python技术站

(0)
上一篇 2023年6月7日
下一篇 2023年6月7日

相关文章

  • 使用C# 判断给定大数是否为质数的详解

    使用C# 判断给定大数是否为质数的详解 判断一个大数是否为质数是一个常见的问题。早期的解决方式是通过试除法,即将该数不断除以比它小的所有正整数,如果在这些正整数中存在约数,那么这个数就不是质数。 但是,这种试除法效率极低,在判断大数时会消耗大量时间和资源。因此,我们需要更快速且高效的方式来判断大数是否为质数。 下面我们将介绍一种使用“Miller-Rabin…

    C# 2023年6月7日
    00
  • 关于ASP.NET中TreeView用法的一个小例子

    我来详细讲解一下“关于ASP.NET中TreeView用法的一个小例子”的完整攻略。 标题 首先,我们需要明确标题。根据要求,这个攻略的标题应该是“关于ASP.NET中TreeView用法的一个小例子”,因此我们可以将其作为一级标题: # 关于ASP.NET中TreeView用法的一个小例子 描述 接下来,我们需要对这个小例子的背景和目标进行描述。因为我们需…

    C# 2023年5月31日
    00
  • NET页面导出Excel实例代码

    首先,要实现.NET页面导出Excel功能,需要用到以下两个类库: NPOI:用于操作Excel文档的类库。 Microsoft.AspNet.WebApi.Core:用于处理Web API相关请求与响应的类库。 下面是一个.NET页面导出Excel的典型实现步骤: 步骤一:创建Web API控制器 在.NET项目中创建一个Web API控制器,用于处理导出…

    C# 2023年5月31日
    00
  • C#可以减少或不使用switch有什么方法

    使用C#语言时,我们常常需要使用switch语句来对某一个变量的不同取值进行判断并执行对应的代码。但是使用switch语句存在一些局限性,如: switch语句仅支持整数、字符和枚举类型的判断,无法使用字符串等其他类型的数据进行判断。 switch语句存在层次嵌套时,可能会影响代码的可读性和可维护性。 为了避免使用switch语句带来的这些问题,C#提供了一…

    C# 2023年6月7日
    00
  • asp.net Execl的添加,更新操作实现代码

    接下来我会详细讲解如何在ASP.NET中进行Excel的添加和更新操作。 准备工作 在进行Excel操作之前,我们需要安装EPPlus包,它是一个免费的开源库,可以让我们在ASP.NET中轻松地操作Excel文件。在Visual Studio中,可以通过NuGet包管理器安装EPPlus。 添加Excel文件 要向Excel文件中添加数据,我们需要使用EPP…

    C# 2023年5月31日
    00
  • C#读取word中表格数据的方法实现

    C#读取word中表格数据的方法实现 在C#中读取Word中表格数据,可以通过Microsoft.Office.Interop.Word库中提供的API来实现。下面是具体的实现方法。 步骤一:引用Microsoft.Office.Interop.Word库 在C#项目中添加Microsoft.Office.Interop.Word库的引用,引用方法如下: 在…

    C# 2023年5月15日
    00
  • C++ 和 C# 中的 lambda的方法技巧

    C++ 和 C# 中的 Lambda 表达式 什么是 Lambda 表达式 Lambda 表达式是从匿名函数演化而来的一种语法。用来简化函数对象的创建。Lambda 表达式可以作为参数传递给函数,也可以被定义为变量和成员变量。Lambda 表达式可以是任何有返回值的函数。 C++ 使用 Lambda 表达式 C++11 开始支持 Lambda 表达式,通过使…

    C# 2023年6月6日
    00
  • C# 设计模式系列教程-单例模式

    对于单例模式的详细讲解可以分成以下几个部分: 什么是单例模式? 单例模式是一种创建型的设计模式,用于保证某一个类仅有一个实例,并提供全局的访问点。 通常情况下,我们可以通过类创建多个对象,但是有时候我们需要只创建一个对象,比如全局的配置、日志等。这时候单例模式就派上用场了。 如何实现单例模式? 实现单例模式有多种方式,以下是其中比较常用的几种: 饿汉式单例模…

    C# 2023年5月31日
    00
合作推广
合作推广
分享本页
返回顶部