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

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#:WebBrowser中伪造referer

    要理解流量统计器如何鉴别C#: WebBrowser中伪造referer,需要先了解什么是referer。 Referer通常指HTTP请求头中的Referer(即引用),它是由HTTP客户端(例如网页浏览器)发送的一种HTTP Headers。它表示了客户端是从哪个URL提交请求的。这个Header让Web浏览器和服务器能够更好地进行访问分析、日志记录、反…

    C# 2023年6月6日
    00
  • asp.net 无重复随机数代码

    针对“asp.net 无重复随机数代码”的问题,下面我介绍一下这个问题的解决思路和具体实现过程: 解决思路 实现无重复随机数,需要考虑两个方面: 随机数不能重复,需要做到去重。 生成的随机数需要随机分布,不能出现指定的规律。 基于这两个需求,我们可以采用以下思路来解决问题: 定义一个范围内的数组,用于存储随机数。 定义一个随机数生成器,用于生成指定范围内的随…

    C# 2023年5月31日
    00
  • 详解如何在C#中使用投影(Projection)

    在C#中,投影(Projection)是一种将数据从一种形式转换为另一种形式的技术。投影可以用于将数据从一种数据结构转换为另一种数据结构,或者将数据从一种表示形式转换为另一种表示形式。本文将提供详解如何在C#中使用投影的完整攻略,包括创建投影、使用投影、处理投影等。同时,本文还提供两个示例,演示如何在C#中使用投影。 创建投影 要创建投影,可以使用以下方法:…

    C# 2023年5月15日
    00
  • c#与WMI使用技巧集第1/2页

    c#与WMI使用技巧集第1/2页是一篇介绍C#与WMI使用技巧的文章,主要包括WMI的基础知识、C#中如何使用WMI等方面的内容。以下是该文章完整攻略的详细讲解: WMI基础知识 WMI(Windows Management Instrumentation)是一种用于管理Windows操作系统的工具,可以用于获取系统信息、监控、配置等。在C#中使用WMI可以…

    C# 2023年6月6日
    00
  • C#基于Socket的TCP通信实现聊天室案例

    下面将为您详细讲解“C#基于Socket的TCP通信实现聊天室案例”的完整攻略。 一、概述 本文将介绍如何使用C#语言基于Socket实现TCP协议的聊天室案例,并提供两条示例说明。 二、准备工作 在开始之前,我们需要保证以下几点: 本地已安装Visual Studio或其他C#开发工具; 了解Socket编程基础知识,比如Socket的创建、套接字的监听、…

    C# 2023年6月7日
    00
  • c#定期删除文件的实操方法

    C#定期删除文件的实操方法 在C#编程中,经常需要对指定目录下的文件进行定期删除,常见的应用场景包括删除系统日志文件、清空临时文件等等。下面就为大家详细讲解C#定期删除文件的实操方法,包含以下几个方面的内容: 获取文件目录 遍历目录中的文件 判断文件是否需要删除 删除文件 1. 获取文件目录 在C#中,我们可以通过使用Directory类来获取指定目录下的所…

    C# 2023年6月1日
    00
  • 如何搭建新的WPF项目框架

    如何搭建新的WPF项目框架 搭建新的WPF项目框架可以帮助我们更好地组织和管理WPF应用程序的代码。本文将提供详细的“如何搭建新的WPF项目框架”的完整攻略,包括如何创建项目结构、如何添加基础类以及两个示例。 创建项目结构 要创建新的WPF项目框架,我们需要执行以下步骤: 创建一个新的WPF应用程序项目。 在项目中创建一个名为“Infrastructure”…

    C# 2023年5月15日
    00
  • C#+无unsafe的非托管大数组示例详解(large unmanaged array in c# without ‘unsafe’ keyword)

    “C#+无unsafe的非托管大数组示例详解”是讲述如何在C#语言中创建非托管的大数组,且不使用“unsafe”关键字的方法。具体攻略如下: 为什么需要创建非托管大数组 C#语言是一门高级语言,无需开发人员手动管理内存,这种自动化内存管理方式称为托管内存。在某些场景下,我们可能需要创建大数组或读写大文件,托管内存会影响性能或引发内存不足等问题。这时候,我们可…

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