javascript对变量和函数的声明提前‘hoist’
hoist
vt.升起,提起;
vi.被举起或抬高;
n.起重机,升降机; 升起; <俚>推,托,举;
原文地址:http://www.bootcss.com/article/variable-and-function-hoisting-in-javascript/
这篇文章写的真不错,一看就明白了,先收藏!
这篇文章不讲英语,但是对于某些英语单词找不到很好的翻译,一上来就列出“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 只有函数作用域!)。
参考文献
- Back to Basics: JavaScript Hoisting
- JavaScript Hoisting Explained
- MDN JavaScript reference: var
- MDN JavaScript reference: Scope Cheatsheet
javascript对变量和函数的声明提前‘hoist’的更多相关文章
- Javascript 变量、函数的声明
javascript变量 全局变量和局部变量 按照变量的作用域来区分,和大多数编程语言类似,javascript变量也分为全局变量和局部变量.全局变量的作用域是整个js文件,而局部变量的作用域是 ...
- JavaScript中变量和函数声明的提升
现象: 1.在JavaScript中变量和函数的声明会提升到最顶部执行. 2.函数的提升高于变量的提升. 3.函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找. 4.匿名函数不会提升. ...
- JS基础_变量的声明提前、函数的声明提前
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- JavaScript进阶系列01,函数的声明,函数参数,函数闭包
本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...
- 关于javascript中变量及函数的提升
javascript中变量以及函数的提升,在我们平时的项目中其实还是挺常用的,尤其是大型项目中,不知不觉就会顺手添加一些变量,而有时候自己的不小心就会酿成一些不必要错误,趁有时间整理一下自己对于js中 ...
- JavaScript权威设计--JavaScript变量,作用域,声明提前(简要学习笔记四)
1.宿主对象与宿主环境 宿主对象:由ECMAScript实现的宿主环境提供的对象,可以理解为:浏览器提供的对象.所有的BOM和DOM都是宿主对象. 宿主环境:一般宿主环境由外壳程序创建与维护,只要 ...
- 关于JavaScript的变量和函数提升
第一种理解方式:let和const不能被使用,直到他们被声明 对于var定义的变量,解析器会提升其到作用域顶部. // Outputs: undefined console.log(x); var x ...
- JavaScript局部变量变量和函数命名提升
之前接触了一些javascript局部变量命名提升的问题但是一直没有总结今天特地好好总结一下 变量提升 一个变量的作用域是程序源代码中定义的这个变量的区域.全局变量拥有全局作用域,在javascrip ...
- 声明提前(hoist)
程序执行前,都会先找到var声明的变量和function声明的函数. 一.var声明的变量 程序 结果 console.log(a); var a=10; console.log(a); //unde ...
随机推荐
- 运维命令:tcpdump
tcpdump命令 tcpdump 命令是一款sniffer工具,它可以打印所有经过网络接口的数据包的头信息,也可以使用 -w 选项将数据包保存到文件中,方便以后分析. 常用参数: -a:尝试将网络 ...
- Linux的POSIX线程属性
创建POSIX线程的函数为 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routin ...
- Log4j配置概述
一.Log4j 简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合 ...
- 黄聪:使用Add-on SDK开发火狐扩展
2014年3月7号更新:火狐已经关闭Add-on SDK服务啦!我也转用360急速浏览器来开发浏览器插件了. 如果精通JS.HTML.CSS开发的朋友,直接看这个教程估计就能懂了 ---------- ...
- coding style 的两点
通俗介绍coding style两点建议: 模块划分 这个如果做得不好,简直不能忍.有的代码非常莫名其妙,有些东西本身不复杂,非要将其拆成好几个部分,然后做成一个一个碎散的模块,这样并不好.举个例子, ...
- jmeter踩坑系列
1.踩坑系列一: 抓包出来有host的字段,放到jmeter里面一起请求就报错了,去掉就请求正常了 1.踩坑系列二: 从花瓶复制过去 的values 前面有空格,肉眼看起来没有
- python高手的自修课
python高手的自修课 作者:相国大人 目录 0.第0课:前言与参考文献 目标读者: 具有一定python基础的编程爱好者. 本系列博文为了尽可能少说废话,凡是能够用代码表达的,都尽量直接用代码.读 ...
- error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory
编译出现如下错误: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such ...
- EF中创建、使用Oracle数据库的Sequence(序列)功能
** 背景 ** 项目中订单号原来的生成规则由日期加随机数组成,后期需求决定将订单号生成规则更改为生成日期加当天当前订单数. 每天的订单数都是从0开始的,每生成一个订单,订单数就应该加1.订单数应该是 ...
- Django-MTV模型
MTV模型 Django的MTV分别代表: Model(模型):负责业务对象与数据库的对象(ORM) Template(模版):负责如何把页面展示给用户 View(视图):负责业务逻辑,并在适当的时候 ...