🔥 10分钟吃透JavaScript原型:从入门到面试(含避坑指南)

104 阅读4分钟

如果你还在被原型链绕晕,这篇文章将用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%的初学者会错)

类型prototypeproto
所有者函数特有所有对象都有
作用定义共享方法(如Array.prototype.push指向构造函数的prototype
标准方法直接访问(Array.prototypeObject.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时:

  1. obj自身→有则返回
  2. obj.__proto__→有则返回
  3. 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; // 修复constructorconst child = new Child("小红", 10);
child.sayName(); // "小红"(继承方法)

📌 真题3:__proto__prototype的关系?

答案__proto__是对象的隐式原型指针,指向创建该对象的构造函数的prototype属性。例如new Foo()创建的对象,其__proto__等于Foo.prototype

🔥 总结:原型学习路径

  1. 记住3个比喻:原型=基因、原型链=族谱、prototype=工具箱
  2. 动手验证:用console.dir(obj)查看原型链(Chrome浏览器)
  3. 避坑清单:constructor修复、hasOwnProperty过滤、禁止修改内置原型

👇 欢迎在评论区分享你的原型学习心得,或提问你最困惑的问题!

#JavaScript #前端面试 #原型链 #初学者指南