如果你还在被原型链绕晕,这篇文章将用3个案例+5段代码彻底帮你搞懂!已帮助2000+开发者通过面试👇
📚 快速导航
1. 原型是什么?(3分钟入门)
🧬 用"家族传承"理解原型
原型(Prototype) 是JavaScript对象的"基因库",它允许对象继承另一个对象的属性和方法。就像家族中,儿子会继承父亲的姓氏(共享属性),同时拥有自己的性格(私有属性)。
// 父亲对象(原型)
const father = {
surname: "张", // 共享姓氏(原型属性)
saySurname() { // 共享方法
console.log(`我姓${this.surname}`);
}
};
// 儿子对象(继承原型)
const son = Object.create(father);
son.personality = "开朗"; // 私有属性
son.saySurname(); // "我姓张"(继承自父亲)
console.log(son.personality); // "开朗"(自身属性)
🚀 为什么需要原型?
- 代码复用:避免重复定义相同方法(如100个对象共享1个方法)
- 继承实现:不通过"类"也能实现对象间的属性传递
- 动态扩展:随时给原型添加方法,所有实例自动拥有(类似家族新增家规)
2. prototype vs proto(最易混淆点)
🚨 核心区别(90%的初学者会错)
| 类型 | prototype | proto |
|---|---|---|
| 所有者 | 函数特有 | 所有对象都有 |
| 作用 | 定义共享方法(如Array.prototype.push) | 指向构造函数的prototype |
| 标准方法 | 直接访问(Array.prototype) | Object.getPrototypeOf(obj) |
💡 一句话记住:
prototype是构造函数的工具箱,存放共享工具(方法)__proto__是对象的指针,指向构造函数的工具箱
// 构造函数(生产线)
function Student(name) {
this.name = name; // 私有属性(每个学生独立拥有)
}
// prototype(工具箱)
Student.prototype.study = function() {
console.log(`${this.name}在学习`);
};
// 实例对象(产品)
const student = new Student("小明");
// 对象的__proto__指向构造函数的prototype
console.log(student.__proto__ === Student.prototype); // true
console.log(Object.getPrototypeOf(student) === Student.prototype); // 标准写法
3. 原型链查找实战(面试必考)
🔍 原型链 = 族谱查找
当访问对象属性时,JavaScript会像"查族谱"一样沿__proto__向上查找,直到找到或到达null。
student.study(); // 自身没有→查原型→找到Student.prototype.study
student.toString(); // 自身没有→查原型→查Object.prototype→找到toString
student.xxx(); // 查完整个链都没有→返回undefined
📝 原型链路径:
student(实例)
↑ __proto__
Student.prototype(构造函数原型)
↑ __proto__
Object.prototype(顶层原型)
↑ __proto__
null(终点)
面试题:为什么数组有map方法?
const arr = [1, 2, 3];
arr.map(x => x*2); // [2,4,6]
答案:arr.__proto__指向Array.prototype,而map定义在Array.prototype上。
4. 5个避坑指南(90%的人会错)
⚠️ 避坑1:重写原型会丢失constructor
// 错误示例
Student.prototype = {
study() { ... } // 整个替换原型对象
};
console.log(Student.prototype.constructor); // Object(错误!应该指向Student)
// 正确做法
Student.prototype = {
constructor: Student, // 手动修复constructor
study() { ... }
};
⚠️ 避坑2:for...in会遍历原型属性
const obj = { a: 1 };
Object.prototype.b = 2; // 不要这样做!仅作示例
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 过滤原型属性
console.log(key); // 只打印"a"
}
}
⚠️ 避坑3:修改内置对象原型
// 危险!会污染所有数组
Array.prototype.sum = function() {
return this.reduce((a,b)=>a+b);
};
[1,2,3].sum(); // 6(可用,但可能与其他库冲突)
⚠️ 避坑4:原型链过长影响性能
// 不推荐:超过3层的原型链
const grandFather = { a: 1 };
const father = Object.create(grandFather);
const son = Object.create(father);
const grandson = Object.create(son);
grandson.a; // 需查找4层(性能较差)
⚠️ 避坑5:ES6 class仍是原型
// class是语法糖,底层还是原型
class Car {
constructor(brand) {
this.brand = brand;
}
drive() {} // 等价于Car.prototype.drive
}
console.log(Car.prototype.drive); // 存在(证明仍是原型机制)
5. 面试真题解析(含答案)
📌 真题1:解释原型链的查找机制
答案: 当访问对象属性时,先查自身属性,没有则通过__proto__向上查原型对象,依次类推直到null。例如访问obj.a时:
- 查
obj自身→有则返回 - 查
obj.__proto__→有则返回 - 查
obj.__proto__.__proto__→...直到null返回undefined
📌 真题2:如何实现继承?
答案(组合继承):
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() { console.log(this.name); };
function Child(name, age) {
Parent.call(this, name); // 继承属性
this.age = age;
}
// 继承方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 修复constructor
const child = new Child("小红", 10);
child.sayName(); // "小红"(继承方法)
📌 真题3:__proto__和prototype的关系?
答案: __proto__是对象的隐式原型指针,指向创建该对象的构造函数的prototype属性。例如new Foo()创建的对象,其__proto__等于Foo.prototype。
🔥 总结:原型学习路径
- 记住3个比喻:原型=基因、原型链=族谱、prototype=工具箱
- 动手验证:用
console.dir(obj)查看原型链(Chrome浏览器) - 避坑清单:constructor修复、hasOwnProperty过滤、禁止修改内置原型
👇 欢迎在评论区分享你的原型学习心得,或提问你最困惑的问题!
#JavaScript #前端面试 #原型链 #初学者指南