解释 JavaScript 中原型继承的工作原理
主题
JavaScriptOOP
在GitHub上编辑
TL;DR
JavaScript 中的原型继承是对象从其他对象继承属性和方法的一种方式。每个 JavaScript 对象都有一个名为 [[Prototype]]
的特殊隐藏属性(通常通过 __proto__
或使用 Object.getPrototypeOf()
访问),它引用另一个对象,该对象被称为对象的“原型”。
当访问对象的属性,并且在该对象上找不到该属性时,JavaScript 引擎会查看对象的 __proto__
,以及 __proto__
的 __proto__
,依此类推,直到它在其中一个 __proto__
上找到定义的属性,或者直到它到达原型链的末尾。
这种行为模拟了经典继承,但实际上它更像是委托而不是继承。
以下是原型继承的示例:
// Parent object constructor.function Animal(name) {this.name = name;}// Add a method to the parent object's prototype.Animal.prototype.makeSound = function () {console.log('The ' + this.constructor.name + ' makes a sound.');};// Child object constructor.function Dog(name) {Animal.call(this, name); // Call the parent constructor.}// Set the child object's prototype to be the parent's prototype.Object.setPrototypeOf(Dog.prototype, Animal.prototype);// Add a method to the child object's prototype.Dog.prototype.bark = function () {console.log('Woof!');};// Create a new instance of Dog.const bolt = new Dog('Bolt');// Call methods on the child object.console.log(bolt.name); // "Bolt"bolt.makeSound(); // "The Dog makes a sound."bolt.bark(); // "Woof!"
需要注意的是:
.makeSound
未在Dog
上定义,因此 JavaScript 引擎会向上查找原型链,并在继承的Animal
上找到.makeSound
。- 不再推荐使用
Object.create()
来构建继承链。请改用Object.setPrototypeOf()
。
Javascript 中的原型继承
原型继承是 JavaScript 中用于创建从其他对象继承属性和方法对象的特性。JavaScript 使用基于原型的模型,而不是基于类的继承模型,对象可以直接从其他对象继承。
关键概念
- 原型:JavaScript 中的每个对象都有一个原型,它也是一个对象。当您使用对象字面量或构造函数创建对象时,新对象将链接到其构造函数的原型,如果未指定原型,则链接到
Object.prototype
。这通常使用__proto__
或[[Prototype]]
引用。您还可以使用内置方法Object.getPrototypeOf()
获取原型,并且可以通过Object.setPrototypeOf()
设置对象的原型。
// Define a constructor functionfunction Person(name, age) {this.name = name;this.age = age;}// Add a method to the prototypePerson.prototype.sayHello = function () {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);};// Create a new object using the constructor functionlet john = new Person('John', 30);// The new object has access to the methods defined on the prototypejohn.sayHello(); // "Hello, my name is John and I am 30 years old."// The prototype of the new object is the prototype of the constructor functionconsole.log(john.__proto__ === Person.prototype); // true// You can also get the prototype using Object.getPrototypeOf()console.log(Object.getPrototypeOf(john) === Person.prototype); // true// You can set the prototype of an object using Object.setPrototypeOf()let newProto = {sayGoodbye: function () {console.log(`Goodbye, my name is ${this.name}`);},};Object.setPrototypeOf(john, newProto);// Now john has access to the methods defined on the new prototypejohn.sayGoodbye(); // "Goodbye, my name is John"// But no longer has access to the methods defined on the old prototypeconsole.log(john.sayHello); // undefined
-
原型链:当访问对象的属性或方法时,JavaScript 首先在对象本身上查找它。如果找不到,它会查看对象的原型,然后是原型的原型,依此类推,直到找到该属性或到达链的末尾(即
null
)。 -
构造函数:JavaScript 提供了构造函数来创建对象。当一个函数与 new 关键字一起用作构造函数时,新对象的原型 (
[[Prototype]]
) 将设置为构造函数 的原型属性。
// Define a constructor functionfunction Animal(name) {this.name = name;}// Add a method to the prototypeAnimal.prototype.sayName = function () {console.log(`My name is ${this.name}`);};// Define a new constructor functionfunction Dog(name, breed) {Animal.call(this, name);this.breed = breed;}// Set the prototype of Dog to be a new instance of AnimalDog.prototype = Object.create(Animal.prototype);// Add a method to the Dog prototypeDog.prototype.bark = function () {console.log('Woof!');};// Create a new object using the Dog constructor functionlet fido = new Dog('Fido', 'Labrador');// The new object has access to the methods defined on its own prototype and the Animal prototypefido.bark(); // "Woof!"fido.sayName(); // "My name is Fido"// If we try to access a method that doesn't exist on the Dog prototype or the Animal prototype, JavaScript will return undefinedconsole.log(fido.fly); // undefined
Object.create()
:此方法使用指定原型对象和属性创建一个新对象。这是设置原型继承的一种直接方法。如果您通过Object.create(null)
创建一个对象,它将不会从Object.prototype
继承任何属性。这意味着该对象将没有任何内置属性或方法,例如toString()
、hasOwnProperty()
// Define a prototype objectlet proto = {greet: function () {console.log(`Hello, my name is ${this.name}`);},};// Use `Object.create()` to create a new object with the specified prototypelet person = Object.create(proto);person.name = 'John';// The new object has access to the methods defined on the prototypeperson.greet(); // "Hello, my name is John"// Check if the object has a propertyconsole.log(person.hasOwnProperty('name')); // true// Create an object that does not inherit from Object.prototypelet animal = Object.create(null);animal.name = 'Rocky';// The new object does not have any built-in properties or methodsconsole.log(animal.toString); // undefinedconsole.log(animal.hasOwnProperty); // undefined// But you can still add and access custom propertiesanimal.describe = function () {console.log(`Name of the animal is ${this.name}`);};animal.describe(); // "Name of the animal is Rocky"