ChatGPT解决这个技术问题 Extra ChatGPT

JavaScript 中变量的作用域是什么?

javascript中变量的范围是什么?它们在函数内部和外部具有相同的范围吗?或者它甚至重要吗?另外,如果变量是全局定义的,它们存储在哪里?

这里有另一个很好的 link 来记住这个问题:“Explaining JavaScript scope and closures”。
这是一篇很好地解释它的文章。 Everything you need to know about Javascript variable scope
Kyle Simpson 的 previously mentioned 电子书可在 Github 上阅读,它告诉您有关 JavaScript 作用域和关闭。您可以在此处找到它:github.com/getify/You-Dont-Know-JS/blob/master/… 它是 "You don't know JS" book series 的一部分,非常适合所有想了解更多 JavaScript 的人。
var 规则。 javascript不需要'const'和'let'的“添加”,这违背了它的精神。 - 我知道这两个不是你问题的一部分 - 在看到这么多“推”它们之后不得不添加这个。

E
E. Reiner

TLDR

JavaScript 具有词法(也称为静态)作用域和闭包。这意味着您可以通过查看源代码来判断标识符的范围。

四个范围是:

全局 - 对任何事物都可见 功能 - 在函数(及其子函数和块)内可见 块 - 在块(及其子块)内可见 模块 - 在模块内可见

在全局和模块范围的特殊情况之外,使用 var(函数范围)、let(块范围)和 const(块范围)声明变量。大多数其他形式的标识符声明在严格模式下具有块范围。

概述

范围是标识符有效的代码库区域。

词法环境是标识符名称和与之关联的值之间的映射。

范围由词汇环境的链接嵌套构成,嵌套中的每一级对应于祖先执行上下文的词汇环境。

这些链接的词法环境形成了一个作用域“链”。标识符解析是沿着该链搜索匹配标识符的过程。

标识符解析只发生在一个方向:向外。这样,外部词汇环境就无法“看到”内部词汇环境。

决定 JavaScript 中 identifierscope 的三个相关因素:

如何声明标识符 声明标识符的位置 处于严格模式还是非严格模式

可以声明标识符的一些方法:

var、let 和 const 函数参数 Catch 块参数 函数声明 命名函数表达式 在全局对象上隐式定义的属性(即,在非严格模式下丢失 var) import 语句 eval

可以声明一些位置标识符:

全局上下文 函数体 普通块 控制结构的顶部(例如,循环、if、while 等) 控制结构体 模块

声明样式

变量

使用 var 声明的标识符具有函数范围,除非它们直接在全局上下文中声明,在这种情况下,它们作为属性添加到全局对象上并具有全局范围。在 eval 函数中使用它们有单独的规则。

让和常量

使用 letconst 声明的标识符具有块范围,除非它们直接在全局上下文中声明,在这种情况下它们具有全局范围。

注意:letconstvar are all hoisted。这意味着它们的逻辑定义位置是它们封闭范围(块或函数)的顶部。但是,在控制通过源代码中的声明点之前,无法读取或分配使用 letconst 声明的变量。过渡时期被称为时间死区。

function f() { function g() { console.log(x) } let x = 1 g() } f() // 1 因为 x 被提升了,即使用 `let` 声明了!

函数参数名称

函数参数名称的范围是函数体。请注意,这有点复杂。声明为默认参数的函数靠近 parameter list,而不是函数体。

函数声明

函数声明在严格模式下具有块作用域,在非严格模式下具有函数作用域。注意:非严格模式是基于不同浏览器古怪的历史实现的一组复杂的紧急规则。

命名函数表达式

命名函数表达式的范围仅限于自身(例如,出于递归的目的)。

全局对象上隐式定义的属性

在非严格模式下,全局对象上隐式定义的属性具有全局范围,因为全局对象位于范围链的顶部。在严格模式下,这些是不允许的。

评估

eval 字符串中,使用 var 声明的变量将被放置在当前范围内,或者,如果 eval 被间接使用,则作为全局对象的属性。

例子

以下将引发 ReferenceError,因为名称 xyz 在函数 f 之外没有任何意义。

function f() { var x = 1 let y = 1 const z = 1 } console.log(typeof x) // 未定义(因为 var 有函数作用域!) console.log(typeof y) // 未定义(因为主体函数的块是块)console.log(typeof z)//未定义(因为函数的主体是块)

以下将为 yz 引发 ReferenceError,但不会为 x 引发 ReferenceError,因为 x 的可见性不受块的限制。定义控制结构体(如 ifforwhile)的块的行为类似。

{ var x = 1 let y = 1 const z = 1 } console.log(x) // 1 console.log(typeof y) // 未定义,因为 `y` 有块作用域 console.log(typeof z) // 未定义因为 `z` 有块作用域

在下文中,x 在循环外可见,因为 var 具有函数范围:

for(var x = 0; x < 5; ++x) {} console.log(x) // 5(注意这是在循环之外!)

...由于这种行为,您需要小心关闭在循环中使用 var 声明的变量。这里只声明了一个变量 x 的实例,它在逻辑上位于循环之外。

以下将 5 打印五次,然后为循环外的 console.log 打印 5 第六次:

for(var x = 0; x < 5; ++x) { setTimeout(() => console.log(x)) // 关闭逻辑上位于封闭范围顶部的 `x`循环 } console.log(x) // 注意:在循环外可见

以下打印 undefined 因为 x 是块范围的。回调是异步运行的。 let 变量的新行为意味着每个匿名函数都关闭一个名为 x 的不同变量(与 var 不同),因此会打印 04 的整数。:

for(let x = 0; x < 5; ++x) { setTimeout(() => console.log(x)) // `let` 声明在每次迭代的基础上重新声明,因此闭包捕获不同的变量 } console.log(typeof x) // 未定义

以下不会抛出 ReferenceError,因为 x 的可见性不受块的限制;但是,它将打印 undefined,因为变量尚未初始化(因为 if 语句)。

if(false) { var x = 1 } console.log(x) // 这里,`x` 已经被声明了,但是没有被初始化

使用 letfor 循环顶部声明的变量的作用域为循环体:

for(let x = 0; x < 10; ++x) {} console.log(typeof x) // 未定义,因为 `x` 是块作用域的

以下将引发 ReferenceError,因为 x 的可见性受到块的限制:

if(false) { let x = 1 } console.log(typeof x) // 未定义,因为 `x` 是块作用域的

使用 varletconst 声明的变量都作用于模块:

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

以下将在全局对象上声明一个属性,因为在全局上下文中使用 var 声明的变量将作为属性添加到全局对象:

var x = 1 console.log(window.hasOwnProperty('x')) // true

全局上下文中的 letconst 不向全局对象添加属性,但仍具有全局范围:

let x = 1 console.log(window.hasOwnProperty('x')) // false

函数参数可以认为是在函数体中声明的:

function f(x) {} console.log(typeof x) // 未定义,因为 `x` 的作用域是函数

捕获块参数的范围为捕获块主体:

try {} catch(e) {} console.log(typeof e) // 未定义,因为 `e` 的作用域是 catch 块

命名函数表达式的范围仅限于表达式本身:

(function foo() { console.log(foo) })() console.log(typeof foo) // 未定义,因为 `foo` 的作用域是它自己的表达式

在非严格模式下,全局对象上隐式定义的属性是全局范围的。在严格模式下,您会收到错误消息。

x = 1 // 全局对象上隐式定义的属性(没有“var”!) console.log(x) // 1 console.log(window.hasOwnProperty('x')) // true

在非严格模式下,函数声明具有函数范围。在严格模式下,它们具有块范围。

'use strict' { function foo() {} } console.log(typeof foo) // 未定义,因为 `foo` 是块作用域

它是如何在引擎盖下工作的

范围定义为标识符有效的代码的 lexical 区域。

在 JavaScript 中,每个函数对象都有一个隐藏的 [[Environment]] 引用,它是对创建它的 execution context(堆栈帧)的 lexical environment 的引用。

调用函数时,会调用隐藏的 [[Call]] 方法。此方法创建一个新的执行上下文,并在新的执行上下文和函数对象的词法环境之间建立链接。它通过将函数对象上的 [[Environment]] 值复制到新执行上下文的词法环境中的 outer reference 字段中来实现这一点。

请注意,新的执行上下文和函数对象的词法环境之间的链接称为 closure

因此,在 JavaScript 中,作用域是通过外部引用以“链”链接在一起的词法环境来实现的。这个词法环境链称为作用域链,标识符解析由 searching up the chain 进行,以获取匹配的标识符。

找出more


甚至没有接近全面,但这可能是一个必须知道的 Javascript 范围技巧,甚至需要有效地阅读现代 javascript。
一个高度评价的答案,不知道为什么。这只是一堆没有适当解释的示例,然后似乎将原型继承(即属性解析)与范围链(即变量解析)混淆了。 comp.lang.javascript FAQ notes 中提供了有关范围和属性解析的全面(准确)说明。
@RobG 它之所以受到高度评价,是因为它对广泛的程序员有用且易于理解,尽管有轻微的冲突。您发布的链接虽然对某些专业人士有用,但对于当今大多数编写 Javascript 的人来说却是难以理解的。随时通过编辑答案来解决任何命名问题。
@triptych——我只编辑答案来解决小问题,而不是主要问题。将“范围”更改为“属性”将修复错误,但不能解决混合继承和范围的问题而没有非常明确的区别。
如果您在外部范围内定义一个变量,然后让 if 语句在函数内定义一个同名变量,即使没有到达该 if 分支,它也会被重新定义。一个例子 - jsfiddle.net/3CxVm
k
krosenvold

Javascript 使用范围链来建立给定函数的范围。通常有一个全局范围,并且定义的每个函数都有自己的嵌套范围。在另一个函数中定义的任何函数都具有链接到外部函数的局部范围。定义范围的始终是源中的位置。

作用域链中的元素基本上是一个带有指向其父作用域的指针的 Map。

解析变量时,javascript 从最里面的范围开始向外搜索。


范围链是 [memory] Closures 的另一个术语......对于那些阅读这里学习/进入 javascript 的人。
g
gilly3

全局声明的变量具有全局范围。在函数中声明的变量的作用域是该函数,并隐藏同名的全局变量。

(我敢肯定,真正的 JavaScript 程序员可以在其他答案中指出许多微妙之处。特别是我遇到了 this page,了解 this 的确切含义。希望 this more introductory link 足以理解不过你开始了。)


我什至不敢开始回答这个问题。作为一名真正的 Javascript 程序员,我知道答案会很快失控。不错的文章。
@Triptych:我知道你所说的事情失控是什么意思,但无论如何请添加一个答案。我只是通过几次搜索得到了上述内容......由有实际经验的人写的答案肯定会更好。请更正我的任何答案,但这绝对是错误的!
不知何故,Jon Skeet 负责我在 Stack Overflow 上最受欢迎的答案。
C
Community

老派 JavaScript

传统上,JavaScript 实际上只有两种类型的作用域:

全局范围:变量在整个应用程序中都是已知的,从应用程序的开始 (*) 功能范围:变量在它们声明的函数中是已知的,从函数的开始 (*)

我不会详细说明这一点,因为已经有许多其他答案可以解释差异。

现代 JavaScript

most recent JavaScript specs 现在还允许第三个范围:

块作用域:标识符在声明它们的作用域的顶部是“已知的”,但在声明行之后才能分配或取消引用(读取)。这个过渡时期被称为“时间死区”。

如何创建块范围变量?

传统上,您可以像这样创建变量:

var myVariable = "Some text";

块范围变量是这样创建的:

let myVariable = "Some text";

那么函数作用域和块作用域有什么区别呢?

要了解功能范围和块范围之间的区别,请考虑以下代码:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

在这里,我们可以看到我们的变量 j 只在第一个 for 循环中知道,而在之前和之后都不知道。然而,我们的变量 i 在整个函数中是已知的。

另外,考虑到块范围的变量在声明之前是未知的,因为它们没有被提升。您也不允许在同一个块内重新声明同一个块范围的变量。这使得块范围的变量比全局或功能范围的变量更不容易出错,全局或功能范围的变量被提升并且在多个声明的情况下不会产生任何错误。

今天使用块范围变量是否安全?

今天使用是否安全,取决于您的环境:

如果您正在编写服务器端 JavaScript 代码 (Node.js),则可以安全地使用 let 语句。

如果您正在编写客户端 JavaScript 代码并使用基于浏览器的转译器(如 Traceur 或 babel-standalone),则可以安全地使用 let 语句,但是您的代码可能在性能方面并非最佳。

如果您正在编写客户端 JavaScript 代码并使用基于节点的转译器(如 traceur shell 脚本或 Babel),则可以安全地使用 let 语句。而且因为您的浏览器只会知道转译的代码,所以性能缺陷应该是有限的。

如果您正在编写客户端 JavaScript 代码并且不使用转译器,则需要考虑浏览器支持。以下是一些完全不支持 let 的浏览器: Internet explorer 10 及以下 Firefox 43 及以下 Safari 9 及 Android 浏览器 4 及以下 Opera 27 及 Chome 40 及以下 任何版本的 Opera Mini 和黑莓浏览器

Internet Explorer 10 及以下版本

火狐 43 及以下

Safari 9 及以下

安卓浏览器4及以下

Opera 27 及以下

丁目40以下

任何版本的 Opera Mini 和黑莓浏览器

https://i.stack.imgur.com/J9kEC.png

如何跟踪浏览器支持

有关在您阅读此答案时哪些浏览器支持 let 语句的最新概述,请参阅 this Can I Use page

(*) 全局和函数范围的变量可以在声明之前初始化和使用,因为 JavaScript 变量是 hoisted 这意味着声明总是位于范围的顶部。


“未知”具有误导性,因为该变量是由于提升而在此处声明的。
上面的示例具有误导性,变量“i”和“j”在块外是未知的。 “让”变量仅在该特定块中具有范围,而不在该块之外。 Let 还有其他优点,您不能再次重新声明变量并且它保持词法范围。
这很有帮助,谢谢!我认为具体说明“现代 JavaScript”和“老派 JavaScript”的含义会更有帮助;我认为这些分别对应于 ECMAScript 6 / ES6 / ECMAScript 2015 和早期版本?
@JonSchneider:正确!在我说“老派 JavaScript”的地方,我需要谈论 ECMAScript 5,而在我指的是“现代 JavaScript”的地方,我正在谈论 ECMAScript 6(又名 ECMAScript 2015)。不过,我认为在这里详细介绍并不那么重要,因为大多数人只是想知道(1)块作用域和功能作用域之间的区别,(2)浏览器支持块作用域和(3)今天在他们正在从事的任何项目中使用块范围是否安全。所以我把我的答案集中在解决这些问题上。
@JonSchneider:(继续)不过,我只是添加了一个链接,指向一篇关于 ES6 / ES2015 的 Smashing Magazine 文章,供那些想要了解更多关于过去几年 JavaScript 中添加了哪些功能的人......可能想知道我所说的“现代 JavaScript”是什么意思。
g
geowa4

这是一个例子:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

您需要研究闭包,以及如何使用它们来制作 private members


J
James McMahon

据我了解,关键是 Javascript 具有函数级别的范围,而不是更常见的 C 块范围。

Here is a good article on the subject.


k
kennytm

在“Javascript 1.7”(Mozilla 对 Javascript 的扩展)中,还可以使用 let statement 声明块范围变量:

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4

是的,但是使用安全吗?我的意思是,如果我的代码将在 WebKit 中运行,我真的会选择这个实现吗?
@Python:不,WebKit 不支持 let
我想唯一有效的用途是如果您知道所有客户都将使用 Mozilla 浏览器,就像公司内部系统一样。
或者,如果您使用 XUL 框架进行编程,Mozilla 的界面框架是您使用 css、xml 和 javascript 构建的。
@GazB 即使这是一个可怕的想法!因此,今天您知道您的客户正在使用 Mozilla,然后发布了一份新备忘录,说明他们现在正在使用其他东西。 IE 是我们的支付系统糟糕透顶的原因……你必须使用 IE8,而不是 IE9、IE10 或 Firefox 或 Chrome,因为它根本无法正常工作……
T
Travis J

最初由 Brendan Eich 设计的 JavaScript 范围的想法来自 HyperCard 脚本语言HyperTalk

在这种语言中,显示的方式类似于一叠索引卡。有一张被称为背景的主卡。它是透明的,可以看作是底牌。此基础卡上的任何内容都与放置在其上的卡共享。放在最上面的每张卡片都有自己的内容,优先于前一张卡片,但如果需要,仍然可以访问之前的卡片。

这正是 JavaScript 范围系统的设计方式。它只是有不同的名称。 JavaScript 中的卡片称为 Execution ContextsECMA。这些上下文中的每一个都包含三个主要部分。一个变量环境、一个词法环境和一个 this 绑定。回到卡片参考,词法环境包含堆栈中较低的先前卡片的所有内容。当前上下文位于堆栈的顶部,在那里声明的任何内容都将存储在变量环境中。在命名冲突的情况下,变量环境将优先。

this 绑定将指向包含对象。有时作用域或执行上下文会发生变化,但包含对象不会发生变化,例如在包含对象可能是 window 或构造函数的声明函数中。

这些执行上下文是在控制权转移的任何时候创建的。当代码开始执行时,控制权就会转移,这主要是通过函数执行来完成的。

这就是技术解释。在实践中,重要的是要记住在 JavaScript 中

范围在技术上是“执行上下文”

上下文形成了存储变量的环境堆栈

堆栈顶部优先(底部是全局上下文)

每个函数都会创建一个执行上下文(但并不总是一个新的 this 绑定)

将此应用于此页面上的先前示例之一(5.“闭包”),可以跟踪执行上下文的堆栈。在此示例中,堆栈中有三个上下文。它们由外部上下文、var 6 调用的立即调用函数中的上下文以及 var 6 立即调用函数内部返回函数中的上下文定义。

i) 外部环境。它有一个 a = 1 的变量环境 ii) IIFE 上下文,它有一个 a = 1 的词法环境,但在堆栈中具有优先权的 a = 6 的变量环境 iii) 返回的函数上下文,它有一个词法环境a = 6 的环境,这是调用时警报中引用的值。

https://i.stack.imgur.com/v45hL.png


Javascript 真的是受到 Hypertalk 的启发吗?我不记得 Hypertalk 有这么有趣的作用域,但灵感可能会解释 Javascript 的奇怪运算符重载,其中 10=="10.0" 和 10=="10",但是 "10.0"!="10"。尽管 Hypertalk 的操作员表现得更加有趣。
@supercat - 是的,先生。大约在这个时候,我一直在研究 Internet Explorer(可以追溯到 Mosaic)的起源,试图弄清楚为什么 IE10 是一个如此安全的问题,并将部分研究发送给了 Jonathan Sampson。也许巧合的是,他们很快就开始开发 Edge,并消除了许多建议的安全问题。不过,这篇文章实际上有点过时了,因为最近对 EcmaScript 的迭代和微任务的包含,在某些场景中创建了一个稍微复杂的模型来处理幕后的内存管理。
@supercat - 对于一些仍然可用的引用,“我开始研究像 Logo 和 Smalltalk 以及 Self 和 HyperTalk 这样的语言,这是 Bill Atkinson 的 HyperCard 语言”-Brendan Eich,“JavaScript(其创建者 Brendan Eich 的灵感来自HyperTalk[32])”-Wiki citing his book。这是我写给微软乔纳森的电子邮件:jsfiddle.net/fwchpvrj
可能有一些概念上的灵感,但是在使用过 Hypertalk 和 Javascript 之后,我看不出它们之间有任何设计共性。 Hypercard 堆栈直接影响包含系统的能力是因为在遇到不熟悉的命令或函数时,Hypercard 将搜索类型为 XCMD 或(如果有记忆的话)XFCN 的资源,其名称与不熟悉的命令的名称匹配或函数,并且——如果找到了——将它作为代码资源加载到内存中并调用它。根据设计,任何资源在...
...当前文档将通过这样的搜索找到。这使得 Hypercard 堆栈可以做在语言中不可能完成的事情,但这意味着堆栈不会以任何有意义的方式被沙盒化。相比之下,Web 浏览器应该提供一个沙盒环境来处理已移除的内容。未能充分沙箱是由于错误,而 Hypercard 没有沙箱的事实是由于设计决定不限制堆栈可以执行的任务范围。
G
Gerard ONeill

1) 有一个全局作用域、一个函数作用域以及 with 和 catch 作用域。变量通常没有“块”级别的范围——with 和 catch 语句将名称添加到它们的块中。

2) 范围由函数一直嵌套到全局范围。

3) 通过原型链解析属性。 with 语句将对象属性名称带入 with 块定义的词法范围。

编辑:ECMAAScript 6 (Harmony) 被指定支持 let,我知道 chrome 允许使用“harmony”标志,所以也许它确实支持它..

Let 将支持块级范围,但您必须使用关键字才能实现。

编辑:根据本杰明在评论中指出 with 和 catch 语句,我编辑了帖子,并添加了更多内容。 with 和 catch 语句都将变量引入各自的块中,这就是块作用域。这些变量是传递给它们的对象的属性的别名。

 //chrome (v8)

 var a = { 'test1':'test1val' }
 test1   // error not defined
 with (a) { var test1 = 'replaced' }
 test1   // undefined
 a       // a.test1 = 'replaced'

编辑:澄清示例:

test1 的作用域是 with 块,但别名为 a.test1。 'Var test1' 在上层词汇上下文(函数或全局)中创建一个新变量 test1,除非它是 a 的属性 -- 它就是。

哎呀!小心使用 'with' - 就像 var 是一个 noop 如果变量已经在函数中定义,它也是一个 noop 相对于从对象导入的名称!对已经定义的名称稍加注意会使这更安全。因此,我个人永远不会使用 with。


这里有一些错误,因为其中一个 JavaScript 确实具有块作用域的形式。
我的耳朵(眼睛)是张开的,本杰明——我上面的陈述是我一直在处理 Javascript 范围,但它们不是基于阅读规范。我希望您不是指 with 语句(这是一种对象作用域的形式)或 Mozilla 的特殊“let”语法。
好吧,with 语句 块作用域的一种形式,但 catch 子句是一种更常见的形式(有趣的是,v8 使用 with 实现了 catch) - 这几乎就是只有 JavaScript 本身中的块作用域形式(即函数、全局、try/catch、with 及其派生类),但是宿主环境具有不同的作用域概念 - 例如浏览器中的内联事件和 NodeJS 的 vm 模块。
本杰明——据我所知, with 和 catch 都只将对象引入当前范围(以及属性),但是在各自的块结束后,变量被重置。但是例如,在 catch 中引入的新变量将具有封闭函数/方法的范围。
这正是块范围的含义:)
a
austincheney

我发现许多 JavaScript 新手很难理解在语言中默认情况下继承是可用的,并且到目前为止,函数作用域是唯一的作用域。我为去年年底编写的美化器 JSPretty 提供了一个扩展。代码中的功能颜色函数作用域并始终将颜色与该作用域中声明的所有变量相关联。当具有来自一个范围的颜色的变量在不同的范围中使用时,可以直观地演示闭包。

在以下位置尝试该功能:

http://prettydiff.com/jspretty.xhtml?c=white&jsscope

在以下位置查看演示:

http://prettydiff.com/jspretty.xhtml?c=white&jsscope&s=http://prettydiff.com/lib/markup_beauty.js

查看代码:

http://prettydiff.com/lib/jspretty.js

https://github.com/austincheney/Pretty-Diff/blob/master/lib/jspretty.js

目前该功能支持深度为 16 个嵌套函数,但目前不为全局变量着色。


不适用于 Firefox 26。我粘贴代码或加载文件,单击执行,但没有任何反应。
范围和继承是两个不同的东西。
C
CertainPerformance

内联处理程序

前端编码人员经常遇到的一个尚未描述的非常常见的问题是 HTML 中的内联事件处理程序可见的范围 - 例如,使用

<button onclick="foo()"></button>

on* 属性可以引用的变量的范围必须是:

全局(工作内联处理程序几乎总是引用全局变量)

文档的属性(例如,作为独立变量的 querySelector 将指向 document.querySelector;很少见)

处理程序附加到的元素的属性(如上;罕见)

否则,您将在调用处理程序时收到 ReferenceError。因此,例如,如果内联处理程序引用了定义在 inside window.onload$(function() { 的函数,则引用将失败,因为内联处理程序只能引用全局范围内的变量,并且该函数不是全局的:

window.addEventListener('DOMContentLoaded', () => { function foo() { console.log('foo running'); } });

document 的属性和处理程序所附加到的元素的属性也可以作为内联处理程序内的独立变量引用,因为内联处理程序被调用 inside of two with blocks,一个用于 document,一个用于元素。这些处理程序内的变量范围链是 extremely unintuitive,一个工作的事件处理程序可能需要一个全局函数(以及不必要的全局污染 should probably be avoided)。

由于内联处理程序中的作用域链很奇怪,而且内联处理程序需要全局污染才能工作,而且内联处理程序有时需要在传递参数时进行丑陋的字符串转义,因此避免它们可能更容易。相反,使用 Javascript(如使用 addEventListener)而不是 HTML 标记附加事件处理程序。

function foo() { console.log('foo running'); } document.querySelector('.my-button').addEventListener('click', foo);

模块 (