var x = y = 100:声明语句与语法改变了JavaScript 语言核心性质
1. 变量提升
console.log(hello); // undefined
if (true) {
var hello = '234';
}
从上面代码可以看到,变量 hello 已经提升到顶层去声明了,所以 console.log(hello) 可以得到 undefined 的结果(下面会解释为什么会得到这个值)。这种现象就称为,变量提升。
那么问题来了,是否只有 var 存在变量提升呢?
答案是否定的。let/const/var 都存在变量提升。
既然存在变量提升,那为什么以下代码中 y 会出错呢?
x = y = "global";
(function() {
x; // undefined
y; // Reference error: y is not defined
var x = "local";
let y = "local";
}());
标识符(Identifier)就是一个名字,用来对变量、函数、属性、参数进行命名,或者用做某些循环语句中的跳转位置的标记
其实是因为 javaScript 拒绝访问未绑定值的 let/const 标识符,如果访问了,那就返回 Reference error: xxx is not defined 这是正常的。所以这个报错并不能说明let声明的变量没有提升,只能说明了 let 声明的变量,未绑定值而已。
那么根据 var 跟 let 的不同这里可以得到一个结论:var 在声明时,会自动初始化绑定了一个 undefined 值 而 let/const 创建后并不会自动初始化绑定值(也没有初始化这一步),他们声明的变量缺省的情况就是没有绑定值的标识符。因此他们在绑定值之前,不允许访问。
那么将引出下一个问题:为什么 let/coust 创建后为什么不会自动初始化绑定值呢?
这个问题,答案可能是:规则就是这样定义的。但真实情况我不清楚,留待以后回来填坑。
回到主题来,以上,说明了,报错的原因不是因为变量未提升就访问导致的,那变量到底有没有提升了呢?仔细看下报错,以及全局变量 y 。我们可以假如没有发生变量提升,那么,函数内的 y 应打印全局变量的值,但是这里报错了,是不是可以说明,已经发生了变量提升了呢?
相信你也听说过,let/const 声明后会有一个暂时性死区。也就是声明但未绑定值之前,不允许访问,访问就会报错。相信你看到这里,应该知道是怎么回事了吧。
let / const 声明的标识符不会绑定初值,而javascript拒绝访问未绑定值的 let/const 声明的标识符,同时又存在变量提升了,变量声明提到了该块的最顶层,所以,直到他们声明的标识符赋值完成之前,都不允许访问的,于是造成了声明时的绑定的那一块区域,不允许访问/赋值,称为暂时性死区。
再次引出一个问题,或者说争议,在ECMAScript6 入门中 let命令中 提到:let 不存在变量提升 和 暂时性死区的解释。跟现在说的均不同。
但查询资料时,发现还是有支持let存在变量提升这个意见的,如 Are variables declared with let or const not hoisted in ES6?
对此分歧,我坚持我的解释是正确的。因为我觉得这样的解释不存在矛盾点,可以解释清楚原因。
2. var ,let ,const 区别
声明:
在全局环境中,var 是声明在 global 的属性表中,且在 varNames 中有一个登记,并自定初始化绑定初值(undefined)。
而let,const 声明在词法环境中,跟 global 区分开来,不能通过 window.x 这种方式访问,且没有初始化绑定初值,所以存在暂时性死区。
发现他们都不能删除。var 可以通过 Object.getOwnPropertyDescriptor(window, 'xx') 来看 configurable 为 false 知晓不可删除。let 和 const 无法得知。仅从浏览器中验证得到(let a = 100;delete a;//false)。
作用域:
再回头看文章刚开始的代码:
console.log(hello); // undefined
if (true) {
var hello = '234';
let a = 123;
}
console.log(hello);// '234'
console.log(a); // a is not defined
显而易见地看到,新增加的 let 部分的代码,并不能脱离声明后的那一块区域来使用,而var 畅通无阻。
3. varNames
为了兼容旧的 JavaScript 语言设计,现在的 JavaScript 环境仍然是通过将全局对象初始化为这样的一个全局闭包来实现的。但是为了得到一个“尽可能”与其它变量环境相似的声明效果(varDecls),ECMAScript 规定在这个全局对象之外再维护一个变量名列表(varNames),所有在静态语法分析期或在 eval() 中使用var声明的变量名就被放在这个列表中。然后约定,这个变量名列表中的变量是“直接声明的变量”,不能使用delete删除。但 varNames不限制删除。一个变量能否删除,是由其他机制来确保的。如全局环境中,使用global属性的configurable性质;而函数中则使用 CreateMutableBinding()的传入的第二个参数(isDeletable)来就决定的。
从引擎的角度上来说,如果没有varNames,它也不能在运行期识别哪个全局变量是从var声明来的,而哪个又是直接在global上创建的属性。
4. 表达式和语句的区别
表达式发生在编译器,语句发生在运行时。
5. 理解一段代码
console.log(a);// undefined
var a = 100;
// 从词法分析看,并不等同以下代码
var a; // 赋值undefined
console.log(a); // 得到初值 undefined
a = 100;
应该从变量声明提升,且同时初始化绑定初值,然后赋值100,这样的分析才对。
但第二段代码的分析看起来也是这样呀,为什么说不等同呢。
因为第二段代码时显示地绑定值(也就是 var a ),可以说这是第二次绑定值了,第一次就是初始化的时候绑定初值 undefined 了,然后 运行到 var a 时 这个语句等同于 var a = undefined。所以,他们看起来像是一回事,但其实不是。
6. 再次理解一段代码
obj = { f: function() { return this === obj } };
(a = obj.f)();// false
(obj.f)();// true
obj.f();// true
首先需要知道赋值运算是怎样的一回事。
简略版:等号右侧是一个值,如果是一个表达式,那就是表达式的值(通过GetValue来取到的),等号左侧,是一个变量的引用。
举个例子,x = x 是指将x的值(GetValue(x))赋给变量 x。
那么,a = obj.f 这个表达式执行的结果是得到了 f 函数,然后 (f)() 即是执行了函数调用,显而易见地,这个 (f)() 跟 obj 一点关系都没有,所以,结果不出意外地是 false 啦。
至于 obj.f() 这个结果返回 true 应该不难理解,此处是执行了挂在在 obj 上的方法 f ,自然会传入 obj 作为 this 啦。
而 (obj.f)() 这个,我的理解是,(obj.f) 这表达式得到的是 obj.f 然后再执行,结果等同于 obj.f() 。
7. 全局环境中,采取词法环境优先的顺序
window.x = 100;
let x = 200;
console.log(x); // 200