一道原型链面试题引发的血案:为什么99%的人都答错了
一道面试题引发的原型链血案,彻底搞懂 Function.prototype 和 Object.prototype
前言
前几天面试一个候选人,问了这样一道题:
javascript
Function.prototype.a = 1;
Object.prototype.b = 2;
function F() {}
const f = new F();
console.log(F.a); // ?
console.log(F.b); // ?
console.log(f.a); // ?
console.log(f.b); // ?
结果让我很意外:这个工作了3年的前端,完全答错了。这让我意识到,很多开发者对JavaScript的原型链机制存在深度误解。
今天就来彻底分析一下这个问题,帮大家理清函数和实例在原型链中的真实关系。
目录
问题分析与答案揭晓
让我们先公布正确答案,然后深入分析原因:
javascript
Function.prototype.a = 1;
Object.prototype.b = 2;
function F() {}
const f = new F();
console.log(F.a); // 1 ✅
console.log(F.b); // 2 ✅
console.log(f.a); // undefined ❌ 很多人以为是1
console.log(f.b); // 2 ✅
为什么 f.a
是 undefined
而不是 1
?
这是因为函数和实例的原型链路径完全不同!
原型链的本质机制
JavaScript中的两条原型链
在JavaScript中,存在两条不同的原型链:
graph TD
A[Function F] --> B[Function.prototype]
B --> C[Object.prototype]
C --> D[null]
E[Instance f] --> F[F.prototype]
F --> C
javascript
// 函数的原型链
F.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 实例的原型链
f.__proto__ === F.prototype; // true
F.prototype.__proto__ === Object.prototype; // true
关键理解:F.prototype ≠ Function.prototype
这是最容易混淆的地方:
javascript
// F.prototype 是给 F 的实例用的
F.prototype.constructor === F; // true
// Function.prototype 是给函数 F 自己用的
F.__proto__ === Function.prototype; // true
函数的双重身份
身份一:作为对象的函数
javascript
// F 作为对象,它的原型链是:
// F -> Function.prototype -> Object.prototype -> null
Function.prototype.sayHello = function() {
return `Hello, I'm ${this.name}`;
};
F.sayHello(); // "Hello, I'm F"
// F 可以访问 Function.prototype 上的方法
身份二:作为构造函数的函数
javascript
// F 作为构造函数,它为实例提供原型:
// f -> F.prototype -> Object.prototype -> null
F.prototype.greet = function() {
return "Hello from instance";
};
const f = new F();
f.greet(); // "Hello from instance"
// 实例 f 可以访问 F.prototype 上的方法
实例的原型链路径
为什么实例访问不到 Function.prototype?
javascript
Function.prototype.a = 1;
Object.prototype.b = 2;
F.prototype.c = 3;
function F() {}
const f = new F();
// 沿着原型链查找过程
console.log(f.a); // undefined
/* 查找路径:
* 1. f 自身 -> 没有 a 属性
* 2. f.__proto__ (F.prototype) -> 没有 a 属性
* 3. F.prototype.__proto__ (Object.prototype) -> 没有 a 属性
* 4. Object.prototype.__proto__ (null) -> 查找结束
*
* Function.prototype 根本不在这条链上!
*/
console.log(f.b); // 2
/* 查找路径:
* 1. f 自身 -> 没有 b 属性
* 2. f.__proto__ (F.prototype) -> 没有 b 属性
* 3. F.prototype.__proto__ (Object.prototype) -> 找到 b = 2 ✅
*/
console.log(f.c); // 3
/* 查找路径:
* 1. f 自身 -> 没有 c 属性
* 2. f.__proto__ (F.prototype) -> 找到 c = 3 ✅
*/
图解原型链查找
javascript
// 创建一个更复杂的例子来理解
Function.prototype.funcMethod = 'I am from Function.prototype';
Object.prototype.objMethod = 'I am from Object.prototype';
F.prototype.instanceMethod = 'I am from F.prototype';
function F() {
this.ownProp = 'I am own property';
}
const f = new F();
// 查找顺序可视化
const searchOrder = {
'f.ownProp': {
found: 'f (own property)',
value: 'I am own property'
},
'f.instanceMethod': {
found: 'F.prototype',
value: 'I am from F.prototype'
},
'f.objMethod': {
found: 'Object.prototype',
value: 'I am from Object.prototype'
},
'f.funcMethod': {
found: 'not found',
value: undefined,
reason: 'Function.prototype not in prototype chain'
}
};
常见误区与避坑指南
误区1:混淆 F.prototype 和 Function.prototype
javascript
// ❌ 错误理解
"F 继承自 Function.prototype,所以 F 的实例也能访问 Function.prototype"
// ✅ 正确理解
"F 继承自 Function.prototype,但 F 的实例继承自 F.prototype"
误区2:认为所有对象都能访问 Function.prototype
javascript
// ❌ 错误认知
const obj = {};
console.log(obj.call); // undefined,不是 Function.prototype.call
// ✅ 正确认知
// 只有函数才能直接访问 Function.prototype
function fn() {}
console.log(fn.call); // Function.prototype.call
误区3:不理解 constructor 的指向
javascript
function F() {}
const f = new F();
// 这些都是 true,但原因不同
console.log(F.constructor === Function); // F是函数,继承自Function.prototype
console.log(f.constructor === F); // f是实例,F.prototype.constructor指向F
// 原型链路径
F.constructor === Function.prototype.constructor; // true
f.constructor === F.prototype.constructor; // true
实际开发中的应用
扩展所有函数的能力
javascript
// 给所有函数添加缓存功能
Function.prototype.cached = function() {
const cache = new Map();
const originalFn = this;
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = originalFn.apply(this, args);
cache.set(key, result);
return result;
};
};
// 使用示例
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const cachedFib = fibonacci.cached();
console.log(cachedFib(40)); // 第一次计算,较慢
console.log(cachedFib(40)); // 从缓存读取,很快
扩展所有实例的能力
javascript
// 给所有对象添加深拷贝功能
Object.prototype.deepClone = function() {
if (this === null || typeof this !== 'object') {
return this;
}
if (this instanceof Date) {
return new Date(this.getTime());
}
if (this instanceof Array) {
return this.map(item =>
typeof item === 'object' ? item.deepClone() : item
);
}
const cloned = {};
for (let key in this) {
if (this.hasOwnProperty(key)) {
cloned[key] = typeof this[key] === 'object'
? this[key].deepClone()
: this[key];
}
}
return cloned;
};
// 所有对象都可以使用
const obj = { a: 1, b: { c: 2 } };
const cloned = obj.deepClone();
检测原型链关系
javascript
// 实用的原型链检测工具
function analyzePrototypeChain(obj, name = 'obj') {
const chain = [];
let current = obj;
while (current !== null) {
chain.push({
level: chain.length,
object: current,
constructor: current.constructor?.name || 'Unknown',
isPrototype: current !== obj
});
current = Object.getPrototypeOf(current);
}
console.log(`\n=== ${name} 的原型链分析 ===`);
chain.forEach(({ level, object, constructor, isPrototype }) => {
const prefix = ' '.repeat(level);
const type = isPrototype ? '(prototype)' : '(self)';
console.log(`${prefix}${level}. ${constructor} ${type}`);
});
return chain;
}
// 使用示例
function MyClass() {}
const instance = new MyClass();
analyzePrototypeChain(MyClass, 'MyClass');
analyzePrototypeChain(instance, 'instance');
/* 输出:
=== MyClass 的原型链分析 ===
0. MyClass (self)
1. Function (prototype)
2. Object (prototype)
=== instance 的原型链分析 ===
0. MyClass (self)
1. Object (prototype)
*/
进阶:手写 instanceof
理解了原型链,我们可以手写 instanceof
操作符:
javascript
function myInstanceof(instance, Constructor) {
// 获取构造函数的原型
const prototype = Constructor.prototype;
// 获取实例的原型链起点
let current = Object.getPrototypeOf(instance);
// 沿着原型链查找
while (current !== null) {
if (current === prototype) {
return true;
}
current = Object.getPrototypeOf(current);
}
return false;
}
// 测试
function F() {}
const f = new F();
console.log(myInstanceof(f, F)); // true
console.log(myInstanceof(f, Object)); // true
console.log(myInstanceof(f, Function)); // false ❗
console.log(myInstanceof(F, Function)); // true
总结
核心要点
-
函数有双重身份:既是对象(继承自Function.prototype),又是构造函数(为实例提供F.prototype)
-
原型链路径不同:
- 函数:
F → Function.prototype → Object.prototype → null
- 实例:
f → F.prototype → Object.prototype → null
- 函数:
-
关键区别 :
F.prototype
≠Function.prototype
记忆口诀
javascript
函数是对象,走Function链
实例找原型,走构造函数链
Function.prototype,只有函数能访问
F.prototype,实例的专属通道
实践建议
- 调试时 :使用
Object.getPrototypeOf()
而不是__proto__
- 扩展时:区分是扩展函数能力还是实例能力
- 检测时 :理解
instanceof
的真正含义 - 性能考量:原型链越长,查找越慢
如果这篇文章帮你理清了原型链的概念,欢迎点赞收藏!有任何JavaScript相关的问题,也欢迎在评论区交流讨论。