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,你完全正确.但是如果 ...
随机推荐
- uva 10391
这个题,单纯做出来有很多种方法,但是时间限制3000ms,因此被TL了不知道多少次,关键还是找对最优解决方法,代码附上: #include<bits/stdc++.h> using nam ...
- "HK"日常之用C# Process写一只小病毒
众所周知,Process可以启动和关闭电脑上的进程,命名空间位于 System.Diagnostics,本次的教程就是利用其中的GetProcesses和Kill方法来实现的. 一.编码工作 首先我们 ...
- Print Article hdu 3507 一道斜率优化DP 表示是基础题,但对我来说很难
Print Article Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others)To ...
- VB.net shell、IO.File.Open、Process.Start、Shellexecute API 运用经验总结
打开文件还有很多方法,但我了解到运用较多的是上面几种- -,为了防止以后忘记,先把了解到的写下来. 1.Shell 这个看了很多网页,最靠谱的运用方法: Shell("cmd.exe /c ...
- 聊聊Java语言中的单例
package com.xinke.mybatis.test; public class TestSingleton { private static TestSingleton ts = null; ...
- iOS在类内部怎么访问实例变量比较好?
OC在类文件的内部访问实例变量,有直接访问和使用getter/setter方法访问两种方式,它们的区别有: 1.直接访问不经过OC的方法分发(method dispatch),所以访问速度比较快,在这 ...
- jquery层次选择器:空格 > next + nextAll ~ siblings
全栈工程师开发手册 (作者:栾鹏) jquery系列教程1-选择器全解 jquery层次选择器 jquery层次选择器,包括空格.>.next.+.nextAll.~.siblings等函数或表 ...
- vim 自动在操作符 前后加上空格 C语言
function! Align_Space() let current_line = getline('.') let replacement = substitute(current_line,'\ ...
- vue——实例方法 / 数据
目录 1.vm.$set 2.vm.$watch 3.vm.$delete 1.vm.$set 哎呀呀,周五啦,大家应该都很开心吧,放假了可以好好休息休息啦,然而小颖明天要面试,所以小颖今天就回去放了 ...
- 初学python之,IDLE安装
首先新手上路,写的第一篇博客,希望大家不要吐槽. 1.首先在python官网下载最新python版本 https://www.python.org/(注意根据自己的操作系统来选版本) 安装很简单傻瓜式 ...