JavaScript中的显式原型与隐式原型:深入理解原型链
目录
一、核心概念解析
二、原型链运作机制
三、构造函数的原型三角关系
四、ES6类语法糖的本质
五、实现原型继承的三种方式
六、关键注意事项
七、原型链应用场景
八、总结核心要点
一、核心概念解析
-
显式原型 (prototype):
-
是函数特有的属性
-
当函数作为构造函数使用时,其实例会继承该函数的prototype
-
本质是一个包含
constructor
属性和共享方法的普通对象function Person(name) {this.name = name; }// 向显式原型添加方法 Person.prototype.sayHello = function() {console.log(`Hello, I'm ${this.name}`); };// 验证类型 console.log(typeof Person.prototype); // "object" console.log(Person.prototype.constructor === Person); // true
-
-
隐式原型 (proto):
-
是每个对象都拥有的属性
-
指向创建该对象时使用的构造函数的prototype
-
形成原型链的关键连接点(ES6后建议使用
Object.getPrototypeOf()
获取)const john = new Person("John");// 对象通过__proto__访问构造函数的原型 console.log(john.__proto__ === Person.prototype); // true// 推荐的标准获取方式 console.log(Object.getPrototypeOf(john) === Person.prototype); // true
-
二、原型链运作机制
[实例对象] john│├── __proto__ → [Person.prototype]│ ││ ├── sayHello() │ ││ └── __proto__ → [Object.prototype]│ ││ ├── toString()│ ├── valueOf()│ ││ └── __proto__ → null│└── name: "John"
原型链查找规则:
-
访问对象属性时,先在自身查找
-
找不到则通过
__proto__
向上级原型查找 -
直到Object.prototype(所有对象的基类)
-
最终到达null停止查找
console.log(john.toString()); // 来自Object.prototype console.log(john.sayHello()); // 来自Person.prototype// 验证原型链关系 console.log(john instanceof Person); // true console.log(john instanceof Object); // true
三、构造函数的原型三角关系
function Animal(type) {this.type = type;
}const dog = new Animal("哺乳动物");// 经典三角关系:
console.log(dog.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.constructor === Animal); // true
console.log(Object.getPrototypeOf(Animal) === Function.prototype); // true
四、ES6类语法糖的本质
class Vehicle {constructor(wheels) {this.wheels = wheels;}drive() {console.log(`Driving with ${this.wheels} wheels`);}
}// 类本质仍是构造函数
console.log(typeof Vehicle); // "function"const car = new Vehicle(4);// 验证原型关系
console.log(car.__proto__ === Vehicle.prototype); // true
console.log(Vehicle.prototype.__proto__ === Object.prototype); // true
五、实现原型继承的三种方式
-
组合继承(最经典方式):
function Parent(name) {this.name = name; }Parent.prototype.speak = function() {console.log(`I'm ${this.name}`); };function Child(name, age) {Parent.call(this, name); // 继承属性this.age = age; }// 建立原型链 Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child;// 添加子类方法 Child.prototype.cry = function() {console.log("Waahh!"); };
-
ES6类继承:
class Employee {constructor(name) {this.name = name;}work() {console.log(`${this.name} is working`);} }class Manager extends Employee {constructor(name, teamSize) {super(name);this.teamSize = teamSize;}manage() {console.log(`Managing ${this.teamSize} people`);} }
-
Object.create实现纯对象继承:
const parent = {greet() {console.log("Hello from parent!");} };const child = Object.create(parent, {cry: {value: function() {console.log("Waaaah!");}} });
六、关键注意事项
-
原型污染风险:
// 修改内置原型需谨慎 Array.prototype.sum = function() {return this.reduce((a, b) => a + b, 0); };const nums = [1, 2, 3]; console.log(nums.sum()); // 6 - 但可能引发冲突
-
性能优化技巧:
// 在构造函数中声明方法(适用于不共享的独特方法) function Player(id) {this.id = id;// 每个实例都有独立方法副本this.getID = function() {return this.id;}; }// 在原型上声明方法(节省内存) Player.prototype.getScore = function() {return this.score; };
-
现代替代方案:
-
使用
Object.getPrototypeOf()
替代__proto__
-
使用
Object.setPrototypeOf()
设置原型 -
ES6的
Reflect
API提供更多元操作
七、原型链应用场景
-
实现混入模式(Mixins):
const canEat = {eat() {console.log(`${this.name} is eating`);} };const canSleep = {sleep() {console.log(`${this.name} is sleeping`);} };function assignPrototypes(target, ...sources) {Object.assign(target.prototype, ...sources); }function Cat(name) {this.name = name; }assignPrototypes(Cat, canEat, canSleep);const garfield = new Cat("Garfield"); garfield.eat(); // "Garfield is eating"
八、总结核心要点
-
显式原型(prototype):函数的属性,用于实现方法共享
-
隐式原型(proto):对象的内部链接,指向其构造函数的prototype
-
原型链:通过
__proto__
连接形成的继承链 -
最佳实践:
-
方法定义在prototype上节省内存
-
属性定义在构造函数中保持独立
-
避免直接修改内置对象原型
-
优先使用ES6类语法提高可读性