JavaScript核心原理解析(二)

112 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情

声明和赋值

JavaScript 只有变量和常量两种标识符

  • 变量:let、var、function、class
  • 常量:const、import、还有特殊的for ( var | let | const x …)、try … catch (x)

将可以通过静态语法分析发现那些声明的标识符;标识符对应的变量 / 常量一定会在用户代码执行前就已经被创建在作用域中;并且因此它会使得当前代码上下文在正式执行之前就拥有了被声明的标识符。

JavaScript 是允许访问还没有绑定值的 var 所声明的标识符的。这种标识符后来统一约定称为变量声明(varDelcs),而 let/const 则称为词法声明(lexicalDecls)。JavaScript 环境在创建一个变量名(varName in varDecls) 后,会为它初始化绑定一个 undefined 值,而词法名字(lexicalNames) 在创建之后就没有这项待遇,所以它们在缺省情况下就是还没有绑定值的标识符(var 有声明提前,但是let/const 就没有)。

var x = y = 100

xy 是两个不同的东西,前者是声明的名字,后者是一个赋值过程可能创建的变量名。使用 = 这个符号引导了一个初始化语法——通常情况下可以将它理解为一个赋值运算;所有赋值操作的含义,是将右边的,赋给左边用于包含该值的引用

变量 y 会因为赋值操作而导致 JavaScript 引擎意外创建一个全局变量。

声明和语句的区别在于发生的时间点不同,声明发生在编译期,语句发生在运行期。声明发生在编译期,由编译器为所声明的变量在相应的变量表,增加一个名字。语句是要在运行时执行的程序代码。因此,如果声明不带初始化,那么可以完全由编译器完成,不会产生运行时执行的代码。

声明语义就是静态语言的处理,执行语义就是动态语言的处理。这是两种语言范型的分水岭。

表达式与语句

如果说在语法 var x = 100 中, = 100 是向 x 绑定值,那么 var x 就是单纯的标识符声明。这意味着非常重要的一点—— x 只是一个表达名字的、静态语法分析期作为标识符来理解的字面文本,而不是一个表达式。

var 声明语法中,变量名位置上就是写不成 a.x 的。

var a.x = ...   // 这里将导致语法出错

所谓 a.x 也是一个表达式,其结果是一个引用

a.x = a = { n: 2 }

计算单值表达式 a,得到 a 的引用;

将右侧的名字 x 理解为一个标识符,并作为 . 运算的右操作数;

计算 a.x 表达式的结果(Result)

保存在 a.x 这个引用中的 a 是当前的 { n: 1 } 这个对象。

接下来再继续往下执行:

a = { n: 2 }

左操作数 a 作为一个引用被覆盖了,这个引用仍然是当前上下文中的那个变量 a。因此,这里真实地发生了一次 a = { n: 2 }

最左侧的 a.x 的计算结果中的原始的变量a在引用传递的过程中丢失了,且 a.x 被同时丢弃。

那么现在,表达式最开始被保留在一个结果(Result) 中的引用 a 会更新吗?不会的。这是因为那是一个运算结果(Result),这个结果有且仅有引擎知道,它现在是一个引擎才理解的引用(规范对象),对于它可能操作的只有:取值或置值(GetValue/PutValue),以及作为一个引用向别的地方传递等。

let obj = {
    foo() {
        return this
    }
}

obj.foo() === obj // true foo在对象上面的引用
eval('obj.foo')() == obj // false 因为 eval 返回的是一个结果不支持引用,所以返回foo的时候就返回了它本身
  • 执行表达式:obj.foo() 可以返回的是一个引用,函数式范型的体现;
  • 执行语句:eval('obj.foo') 返回的是值,命令式范型的体现;