【问题标题】:Is a variable declaration the same as a variable's binding?变量声明是否与变量的绑定相同?
【发布时间】:2018-11-26 15:55:12
【问题描述】:

MDN documentation 声明:

let 绑定在(块)范围的顶部创建,包含 声明,通常称为“吊装”。与变量不同 用 var 声明,它将以 undefined 值开头,让 变量在其定义被评估之前不会被初始化。 在初始化之前访问变量会导致 参考错误。该变量位于“时间死区”中 块的开始,直到处理初始化。

所指的“let绑定”(letconst的提升)只是关键字let,还是只是创建存储空间(与关键字)?

以前我以为变量关键字和变量名一起构成一个声明,但在我最近问的question中,回答者说它们实际上是一个初始化。

【问题讨论】:

  • 变量绑定是当编译器将变量分配给特定范围(该变量将在该范围内可用)时,声明是将值分配给该变量。
  • 这是useful excerpt in the spec。文本块以及下面的作品在这种情况下非常有用。请注意,afaik 从技术上讲,声明和定义之间存在差异,但在 javascript 中不存在,因为声明始终是定义(与 c++ 不同)。
  • 作为对比,这里是same section for var,注意“Var 变量是在实例化它们包含的词法环境时创建的,并在创建时初始化为未定义。”,与let 相比,其中“由具有初始化程序的 LexicalBinding 定义的变量在评估 LexicalBinding 时分配其初始化程序的 AssignmentExpression 的值,而不是在创建变量时。”。这很可能是临时死区的原因,没有读取未初始化的 RAM!
  • 总而言之,恕我直言,“提升部分”(变量的创建)是声明/定义(引入名称并分配/保留内存),而非提升部分(甚至命名let) 的“LexicalBinding”是变量的“绑定”或初始化。

标签: javascript computer-science


【解决方案1】:

我很抱歉在编写 MDN 段落时使用了两个不同的术语。出于该文章中的所有目的,“变量”和“绑定”应理解为同一事物。但是,让我们详细介绍一下。

variable declaration 创建变量(作为抽象实体)。它告诉编译器它应该引入一个新变量,还可以告诉它名称、要保存的类型、初始值、范围等(取决于语言)。在 JS 中,有不同种类的声明做不同的事情,例如

  • var 有一个名称、一个可选的初始化程序和特定于 var 的范围规则
  • function 有一个(有时是可选的)名称,该值始终是给定的并且已知是一个函数
  • const 有一个名字,一个必需的初始化器,应该是不可变的,并且有词法作用域

binding 是变量名与变量实体的关联,例如“x 指的是用class x 声明的变量”。此类绑定取决于作用域,即在每个不同的作用域中都有不同的绑定,因此标识符 x 可能指代不同作用域中的不同事物
鉴于 JavaScript 的作用域规则,变量声明也会导致在各自的作用域中创建自身的绑定。

所以绑定是使名称可用的原因。这就是我所说的“let 绑定是在作用域的顶部创建的”。它与存在的变量、为其分配的内存或正在初始化的变量无关。

【讨论】:

  • 如果绑定使名称可在其范围的顶部使用,为什么它试图在其声明之前使用它(或者至少我认为这就是“它的初始化”的意思) 导致ReferenceError?你写的也很酷!
  • 我不明白的另一件事——如果时间死区是绑定和声明之间的空间,但声明是自绑定的......那么,到底发生了什么?
  • @natalie 变量声明会导致范围内的绑定和变量被初始化,绑定被提升初始化保持不变。临时死区的存在只是因为在声明之前使用变量会造成混淆。
  • @NatalieCardot 该名称可能已经引用了该变量,但该变量在初始化之前还不能使用。详情请参阅What is the temporal dead zone?Are variables declared with let or const not hoisted in ES6?
【解决方案2】:

声明只是说某事存在。在 JavaScript 中,您可以声明变量、函数和(最近的)类。

在某些语言(例如 C、C++)中,可以声明某些东西而无需定义它。例如:

// this declares a function exists with a given signature, but doesn't define its implementation
void someFunction();

someFunction(); // here we call the function, since we know it exists

// here we define the function, which we have to do at some point
void someFunction() { /* ... */ }

这种模式在现代语言中不太常见,在现代语言中,声明和定义往往结合在一起,但理解区别很有用,因为您的问题似乎主要与术语有关。

可以声明变量,但它们没有定义。

let b; // we declare that there's a variable 'b'

相反,您可以分配一个变量:

b = 5; // assignment
let c = 6; // declaration and assignment in one statement

绑定在计算机科学中的概念有many forms。例如,当您在代码中键入foo 时,绑定就是确定应该使用哪个变量/函数/类型/...的行为。在 JavaScript 中,这非常简单,但在某些语言中,它可能会变得很复杂(由于重载决议等原因)。

但是,当 MDN 谈论 let 绑定 时,我不相信这就是他们的意思。正如我们在上面看到的,我相信它是“let 声明和赋值”的简写。

无论如何,我不会太担心这个词。从您引用的段落中要理解的最重要一点是letconstvar 的更严格版本,在该语言的最新版本中引入以解决var 方式带来的陷阱和惊喜有效。

以前我认为变量关键字和变量名一起构成一个声明

你是对的。

var a;
var b = 1;
let c;
let c = 2;
const d = 3;

这些都是变量声明(尽管const 技术变量不能改变,或者更准确地说,它们不能被重新分配)。

只是var有点草率和意外。

您可以在同一范围内多次声明一个 var:

var a = 1;
var a = 2;

这不适用于let

let a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared

var 的范围也可能令人惊讶:

for (var i = 0; i < 10; i++)
{
    var inner = 1;
}

console.log(inner); // prints 1 even though you might think this would be an error

或者更糟:

for (var i = 0; i < 10; i++)
{
    for (var i = 0; i < 10; i++)
    {
        console.log('hello');
    }
}

乍一看你可能认为这会打印hello 100 次 (10*10),但实际上它只打印了 10 次,因为两个循环使用相同的变量。这是语言应该真正防止的一种程序员错误。如果该代码改用let i,则会产生语法错误。

至于提升,你可以把它想成好像所有的var 声明都被移到了包含函数的顶部。

function foo()
{
    doThing();
    var i = 0;
    doSomethingElse();
    for (var j = 0; j < 10; j++)
    {
        var k = 10;
    }
}

即使您可能会这样编写代码,但它的行为就像您编写的一样:

function foo()
{
    var i; // all declarations hoisted to top of containing function scope
    var j;
    var k;

    doThing();
    i = 0;
    doSomethingElse();
    for (j = 0; j < 10; j++)
    {
        k = 10;
    }
}

这就是你可以写作的原因:

i = 10;
var i;

var 在代码中向上移动,因此它的行为如下:

var i;
i = 10;

你可以认为let 没有被移动。因此在声明之前引用它是错误的。

【讨论】:

  • +1 用于声明示例。一个更好的例子可能是一个extern void someFunction();,程序甚至没有定义它自己——它告诉链接器找到一个someFunction来使用。
  • "但是我不相信 MDN 在他们谈论 let 绑定时的意思" - 实际上是的,正是它在谈论什么! (我应该知道,我编辑了那篇文章 :-D)
  • 我认为您的回答可能更简洁,如果您放弃“只是 var 有点草率和令人惊讶。”以及之后的所有内容,仍然可以完美回答问题
  • @Bergi 关于extern 功能的好主意,谢谢。可能有点高级。
  • @Bergi 你是说 let binding 中的 binding 是指 name binding 吗?
【解决方案3】:

这里要理解的主要事情是,js 引擎实际上会在两种不同的情况下访问let 语句(以及所有其他语句,但在这里特别重要)。它在解析期间被访问一次,当它生成一个 AST 并分析范围和变量时。它还为每个范围创建一个变量列表。现在,当代码执行时,引擎会再次访问该语句(或者如果它在循环/函数/其他任何内容中,则更频繁),现在最终初始化变量并为其赋值。所以“提升”基本上只是因为解析/执行阶段造成的,引擎在执行过程中到达声明语句之前就知道变量存在,因为它之前已经解析过。

所指的“let 绑定”(let 和 const 的提升)只是关键字 let,还是只是创建存储空间(与关键字无关)?

关键字实际上会在范围记录中产生一个条目,然后在执行过程中将其变成存储空间。另一方面,语句本身会在执行期间导致初始化。所以实际上很难说何时发生声明,这是一个文字问题。常说

该变量在第 10 行声明

它在那个块中声明

所以“声明”是指声明还是范围分配取决于您:)

回答者说它们实际上是一个初始化。

实际上,回答者更愿意称其为“初始化”而不是“声明”以免混淆读者,但实际上它令人困惑,因为人类语言不像机器语言那样明确定义。

【讨论】:

    猜你喜欢
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    • 1970-01-01
    • 2012-07-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多