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

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

变量声明“被提前”

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

总结

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

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

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

JavaScript 中对变量和函数声明的“提前(hoist)”的更多相关文章

  1. JavaScript 中对变量和函数声明的“提前”

    变量声明“被提前” JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对“先声明.后使用”的规则很熟悉,如果使用未经声明的变量或 ...

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

    变量声明“被提前” JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对“先声明.后使用”的规则很熟悉,如果使用未经声明的变量或 ...

  3. JavaScript 中对变量和函数声明提前的演示样例

    如题所看到的,看以下的演示样例(能够使用Chrome浏览器,然后F12/或者右键,审查元素.调出开发人员工具,进入控制台console输入)(使用技巧: 控制台输入时Shift+Enter能够中途代码 ...

  4. 在javascript中关于变量与函数的提升

    在javascript中关于变量与函数的提升 一.简介 在javascript中声明变量与函数的执行步骤: 1.先预解析变量或函数声明代码,会把用var声明的变量或者函数声明的代码块进行提升操作 2. ...

  5. javascript基础之变量和函数声明

    1.变量的声名 window.name = 'gjlin' ; //全局变量  直接name = 'gjlin'  也表示全局变量,但是建议使用window.name = 'gjlin' 这种形式表示 ...

  6. JavaScript中的变量定义和声明

    变量声明旨在分配内存,定义为这个分配的内存分配一个值.

  7. JavaScript 中定义变量时有无var声明的区别

    关于JavaScript中定义变量时有无var声明的区别 var a=5; //正确 a=5; //正确 在javascript中,以上两种方法都是定义变量的正确方法.微软的Script56.CHM中 ...

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

    这是我的第一篇博客文章,本人不才,文笔也不好,所以可能写的有点凌乱.有什么不对的地方还望见谅.不过每天进步一小步,总有一天会迈出那一大步.以下内容是我对变量.函数声明及函数表达式.作用域的理解. [变 ...

  9. JS中的提升(即变量和函数声明移动到代码顶部)

    先看代码(第一个代码片段): console.log(a); var a = 1; 如果你认为这是一段不合法的代码,在调用console.log()的时候会输出undefined,你完全正确.但是如果 ...

随机推荐

  1. WaitAll 和 WhenAll 的使用及区别

    用过.net 异步编程的同学都知道,比以前的多线程编程实现起来真的方便很多,今天把WaitAll和WhenAll这两种编程方式回顾总结一下(当然WaitAny.WhenAny是一样的操作) 1:Wai ...

  2. jquery模板下载网站

    jquery模板下载网站 http://www.jqshare.com/

  3. 【转】深入理解CSS定位中的偏移

    前面的话 CSS有三种基本的定位机制:普通流.浮动和绝对定位.利用定位,可以准确地定义元素框相对于其正常位置应该出现的位置,或者相对于父元素.另一个元素甚至浏览器窗口本身的位置.但元素究竟如何定位,定 ...

  4. 当前页面的url未注册 微信支付

    原因1:公众号支付授权目录或测试授权目录设置不正确. 原因2:微信SDK"WxPay.JsApiPay.php"文件中GetOpenid方法中$baseUrl的拼接的结果与支付授权 ...

  5. javascript(js)创建对象的模式与继承的几种方式

    1.js创建对象的几种方式 工厂模式 为什么会产生工厂模式,原因是使用同一个接口创建很多对象,会产生大量的重复代码,为了解决这个问题,产生了工厂模式. function createPerson(na ...

  6. Python面试题之copy/deepcopy详解

    copy和deepcopy有什么区别? http://blog.csdn.net/qq_32907349/article/details/52190796 http://iaman.actor/blo ...

  7. Java面向对象 网络编程 上

     Java面向对象 网络编程 上 知识概要:                     (1)网络模型 (2)网络通讯要素 (3)UDP TCP 概念 (4)Socket (5)UDP TCP 传输 ...

  8. winwebmail设置能用foxmail收发邮件

    域名解析注意 1.首先做A记录解析: 主机名处:输入 mail IP地址处:输入IP地址 2.做MX记录: 主机名处: 大都保持空输入,什么也不用输入   TTL:默认就可以了,不需要改动 优先级:一 ...

  9. springboot高并发redis细粒度加锁(key粒度加锁)

    本文探讨在web开发中如何解决并发访问带来的数据同步问题. 1.需求: 通过REST接口请求并发访问redis,例如:将key=fusor:${order_id} 中的值+1: 2.场景: 设想,多线 ...

  10. php 变量 循环关键词以及方法

    <?php/* 多行注释 */常用数据类型int string double/float bool变量的定义$a = 123;$b = "123";$c = '456';$d ...