你是否在学习 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
八、原型的常见面试题
-
如何判断一个属性是对象自身的,还是原型上的?
- 用
hasOwnProperty方法。
p1.hasOwnProperty('name'); // true p1.hasOwnProperty('sayHello'); // false - 用
-
如何获取对象的原型?
Object.getPrototypeOf(obj)或obj.__proto__
-
原型链的尽头是什么?
Object.prototype.__proto__ === null
九、初学者常见误区
- 以为
prototype是每个对象都有的,其实只有函数才有。 - 以为
__proto__是标准属性,其实早期不是,但现在已被标准化。 - 以为 class 不是原型,其实底层还是。
十、写在最后
原型是 JavaScript 的灵魂,理解了原型和原型链,你就能看懂大部分 JS 源码和框架底层实现。别被术语吓到,多写多试,原型其实很简单!