js中几种继承方式

本文详细探讨了JavaScript中实现继承的六种方式:原型链继承、构造函数内部继承、组合继承、原型式继承、寄生式继承和组合寄生式继承。每种方式都有其特点和适用场景,其中寄生组合式继承被视为最有效的继承方法。文章还分析了各种继承方式的优缺点,如原型链继承中引用类型值的共享问题,构造函数内部继承无法复用方法等。
摘要由CSDN通过智能技术生成

本文基于 原型链与继承别再被问倒了 增删 作者:路易斯 来源:掘金。

继承

js中继承 ?

Javascript中没有真正类的概念 , 它的"类"是通过构造函数实现的 .此后称类即构造函数 , 构造函数即类 .

子类实例拥有父类实例的所有属性(包括访问到原型链上的) , 然后在此基础上 , 添加自己的属性

比如有个父类实例 farther : 有 farther.xx , 就有son.xx .

确定原型和实例的关系

使用原型链后, 我们怎么去判断原型和实例的这种继承关系呢? 方法一般有两种.

第一种是使用 instanceof 操作符, 只要用这个操作符来测试实例(instance)与原型链中出现过的构造函数,结果就会返回true. 以下几行代码就说明了这点.

alert(instance instanceof Object);//true
alert(instance instanceof Father);//true
alert(instance instanceof Son);//true

由于原型链的关系, 我们可以说instance 是 Object, Father 或 Son中任何一个类型的实例. 因此, 这三个构造函数的结果都返回了true.

第二种是使用 isPrototypeOf() 方法, 同样只要是原型链中出现过的原型,isPrototypeOf() 方法就会返回true, 如下所示.

alert(Object.prototype.isPrototypeOf(instance));//true
alert(Father.prototype.isPrototypeOf(instance));//true
alert(Son.prototype.isPrototypeOf(instance));//true

总结: instanceofisPrototypeOf() 都是用来测试某个对象是否是某个构造函数的实例 , 他们的依据都是实例是否在构造函数原型对象组成的原型链上 ;

继承什么 ?
  • 构造函数执行中的属性

  • 构造函数的原型对象上的属性

  • 构造函数作为对象, 构造函数的属性 (es6 class中涉及)

    //这里有两个构造函数 , Son要继承Father
    --------
    function Father(poperty){
        this.poperty = poperty;//第一点
    }
    //子类继承父类后, 子类的实例上也应该有这个属性
    //比如var obj = new Son(); obj上有poperty这个属性 
    --------
    Farther.prototype.poperty;//第二点
    //比如Son实例沿原型链可以访问到父类原型对象的poperty这个属性
    --------
    Farther.poperty;//第三点
    //子类应该继承父类上的属性
    //比如Son上有poperty这个属性
    

    上面代码中三部分的东西没有关联 , 只是想说明我们要继承什么 .

把父类的东西继承下来是目的 , 所以不必拘泥于形式 , 而是寻求最好的方法

继承的方式 ?
1.原型链继承(寻找属性时:子类实例自身->父类实例->父类原型)
// 定义父类
function Parent(name){
    this.name = name;
    this.fnPa = function(){console.log(111)}
}
Parent.prototype.age = 22;
Parent.prototype.fn = function(){console.log(888)};

function Child(){
    this.type = "child";
}
Child.prototype = new Parent('parent'); // 借助原型链实现继承
var child = new Child();
console.log(child);

继承方式:子类的原型对为父类的一个实例

继承特点:利用原型链, 子类实例寻找属性时:子类实例自身->父类实例->父类原型

缺点:

  • 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;

  • 在创建子类型时,不能向超类型的构造函数中传递参数.

即子类的原型对象是父类实例 , 对比第下面的方法 , 子类在创建完成后作为子类的原型对象父类的实例就不再改变了(也只能有一个).

2.构造函数内部继承(父类混入子类)
// 定义父类
function Parent(color){
	this.name = "parent";
    this.color = color
}
Parent.prototype.fn = function(){
	console.log(1111)
}
// 子类
function Child(){
	Parent.call(this,'yellow'); // 这句代码就是借助构造函数实现部分继承,绑定this并执行父构造函数
	this.type = 'child';
}
var child1 = new Child();
console.log(child1);

继承方式:call执行父构造函数(不是 new )并绑定this

继承特点: 如同把父构造函数的内容混入子构造函数

解决了原型链的两大问题:

  • 保证了原型链中引用类型值的独立

  • 不再被所有实例共享;子类型创建时也能够向父类型传递参数.

缺点: 如果仅仅借用构造函数,那么将无法避免构造函数模式存在的问题–方法都在构造函数中定义, 因此函数复用也就不可用了. 而且超类型(如Father)中定义的方法,对子类型而言也是不可见的

3.组合继承(结合原型链继承和构造函数继承)

既然二者都有长处 , 小孩子才做选择 , 我们攻城狮当然是都要了 .

function Father(name){
	this.name = name;
	this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
	alert(this.name);
};
function Son(name,age){
	Father.call(this,name);//继承实例属性,第一次调用Father()
	this.age = age;
}
Son.prototype = new Father();//继承父类方法,第二次调用Father()
Son.prototype.sayAge = function(){
	alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5

var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10

继承方式 : 基于原型继承的基础上 , 在子类构造函数中加入构造函数内部继承的核心代码 Father.call(this,xxx)

继承特点:

  • 原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性
  • instanceof 和 isPrototypeOf( )也能用于识别基于组合继承创建的对象(上面的构造函数内部继承的方式不行).

缺点:我们很容易可以看出来 , Father.call(this,xxx) 已经帮我们实现了对父类中属性的继承 , 所以让子类原型对象等于父类实例 Son.prototype = new Father(); , 对于子类实例寻找属性是完全无用的 . 此举只是为了把子类实例拽到原型链上 , 使它可以访问到父类的原型对象上的属性 . 所以其仍有改进的空间 .

4.原型式继承(不使用构造函数,把一个对象作为多个生成对象的原型)

此为原型式继承 , 第一种是原型链继承. 它的意味好比 , 类模板就是一个原型对象 .

function object(o){
	function F(){}
	F.prototype = o;
	return new F();
}
var person = {
	friends : ["Van","Louis","Nick"]
};
var anotherPerson = object(person);
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.friends.push("Style");
alert(person.friends);//"Van,Louis,Nick,Rob,Style"

继承方式: 在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例.

继承特点: 我们看到 , 这种继承方式实际上没有真正的构造函数了.object完成了一个功能 , 在使用者不使用构造函数的情况下 , 返回一个自定义原型(传入的参数)的对象 . 创造多个实例时 , 如果这个自定义原型都是同一个对象 , 则他们都会继承这个对象的属性 .

缺点:同原型链继承一样 , 原型式继承也有原型对象上引用对象被共享的问题 .

在 ECMAScript5 中,通过新增 object.create() 方法规范化了上面的原型式继承.

object.create() 接收两个参数:

  • 一个用作新对象原型的对象
  • (可选的)一个为新对象定义额外属性的对象
var person = {
	friends : ["Van","Louis","Nick"]
};
var anotherPerson = Object.create(person);
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.friends.push("Style");
alert(person.friends);//"Van,Louis,Nick,Rob,Style"
复制代码

object.create() 只有一个参数时功能与上述object方法相同, 它的第二个参数与Object.defineProperties()方法的第二个参数格式相同: 每个属性都是通过自己的描述符定义的.以这种方式指定的任何属性都会覆盖原型对象上的同名属性.例如:

var person = {
	name : "Van"
};
var anotherPerson = Object.create(person, {
	name : {
		value : "Louis"
	}
});
alert(anotherPerson.name);//"Louis"
5.寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路

function createAnother(original){
	var clone = object(original);//通过调用object函数创建一个新对象
	clone.sayHi = function(){//以某种方式来增强这个对象(增强:为其添加属性或方法)
		alert("hi");
	};
	return clone;//返回这个对象
}
复制代码

继承方式:在原型式继承的基础上 , 为创造的对象添加属性 , 这些过程都放入一个函数中完成

缺点: 使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似.

6.组合寄生式继承(组合基础上,改变原型赋值时,采用原型式的方法)
function extend(subClass,superClass){
	var prototype = object(superClass.prototype);//创建对象
	prototype.constructor = subClass;//增强对象
	subClass.prototype = prototype;//指定对象
}
function Father(name){
	this.name = name;
	this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
	alert(this.name);
};
function Son(name,age){
	Father.call(this,name);//继承实例属性,第一次调用Father()
	this.age = age;
}
//Son.prototype = new Father();不创建新的父类实例而是,用extend完成
extend(Son,Father);
Son.prototype.sayAge = function(){
	alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5

var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10

继承方式:在组合继承的基础上 , 更改原型替换的方式(采用寄生式继承) , 就无需二次创建父类实例来完成原型链继承的目的了.

继承特点: extend的高效率体现在它没有调用superClass构造函数,因此避免了在subClass.prototype上面创建不必要,多余的属性. 于此同时,原型链还能保持不变; 因此还能正常使用 instanceof 和 isPrototypeOf() 方法.

以上,寄生组合式继承,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方法.

回顾继承方法
  • 原型链继承:子类的原型对象是父类的实例
  • 构造函数内部继承:用call(this)使父类内容混入子类

tip:这两者分别完成了继承原型链和父类内部内容的继承 , 但是构造函数内部继承不利于复用方法 , 纯原型链继承无法解决引用类型共用问题 , 不能传递参数 .

  • 组合继承:在原型链继承继承上 添加构造函数继承的核心call(this)

tip:集二者之长 , 但是有一个缺点 , 调用了两次父类构造函数 .导致不必要的开销

  • 寄生式继承:核心是可以创造一个原型是父类原型对象的空对象

  • 寄生组合式继承:在组合继承的基础上 , 使用寄生式继承的方法来完成原型链的继承 . 以解决二次调用父类构造函数的问题

问题:为什么不能调用两次"new F()"呢

如果父类属性较少还好 , 可是在大型库中 , 父类的属性是非常的多的 , 用一个空对象完成继承比用父类实例完成继承好得多 .

问题:为什么要 extend, 既然extend的目的是将子类型的 prototype 指向超类型的 prototype,为什么不直接做如下操作呢?

subClass.prototype = superClass.prototype;//直接指向超类型prototype

如果你想给子类的原型对象上添加属性 , 但是子类和父类共用原型 , 你根本无法把他们区分开 , 这是一个大问题 . 所以 , 基于如上操作, 子类型原型将与超类型原型共用, 根本就没有继承关系.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值