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 ...
随机推荐
- Android Activity活动状态及生存周期
1.活动状态 每个活动在其生命周期中最多可能会有4中状态. (1)运行状态 当一个活动位于返回栈的栈顶时,此时活动就处于运行状态.系统不会回收处于运行状态的活动. (2)暂停状态 当一个活动不再处于栈 ...
- MxNet C++和python环境配置
MxNet C++和python环境配置 安装文件: 1.为了与python已经安装好的版本一致,在这个网站下载mxnet 1.0.0的源码 https://github.com/apache/inc ...
- beego的配置文件记录
摘自https://github.com/beego/tutorial/blob/master/zh/3/params.slide * beego的默认参数 - AppName 应用名称,默认是 be ...
- jmetr _MD5加密_获取签名
要达到的目的: app每个请求里面 请求头都带有一个 sign 的参数, 他的值是通过 开发自己设计的拼接方式 再通过md5加密生成 我们就是要生成这个sign的值出来 准备: 和开发要到签名组成公式 ...
- PS1 长命令回到行首进行覆盖
linux shell 命令输入过长导致折行后的命令回到行首进行覆盖,第二行的时候又能自动换到下一行了,导致这个问题的原因是没有设置正确的 PS1 变量 家目录下打开 vim .bashrc 修改 P ...
- 安装scikit-image问题
参考地址: Image Processing Using Python https://code.tutsplus.com/tutorials/image-processing-using-pytho ...
- 【BZOJ】2819: Nim(树链剖分 / lca+dfs序+树状数组)
题目 传送门:QWQ 分析 先敲了个树链剖分,发现无法AC(其实是自己弱,懒得debug.手写栈) 然后去学了学正解 核心挺好理解的,$ query(a) $是$ a $到根的异或和. 答案就是$ l ...
- 【UVA】1589 Xiangqi(挖坑待填)
题目 题目 分析 无力了,noip考完心力憔悴,想随便切道题却码了250line,而且还是错的,知道自己哪里错了,但特殊情况判起来太烦了,唯一选择是重构,我却没有这勇气. 有空再写吧,最近真的 ...
- PHP环境基础配置
域名访问设置(本地局域网) 用记事本打开 127.0.0.1是本地回环地址 配置完后 通过在本地浏览器输入www.0705.com就可以访问本地站点了 Wamp集成环境多站点配置 配置条件: 一个服务 ...
- ERROR无法从静态上下文中引用非静态变量
ERROR无法从静态上下文中引用非静态变量 2012-06-16 20:58:52 分类: Java 什么是“static”? 学习过java.C++或C的人都应该认识这个关键字.用这个关键字修饰的变 ...