你不知道的JavaScript--Item6 var预解析与函数声明提高(hoist )

一、var 变量预编译

JavaScript 的语法和 C 、Java、C# 相似,统称为 C 类语法。有过 C 或 Java 编程经验的同窗应该对“先声明、后使用”的规则很熟悉,若是使用未经声明的变量或函数,在编译阶段就会报错。然而,JavaScript 却可以在变量和函数被声明以前使用它们。下面咱们就深刻了解一下其中的玄机。javascript

先来看一段代码:java

(function() { console.log(noSuchVariable);//ReferenceError: noSuchVariable is not defined })();

运行上面代码立马就报错,不过,这也正是咱们指望的,由于 noSuchVariable 变量根本就没有定义过嘛!再来看看下面的代码:编程

(function() { console.log(declaredLater); //undefined var declaredLater = "Now it's defined!"; console.log(declaredLater);// "Now it's defined!" })();

首先,上面这段代码是正确的,没有任何问题。可是,为何不报错了?declaredLater 变量是在调用语句后面定义的啊?为何竟然输出的是 undefined?markdown

这实际上是 JavaScript 解析器搞的鬼,解析器将当前做用域内声明的全部变量和函数都会放到做用域的开始处,可是,只有变量的声明被提早到做用域的开始处了,而赋值操做被保留在原处。上述代码对于解析器来讲实际上是以下这个样子滴:闭包

(function() { var declaredLater; //声明被提早到做用域开始处了! console.log(declaredLater); // undefined declaredLater = "Now it's defined!"; //赋值操做还在原地! console.log(declaredLater);//"Now it's defined!" })();

这就是为何上述代码不报异常的缘由!变量和函数通过“被提早”以后,declaredLater 变量其实就被放在了调用函数的前面,根据 JavaScript 语法的定义,已声明而未被赋值的变量会被自动赋值为 undefined ,因此,第一次打印 declaredLater 变量的值就是 undefined,后面咱们对 declaredLater 变量进行了赋值操做,因此,第二次再打印变量就会输出Now it’s defined!。app

再来看一个例子:函数

var name = "Baggins";
(function () {
    console.log("Original name was " + name);// "Original name was undefined"
    var name = "Underhill";
    console.log("New name is " + name);// "New name is Underhill"
})();

上述代码中,咱们先声明了一个变量 name ,咱们的本意是但愿在第一次打印 name 变量时可以输出全局范围内定义的 name 变量,而后再在函数中定义一个局部 name 变量覆盖全局变量,最后输出局部变量的值。但是第一次输出的结果和咱们的预期彻底不一致,缘由就是咱们定义的局部变量在其做用域内被“提早”了,也就是变成了以下形式:测试

var name = "Baggins";
(function () {
    var name;  //注意:name 变量被提早了!
    console.log("Original name was " + name);// "Original name was undefined"
    name = "Underhill";
    console.log("New name is " + name);//"New name is Underhill"
})();

因为 JavaScript 具备这样的“怪癖”,因此建议你们将变量声明放在做用域的最上方,这样就能时刻提醒本身注意了。ui

二、函数声明“被提早”

前边说的是变量,接下来咱们说说函数。this

函数的“被提早”还要分两种状况,一种是函数声明,第二种是函数做为值赋值给变量,也即函数表达式

先说第一种状况,上代码:

isItHoisted();//"Yes!"
function isItHoisted() {  
    console.log("Yes!");
}

如上所示,JavaScript 解释器容许你在函数声明以前使用,也就是说,函数声明并不只仅是函数名“被提早”了,整个函数的定义也“被提早”了!因此上述代码可以正确执行。

再来看第二种状况:函数表达式形式。仍是先上代码:

definitionHoisted();// "Definition hoisted!"
definitionNotHoisted();// TypeError: undefined is not a function
function definitionHoisted() {  
    console.log("Definition hoisted!");
}
var definitionNotHoisted = function () {  
    console.log("Definition not hoisted!");
};

咱们作了一个对比,definitionHoisted 函数被妥妥的执行了,符合第一种类型;definitionNotHoisted 变量“被提早”了,可是他的赋值(也就是函数)并无被提早,从这一点上来讲,和前面咱们所讲的变量“被提早”是彻底一致的,而且,因为“被提早”的变量的默认值是 undefined ,因此报的错误属于“类型不匹配”,由于 undefined 不是函数,固然不能被调用。

总结
经过上面的讲解能够总结以下:

  • 变量的声明被提早到做用域顶部,赋值保留在原地
  • 函数声明整个“被提早”
  • 函数表达式时,只有变量“被提早”了,函数没有“被提早”

三、var的反作用

隐式全局变量和明肯定义的全局变量间有些小的差别,就是经过delete操做符让变量未定义的能力。

  • 经过var建立的全局变量(任何函数以外的程序中建立)是不能被删除的。
  • 无var建立的隐式全局变量(无视是否在函数中建立)是能被删除的。

这代表,在技术上,隐式全局变量并非真正的全局变量,但它们是全局对象的属性。属性是能够经过delete操做符删除的,而变量是不能的:

// 定义三个全局变量
var global_var = 1;
global_novar = 2;       // 反面教材
(function () {
   global_fromfunc = 3; // 反面教材
}());

// 试图删除
delete global_var;      // false
delete global_novar;    // true
delete global_fromfunc; // true

// 测试该删除
typeof global_var;      // "number"
typeof global_novar;    // "undefined"
typeof global_fromfunc; // "undefined"

在ES5严格模式下,未声明的变量(如在前面的代码片断中的两个反面教材)工做时会抛出一个错误。

四、单var形式声明变量

在函数顶部使用单var语句是比较有用的一种形式,其好处在于:

  • 提供了一个单一的地方去寻找功能所须要的全部局部变量
  • 防止变量在定义以前使用的逻辑错误
  • 少代码(类型啊传值啊单线完成)
    单var形式长得就像下面这个样子:
function func() {
   var a = 1,
       b = 2,
       sum = a + b,
       myobject = {},
       i,
       j;
   // function body...
}

您可使用一个var语句声明多个变量,并以逗号分隔。像这种初始化变量同时初始化值的作法是很好的。这样子能够防止逻辑错误(全部未初始化但声明的变量的初始值是undefined)和增长代码的可读性。在你看到代码后,你能够根据初始化的值知道这些变量大体的用途。


系列文章导航:

一、你不知道的JavaScript–Item1 严格模式

二、你不知道的JavaScript–Item2 浮点数精度

三、你不知道的JavaScript–Item3 隐式强制转换

四、你不知道的JavaScript–Item4 基本类型和基本包装类型(引用类型)

五、你不知道的JavaScript–Item5 全局变量

六、你不知道的JavaScript–Item6 var预解析与函数声明提高(hoist )

七、你不知道的JavaScript–Item7 函数和(命名)函数表达式

八、你不知道的JavaScript–Item8 函数,方法,构造函数调用

九、你不知道的JavaScript–Item9 call(),apply(),bind()与回调

十、你不知道的JavaScript–Item10 闭包(closure)

十一、你不知道的JavaScript–Item11 arguments对象

十二、你不知道的JavaScript–Item12 undefined 与 null

1三、你不知道的JavaScript–Item13 理解 prototype, getPrototypeOf 和_ proto_

1四、你不知道的JavaScript–Item14 使用prototype的几点注意事项

1五、你不知道的JavaScript–Item15 prototype原型和原型链详解

1六、你不知道的JavaScript–Item16 for 循环和for…in 循环的那点事儿

1七、你不知道的JavaScript–Item17 循环与prototype最后的几点小tips

1八、你不知道的JavaScript–Item18 JScript的Bug与内存管理

1九、你不知道的JavaScript–Item19 执行上下文(execution context)

20、你不知道的JavaScript–Item20 做用域与做用域链(scope chain)

2一、你不知道的JavaScript–Item21 漂移的this


持续更新中……………….

相关文章
相关标签/搜索