相信大家很多时候会遇到这样定义变量的方法:var a = b = 3; 结果大家也都知道,a b的值都是3,但是他们有什么区别呢?
var a = b = 3;发生了什么?
说这个问题之前,我们先了解一些基础知识
声明
JavaScript中有六条声明用的语句:
- let x
- const x
- var x
- function x
- class
- import
除了这6种声明语句之外,还有两个语句可以隐式声明标识符
- for ()
- try ... catch(x) {}
除了这几种声明方法,再没有别的方法可以声明一个标识符,并且声明有以下两个特点:
1 意味着JavaScript将可以通过"静态"语法分析发现那些声明的标识符
2 标识符对应的变量“一定”会在用户代码执行前已经被创建在作用域中了
读取值到赋值
声明是在语法分析阶段处理的,所以代码执行的时候,肯定是可以保证已经存在了。
var 声明的标识符被约定称为"变量声明varDelcs", 并且有初始值undefined,而"let、const"则称为"词法声明(lexicalDelcs)", 没有初始值
其实函数声明是varDelcs, class内部是let, import是const规则
赋值的正确表达式是:
lRef = rValue
将右操作数的值赋给左做操作数的引用,严格表达应该是:
LeftHandSideExpression < = | AssignmentOperator> AssignmentExpression
向一个不存在的变量赋值
ES6之前向一个不存在的变量赋值,结果是:JavaScript 会在全局范围内创建它,这带来的副作用是造成“变量泄漏”
当向一个不存在的变量赋值的时候,由于全局对象的属性表是可以动态添加的,因此 JavaScript 将变量名作为属性名添加给全局对象。而访问所谓全局变量时,就是访问这个全局对象的属性
使用var声明的变量和动态添加 到全局对象的属性有什么不一样呢?
var a = 100;
x = 200;
// `a`和`x`都是global的属性
Object.getOwnPropertyDescriptor(window, 'a');
// { value: 100, writable: true, enumerable: true, configurable: false }
Object.getOwnPropertyDescriptor(window, 'x');
// { value: 200, writable: true, enumerable: true, configurable: true }
// `a`不能删除, `x`可以被删除
delete a; // false
delete x; // true
a; // 100
x; // ReferenceError: x is not defined
从上面的代码可以看到:表面上使用var声明的全局变量,和泄漏到全局的全局变量,都是全局变量
并且都实现为window的属性,但事实上还是有不同,能不能删除和该属性的configurable有关系
回到正题
现在回到今天讨论的这行代码var x = y = 100,在这行代码中,等号的右边是一个表达式y = 100,它发生了一次“向不存在的变量赋值”,所以它隐式地声明了一个全局变量y,并赋值为 100。
赋值表达式操作本身也是有结果的,是右操作数的值,但也只是值,不是引用, 下面的例子中,a是一个函数
// 调用obj.f()时将检测this是不是原始的obj
> obj = { f: function() { return this === obj } };
// false,表明赋值表达式的“结果(result)”只是右侧操作数的值,即函数f
> (a = obj.f)();
false
声明和语句的区别在于发生的时间点不同,声明发生在编译期,语句发生在运行期。