关键词

javascript 用函数实现继承详解

下面是“javascript 用函数实现继承详解”的完整攻略,内容包括以下几部分:

  1. 什么是继承?
  2. 原型链继承
  3. 借用构造函数实现继承
  4. 组合继承
  5. 原型式继承
  6. 寄生式继承
  7. 寄生组合式继承

什么是继承?

继承是 JavaScript 中的一个重要概念,它允许我们可以在已有对象的基础上创建新的对象,并继承已有对象的属性和方法。通过继承,我们可以大大提高代码重用的效率,从而避免在代码中重复书写相同的功能。

原型链继承

原型链继承是最基本的一种继承方式,它的核心思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。具体来说,我们可以通过以下代码实现原型链继承:

function SuperType() {
  this.property = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.property;
};

function SubType() {
  this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
  return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue()); // true

在这段代码中,我们首先定义了一个 SuperType 构造函数,它有一个公共属性 property 和一个原型方法 getSuperValue。然后我们定义了一个 SubType 构造函数,它有一个公共属性 subproperty 和一个原型方法 getSubValue。接着,我们将 SubType 的原型指向了一个 SuperType 的实例对象,从而实现了原型链继承的效果。

需要注意的是,这种方式存在一个问题,即所有子类型的实例都会共享父类型的实例。例如:

var instance1 = new SubType();
var instance2 = new SubType();
console.log(instance1.getSuperValue()); // true
instance1.property = false;
console.log(instance2.getSuperValue()); // false

这说明在实例化 SubType 构造函数时,两个对象都指向了同一个原型对象 SuperType 的实例,所以一个实例对象的变更也会影响到另一个实例对象。

借用构造函数实现继承

为了解决原型链继承的问题,我们可以采用借用构造函数的方法。借用构造函数的核心就是在子类型的构造函数中调用父类型的构造函数,从而继承父类型的属性和方法。具体来说,我们可以通过以下代码实现借用构造函数实现继承:

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "green", "blue"];
}

function SubType(name) {
  SuperType.call(this, name);
}

var instance1 = new SubType("Nicholas");
instance1.colors.push("black");
console.log(instance1.name); // "Nicholas"
console.log(instance1.colors); // ["red", "green", "blue", "black"]

var instance2 = new SubType("Greg");
console.log(instance2.name); // "Greg"
console.log(instance2.colors); // ["red", "green", "blue"]

在这段代码中,我们首先定义了一个 SuperType 构造函数,它有一个公共属性 name 和一个属性 colors。然后我们定义了一个 SubType 构造函数,并在其中通过 SuperType.call(this, name) 实现了继承。最后我们分别实例化了两个对象,并对它们的 colors 属性进行了修改。

需要注意的是,这种方式解决了原型链继承的问题,但也带来了另一个问题,即无法继承父类型的原型方法。而且每次创建子类型实例都要调用一次父类型的构造函数,这实际上会导致子类型无法完全独立,因而无法实现代码复用。

组合继承

为了克服原型链继承和借用构造函数的问题,我们可以采用组合继承的方法,即将原型链继承和借用构造函数结合起来使用。具体来说,我们可以通过以下代码实现组合继承:

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "green", "blue"];
}

SuperType.prototype.sayName = function() {
  console.log(this.name);
};

function SubType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
  console.log(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.name); // "Nicholas"
console.log(instance1.colors); // ["red", "green", "blue", "black"]
instance1.sayName(); // "Nicholas"
instance1.sayAge(); // 29

var instance2 = new SubType("Greg", 27);
console.log(instance2.name); // "Greg"
console.log(instance2.colors); // ["red", "green", "blue"]
instance2.sayName(); // "Greg"
instance2.sayAge(); // 27

在这段代码中,我们首先定义了一个 SuperType 构造函数,它有一个公共属性 name 和一个属性 colors,还有一个公用方法 sayName。然后我们定义了一个 SubType 构造函数,并在其中通过 SuperType.call(this, name) 实现了继承,同时在 SubType.prototype 上定义了一个方法 sayAge。最后我们分别实例化了两个对象,并对它们的 colors 属性进行了修改。

需要注意的是,这种方式成功地克服了前两种方式各自的缺点,实现了完美的继承效果。但在使用时也会导致父类型构造函数被调用两次的问题,一次是在创建子类型原型时调用,另一次是在子类型构造函数内部调用了父类型构造函数。

原型式继承

原型式继承是一种以某种对象为模板,创建另一个实现继承的对象的方法。具体来说,我们可以通过以下代码实现原型式继承:

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]

在这段代码中,我们首先定义了一个 person 对象,它有一个属性 name 和一个属性 friends。然后我们通过 Object.create(person) 创建了一个新对象 anotherPerson,它继承了 person 对象的所有属性和方法,并可以自由修改。最后我们又创建了另一个对象 yetAnotherPerson,并对它的属性进行了修改。

需要注意的是,原型式继承采用了一种类似于对象迭代器的方法,可以继承一切可枚举的属性。但并没有解决对象之间相互影响的问题。

寄生式继承

寄生式继承是对原型式继承的一种增强,它的思想就是创建一个实现继承的函数,以某个对象为模板并增强它,最后返回这个实现继承的新对象。具体来说,我们可以通过以下代码实现寄生式继承:

function createAnother(original) {
  var clone = Object.create(original);
  clone.sayHi = function() {
    console.log("hi");
  };
  return clone;
}

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

console.log(person.friends); // ["Shelby", "Court", "Van", "Rob"]

在这段代码中,我们首先定义了一个 createAnother 函数,它接受一个对象作为参数,然后创建一个新对象 clone,并增加一个方法 sayHi。接着,我们通过 createAnother(person) 创建了一个新对象 anotherPerson,继承了 person 对象的所有方法,并增加了一个方法 sayHi。最后我们对 anotherPerson 对象的属性进行了修改,并输出了 person 对象的 friends 属性,验证了它们的关系。

需要注意的是,寄生式继承本质上仍然是原型式继承的增强版本,因此它也有继承同样的缺陷。

寄生组合式继承

寄生组合式继承是对组合继承的另一种改进方式,它通过寄生式继承来继承父类型的原型对象,并实现子类型的继承效果。具体来说,我们可以通过以下代码实现寄生组合式继承:

function inheritPrototype(subType, superType) {
  var prototype = Object.create(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "green", "blue"];
}

SuperType.prototype.sayName = function() {
  console.log(this.name);
};

function SubType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
  console.log(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.name); // "Nicholas"
console.log(instance1.colors); // ["red", "green", "blue", "black"]
instance1.sayName(); // "Nicholas"
instance1.sayAge(); // 29

var instance2 = new SubType("Greg", 27);
console.log(instance2.name); // "Greg"
console.log(instance2.colors); // ["red", "green", "blue"]
instance2.sayName(); // "Greg"
instance2.sayAge(); // 27

与组合继承方式类似,我们同样先定义一个 SuperType 构造函数和一个 SubType 构造函数,然后通过 inheritPrototype(SubType, SuperType) 实现继承。这一次不同于组合继承方式是,我们将父类型的实例和子类型的原型绑定到一起,从而实现了集成的效果。用寄生组合式继承的方式来继承 SuperType,实例化后得到的 SubType,既拥有父类构造函数中的属性,也拥有父类原型中的方法。

需要注意的是,使用这种方式可以实现完美的继承,而且避免了调用父类型构造函数多次的问题。同时,它也避免了在子类型原型中创建不必要的属性,但相对地也增加了代码复杂度和一定的初始化时间。

本文链接:http://task.lmcjl.com/news/9281.html

展开阅读全文