JavaScript中的继承

原型式继承

定义一个拥有一些属性的构造器Person()

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

所有的方法都定义在构造器的原型上,比如:

Person.prototype.greeting = function() {
  alert('Hi! I\'m ' + this.name.first + '.');
};

创建一个类Teacher,继承Person的所有成员,同时也包括:
1、一个新属性,subject;
2、一个被更新的greeting()方法

定义Teacher() 构造器函数

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

这样,Teacher()构造函数就拥有了Person()构造函数中的所有属性(通过call(this),修改绑定,this指向Teacher函数)。

从无参构造函数继承

如果继承的构造函数不需要从传入的参数中获取其属性值,则不需要从call()中为其指定其他参数

function Brick() {
  this.width = 10;
  this.height = 20;
}

function BlueGlassBrick() {
  Brick.call(this);

  this.opacity = 0.5;
  this.color = 'blue';
}
设置Teacher()的原型和构造器引用

目前,已经定义了一个新的构造器函数Teacher,这个构造器函数默认有一个空的原型属性,现在需要让Teacher()从Person()的原型对象中继承方法

Teacher.prototype = Object.create(Person.prototype);

但是,Teacher.prototype.contructor 指向的是谁呢?
浏览器控制台打印看看结果,
在这里插入图片描述
现在Teacher()的prototype的constructor属性指向的是Person(), 这是由我们生成Teacher()的方式决定的,于是需要进行正确的设置

Teacher.prototype.constructor = Teacher;

注:
1.每一个函数对象(Function)都有一个prototype属性,并且只有函数对象有prototype属性,因为prototype属性本身就是定义在Function对象下。当我们输入类似 var person1 = new Person()来构造对象时,JavaScript实际上参考的是Person.prototype指向的对象来生成person1。另一方面,Person()函数是Person.prototype的构造函数,即,Person === Person.prototype.constructor。
2.在定义新的构造函数Teacher时,我们通过function.call来调用父类的构造函数,但是这样无法自动指定Teacher.prototype的值,这样Teacher.prototype就只能包含在构造函数里构造的属性,而没有方法。因此我们利用Object.create()方法将Person.prototype作为Teacher.prototype的原型对象,并改变其构造器指向,使之与Teacher关联。

3.任何想要被继承的方法都应该定义在构造函数的prototype对象里,并且永远使用父类的prototype来创造子类的prototype,这样才不会打乱类继承结构。

向 Teacher() 添加一个新的greeting()函数

此时Teacher.prototype.greeting 还是Person.prototype.greeting,因此我们还需要在构造函数Teacher()上定义一个新的函数greeting(),最简单的方法是在Teacher的原型上定义它:

Teacher.prototype.greeting = function() {
  var prefix;

  if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
    prefix = 'Mr.';
  } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
    prefix = 'Mrs.';
  } else {
    prefix = 'Mx.';
  }

  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};
对象成员总结

1、那些定义在构造器函数中的、用于给予对象实例的。这些都很容易发现 - 在您自己的代码中,它们是构造函数中使用this.x = x类型的行;在内置的浏览器代码中,它们是可用于对象实例的成员(通常通过使用new关键字调用构造函数来创建,例如var myInstance = new myConstructor())。
2、那些直接在构造函数上定义、仅在构造函数上可用的。这些通常仅在内置的浏览器对象中可用,并通过被直接链接到构造函数而不是实例来识别。 例如Object.keys()。
3、那些在构造函数原型上定义、由所有实例和对象类继承的。这些包括在构造函数的原型属性上定义的任何成员,如myConstructor.prototype.x()。

实现继承的6种方式

1、原型链继承
将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的所有属性和方法,可以继续往上,最终形成原型链。

function fn() {
	this.name = "123";
}

function fn1() {
	this.age = 17;	
}

fn1.prototype = new fn();

var fn2 = new fn1();
console.log(fn2.name); // "123"
console.log(fn2.age); // 17

存在问题:
1、当实现继承后,另一个原型的实例属性,变成了现在这个原型的原型属性,然后该原型的引用类型属性会被所有的实例共享,这样继承原型引用类型属性的实例之间不再具有自己的独特性了
2、在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数
2、借用构造函数继承
为了解决原型中包含引用类型值的问题,开始使用借用构造函数,也叫伪造对象或经典继承

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

function SubType() {
	//继承SuperType
	SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green" 

1、将SuperType函数在SubType构造函数中调用,在每个实例中执行,这样每个实例中都会有一份SuperType中的属性方法的副本,也就实现继承SuperType。
2、这种模式的优势是可以在子类型构造函数中向超类型构造函数传递参数。
注:存在的问题:所有类型只能使用构造函数模式(因为超类型的原型中定义的方法对于子类型不可见),因此方法都在构造函数中定义,函数复用就无从谈起了。
3、组合继承
将原型链和借用构造函数的技术组合到一块,使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承

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

SuperType.prototype.sayName = function() {
	alert(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() {
	alert(this.age);
}
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
  • 将SubType的原型指定为SuperType的一个实例,大致步骤和原型链继承类似,只是多了在SubType中借调SuperType的过程
  • 实例属性定义在构造函数中,而方法则定义在构造函数的新原型中,同时将新原型的constructor指向构造函数
  • 可以通过instanceof和isPrototypeOf()来识别基于组合继承创建的对象
  • 避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式

实际上是借用了构造函数,以覆盖的方式,解决了在原型链继承中原型的引用类型属性共享在所有实例中的问题。

因为在子类型中借调构造函数(SuperType.call(this))时,会在自己的所有实例中执行一遍SuperType中的代码,由于每个实例this都是不同的,因此SuperType中定义的属性会在每个实例中有一份副本,也就避免了原型链继承中,原型属性共享的问题(覆盖了原型属性)。

4、原型式继承

不自定义类型的情况下,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型,然后在此基础实例化对象,并返回。

function object(o) {
	function F() {};
	F.prototype = o;
	return new F();
}

本质上是object()对传入的对象执行了一次浅复制

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

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

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

alert(person.friends); //"Shelby,Court,Van,Rob,Barbie" 
  • 原型的引用类型属性会在各实例之间共享
  • 当只想单纯地让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的

注意:
ES5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与这里的object()方法的行为相同。第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。

5、寄生式继承

其实就是在原型式继承得到的对象的基础上,在内部再以某种方式来增强对象,然后返回。

function createAnother(original) {
	var clone = object(original);
	clone.sayHi = function () {
		alert("hi");
	}
	return clone;
}
  • 思路与寄生构造函数和工厂模式类似。
  • 新的对象中不仅具有original的所有属性和方法,而且还有自己的sayHi()方法。
  • 寄生式继承在主要考虑对象而不是自定义类型和构造函数的情况下非常有用。
  • 由于寄生式继承为对象添加函数不能做到函数复用,因此效率降低。

6、寄生组合式继承

组合继承是JS中最常用的继承模式,但其实它也有不足,组合继承无论什么情况下都会调用两次超类型的构造函数,并且创建的每个实例中都要屏蔽超类型对象的所有实例属性。
寄生组合式继承就解决了上述问题,被认为是最理想的继承范式。

function object(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

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

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

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

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

inheritPrototype(SuperType, SubType);	// 这一句,替代了组合继承中的SubType.prototype = new SuperType()

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

既然在组合模式中我们通过借调构造函数来为每个实例定义实例属性,从而覆盖原型属性,影响了效率,那么是否可以把原型改变一下呢,不让它作为SuperType的实例,这样就不会有一些无用的原型属性了。

不必为了指定子类型的原型而调用超类型的构造函数,我们需要的只不过是超类型原型的一个副本。

在inheritPrototype()函数中所做的事:

1、在inheritPrototype函数中用到了原型式继承中的object()方法,将超类型的原型指定为一个临时的空构造函数的原型,并返回构造函数的实例。
2、此时由于构造函数内部为空(不像SuperType里面有实例属性),所以返回的实例也不会自带实例属性,这很重要!因为后面用它作为SubType的原型时,就不会产生无用的原型属性了,借调构造函数也就不用进行所谓的“重写”了。
3、然后为这个对象重新指定constructor为SubType,并将其赋值给SubType的原型。这样,就达到了将超类型构造函数的实例作为子类型原型的目的,同时没有一些从SuperType继承过来的无用原型属性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值