【面向对象】1. 原型与原型链的概念
文章目录
- 前言
- 1. 原型
- 2. 原型链
- 3. `__proto__` 与 `Object.getPrototypeOf()`
- 4. 构造函数的 prototype 属性与实例的 `__proto__`
- 5. 原型链的终点
- 6. 继承
- 7. 验证原型的方法
- 总结
前言
在 JavaScript 中,每个对象(除了null)都有一个与之关联的原型(prototype)对象。原型对象也是一个对象,它包含可以由特定对象共享的属性和方法。当我们试图访问一个对象的属性时,如果该对象自身没有这个属性,那么 JavaScript 引擎会去该对象的原型上查找,如果原型对象上也没有,那么就会去原型的原型上查找,如此层层向上,直到找到一个匹配的属性或到达原型链的末端(null)。这种查找机制所形成的链式结构被称为原型链。
1. 原型
- 每个函数(除了箭头函数)都有一个
prototype
属性,这个属性指向一个对象,称为原型对象。 - 当使用 new 关键字调用构造函数创建实例时,该实例的内部指针(
__proto__
,现在更推荐使用Object.getPrototypeOf(obj)
)会指向构造函数的 prototype 对象。
function Person(name) {this.name = name;
}
// 给Person函数的原型添加方法
Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name}`);
};const person1 = new Person('Alice');
person1.sayHello(); // 输出:Hello, my name is Alice
这里,person1 是 Person 的实例。person1 本身没有 sayHello() 方法,所以它会去 Person.prototype 上查找。
2. 原型链
- 每个对象都有一个原型对象,而原型对象本身也可能有原型对象,这样就形成了一条链。
- 例如,person1 的原型是 Person.prototype,而 Person.prototype 也是一个对象,它的原型是 Object.prototype(因为Person.prototype 是一个普通对象,由 Object 构造函数创建),而 Object.prototype 的原型是 null,这样就构成了一个原型链:person1 -> Person.prototype -> Object.prototype -> null。
- 当我们访问 person1.toString() 时,查找过程如下:
- 首先在 person1 自身查找,没有找到;
- 然后去 Person.prototype 上查找,也没有;
- 接着去 Object.prototype 上查找,找到了 toString 方法,于是调用它。
3. __proto__
与 Object.getPrototypeOf()
__proto__
(注意:前后各两个下划线)是对象的一个内部属性,它指向该对象的原型。虽然它在很多环境中可用,但并不是标准的一部分,因此更推荐使用 Object.getPrototypeOf(obj) 来获取对象的原型。- 使用 Object.setPrototypeOf(obj, prototype) 可以设置对象的原型,但修改对象的原型可能会带来性能问题,因此应谨慎使用。
4. 构造函数的 prototype 属性与实例的 __proto__
构造函数的 prototype 属性在创建实例时会被赋值给实例的 __proto__
。即:instance.__proto__ === Constructor.prototype
。
5. 原型链的终点
所有原型链的终点都是 Object.prototype,它是所有对象的原型。而 Object.prototype 的原型是 null。
6. 继承
JavaScript 的继承是通过原型链实现的。子类的原型对象指向父类的一个实例,从而继承父类的属性和方法。
例如,实现继承的一种方式(不推荐使用,仅作为示例):
function Animal(name) {this.name = name;
}
Animal.prototype.speak = function() {console.log(`${this.name} makes a noise.`);
};function Dog(name) {Animal.call(this, name); // 调用父类构造函数
}
// 设置Dog的原型为Animal的实例
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数指向Dog.prototype.bark = function() {console.log(`${this.name} barks.`);
};const dog = new Dog('Rex');
dog.speak(); // 继承自Animal: Rex makes a noise.
dog.bark(); // Rex barks.
在这个例子中,Dog 继承了 Animal。Dog 的实例 dog 的原型链是:dog -> Dog.prototype(也是Animal的一个实例)-> Animal.prototype -> Object.prototype -> null。
注意:现代JavaScript中,更推荐使用 class 和 extends 关键字来实现继承,但其底层仍然是基于原型链。
7. 验证原型的方法
instanceof:检测构造函数的 prototype 是否在对象的原型链上。
console.log(alice instanceof Person); // true
console.log(alice instanceof Object); // true
Object.getPrototypeOf():获取对象的 [[Prototype]]。
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true
isPrototypeOf():检测对象是否在另一个对象的原型链上。
console.log(Person.prototype.isPrototypeOf(alice)); // true
总结
- 原型(prototype)是函数对象的一个属性,它指向一个对象,该对象包含可以由该函数创建的所有实例共享的属性和方法。
- 每个对象都有一个原型对象(通过
__proto__
或 Object.getPrototypeOf 访问),对象从原型对象继承属性和方法。 - 原型链是由原型对象通过
__proto__
连接起来的链,用于实现属性和方法的查找和继承。