es5类的继承

一、什么是继承?

继承是面向对象的一个重要特性。比如说,我们使用原型模式创建了Person,现在又有需求了,有一个Student 类,它不仅有name sex 还有 一个 number 值,表示学号,sayNumber函数,用来输出number;还有一个 techer 类,它不仅有name sex 还有 一个 course值,表示所教的课程,sayCourse函数,用来输出course 。那么,我们是不是得重新创建两个类,然后把Person 拷贝两份,然后再分别加上它们的特殊的值和函数吗?那太糟糕了!而继承就是用来解决这一问题,即继承父类的所有方法和属性,并扩展。

二、实现继承的方式

1、原型链式继承

function Person (){
	this.name = 'zhangsan';
	this.sex = 'man';
	this.friends = ['lisi','wangwu'];
}
Person.prototype.sayName = function(){
	console.log(this.name);
}
function Student (number){
	this.number = number
}
Student.prototype = new Person();
const student1 = new Student(1);
const student2 = new Student(2);
console.log(student1.number);  // 1
console.log(student2.number);  // 2

我们将类Person的实例赋值给类Student的原型对象,这样Student的实例就会拥有Person的所有属性和方法;
原型链式继承存在其中的一个问题就是,friends是应用类型的值,当修改student1的friends时,student2的friends属性也随之更改,如以下代码:

student1.friends.push('zhaoliu');
console.log(student1.friends);  // ['lisi', 'wangwu', 'zhaoliu']
console.log(student2.friends);  // ['lisi', 'wangwu', 'zhaoliu']

原型链继承的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数(Person函数)中传递参数。实际上, 应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数(Person函数)传递参数。有鉴于此,再加上 前面刚刚讨论过的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链继承。

2、借用构造函数继承

在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数 (constructor stealing)的技术。这种技术的基本思想相当简单,即在子类型构造函数(Student函数)的内部调用超类型构造函数(Person函数)。别忘了,函数只不过是在特定环境中执行代码的对象, 因此通过使用 apply()和 call()方法也可以在(将来)新创建的对象上执行构造函数,如下所示:

function Person (){
	this.name = 'zhangsan';
	this.sex = 'man';
	this.friends = ['lisi','wangwu'];
}
Person.prototype.sayName = function(){
	console.log(this.name);
}
function Student (number){
	Person.call(this)
	this.number = number
}
const student1 = new Student(1);
const student2 = new Student(2);
student1.friends.push('zhaoliu');
console.log(student1.friends);  // ['lisi', 'wangwu', 'zhaoliu']
console.log(student2.friends);  // ['lisi', 'wangwu']

1)传递参数

相对于原型链继承而言,借用构造函数继承有一个很大的优势,即可以在子类型构造函数(student函数)中向超类型构造函数(Person)传递参数。看下面这个例子。

function Person (name,sex){
	this.name = name;
	this.sex = sex;
	this.friends = ['lisi','wangwu'];
}
Person.prototype.sayName = function(){
	console.log(this.name);
}
function Student (number,name,sex){
	Person.call(this,name,sex)
	this.number = number
}
const student1 = new Student(1,'zhangsan','man');
const student2 = new Student(2,'lisi','woman');
console.log(student1.name);  // 'zhangsan'
console.log(student2.name);  // 'lisi'

2)借用构造函数继承问题

如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定 义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结 果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。

3、组合式继承

组合继承(combination inheritance),指的是将原型链式继承和借用构造函数继承的 技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链继承实现对原型属性和方 法的继承,而通过借用构造函数继承来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数 复用,又能够保证每个实例都有它自己的属性。下面来看一个例子。

function Person (name,sex){
	this.name = name;
	this.sex = sex;
	this.friends = ['lisi','wangwu'];
}
Person.prototype.sayName = function(){
	console.log(this.name);
}
function Student (number,name,sex){
	Person.call(this,name,sex)
	this.number = number
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.sayNumber = function(){
	console.log(this.number);
}
const student1 = new Student(1,'zhangsan','man');
const student2 = new Student(2,'lisi','woman');
console.log(student1.sayName());  // 'zhangsan'
console.log(student1.sayNumber());  // 1
console.log(student2.sayName());  // 'lisi'
console.log(student2.sayNumber())  // 2

组合继承避免了原型链j继承和借用构造函数继承的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继 承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。

4、寄生式继承

寄生式继承,是指基于某个对象或某些信息创建一个对象,然后增强对象,最后返回这个对象;在此之前需要实例化一个临时副本实现相同的原型链继承。

function object(o){
	function Superfn(){};
	Superfn.prototype = o;
	return new Superfn();
}
function Student(o){
	const con = object(o);
	con.say = function(){
		console.log('i am a student.');
	}
	return con;
}
const Person = {
	name: 'zhangsan',
	sex: 'man'
}
const student1 = new Student(Person);
console.log(student1.name);  // 'zhangsan'
student1.say();  // 'i am a student.'

优点:解决了组合继承中每次创建子类实例时都执行了两次构造函数的问题;
缺点:1)原型链继承存在的问题,他都有;
2)使用寄生式继承为对象添加方法,会由于不能做到方法的复用而降低效率,这一点和构造函数模式类似;

5、寄生式组合继承

寄生式组合继承,是指通过借用构造函数来继承属性,通过原型链式来继承方法。
基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的是超类型原型的一个副本。
第一步:使用寄生式继承来继承超类型的原型。
第二步:将结果指定给子类型的原型。

function Person(name,sex){
	this.name = name;
	this.sex = sex;
}
Person.prototype.sayName = function(){
	console.log(this.name);
}
function inherit(son,father){
	function Superfn(){};
	Superfn.prototype = father.prototype;
	const prototype = new Superfn();
	prototype.constructor = son;
	son.prototype = prototype;
}
function Student(name,sex,number){
	Person.call(this,name,sex);
	this.number = number;
}
inherit(Student,Person);
Student.prototype.sayNumber = function(){
	console.log(this.number);
}
const student1 = new Student('zhangsan','man',1);
student1.sayName();  // 'zhangsan'
student1.sayNumber();  // 1

优点:1)集寄生式继承和组合式继承的优点与一身,实现基本类型继承的最有效方法。
2)只调用了一次父类(father),并且避免了(子类原型)Student.prototype上面创建多余的不必要属性,同时保持原型链不变

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值