JS中的几种继承方式举例


在面向对象编程语言中,最基础最重要的概念就是 类和实例

类的三大特性——继承、封装、多态

  • 封装:类作为一个函数,把实现功能的代码进行封装,实现“低耦合高内聚”。

  • 多态:包括方法的重载和重写。

    • 重载:在其他语言中(如Java、C++等)重载是指相同的方法,参数或返回值不同,具备不同的功能。但JS不具备严格意义上的重载。
    // java
    public void fn(int x,int y){}
    public void fn(int x){}
    
    fn(10,20);      // 执行第一个函数
    fn(10);         // 执行第二个函数
    
    // JS
    function fn(x,y){}
    function fn(x)  // 会把前一个覆盖
    fn(10,20)       // 执行第二个函数
    fn(10)          // 同上
    
    • 重写:指的是子类重写父类上的方法,一般伴随继承。
  • 继承:子类继承父类中的属性、方法。继承的目的是让子类的实例同时也能使用父类中的私有 / 共有的属性 & 方法。

    • 其他后端语言的继承类似自然界的机制,子类(儿子)把父类(父亲)身上的部分基因“copy”一份到自己身上,子类在自身上重写继承来的属性或方法,不会影响到父类。
    • JS中的继承不是copy继承,而是基于原型链__proto__查找指向式的继承

JS中的多种继承方式

原型继承

function Parent(){
	this.x = 100;
}
Parent.prototype.getX = function getX(){
	return this.x;
}
function Child(){
	this.y = 200;
}
Child.prototype.getY = function getY(){
	return this.y;
}
let c1 = new Child;
console.log(c1);

从前,Parent类和Child类分别同他们的原型、实例生活在一起,两个家族互不相干。
在这里插入图片描述
突然有一天他们相遇了,Parent强行要成为Child的爸爸,并许诺我家的东西你都可以用,但不是复制一份给你,也不是送给你,而是你可以通过秘密通道来拿我家的东西用。
秘密通道如何建立?那就是让子类的原型=父类的实例

function Parent(){
	this.x = 100;
}
Parent.prototype.getX = function getX(){
	return this.x;
}
function Child(){
	this.y = 200;
}
Child.prototype = new Parent;    // 原型继承的核心代码
Child.prototype.getY = function getY(){
	return this.y;
}
let c1 = new Child;
console.log(c1);

在这里插入图片描述
我们把子类的原型指向了父类的一个实例,子类的实例就可以通过__proto__找到子类的原型(这时是父类的实例),再继续沿__proto__找到父类的原型,如黄色的秘密通道所示。

原型继承有以下几个特点:

  • 子类实例中只有子类的私有属性 / 方法(本例中是y),而无论是父类的私有还是公有的属性方法,都变成了子类实例公有的(x,getY,getX)。
  • 是基于__proto__的指向查找式继承。c1.__proto__.xxx = xxx这样修改子类原型上的属性方法,会对子类的其他实例有影响(公有属性方法被修改了),而对父类的其他实例没有影响。通过c1.__proto__.__proto__.xxx = xxx修改了父类原型上的属性方法,则对父类和子类的实例均有影响。

CALL继承

在原型继承的特点1中,父类的私有&公有属性方法都变成了子类的公有。这样不好…不好,我们希望通过继承,实现父类的私有(属性和方法,下同)成为子类的私有,父类的公有成为子类的公有。即私对私,公对公——公私分明!

沿用上例。

// 部分代码
function Parent(){
	this.x = 100;
}
function Child(){
	Parent.call(this);     // 核心代码
	this.y = 200;
}
let c1 = new Child;

在子类中把父类当作普通函数执行。默认Parent()函数中的this->window,而我们希望在c1实例中添加x属性,因此要通过call修改函数中的this,让其指向c1,即Child类中的this。这样就实现了父类的私有属性x也成为了子类的私有属性(这里其实是copy过来的)。

但有一点不好,父类当作普通函数执行,那它就失去了作为类的功能,子类无论如何都找不到父类的原型了,也就丢失了在父类原型上的属性方法…

寄生组合继承

这个方法有点意思,融合了原型继承和call继承的优点,真正实现了私对私,公对公~为此,我们需要在call继承的基础上加上原型继承。

原型继承的思想是让子类原型=父类的一个实例,即Child.prototype = new Parent,换句话说,即Child.prototype.__proto__ = Parent.prototype(父类实例原型链指向父类原型,没毛病吧)。
从另一个角度看,我们不创建新的父类实例了!而是让子类原型的__proto__重定向,原本指向Object.prototype,现在让它指向Parent.prototype。实现了原型继承同样的效果。——实际上就是原型继承。

但还有一个问题,IE6~8下是不允许我们操作__proto__的,我们使用Object.create的方法代替。
Object.create(A):创建一个空对象,让这个空对象的__proto__指向A。

// 寄生组合继承
function Parent(){
	this.x = 100;
}
Parent.prototype.getX = function getX(){
	return this.x;
}
function Child(){
	Parent.call(this);      // call继承当然要保留,实现私对私
	this.y = 200;
}
Child.prototype = Object.create(Parent.prototype);    // 另类原型继承
Child.prototype.getY = function getY(){
	return this.y;
}
let c1 = new Child;
console.log(c1);

Child.prototype = Object.create(Parent.prototype)这句核心代码实际上和Child.prototype = new Parent别无二致,只是创建的不是父类的实例,而是一个空对象,然后手动更改原型链指向而已。这个操作也可以让父类的公有属性方法成为子类的公有属性方法。

ES6中的类和继承

class Parent{
	constructor(){         // constructor函数实际上是Parent函数的私有属性方法
		this.x = 100;
	}
	getX(){             // 在Parent.prototype上的公有属性方法
		return this.x;
	}
}
// 原本毫无关联的两个类,因为extends而成为了父子
class Child extends Parent{
	constructor(){
		super();            // 只要用extends实现继承,就要在constructor函数的第一行加一句super(),否则会报错         
		this.y = 200;
	}
	getY(){          
		return this.y;
	}
}

ES6中的继承可以实现与寄生组合继承同样的效果,但写法是不是极其简单呐~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值