你以为你懂“原型”?JavaScript原型那些不得不说的秘密!

117 阅读3分钟

你是否在学习 JavaScript 时,被“原型”、“原型链”、“proto”这些词绕晕过?你以为对象就是对象,函数就是函数,结果一查属性,怎么多出来一堆 prototype?别急,今天就用最通俗的语言,带你彻底搞懂 JavaScript 原型的前世今生!


一、什么是原型?为什么要有原型?

在 JavaScript 里,原型(prototype)是实现对象继承和属性共享的核心机制。每个函数都有一个 prototype 属性,每个对象都有一个 __proto__ 属性。它们共同构成了 JS 的“原型链”体系。

为什么要有原型?
因为 JavaScript 没有传统意义上的“类”,但我们又希望多个对象能共享方法和属性,于是就有了原型。


二、构造函数、prototype 和 proto 的关系

来看一段代码:

function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log('Hello, I am ' + this.name);
};

const p1 = new Person('小明');
p1.sayHello(); // Hello, I am 小明

分析:

  • Person 是构造函数。
  • Person.prototype 是它的“显式原型”,所有用 new Person() 创建的对象,都会把自己的 __proto__ 指向 Person.prototype
  • p1.__proto__ === Person.prototype,这就是原型链的第一步。

三、原型链:对象查找属性的秘密通道

当你访问 p1.sayHello 时,JS 会先在 p1 自己身上找,没有就去 p1.__proto__(也就是 Person.prototype)上找。如果还没有,就继续往上找,直到 Object.prototype,最后到 null

这条查找路径,就是原型链

代码验证:

console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

四、原型的常见用法与“坑”

1. 方法共享

所有实例共享原型上的方法,节省内存:

function Dog(name) {
  this.name = name;
}
Dog.prototype.bark = function() {
  console.log(this.name + ' 汪汪!');
};

const d1 = new Dog('旺财');
const d2 = new Dog('小黑');
d1.bark(); // 旺财 汪汪!
d2.bark(); // 小黑 汪汪!

2. 原型属性的“共享陷阱”

如果原型上定义的是引用类型,所有实例会共享同一个对象!

function Cat() {}
Cat.prototype.favorites = [];
const c1 = new Cat();
const c2 = new Cat();
c1.favorites.push('鱼');
console.log(c2.favorites); // ['鱼'],坑!

解决方法:把引用类型属性写在构造函数里。


五、proto 和 prototype 到底有啥区别?

  • prototype函数对象才有的属性,用于生成实例的原型。
  • __proto__所有对象都有的属性,指向创建它的构造函数的 prototype。

口诀:

  • 构造函数.prototype 是“显式原型”
  • 实例.__proto__ 是“隐式原型”
  • 实例.__proto__ === 构造函数.prototype

六、原型链继承的实现

原型链是 JS 实现继承的基础。比如:

function Animal() {}
Animal.prototype.eat = function() { console.log('吃东西'); };

function Bird() {}
Bird.prototype = new Animal();
Bird.prototype.fly = function() { console.log('飞翔'); };

const b = new Bird();
b.eat(); // 吃东西
b.fly(); // 飞翔

注意:这样继承会让所有 Bird 实例共享 Animal 的属性和方法。


七、ES6 class 语法其实还是原型

ES6 的 class 只是语法糖,本质还是基于原型链:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log('Hi, ' + this.name);
  }
}
const p = new Person('小红');
p.sayHi(); // Hi, 小红
console.log(Object.getPrototypeOf(p) === Person.prototype); // true

八、原型的常见面试题

  1. 如何判断一个属性是对象自身的,还是原型上的?

    • hasOwnProperty 方法。
    p1.hasOwnProperty('name'); // true
    p1.hasOwnProperty('sayHello'); // false
    
  2. 如何获取对象的原型?

    • Object.getPrototypeOf(obj)obj.__proto__
  3. 原型链的尽头是什么?

    • Object.prototype.__proto__ === null

九、初学者常见误区

  • 以为 prototype 是每个对象都有的,其实只有函数才有。
  • 以为 __proto__ 是标准属性,其实早期不是,但现在已被标准化。
  • 以为 class 不是原型,其实底层还是。

十、写在最后

原型是 JavaScript 的灵魂,理解了原型和原型链,你就能看懂大部分 JS 源码和框架底层实现。别被术语吓到,多写多试,原型其实很简单!