JavaScript声明和赋值傻傻分不清?

109 阅读3分钟

变量声明对应的语义

编程语言中的变量声明, 一般都会干这两件事情

  1. 在当前环境中登记一个名字
  2. 还没有执行前就可以在当前环境中找到这个名字

在JavaScript中一共有六种声明方式,对应的语法如下:

  1. var/let/const x
  2. class x
  3. function x
  4. import x
  5. try{} catch(x)
  6. for(let/var/const x)

上面说过在代码还没有执行的时候,就可以在当前环境中找到声明的名字, 在一些静态类型的语言中往往会给变量绑定一个对应类型的值, 但是由于JavaScript的动态性, 我们只能给JavaScript绑定一个undefined值, (注意这项待遇只有varDecls声明的变量有), 并且你可以访问到, 但是lexericlDecls声明是不具备的这种特性的(且表现形式为拒绝访问)。

console.log(a);
console.log(b);
var a;
let b;

在ECMA规范中, 将类似var声明的名字称之为变量声明(varDecls), 而let/const声明的称之为词法声名(lexericalDecls), 其余的声明方式为

  1. 函数(function)name声明是按照varDecls的规则声明的
  2. 类(class)的的名字是按照let来处理的(lexicalDecls)
  3. import导入的名字是按照const的规则来处理的(lexicalDecls)

变量赋值对应的语义

变量泄漏

在JavaScript1.0时期, 有一个非常奇怪的东西, 那就是在变量泄漏问题, 如果你向一个不存在的变量名赋值, 那么JavaScript会在全局中创建它, 因为在JavaScript的全局环境是引擎使用一个称为全局对象的东西进行管理, 同时对象的属性可以随意添加(这个特性在Python中也会存在)

我认为这种特性对编程语言是有害处的, 应该加以限制

window = {};
x = 1;
console.log(window.x);

delete x;

那么在ES6之后是否会发生一点改变呢? 为了向前面进行兼容, 仍然是通过全局对象初始化这样的一个全局闭包来实现的, 但是为了做点区分, ECMA在全局对象之外再次维护一个变量名字列表,这个变量名字列表通常会放上两种类型的名字, 一种是使用varDecls声明的变量名, 另外一种是通过eval中使用var声明的名字, 而且比较特殊的是不可以使用delete删除。

变量赋值

变量赋值表示一种求值的结果与一个名字关联, 在我看来它表示一种数据的流向 比如:

a = 1;
b = 2 - 1;
person.name = "wy";

在JavaScript中还有一个比较坑的地方(我的感受), 那就是赋值表达式返回值,你可以写出下面这种类型的东西:

var a = b = 1;

后面这个表达式b=1它返回了一个值,这个值为1,我的想法是前面这个赋值表达式只是将1放进b中了, 这个动作发生就发生了, 它根本不应该返回一个值, 这样会导致错误比较难以追踪。因为你可以把这样的值到处传播, 搞得程序分析困难。

赋值模版

赋值模版是一种类似于赋值的过程, 因为可能没有上面的等号比如下面这种

{name: 1}

function add({a, b}) {
  console.log(a + b);
}

var obj = {a:1, b:2};
add(obj);

左侧这个东西叫赋值模版, 右侧的东西称之为值, 而所有这些地方的模版赋值, 都是在语法分析期间被分析出来, 并在JavaScript内部作为一个可执行结果存放着, 然后在执行期间, 会使用它们来完成一个从右边操作数按照模版取值, 并赋值给左操作数的过程。


到这里你应该对JavaScript的声明和赋值有一个很感性的认识了吧