【🆘JS基础连环问】变量/函数提升(10问) | 创作者训练营

608 阅读9分钟

让我们先从十个问题开始

让我们先来思考下面代码的输出值,如果下面的问题你都答对了,

并且知道原因,你就可以直接关闭这篇文章了。

// 问题1
a = 1;
var a;
console.log('问题1输出为:', a);

// 问题2
console.log('问题2输出为:', b);
var b = 2;

// 问题3
function hoisting1() {
	console.log('问题3输出为:', c);
	var c = 3;
}

hoisting1();

// 问题4
var d = 4;
function hoisting2() {
	console.log('问题4输出为:', d);
	var d = 44;
}
hoisting2();

// 问题5
hoisting3();

function hoisting3() {
	innerHoisting();
    
	function innerHoisting() {
		console.log('问题5输出为:', '我被提升了!');
	}
}

// 问题6
function hoisting4() {
	function innerHoisting() {
		console.log('问题6输出为:', '我被提升了!嘤嘤嘤');
	}

	innerHoisting();
    
	function innerHoisting() {
		console.log('问题6输出为:', '我被提升了!');
	}
}

hoisting4();

// 问题7
function hoisting5() {
	var innerHoisting = 7;
    
	function innerHoisting() {
		console.log('问题7输出为:', '我被提升了!');
	}
    
	console.log('问题7输出为:', innerHoisting);
}

hoisting5();

// 问题8
hoisting6();

var hoisting6;

function hoisting6() {
	console.log('问题8输出为:', '我被提升了!');
}

hoisting6 = function() {
	console.log('问题8输出为:', '我被提升了!嘤嘤嘤');
}

hoisting6();

// 问题9
var hoisting7 = 7;

function hoisting7() {
	console.log('问题9输出为:', '我被提升了!');
}

console.log('问题9输出为:', hoisting7);

hoisting7 = function() {
	console.log('问题9输出为:', '我被提升了!嘤嘤嘤');
}

// 问题10
a();
b();
c();
d();

var a = function () {
	console.log('a');
};
function b() {
	console.log('b');
};
var c = function d() {
	console.log('d')
};

a();
b();
c();
d();

上面👆的代码输出值为:

  • 问题1输出为:1
  • 问题2输出为:undefined
  • 问题3输出为:undefined
  • 问题4输出为:undefined
  • 问题5输出为:我被提升了!
  • 问题6输出为:我被提升了!
  • 问题7输出为:7
  • 问题8输出为:我被提升了! -- 后续输出 --> 我被提升了!嘤嘤嘤
  • 问题9输出为:7
  • 问题10的输出,请移步至最后

什么是提升(Hoisting)?

JavaScript引擎,在执行编写的JavaScript代码前会先对这些代码进行编译。 在这个编译的过程中,其中有一个工作就是:

找到所有的声明,并将声明的变量及函数提升到当前作用域的顶端

为了理解JavaScript引擎的工作,下面将详细的讲述一下上述问题的工作过程

什么是变量提升?

下面将通过四个问题来深入理解变量提升

问题1

回顾问题1,戳我👈
a = 1;
var a;
console.log('问题1输出为:', a);

根据上面的规则

问题1中的代码,会被JS引擎理解为👇的形式进行处理:

// 声明的变量a被提升到当前作用域的顶端
var a;

a = 1;

console.log('问题1输出为:', a);

我们最终得到的输出为:1

问题2

回顾问题2,戳我👈
console.log('问题2输出为:', b);
var b = 2;

同样的,问题2中的代码,会被JS引擎理解为👇的形式进行处理:

// 声明的变量b被提升到当前作用域的顶端
var b;

console.log('问题2输出为:', b);

b = 2;

我们最终得到的输出为:undefined

问题3

回顾问题3,戳我👈
function hoisting1() {
	console.log('问题3输出为:', c);
	var c = 3;
}

hoisting1();

由于函数上下文的关系,c将会被提升到当前函数作用域的顶端

问题3中的代码,会被JS引擎理解为👇的形式进行处理:

function hoisting1() {
	var c
	console.log('问题3输出为:', c);
	c = 3;
}

hoisting1();

我们最终得到的输出为:undefined

问题4

回顾问题4,戳我👈
var d = 4;
function hoisting2() {
	console.log('问题4输出为:', d);
	var d = 44;
}
hoisting2();

虽然上面的代码中定义了一个全局变量d,但是在hoisting2函数的作用域中,同时也定义了一个函数作用域的变量d

此时,hoisting2函数中的变量d,只会被提升到当前函数作用域的顶端,也就是上面规则中提到的当前作用域的顶端

问题4中的代码,会被JS引擎理解为👇的形式进行处理:

var d;
d = 4;

function hoisting2() {
	var d;
	console.log('问题4输出为:', d);
	d = 44;
}

hoisting2();

我们最终得到的输出为:undefined

什么是函数提升?

下面将通过五个问题来深入理解函数提升

简单的函数提升

还能记起来,刚刚的变量/函数提升的规则么?再来复习一遍:

找到所有的声明,并将声明的变量及函数提升到当前作用域的顶端

问题5

回顾问题5,戳我👈
hoisting3();

function hoisting3() {
	innerHoisting();
    
	function innerHoisting() {
		console.log('问题5输出为:', '我被提升了!');
	}
}

根据刚刚复习的规则,来思考一下,问题5的代码会被如何处理?

问题5中的代码,会被JS引擎理解为👇的形式进行处理:

// 声明的函数:hoisting3,被整个提升到当前作用域的顶端
function hoisting3() {
	// 声明的函数:innerHoisting,被整个提升到当前作用域的顶端
	function innerHoisting() {
		console.log('问题5输出为:', '我被提升了!');
	}
    
	innerHoisting();
}
hoisting3();

我们最终得到的输出为:我被提升了!

假如同时声明了两个相同命名的函数,此时会如何处理?

如果遇到命名了相同名称的方法,此时遵循下面的规则:

后声明的函数,会覆盖先前声明的函数

问题6

回顾问题6,戳我👈
function hoisting4() {
	function innerHoisting() {
		console.log('问题6输出为:', '我被提升了!嘤嘤嘤');
	}

	innerHoisting();
    
	function innerHoisting() {
		console.log('问题6输出为:', '我被提升了!');
	}
}

hoisting4();

结合变量/函数提升遇到两个相同命名的函数的规则

问题6中的代码,会被JS引擎理解为👇的形式进行处理:

function hoisting4() {
	function innerHoisting() {
		// 由于被干掉了,所以嘤嘤嘤
		console.log('问题6输出为:', '我被提升了!嘤嘤嘤');
	}
    
	function innerHoisting() {
		console.log('问题6输出为:', '我被提升了!');
	}
    
	innerHoisting();
}

hoisting4();

后面声明的innerHoisting函数会覆盖前面声明的innerHoisting

我们最终得到的输出为:我被提升了!

假如同时存在相同命名的变量和函数会,此时如何处理?

虽然变量及函数都会被提升,请记住下面一条规则

首先会提升函数,其次才提升变量。

PS:如果同时有命名相同的变量和函数,变量会由于重复声明而被忽略

问题7

回顾问题7,戳我👈
function hoisting5() {
	var innerHoisting = 7;
    
	function innerHoisting() {
		console.log('问题7输出为:', '我被提升了!');
	}
    
	console.log('问题7输出为:', innerHoisting);
}

hoisting5();

根据上面的规则

问题7中的代码,会被JS引擎理解为👇的形式进行处理:

function hoisting5() {
	function innerHoisting() {
		console.log('问题7输出为:', '我被提升了!');
	}
    
	innerHoisting = 7;
    
	console.log('问题7输出为:', innerHoisting);
}

我们可以看到,在函数被提升后,变量声明会覆盖函数

我们最终得到的输出为:7

问题8

回顾问题8,戳我👈
hoisting6();

var hoisting6;

function hoisting6() {
	console.log('问题8输出为:', '我被提升了!');
}

hoisting6 = function() {
	console.log('问题8输出为:', '我被提升了!嘤嘤嘤');
}

hoisting6();

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

问题8中的代码,会被JS引擎理解为👇的形式进行处理:

function hoisting6() {
	console.log('问题8输出为:', '我被提升了!');
}

hoisting6();

hoisting6 = function() {
	console.log('问题8输出为:', '我被提升了!嘤嘤嘤');
}

hoisting6();

我们会先得到输出结果:我被提升了!,再得到输出结果我被提升了!嘤嘤嘤

问题9

回顾问题9,戳我👈
var hoisting7 = 7;

function hoisting7() {
	console.log('问题9输出为:', '我被提升了!');
}

console.log('问题9输出为:', hoisting7);

hoisting7 = function() {
	console.log('问题9输出为:', '我被提升了!嘤嘤嘤');
}

结合问题7和问题8我们得到的经验

问题9中的代码,会被JS引擎理解为👇的形式进行处理

function hoisting7() {
	console.log('问题9输出为:', '我被提升了!');
}

hoisting7 = 7

console.log('问题9输出为::', hoisting7);

hoisting7 = function() {
	console.log('问题9输出为:', '我被提升了!嘤嘤嘤');
}

我们最终得到的输出为:7

最后我们为hoisting7赋了一个新的匿名函数,依旧会生效,如果在结尾加上一行hoisting7(),我们就能得到输出我被提升了!嘤嘤嘤

拓展部分

拓展:函数不同的声明方式会对函数提升造成什么影响?(最后一个问题)

问题10

回顾问题10,戳我👈
a();
b();
c();
d();

var a = function () {
	console.log('a');
};
function b() {
	console.log('b');
};
var c = function d() {
	console.log('d')
};

a();
b();
c();
d();

让我们回忆一下,之前我们解答过的问题,相信应该不难得出答案

// 由于a是一个变量,被提升到顶部的是 var a; 我们会得到报错:Uncaught TypeError: a is not a function
a();
// 由于b是一个函数,被提升到顶部,可以被执行,我们会得到输出:b
b();
// 由于c是一个变量,被提升到顶部的是 var c; 我们会得到报错:Uncaught TypeError: a is not a function
c();
// 由于d未被定义,我们会得到报错:Uncaught ReferenceError: d is not defined
d();

// 匿名函数表达式 (anonymous function expression),此时只有变量a被提升
var a = function () {
	console.log('a');
};
// 函数声明 (function declaration),此时整个函数b被提升
function b() {
	console.log('b');
};
// 具名函数表达式 (named function expression),此时只有变量c被提升
var c = function d() {
	console.log('d')
};

// 输出:a
a();
// 输出:b
b();
// 输出:d
c();
// 由于d未被定义,我们会得到报错:Uncaught ReferenceError: d is not defined
d();

作者的碎碎念

变量提升可以说是一个经久不衰的话题。

虽然已经有了ES6的constlet,也有了诸如airbnb在JavaScript书写风格中:在作用域顶部声明变量的建议。

但是你依旧无法阻止你的队友写出这种代码,或者是维护屎山的过程中遇到此类问题,因此深刻的理解变量提升还是一件很重要的事情。

如果发现本文有错误,或者是你有关于变量/函数提升更好的问题,欢迎在评论区留言。

参考资料

【🆘JS基础连环问】变量/函数提升(10问) | 创作者训练营 征文活动正在进行中......