1. 简介
前两天遇到一道很有意思的题目,乍看起来是一道考声明提升和变量提升的题目的,但事实上还包含了更多的知识点。下面我们一起来看一下。
function f(a) {
console.log(a); // function a() {}
var a = 2;
function a() {}
console.log(a); // 2
}
f(1);
2. 详细讲解
这道题目初看上去很简单。首先,你要能解答如下问题:
function f(b) {
console.log(a); // function a() {}
var a = 2;
function a() {}
console.log(a); // 2
}
f(1);
如果,形参与函数内部的变量声明或者函数声明不同名,这道题很容易解答。如果你不会,可以看一下这两篇文章。JS入门难点解析2-JS的变量提升和函数提升和 JS入门难点解析5-变量对象,我剖析了函数声明和变量声明的表现和原理。
但是这里很特别,这题除了考察函数声明,变量声明的提升以外,还考察了同名变量和同名形参的覆盖问题,这种情况该如何分析呢?
我们要分成三种情况,第一种传入变量是具体的值,第二种传入变量是函数声明,第三种传入变量是函数赋值的变量。
2.1 传入变量是具体的值
这种就是题目所述的情况。我们再试着分解题目。看下面两代码:
function f(a) {
console.log(a); // 1
var a = 2;
console.log(a); // 2
}
f(1);
function f(a) {
console.log(a); // function a() {}
function a() {}
}
f(1);
这是什么原因呢?试着回忆一下,函数进入执行流程的步骤是什么?首先是执行环境进行准备工作,这一阶段,变量对象会包括:函数的所有形参 (如果是函数上下文),函数声明,变量声明。我们已经比较过函数声明和变量声明内部与之间的优先级,当这些变量同名时。
1. 函数声明 替换 变量声明
2.后面的函数声明替换前面的函数声明
3.后面的变量声明无效
此外,从这里我们可以看到,f(1)的其实等同于如下效果:
var a = 1;
console.log(a); // function a() {}
var a = 2;
function a() {}
console.log(a); // 2
等于在函数内部第一行执行了一个变量赋值,大家看一下,是不是这样变化以后,结果仍然是遵循之前的规则。
2.2 传入的是函数声明
如下:
function f(a) {
console.log(a); // ƒ a() { console.log('outer'); }
var a = 2;
function a() {}
console.log(a); // 2
}
f(a);
function a() {
console.log('outer');
}
function f(a) {
console.log(a); // function a() {}
var a = 2;
console.log(a); // 2
}
f(a);
function a() {
console.log('outer');
}
可以看出,函数f内部等同于如下效果:
function a() {
console.log('outer');
}
console.log(a);
var a = 2;
function a() {}
console.log(a);
这样看,是不是一目了然呢?
2.3 传入函数赋值的变量
进一步思考,如果这是一个赋值为函数的变量呢?
function f(a) {
console.log(a); // function a() {}
var a = 2;
function a() {}
console.log(a); // 2
}
f(a);
var a = function b() {
console.log('outer');
}
聪明的你应该猜到了,是的,效果就等同于如下:
var a = function b() {
console.log('outer');
}
console.log(a); // function a() {}
var a = 2;
function a() {}
console.log(a); // 2
3. 总结
综上,其实传参这一步,我们可以将其转化到函数内的第一行,改写为变量声明赋值或者函数声明。当这些变量同名时,应用的规则仍然是以下三条:
当这些变量同名时。
1. 函数声明 替换 变量声明
2.后面的函数声明替换前面的函数声明
3.后面的变量声明无效
网友评论