面试题
变量提升和作用域
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
// 下方每行的输出结果
// Foo.getName() // 2
// getName(); // 4
// Foo().getName(); // 1
// getName(); // 1
// new (Foo.getName)(); // 2
// (new Foo()).getName(); // 3
1️⃣ 变量提升分析
JavaScript 引擎在代码执行前会进行变量提升,等效代码如下:
// 变量提升后的等效代码
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
// 函数声明提升
function getName() {
console.log(5);
}
// 变量声明提升
var getName;
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
// 函数表达式赋值
getName = function () {
console.log(4);
};
// Foo.getName() // 2
// getName(); // 4
// Foo().getName(); // 1
// getName(); // 1
// new (Foo.getName)(); // 2
// (new Foo()).getName(); // 3
关键点:
- 函数声明
function getName()
会完全提升到作用域顶部 var getName = function()
只有变量声明提升,赋值操作留在原地
2️⃣ 逐行执行分析
🎯 Foo.getName()
→ 输出 2
Foo.getName = function () {
console.log(2);
};
直接调用 Foo 函数对象上的静态方法,输出 2。
🎯 getName()
→ 输出 4
由于变量提升,全局的 getName
最终指向函数表达式:
getName = function () {
console.log(4);
};
🎯 Foo().getName()
→ 输出 1
分两步执行:
- 执行
Foo()
function Foo() {
// 没有 var,修改全局变量
getName = function () {
console.log(1);
};
return this; // 返回全局对象 (window/global)
}
- 在执行
Foo
函数时,将全局的getName
重新赋值为输出 1 的函数 - 返回全局对象
- 执行
Foo().getName()
相当于 window.getName()
,此时全局的 getName
已经被修改为输出 1 的函数
🎯 getName()
→ 输出 1
由于上一步已经修改了全局的 getName
,所以输出 1。
🎯 new (Foo.getName)()
→ 输出 2
先执行 Foo.getName
得到函数,然后用 new
调用:
new (function () {
console.log(2);
})();
new
操作会执行函数体,输出 2。
🎯 (new Foo()).getName()
→ 输出 3
new Foo()
创建 Foo 的实例对象 (是一个空对象,因为没有通过 this 来对实例对象进行赋值)- 调用实例的
getName
方法
实例对象本身没有 getName
方法,沿原型链查找:
Foo.prototype.getName = function () {
console.log(3);
};