javascript对变量和函数的声明提早‘hoist’

hoist 
vt.升起,提起; 
vi.被举起或抬高; 
n.起重机,升降机; 升起; <俚>推,托,举;javascript

 

原文地址:http://www.bootcss.com/article/variable-and-function-hoisting-in-javascript/css

这篇文章写的真不错,一看就明白了,先收藏!java

 

这篇文章不讲英语,可是对于某些英语单词找不到很好的翻译,一上来就列出“hoist”这个单词的释义是为了让你们有个准备,我在这里将此单词翻译为“提早”,是为了解释 JavaScript 语言中很“古怪”的一个特性。编程

变量声明“被提早”

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

先来看一段代码:编码

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

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

(function() {
  // Outputs: undefined
  console.log(declaredLater);

  var declaredLater = "Now it's defined!";

  // Outputs: "Now it's defined!"
  console.log(declaredLater);
})();

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

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

(function() {
  var declaredLater; //声明被提早到做用域开始处了!

  // Outputs: undefined
  console.log(declaredLater);

  declaredLater = "Now it's defined!"; //赋值操做还在原地!

  // Outputs: "Now it's defined!"
  console.log(declaredLater);
})();

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

再来看一个例子:

var name = "Baggins";

(function () {
    // Outputs: "Original name was undefined"
    console.log("Original name was " + name);

    var name = "Underhill";

    // Outputs: "New name is Underhill"
    console.log("New name is " + name);
})();

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

var name = "Baggins";

(function () {
    var name;  //注意:name 变量被提早了!

    // Outputs: "Original name was undefined"
    console.log("Original name was " + name);

    name = "Underhill";

    // Outputs: "New name is Underhill"
    console.log("New name is " + name);
})();

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

函数声明“被提早”

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

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

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

// Outputs: "Yes!"
isItHoisted();

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

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

再来看第二种状况:函数做为值赋值给变量。(还记得吗?在 JavaScript 中,函数也能够做为值赋予变量!)仍是先上代码:

// Outputs: "Definition hoisted!"
definitionHoisted();

// TypeError: undefined is not a function
definitionNotHoisted();

function definitionHoisted() {  
    console.log("Definition hoisted!");
}

var definitionNotHoisted = function () {  
    console.log("Definition not hoisted!");
};

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

总结

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

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

经过练习上面的实例本身多感觉一下。另外,做为最佳实践:变量声明必定要放在做用域/函数的最上方(JavaScript 只有函数做用域!)。

参考文献

相关文章
相关标签/搜索