JavaScript的变量和函数提升-笔记

1. 提升现象

直觉上认为JavaScript代码在执行时是由上到下一行一行执行的。但实际上这并不完全正确。
思考如下代码:

a = 2;
var a;
console.log(a); // 这里会打印什么呢?

可能会认为会输出undefined,因为var a声明在a = 2之后,会自然而然的认为变量被重新赋值了,因此会赋予默认值undefined。但是这里输出的结果是2。
考虑如下代码:

console.log(a);
var a = 2;

这里会输出什么呢?
根据第一个代码片段可能会认为输出为2。或者由于变量a在使用前没有进行声明,因此会抛出错误。
到底哪个结论正确呢?
结果是两种猜测都不对,这里输出的是undefined

2. 提升的原因

引擎会在解释JavaScript代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来,这也是前面讲到的词法作用域的核心内容。
因此正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码执行前首先处理
当看到var a = 2;时,可能会认为这是一个声明。但JavaScript引擎实际上会将其看做两个声明:var a;a = 2;。第一个定义声明是在编译阶段进行的。第二个赋值声明会被保留在原地等待执行阶段。
对代码进行如下处理:

var a;
a = 2;
console.log(a); // 2

第一部分是编译,第二部分是执行。
类似的对于第二段代码:

var a;
console.log(a); // undefined
a = 2;

这个过程就好像变量和函数声明从它们所在的代码位置被移动到了最前面。这个过程就叫做提升
注意:只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。如果提升改变了代码的执行顺序,那会造成代码运行的混乱。

foo();
function foo() {
	console.log(a); // undefined
	var a = 2;
}

foo函数的声明被提升了,因此在第一行调用可以正常进行。
每个作用域都会进行提升操作,foo函数自身会在内部对var a;进行提升。因此上面这段代码可以理解为如下:

function foo(){
	var a;
	console.log(a);
	a = 2;
}
foo();

注意:函数声明会被提升,但是函数表达式不会被提升

foo(); // 这里会报错
var foo = function bar() {...}

这段程序中foo()被提升到所分配的作用域,因此foo()不会导致错误,但是foo此时还没有被赋值。foo()相当于undefined(),因此会抛出异常。
同时即使使用具名的函数表达式,名称标识符在赋值之前也无法在所在的作用域使用

foo(); // 报错
bar(); // 报错
var foo() = function bar() {...} // 具名函数

这段代码可以理解为如下代码:

var foo;
foo(); // 报错
bar(); // 报错
foo = function() {...}

3. 函数优先

上面说到函数声明和变量声明都会被提升,但是函数首先会被提升然后是变量。
分析如下代码:

foo(); // 1
var foo;
function foo() {
	console.log(1);
}
foo = function() {
	console.log(2);
}

这里执行foo会输出1而不是2
解析代码:

function foo() {
	console.log(1);
}
foo(); // 1
foo = function () {
	console.log(2);
}

尽管var foo出现在function foo() {...}之前,但是它是重复的声明(因此被忽略了),因为函数声明会被提升到变量声明之前。
尽管重复的var foo声明会被忽略掉,但后面的函数声明还是可以覆盖前面的。

foo(); // 3
function foo() {
	console.log(1);
}
var foo = function (){
	console.log(2);
}
function foo() {
	console.log(3);
}

注意:一个普通的块内部的函数声明通常会被提升到所在的作用域顶部,这个过程不会像下面代码暗示的那样被条件语句控制。

foo(); // 报错
if(true) {
	function foo(){
		console.log(1);
	}
}else {
	function foo(){
		console.log(2);
	}
}

这种情况在JavaScript未来版本可能会发生改变,但是要尽可能避免在块内部使用。

4. 小结

我们可能习惯的将var a = 2;当做一个声明,在实际中JavaScript引擎会将var a;a = 2当做两个单独的声明,第一个是编译阶段的任务,第二个是执行阶段的任务。
这就意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,所有声明的变量和函数都会被移动到各自作用域的最顶端,这个过程就是提升。声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候,否则会引起一些意想不到的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值