JavaScript核心原理-第2课笔记

158 阅读6分钟

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