第四轮提问
请阅读以下代码,并在一分钟内告诉我这 4 个 console.log 的输出结果,并简述理由。
function Foo() {
this.name = 'Foo Instance';
}
Foo.prototype.getName = function() {
return this.name;
};
Foo.prototype.name = 'Foo Prototype';
const foo = new Foo();
const getNameRef = foo.getName;
console.log( foo.getName() ); // 1
console.log( foo.__proto__.getName() ); // 2
console.log( getNameRef() ); // 3
console.log( getNameRef.call(Foo) ); // 4
参考答案
真正的输出结果:
- foo.getName() -> 'Foo Instance'
- foo.proto.getName() -> 'Foo Prototype'
- getNameRef() -> undefined(非严格模式下的 Node 环境)或 "" (浏览器环境)
- getNameRef.call(Foo) -> 'Foo'
核心本质剖析(含常见误区)
1. foo.getName() 为什么是 'Foo Instance'?
“foo 自己没有这个方法,要顺着隐式原型找,找到了 Foo.prototype 上,所以 this 就是 Foo.prototype。”
-
本质真相:在 JS 中, “去哪里找函数” 和 “函数里的 this 指向谁” 是绝对割裂的两码事。
- 找函数:引擎发现 foo 本身没有 getName,于是顺着原型链找到了 Foo.prototype.getName。好,函数找到了,原型链的工作到此暂时结束。
- 定 this:函数是怎么调用的?是
foo.getName()!JS 的铁律是:谁点出来的函数,this 就指向谁。点前面是 foo,所以此时this === foo。 - 查属性:代码执行
return this.name,也就是return foo.name。因为构造函数里写了this.name = 'Foo Instance',所以 foo 实例身上是有 name 属性的,直接输出。
2. foo.proto.getName() 为什么是 'Foo Prototype'?
-
本质真相:
- 定 this:调用方式是
foo.__proto__.getName()。.getName()前面是谁?是foo.__proto__(也就是 Foo.prototype 这个原型对象本身)。所以,此时 this === Foo.prototype。 - 查属性:代码执行
return this.name,也就是去 Foo.prototype 身上找 name。注意代码里有一句:Foo.prototype.name = 'Foo Prototype';。所以输出 'Foo Prototype'。
- 定 this:调用方式是
3. getNameRef() 为什么是 undefined(或空字符串)?
- 本质真相:
const getNameRef = foo.getName只是把内存里的函数拿出来了,并没有执行。当直接调用 getNameRef() 时,属于 “裸调用”。非严格模式下,this 指向全局对象(Node 中是 global,浏览器是 window)。全局对象身上如果没有 name,自然返回 undefined。
4. getNameRef.call(Foo) 为什么是 'Foo'?(最经典的坑)
- 本质真相:
- Foo 是谁?它是构造函数本身。
- 代码执行
getNameRef.call(Foo),也就是强行让 this === Foo 这个函数对象。 - 执行
return this.name,也就是return Foo.name。 - JS 引擎底层设定:所有具名函数天生自带一个只读属性 name,它的值就是这个函数的名字! 所以 Foo.name 的值就是字符串 'Foo'。
面试综合回答参考
“面试官您好,这道题考察的是原型链查找与执行上下文(this指向) 的结合。我按顺序作答:
- 第一题输出 'Foo Instance'。虽然 getName 方法是在原型链(Foo.prototype)上找到的,但调用者是 foo 实例,所以隐式绑定了 this 为 foo。foo 自身在实例化时被赋予了 name 属性,因此输出实例的值。
- 第二题输出 'Foo Prototype'。因为直接通过
foo.__proto__发起调用,上下文对象变为了原型对象本身。this 指向 Foo.prototype,获取到的自然是挂载在原型上的 name 属性。 - 第三题输出 undefined。因为将对象方法赋值给变量后执行,发生了隐式绑定的丢失。此时是默认绑定,非严格模式下 this 指向全局。全局没有 name 属性,所以是 undefined。
- 第四题输出 'Foo'。这里使用了显式绑定 call,把 this 强行指向了 Foo 这个构造函数本身。在 JS 中,函数对象自带内置的 name 属性,值为函数名字符串,因此输出 'Foo'。”