JavaScript 中对变量和函数声明的“提前(hoist)”
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)”的更多相关文章
- JavaScript 中对变量和函数声明的“提前”
变量声明“被提前” JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对“先声明.后使用”的规则很熟悉,如果使用未经声明的变量或 ...
- (转)JavaScript 中对变量和函数声明的“提前(hoist)”
变量声明“被提前” JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对“先声明.后使用”的规则很熟悉,如果使用未经声明的变量或 ...
- JavaScript 中对变量和函数声明提前的演示样例
如题所看到的,看以下的演示样例(能够使用Chrome浏览器,然后F12/或者右键,审查元素.调出开发人员工具,进入控制台console输入)(使用技巧: 控制台输入时Shift+Enter能够中途代码 ...
- 在javascript中关于变量与函数的提升
在javascript中关于变量与函数的提升 一.简介 在javascript中声明变量与函数的执行步骤: 1.先预解析变量或函数声明代码,会把用var声明的变量或者函数声明的代码块进行提升操作 2. ...
- javascript基础之变量和函数声明
1.变量的声名 window.name = 'gjlin' ; //全局变量 直接name = 'gjlin' 也表示全局变量,但是建议使用window.name = 'gjlin' 这种形式表示 ...
- JavaScript中的变量定义和声明
变量声明旨在分配内存,定义为这个分配的内存分配一个值.
- JavaScript 中定义变量时有无var声明的区别
关于JavaScript中定义变量时有无var声明的区别 var a=5; //正确 a=5; //正确 在javascript中,以上两种方法都是定义变量的正确方法.微软的Script56.CHM中 ...
- JavaScript(1)——变量、函数声明及作用域
这是我的第一篇博客文章,本人不才,文笔也不好,所以可能写的有点凌乱.有什么不对的地方还望见谅.不过每天进步一小步,总有一天会迈出那一大步.以下内容是我对变量.函数声明及函数表达式.作用域的理解. [变 ...
- JS中的提升(即变量和函数声明移动到代码顶部)
先看代码(第一个代码片段): console.log(a); var a = 1; 如果你认为这是一段不合法的代码,在调用console.log()的时候会输出undefined,你完全正确.但是如果 ...
随机推荐
- 第4章 同步控制 Synchronization ----同步机制的摘要
同步机制摘要Critical Section Critical section(临界区)用来实现"排他性占有".适用范围是单一进程的各线程之间.它是: 一个局部性对象,不是一个核 ...
- TCHAR
定义 TCHAR :通过define 定义的字符串宏 因为C++支持两种字符串:常规的ANSI编码 (使用""包裹).Unicode编码(使用L" "包裹).因 ...
- 【BZOJ】1015 [JSOI2008]星球大战starwar(并查集+离线处理)
Description 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过 ...
- jQuery经典案例
示例1:鼠标点击左侧菜单实现打开和关闭功能: html及css代码部分: <!DOCTYPE html> <html lang="en"> <head ...
- windows 2008 R2 断电重启进入修复模式
windows 2008 R2 意外断电重启进入修复模式 现在出现一个问题,就是当机房停电的时候,计算机自动进入到修复模式,当人不在机房的时候,容易造成服务器无法访问,我相信正常启动应该没什么问题吧, ...
- Jquery 多选全选/取消 选项卡切换 获取选中的值
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- 配合JdbcUtils最终版重写QueryRunner
在使用QueryRunner类的时候,直接new本类,无需传递连接池或连接,如果是普通连接,最终释放连接 /** * * 在使用QueryRunner类的时候,直接new本类,无需传递连接池或连接 * ...
- 如何抽象一个 Vue 公共组件
之前一直想写一篇关于抽象 Vue 组件的随笔,无奈一直没想到好的例子.恰巧最近为公司项目做了一个数字键盘的组件,于是就以这个为例聊聊如何抽象 Vue 的组件. 先上 Demo 与 源码.(demo最好 ...
- 【特效】jquery选项卡插件,页面多个选项卡统一调用
把选项卡整合了一下,写成个插件,这样可以整个站,或整个页面有多个选项卡时,统一调用.代码的具体注意事项已经写进注释了.用于js获取元素的class名称必须有,选项卡本身的样式,另再取一个名字来设置(本 ...
- MYSQL 数据库高频查询语句整理
一查询数值型数据: SELECT * FROM tb_name WHERE sum > 100; 查询谓词:>,=,<,<>,!=,!>,!<,=>,= ...