吃透 JS 原型链:高频面试题+避坑指南(constructor/prototype/__proto__核心解析)
原型链是 JavaScript 语言的核心底层逻辑,也是前端面试的“必考题”——无论是初级前端面试的基础判断,还是中高级面试的手写实现、场景分析,原型链相关知识点都占比极高。
很多开发者对原型链的困惑,本质上是没理清 prototype、__proto__、constructor 三者的关系,再加上面试中常出现的“修改原型”“继承场景”等陷阱,很容易翻车。
一、先夯实基础:搞懂三个核心概念(面试必问)
原型链的所有知识点,都围绕 prototype、__proto__、constructor 展开,先把这三个概念的定义、作用、关联讲透,再看面试题就会豁然开朗。
1. prototype(原型对象)—— 只有函数才有的“共享仓库”
「关键点」:只有函数(普通函数、构造函数,箭头函数除外)才有 prototype 属性,它指向一个对象,这个对象就是“原型对象”。
「作用」:存放该构造函数所有实例共享的属性和方法,实现代码复用(避免每个实例都重复创建相同的属性/方法)。
「核心特性」:原型对象(prototype)自带一个 constructor 属性,默认指向它所属的构造函数。
// 普通构造函数
function Person() {}
// 验证:prototype.constructor 指向构造函数本身
console.log(Person.prototype.constructor === Person); // true
// 箭头函数没有 prototype(陷阱预警!)
const Foo = () => {};
console.log(Foo.prototype); // undefined
2. proto(原型指针)—— 所有对象都有的“链节”
「关键点」:所有对象(包括函数对象、普通对象)都有 proto 属性(ES6 后推荐用 Object.getPrototypeOf(obj) 替代,proto 是非标准属性,但浏览器普遍支持)。
「作用」:构成原型链的核心指针,当访问一个对象的属性/方法时,若对象自身没有该属性/方法,会通过 proto 向上查找其原型对象,直到找到该属性/方法,或查到原型链的终点 null。
「原型链第一规则」:实例的 proto 严格等于其构造函数的 prototype(这是原型链最基础、最常考的关系)。
function Person() {}
const person = new Person(); // person 是 Person 的实例
// 核心关系验证
console.log(person.__proto__ === Person.prototype); // true
// ES6 标准写法(推荐面试时使用)
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
3. constructor(构造器)—— 原型对象的“身份标识”
「关键点」:constructor 是原型对象(prototype)的默认属性,默认指向创建该原型对象的构造函数;实例本身没有 constructor 属性,访问实例的 constructor 时,会通过 proto 向上查找原型对象的 constructor。
「易错点」:不要误以为“实例的 constructor 是实例自身的属性”,它是原型链继承来的。
function Person() {}
const person = new Person();
// person 自身没有 constructor,向上查找
console.log(person.hasOwnProperty('constructor')); // false
// 查找路径:person.__proto__ → Person.prototype → constructor → Person
console.log(person.constructor === Person); // true
二、高频面试题拆解(从基础到进阶,含陷阱)
结合开头的经典题目,扩展3类高频面试题(基础判断、进阶修改、继承场景),每道题都拆解选项逻辑,标注陷阱,贴合面试实际考察重点。
基础题:原型链核心关系判断(入门必考)
这是面试中最基础的题型,直接考察三者的核心关系,也是新手最容易混淆的点。
// 基础代码
function Person() {}
const person = new Person();
// 请判断以下表达式的结果(true/false)
A: person.__proto__ === Person.prototype
B: person.__proto__.constructor === Person.prototype.constructor
C: person.constructor === Person
D: person.constructor === Person.constructor
E: Person.prototype.constructor === person.constructor
选项逐题解析(含陷阱)
- 选项 A:true(核心规则) 解析:实例的 proto 指向其构造函数的 prototype,这是原型链的第一规则,也是最基础的考点。
- 选项 B:true(等价替换) 解析:由选项 A 可知,person.proto === Person.prototype,因此左右两边等价于「Person.prototype.constructor === Person.prototype.constructor」,必然相等。 陷阱:有人会误以为“person.proto 和 Person.prototype 是两个不同对象”,实则是同一个对象,只是引用方式不同。
- 选项 C:true(原型链查找) 解析:person 自身没有 constructor,通过 proto 向上查找,找到 Person.prototype.constructor,而该属性默认指向 Person,因此 person.constructor === Person。
- 选项 D:false(高频陷阱) 解析:这是最容易踩坑的选项! - person.constructor 指向 Person(构造函数); - Person 本身是函数,函数也是对象,其 constructor 指向「Function 构造函数」(所有函数都是由 Function 构造出来的); 因此:person.constructor(Person) !== Person.constructor(Function)。 延伸:console.log(Function.constructor === Function); // true(特殊情况:Function 构造自身)
- 选项 E:true(统一指向) 解析:Person.prototype.constructor 直接指向 Person;person.constructor 经过原型链查找后也指向 Person,两者指向同一个构造函数,因此相等。
「答案」:A、B、C、E 为 true,D 为 false。
进阶题:修改 prototype 后的 constructor 陷阱(高频考点)
面试中常考“手动修改构造函数的 prototype”,此时 constructor 会发生变化,若不手动修正,会导致原型链紊乱,这是高频陷阱。
// 题目代码
function Person() {}
// 手动修改 Person 的 prototype 为一个新对象
Person.prototype = {
name: '张三',
sayHi: function() {}
};
const person = new Person();
// 判断以下表达式的结果
1. person.constructor === Person
2. Person.prototype.constructor === Person
3. Object.getPrototypeOf(person).constructor === Object
解析(核心陷阱)
当我们手动将 Person.prototype 赋值为一个新对象时,新对象默认没有 constructor 属性,会通过 proto 向上查找,最终指向 Object(因为新对象的原型是 Object.prototype)。
-
- person.constructor === Person → false 原因:person.constructor 查找路径为:person.proto(新对象)→ 新对象.proto(Object.prototype)→ Object.prototype.constructor → Object,因此指向 Object,而非 Person。
-
- Person.prototype.constructor === Person → false 原因:新的 Person.prototype 是手动创建的普通对象,没有手动设置 constructor,因此其 constructor 指向 Object。
-
- Object.getPrototypeOf(person).constructor === Object → true 原因:Object.getPrototypeOf(person) 就是新的 Person.prototype,其 constructor 指向 Object。
面试避坑技巧
手动修改构造函数的 prototype 后,一定要手动修正 constructor 的指向,否则会导致原型链紊乱,这是面试中考察“原型链掌握程度”的关键细节。
// 正确写法:修改 prototype 后,手动设置 constructor
Person.prototype = {
constructor: Person, // 手动指向原构造函数
name: '张三',
sayHi: function() {}
};
const person = new Person();
console.log(person.constructor === Person); // true(正确)
拔高题:原型链继承中的 constructor 问题(中高级面试)
原型链继承是面试重点,而 constructor 指向错误是继承场景中最常见的问题,结合实例拆解:
// 父构造函数
function Parent() {
this.age = 30;
}
// 子构造函数
function Child() {
Parent.call(this); // 借用父构造函数
}
// 原型链继承:让 Child 的原型指向 Parent 的实例
Child.prototype = new Parent();
const child = new Child();
// 判断:child.constructor === Child → ?
解析(陷阱点)
-
当 Child.prototype = new Parent() 时,Child.prototype 的 proto 指向 Parent.prototype,而 Child.prototype 的 constructor 会继承 Parent.prototype.constructor,即指向 Parent;
-
因此,child.constructor 的查找路径为:child.proto(Child.prototype)→ Child.prototype.constructor → Parent;
-
最终结果:child.constructor === Child → false,正确指向是 Parent。
解决方案(面试手写继承必写)
继承时,修改 Child.prototype 后,手动修正 constructor 指向,确保原型链正确:
Child.prototype = new Parent();
// 手动修正 constructor 指向 Child
Child.prototype.constructor = Child;
const child = new Child();
console.log(child.constructor === Child); // true(正确)
三、原型链面试高频陷阱总结(避坑必看)
结合上面的题目,总结5个面试中最容易踩的陷阱,记住这些,避免翻车:
陷阱1:混淆 proto 和 prototype
「错误认知」:所有对象都有 prototype,所有函数都有 proto; 「正确认知」:只有函数有 prototype(箭头函数除外),所有对象(包括函数)都有 proto; 「记忆口诀」:函数有 prototype,对象有 proto,实例 proto 指向构造函数 prototype。
陷阱2:认为实例的 constructor 是自身属性
「错误认知」:person.constructor 是 person 自己的属性; 「正确认知」:实例自身没有 constructor,是通过 proto 从原型对象继承来的,默认指向构造函数。
陷阱3:修改 prototype 后不修正 constructor
「错误写法」:直接给构造函数的 prototype 赋值新对象,不设置 constructor; 「后果」:constructor 指向异常(默认指向 Object),原型链紊乱; 「正确写法」:修改 prototype 后,手动设置 constructor 指向原构造函数。
陷阱4:误以为 Function.constructor 指向自身是错误的
「特殊情况」:所有函数的 constructor 都指向 Function,而 Function 的 constructor 指向自身(Function.constructor === Function),这是 JS 引擎的特殊设计,不是 bug。
陷阱5:箭头函数有 prototype 或能作为构造函数
「错误认知」:箭头函数可以作为构造函数,有 prototype 属性; 「正确认知」:箭头函数没有 prototype,不能用 new 关键字调用(会报错),因此也不能作为构造函数。
四、总结:原型链核心逻辑(面试背诵版)
记住3句话,轻松应对原型链基础面试题:
- 实例的 proto === 其构造函数的 prototype(原型链核心);
- 构造函数的 prototype.constructor === 构造函数本身(默认情况);
- 实例的 constructor 是通过原型链继承来的,默认指向其构造函数。
最后提醒:面试中尽量使用 ES6 标准写法(Object.getPrototypeOf() 替代 proto),显得更专业哦~