JavaScript 中对变量和函数声明的“提前(hoist)”的那些事儿

变量声明“被提前”

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

先来看一段代码:

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

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

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

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

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

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

这其实是 JavaScript 解析器搞的鬼,解析器将当前作用域内声明的所有变量和函数都会放到作用域的开始处,但是,只有变量的声明被提前到作用域的开始处了,而赋值操作被保留在原处。上述代码对于解析器来说其实是如下这个样子滴:

(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!

再来看一个例子:

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 不是函数,当然不能被调用。补充:

<script type="text/javascript">   function aaa(){    return 5;  }  window.onload=function(){  var x=aaa; //var x=aaa();  alert(x); }

总结

通过上面的讲解可以总结如下:

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

通过练习上面的实例自己多感受一下。另外,作为最佳实践:变量声明一定要放在作用域/函数的最上方(JavaScript 只有函数作用域!

时间: 2024-10-09 04:24:34

JavaScript 中对变量和函数声明的“提前(hoist)”的那些事儿的相关文章

JavaScript 中对变量和函数声明的提前示例

如题所示,看下面的示例(可以使用Chrome浏览器,然后F12/或者右键,审查元素.调出开发者工具,进入控制台console输入)(使用技巧: 控制台输入时Shift+Enter可以中途代码换行) var name = "xiaoming"; (function(){ var name = name || "小张"; console.info(name); })();// 小张 (function(){ name = name || "小张";

(转)JavaScript 中对变量和函数声明的“提前(hoist)”

变量声明“被提前” JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对“先声明.后使用”的规则很熟悉,如果使用未经声明的变量或函数,在编译阶段就会报错.然而,JavaScript 却能够在变量和函数被声明之前使用它们.下面我们就深入了解一下其中的玄机. 先来看一段代码: (function() { //ReferenceError: noSuchVariable is not defined console.log(noS

JavaScript 中对变量和函数声明的“提前(hoist)”

hoist vt.升起,提起; vi.被举起或抬高; n.起重机,升降机; 升起; <俚>推,托,举; 这篇文章不讲英语,但是对于某些英语单词找不到很好的翻译,一上来就列出“hoist”这个单词的释义是为了让大家有个准备,我在这里将此单词翻译为“提前”,是为了解释 JavaScript 语言中很“古怪”的一个特性. 变量声明“被提前” JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对“先声明.后使用”的规则很熟悉,如果

javascript基础之变量和函数声明

1.变量的声名 window.name = 'gjlin' ; //全局变量  直接name = 'gjlin'  也表示全局变量,但是建议使用window.name = 'gjlin' 这种形式表示 var name = 'gjlin';   //局部变量  var name = 'gjlin' 这种形式表示,一定要记录前面的加  var. 2.js的注释         单行: //           多行:  /* 内容*/ 3.函数的定义和执行 name = '123'  Foo()

JavaScript(1)——变量、函数声明及作用域

这是我的第一篇博客文章,本人不才,文笔也不好,所以可能写的有点凌乱.有什么不对的地方还望见谅.不过每天进步一小步,总有一天会迈出那一大步.以下内容是我对变量.函数声明及函数表达式.作用域的理解. [变量]大意上来讲,变量就是所定义的一个可以用来保存值的占位符.定义变量时要使用var操作符. 变量所保存类型可以被改变,但不建议. var操作符定义的变量将成为该变量的作用域中的局部变量,在函数退出后,变量将被销毁. 若省略var操作符可以定义全局变量,不过在严格模式下会导致抛出ReferenceEr

javascript中的立即执行函数(function(){…})()

javascript中的立即执行函数(function(){…})() 深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是用(function(){…})()包住业务代码,使用jquery时比较常见. ( function(){…} )()和( function (){…} () )是两种javascript立即执行函数的常见写法,最初我以为是一个括号包裹匿名函数,再在后面加个括号调用函数,最后达到函数定义后立即执行的目的,后来发现加括号的原因并非如此.要

javascript语法基础-变量与函数

三 javascript语法基础-变量与函数 (一)变量的声明与运用 JavaScript中的变量与Java.C等强类型语言有很大区别,虽然在JavaScript中具有字符串.数字等数据类型. 变量申明语句的结构是var保留字加标识符,var和标识符之间用空格隔开. 赋值语句的结构是在变量和需要赋的值之间加上一个等号,例如a=1的含义是将变量a的值指定为1. 变量在定义的时候也可以同时赋值,如var a=1. PS:在变量使用前事先进行声明是个良好的编程习惯,这对将来学习Java等其他语言有帮助

以window作为Global目标函数效果域中的变量的函数

匿名函数与闭包运用函数表达式界说函数时,形如varfunctionNam=functionarg0,agr1{}即为匿名函数,由于function关键词后边没有标识符.闭包是指有权拜访另一个函数效果域中的变量的函数.经过在一个函数内创建另一个函数,可以创建闭包.?javascript中,只要办法是有效果域的办法中声明的变量在外部是无法拜访的2.BOMwindow目标表明浏览器的一个实例.既是经过javascript拜访浏览器窗口的一个接口,又是? ECMA Script规则的Global目标.

深入理解javascript中的立即执行函数(function(){…})()

这篇文章主要介绍了深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是用(function(){-})()包住业务代码,使用jquery时比较常见,需要的朋友可以参考下http://www.jb51.net/article/50967.htm javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解. ( function()