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日

相关文章

  • ASP.NET Core MVC 从入门到精通之HttpContext

    随着技术的发展,ASP.NET Core MVC也推出了好长时间,经过不断的版本更新迭代,已经越来越完善,本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容,适用于初学者,在校毕业生,或其他想从事ASP.NET Core MVC 系统开发的人员。 经过前几篇文章的讲解,初步了解ASP.NET Core MVC项目创建,启…

    C# 2023年5月4日
    00
  • 输出的文本实现对齐的方法(超简单)

    概述: 实现文本输出对齐的方法有很多,其中最简单的方法就是使用Markdown语法中的制表符\t。制表符\t可以在文本中插入一个制表符,从而实现对齐。 步骤: 1、在需要对齐的文本中,使用制表符\t进行对齐。制表符\t的作用就是让文本输出一个制表符的位置,从而实现对齐。 2、在Markdown语法中,需要使用代码块(反引号)将代码块包含起来。代码块中的内容会…

    C# 2023年6月7日
    00
  • C# 获取 PC 序列号的方法示例

    下面是详细讲解 “C#获取PC序列号的方法示例” 的完整攻略。 1. 硬件信息获取 获取PC序列号通常需要用到硬件信息,我们可以通过Windows的WMI技术获取各种硬件信息。以下是获取PC序列号的步骤: 在Visual Studio中新建一个C#控制台应用程序,命名为”GetPCSerialNumber”。 右键点击项目,选择”添加”->”引用……

    C# 2023年6月7日
    00
  • c# 爬取优酷电影信息(1)

    下面是针对“c# 爬取优酷电影信息(1)”攻略的详细讲解。 1. 项目概述 该项目旨在使用C#编写一个网页爬虫,从优酷电影网站上爬取指定类型电影的信息,包括电影名称、导演、演员、上映时间、评分等。具体实现时,我们将使用HtmlAgilityPack解析HTML页面并提取数据。 2. 实现步骤 2.1 确定目标URL与请求方式 我们首先需要确定需要爬取的目标页…

    C# 2023年6月2日
    00
  • C#操作SQLite数据库帮助类详解

    C#操作SQLite数据库帮助类详解 什么是SQLite数据库? SQLite是一种轻量级的关系型数据库管理系统,因其跨平台、易操作、高效等特点广受欢迎,可用于很多不同的应用场景。 C#如何操作SQLite数据库? C#作为一种高效的编程语言,内置了SQLite.NET库,可以直接通过System.Data.SQLite命名空间中的类进行对SQLite数据库…

    C# 2023年5月31日
    00
  • C# 压榨cpu的办法(推荐)

    下面是” C# 压榨cpu的办法(推荐) “的完整攻略: 概述 在一些需要高性能的场景下,我们需要在C#代码中尽可能地提高程序的CPU利用率。通过使用一些技巧可以让我们的程序充分利用CPU资源,提高性能。 如何压榨CPU 下面我们介绍一些压榨CPU的办法: 1. 紧密计算 紧密计算是一种流程控制的方式,其目的是在尽量少的时间内进行更多的计算,从而提高CPU的…

    C# 2023年6月6日
    00
  • Asp.Net中文本换行

    让我来给您讲解”Asp.Net中文本换行”的完整攻略吧。 1.使用Html的<br>标签 在Asp.Net中,我们可以使用Html的<br>标签来实现文本换行,这个标签可以在Razor视图中或在代码中使用。以下是使用<br>标签的示例: <p> 第一行文本<br> 第二行文本<br> 第…

    C# 2023年6月3日
    00
  • C#实现的xml操作类完整实例

    下面是详细讲解“C#实现的XML操作类完整实例”的完整攻略。 简介 在C#开发中,我们需要对XML文件进行读取、写入和修改等操作,这时候一款优秀的XML操作类就显得十分重要。本文将介绍一款C#实现的XML操作类的完整实例,包括类的定义、读取XML文件、写入XML文件和修改XML文件等操作。 类的定义 首先,我们需要定义一个XML操作类,这个类需要包含读取、写…

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