从上下文,到作用域(彩蛋:理解闭包)

2024-03-14 02:38

本文主要是介绍从上下文,到作用域(彩蛋:理解闭包),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上下文与作用域的关系


很多人弄不清除,原因当然是既不了解上下文,也不了解作用域——我是说,几乎没有人明白上下文是什么而不明白作用域是什么,反之亦然。上下文(context)和作用域(scope)都是编译原理的知识,具体编程语言有具体的实现规则,本文关注 JavaScript 语言的实现。首先需要关注的是,这两个概念的关系非常密切,所以先了解它们的关系,有助于理解它们到底是什么。


上下文(context)和作用域(scope)的关系:


上下文是一段程序运行所需要的最小数据集合;作用域是当前上下文中,按照具体规则能够访问到的标识符(变量)的范围。


后文是对上下文和作用域更详细的解释,知道了上面指出的关系,往下阅读时就可以加深对这一关系的理解了。


上下文


上下文(context)是一段程序运行所需要的最小数据集合。我们可以从上下文交换(context switch)来理解上下文,在多进程或多线程环境中,任务切换时首先要中断当前的任务,将计算资源交给下一个任务。因为稍后还要恢复之前的任务,所以中断的时候要保存现场,即当前任务的上下文,也可以叫做环境。即上下文就是恢复现场所需的最小数据集合。容易把人弄晕的一点是,我们这里说的上下文、环境有时候也称作作用域(scope),即这两个概念有时候是混用的。不过,它们有不同的侧重点,下一节将会说明。


另外,JavaScript 中常见的情形是一个方法/函数的执行。从一段程序的角度看,这段程序运行所需的所有变量,就是它的上下文。


作用域


作用域(scope)是标识符(变量)在程序中的可见性范围。作用域规则是按照具体规则维护标识符的可见性,以确定当前执行的代码对这些标识符的访问权限。作用域(scope)是在具体的作用域规则之下确定的。


前面说过,有时候上下文、环境、作用域是同义词;不过,上下文(context)指代的是整体环境,作用域关注的是标识符(变量)的可访问性(可见性)。上下文确定了,根据具体编程语言的作用域规则,作用域也就确定了。这就是上下文与作用域的关系。


写 JavaScript 代码时,如果 Function 作为参数,可以指定它在具体对象上调用时,这个对象常常叫做 context:


function callWithContext(fn, context) {

  return fn.call(context);

}

 

const apple = {

  name"Apple"

};

 

const orange = {

  name"Orange"

};

 

function echo() {

  console.log(this.name);

}

 

callWithContext(echo, apple);  // Apple

callWithContext(echo, orange); // Orange


为什么将这个参数叫做 context?因为它关系到调用环境,指定了它,就指定了函数的调用上下文。再加上具体的作用域规则,作用域也确定了。


在 JavaScript 中,这个具体的作用域规则就是词法作用域(lexical scope),也就是 JavaScript 中的作用域链的规则。词法作用域是的变量在编译时(词法阶段)就是确定的,所以词法作用域又叫静态作用域(static scope),与之相对的是动态作用域(dynamic scope)。


You Don’t Know JS: Scope & Closures 用简单例子解释过动态作用域,下面用一个类似的例子说明一下:


function foo() {

  console.log(a);

}

 

function bar() {

  let a = 3;

  foo();

}

 

let a = 2;

 

bar(); // 2


有一定 JavaScript 编程经验的人都能看出,这段程序会输出 2,但如果在动态作用域的规则下,应该输出 3,即 a 的引用不再是编译时确定,而是调用时确定的。这有点像 JavaScript 中的 this,所以 MDN 中,function.bind 的方法签名中第一个形参名称用的是 thisArg 这一更科学的名字:


fun.bind(thisArg[, arg1[, arg2[, …]]])


同样情况的还可见于 Lodash 的文档:


_.bind(func, thisArg, [partials])


彩蛋:理解闭包


上一节中的代码中,之所以输出 2,是因为 foo 是一个闭包函数。如果从本文中理解了上下文和作用域的概念,对于闭包是什么这一问题是不是感到豁然开朗?


前面说过,词法作用域也叫静态作用域,变量在词法阶段确定,也就是定义时确定。虽然在 bar 内调用,但由于 foo 是闭包函数,即使它在自己定义的词法作用域以外的地方执行,它也一直保持着自己的作用域。所谓闭包函数,即这个函数封闭了它自己的定义时的环境,形成了一个闭包,所以 foo 并不会从 bar 中寻找变量,这就是静态作用域的特点。


一个更加典型的例子是:


function fn() {

  let a = 0;

  function func() {

    console.log(a);

  }

  return func;

}

 

let a = 1;

let sub = fn();

 

sub(); // 0;


sub 就是 func 这一返回值,func 定义在 fn 内部并且被传递出来了,所以 fn 执行之后垃圾回收器依然没有回收它的内部作用域,因为 func/sub 在使用。sub 依然持有 func 定义时的作用域的引用,而这个引用就叫作闭包。调用 sub 时,它可以访问 func 定义时的词法作用域,因此找到的 a 是 fn 内部的变量 a,它的值是 0。


参考资料


  • You Don’t Know JS: Scope & Closures

  • Context (computing)

  • Scope (computer science)

  • Function.prototype.bind()

  • Function _.bind()

这篇关于从上下文,到作用域(彩蛋:理解闭包)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/806960

相关文章

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

深入理解C语言的void*

《深入理解C语言的void*》本文主要介绍了C语言的void*,包括它的任意性、编译器对void*的类型检查以及需要显式类型转换的规则,具有一定的参考价值,感兴趣的可以了解一下... 目录一、void* 的类型任意性二、编译器对 void* 的类型检查三、需要显式类型转换占用的字节四、总结一、void* 的

深入理解Redis大key的危害及解决方案

《深入理解Redis大key的危害及解决方案》本文主要介绍了深入理解Redis大key的危害及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、背景二、什么是大key三、大key评价标准四、大key 产生的原因与场景五、大key影响与危

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是