JavaScript

JavaScript 知识量:26 - 101 - 483

7.3 继承><

原型链- 7.3.1 -

在JavaScript中,每个对象都有一个称为原型(prototype)的特殊属性,这个原型也是一个对象。原型链是JavaScript中对象如何从创建它们的构造函数获取属性和方法的机制。

以下是一个简单的JavaScript原型链示例:

// 定义一个构造函数  
function Person(name) {  
  this.name = name;  
}  
  
// 为Person的原型添加一个方法  
Person.prototype.sayHello = function() {  
  return "Hello, I'm " + this.name;  
};  
  
// 创建一个新的Person实例  
let john = new Person('John');  
  
// 访问来自实例本身的属性  
console.log(john.name); // 输出 "John"  
  
// 访问来自原型链的属性  
console.log(john.sayHello()); // 输出 "Hello, I'm John"

在这个例子中,john对象继承了它的原型Person.prototype中的sayHello方法。每个JavaScript对象都有一个指向它的原型的内部链接。当试图访问一个对象的属性或方法时,如果该对象自身不具有该属性或方法,那么JavaScript会在这个对象的原型上查找。如果原型上也没有,那么就会继续向上查找,直到找到原型链的末端。这就是JavaScript原型链的基本概念。

同时,当创建一个新构造函数时,可以选择将构造函数的prototype属性设置为另一个对象。这个对象就成为了所有通过这个构造函数创建的实例的原型。例如:

function Car(make, model, year) {  
  this.make = make;  
  this.model = model;  
  this.year = year;  
}  
  
Car.prototype = {  
  constructor: Car,  
  getYear: function() {  
    return this.year;  
  },  
  setYear: function(year) {  
    this.year = year;  
  }  
};

在这个例子中,Car.prototype就是通过Car构造函数创建的所有实例的原型。这意味着所有Car的实例都可以访问Car.prototype上定义的属性和方法。

盗用构造函数- 7.3.2 -

在JavaScript中,"盗用"构造函数通常指的是一种创建新对象并继承另一个对象或构造函数的功能的技术。这种技术涉及使用Object.create()或new Function()之类的函数来创建一个新的对象,并指定这个新对象的__proto__属性为另一个对象或构造函数。

下面是一个例子说明如何"盗用"一个构造函数:

function Person(name, age) {  
  this.name = name;  
  this.age = age;  
}  
  
let personProto = {  
  greet: function() {  
    console.log(`Hello, my name is ${this.name}`);  
  }  
};  
  
let john = Object.create(personProto);  
john.name = "John";  
john.age = 30;  
  
john.greet(); // 输出 "Hello, my name is John"

在这个例子中,创建了一个Person构造函数,并定义了greet方法。然后使用Object.create()方法创建了一个新的对象john,并指定它的原型为personProto。这样,john就可以访问personProto上的greet方法。

需要注意的是,这种技术虽然可以在某些情况下有用,但它也有一些潜在的问题和风险。例如,使用这种方法创建的对象不会继承构造函数中的任何属性或方法。此外,如果原型对象中的方法使用了this关键字,那么这些方法在盗用构造函数创建的对象中的行为可能会不如预期。

组合继承- 7.3.3 -

JavaScript中的组合继承是一种实现继承机制的一种方法,它结合了原型链和构造函数链的优点。在组合继承中,子类的原型是父类的实例,因此子类可以继承父类的属性和方法。同时,子类的构造函数会首先调用父类的构造函数,以确保父类的属性被正确地初始化。

以下是一个组合继承的示例代码:

function Parent() {  
  this.parentProp = 'parent';  
}  
  
Parent.prototype.parentMethod = function() {  
  console.log('This is a parent method.');  
}  
  
function Child() {  
  Parent.call(this); // 调用父类构造函数  
  this.childProp = 'child';  
}  
  
Child.prototype = Object.create(Parent.prototype); // 子类的原型是父类的实例  
Child.prototype.constructor = Child; // 子类的构造函数是Child  
  
Child.prototype.childMethod = function() {  
  console.log('This is a child method.');  
}  
  
var child = new Child();  
console.log(child.parentProp); // 输出 "parent"  
console.log(child.childProp); // 输出 "child"  
console.log(child instanceof Parent); // 输出 true  
console.log(child instanceof Child); // 输出 true

在这个例子中,Parent和Child都是构造函数,Parent的原型上有一个方法parentMethod,Child的原型上有一个方法childMethod。在Child的构造函数中,使用Parent.call(this)来调用父类的构造函数,以确保父类的属性被正确地初始化。同时,还将Child的原型设置为一个新对象,该对象是Parent的实例,并指定了Child的构造函数。这样,Child就可以继承Parent的属性和方法,同时还可以添加自己特有的属性和方法。

原型式继承- 7.3.4 -

原型式继承是一种常见的继承模式。这种模式基于一个核心概念:每个对象都有一个指向它的原型(prototype)的链接,这个原型对象也是一个对象,它可以包含可以被子类共享的属性和方法。

以下是一个简单的原型式继承的例子:

// 定义一个构造函数,作为原型式继承的基础  
function Person(name) {  
  this.name = name;  
}  
  
// 为Person的原型添加一个方法  
Person.prototype.sayHello = function() {  
  return "Hello, I'm " + this.name;  
};  
  
// 创建一个新的Person实例  
let john = new Person('John');  
  
// 访问来自实例本身的属性  
console.log(john.name); // 输出 "John"  
  
// 访问来自原型链的属性  
console.log(john.sayHello()); // 输出 "Hello, I'm John"

在这个例子中,john对象继承了它的原型Person.prototype中的sayHello方法。当试图访问john的sayHello方法时,JavaScript首先在john自身查找这个方法,如果没有找到,那么它就会沿着原型链向上查找,直到找到或者达到原型链的顶端。

原型式继承模式使得创建大量相似对象时可以节省内存,因为所有的实例可以共享同一个原型。

寄生式继承- 7.3.5 -

寄生式继承(parasitic inheritance)是一种传统的继承方法,它通过创建一个新对象,然后使用现有对象的属性和方法来初始化这个新对象,最后返回新对象。

下面是一个简单的寄生式继承的例子:

function createObject(o) {  
  var newObject = {};  
  for (var i in o) {  
    if (o.hasOwnProperty(i)) {  
      newObject[i] = o[i];  
    }  
  }  
  return newObject;  
}  
  
var person1 = {  
  name: "Tom",  
  age: 30,  
  greet: function() {  
    console.log("Hello, my name is " + this.name);  
  }  
};  
  
var person2 = createObject(person1);  
person2.name = "Jerry";  
person2.greet();  // 输出 "Hello, my name is Jerry"

在这个例子中,定义了一个 createObject 函数,它接受一个对象 o 作为参数。这个函数首先创建一个空对象 newObject,然后通过遍历 o 的所有属性,将 o 的属性复制到 newObject 中。注意:这里使用了 hasOwnProperty 方法来检查 o 的属性是否为自身拥有,而不是从原型链上继承的。最后,函数返回新创建的对象 newObject。

寄生式继承在 JavaScript 中并不常用,因为这种继承方式不能很好地处理继承链中的冲突和问题。例如,如果有两个对象都定义了同名的方法,那么在使用寄生式继承时,这两个方法都会被复制到新对象中,这就造成了冲突。此外,寄生式继承也不能很好地处理构造函数和 this 上下文的问题。

在 JavaScript 中,更推荐使用的是基于构造函数的原型链继承,也就是使用 Function.prototype.call 或 Function.prototype.apply 方法来设置新对象的 __proto__ 属性,从而让新对象继承现有对象的属性和方法。这种继承方式能更好地处理继承链中的冲突和问题,并且能更好地处理构造函数和 this 上下文。

寄生式组合继承- 7.3.6 -

在 JavaScript 中,有一种继承模式被称为"寄生式组合继承"(parasitic combination inheritance)。这种模式是当一个对象需要从多个原型对象继承属性时的一种解决方案。

在寄生式组合继承中,新对象首先从第一个原型对象那里继承属性,然后把这个对象设置为第二个原型对象的原型,最后把新对象作为第二个原型对象的实例。

以下是一个简单的示例:

function object1() {  
  this.name = 'object1';  
}  
  
function object2() {  
  this.name = 'object2';  
}  
  
var obj = new object1();  
obj.__proto__ = object2.prototype;

在这个例子中,obj 首先从 object1 继承了 name 属性,然后将其原型设置为 object2 的原型,从而可以从 object2 继承属性。然而,这样做有一些缺点。例如,如果两个原型对象中有同名的方法或属性,那么新对象会从第二个原型对象那里继承这个属性或方法,而不是从第一个原型对象那里。这可能会导致一些混乱和预期之外的行为。

此外,寄生式组合继承并不是真正的面向对象编程(OOP)的一部分,因为它没有实现真正的继承。在 JavaScript 中,真正的面向对象编程通常使用基于构造函数的原型链继承。