JS中的多种继承方式
JS本身是基于面向对象开发的编程语言:所以我们学习JS主要就是学习类,以及类的实例,在学习类的原型上提供的方法;
类:
- 封装、继承、多态
一、类的封装
- 类也是一个函数,把实现一个功能的代码进行封装,以此实现“低耦合高内聚”
二、多态:重载、重写
- 重写:子类重写父类上的方法(伴随着继承运行的)
- 重载:相同的方法,由于参数或者返回值不同,具备了不同的功能(JS中不具备严格意义上的重载,JS中的重载:同一个方法内,根据传参不同实现不同的功能)
1、在后台语言当中:
-
重载定义:
- 指的是在方法名字相同,都叫fn,但是参数的类型或者参数的个数不同,所以导致这两个方法不是一个方法,而变成了两个方法;(我们认为这就是方法的重载)
-
重载的目的:(为了分担压力的)
- 正常情况下,客户端向服务器发请求,一台服务器允许多个客户端同时发请求,所以服务器要有很强的抗压能力,
- 如果说我们把所有的功能(比如传两个参数干什么,传一个参数干什么)都放在一个方法中,那这个方法承载的压力就会比较大,
- 所以我们一般通过重载的方式来分发出这个方法承载的压力;
2、在JS语言中:这种方式并不是重载
- 由于JS中的变量提升机制,所以只执行第二个
- 所以说JS中不具备严格意义上的重载,
function fn(x, y) {
}
function fn(x) {
}
fn(10, 20); 执行第二个FN
fn(10); 执行第二个FN
- JS中的重载:同一个方法内,根据传参不同实现不同的功能
function fn(x, y) {
if(y===undefined){
// ...
return;
}
// ....
}
fn(10);
fn(10, 20);
三、继承:子类继承父类中的方法
1、继承的定义:
- 在JS语言中,它的继承和其它编程语言还是不太一样的
2、继承的目的:
- 让子类的实例同时也具备父类中私有的属性和公共的方法
3、JS中继承方案
JS中继承方案
第一种:原型继承(让子类的原型等于父类的实例即可)
function Parent() {
this.x = 100;
}
Parent.prototype.getX = function getX() {
return this.x;
};
function Child() {
this.y = 200;
}
//=> 让子类的原型等于父类的实例
Child.prototype = new Parent; //=>原型继承
Child.prototype.constructor = Parent;
Child.prototype.getY = function getY() {
return this.y;
};
let c1 = new Child;
console.log(c1);
* Child.prototype = new Parent
- 1、父类中私有和公有的属性方法,最后都变为子类实例公有的
- 2、和其他语言不同的是
- 原型继承并不会把父类的属性方法“拷贝”给子类,
- 而是让子类实例基于__proto__原型链找到自己定义的属性和方法“指向/查找”方式的
-
c1.proto.xxx = xxx 修改子类原型(原有父类的一个实例)中的内容,内容被修改后,对子类的其他实例有影响,但是对父类的实例不会有影响
-
c1.proto.proto.xxx = xxx直接修改的是父类原型,这样不仅会影响其它父类的实例,也影响其他子类的实例
-
JS中的重写影响很大
第二种:CALL继承(只能继承父类中私有的,不能继承父类中公共的)
function Parent() {
this.x = 100;
}
Parent.prototype.getX = function getX() {
return this.x;
};
function Child() {
// 在子类构造函数中,把父类当做普通方法执行(没有父类实例,父类原型上的那些东西也就和它没关系了)
// this -> Child的实例c1
Parent.call(this); // this.x=100 相当于强制给c1这个实例设置一个私有的属性x,属性值100,相当于让子类的实例继承了父类的私有的属性,并且也变为了子类私有的属性 “拷贝式”
this.y = 200;
}
Child.prototype.getY = function getY() {
return this.y;
};
let c1 = new Child;
console.log(c1);
-
在子类构造函数中,把父类当作普通函数执行(没有父类实例,父类原型上的那些东西也就和他没有关系了)
- 此时 parent(this) 这个 this 是 window
-
通过 call 强制改变 this 的指向为 Child 中的 this
- Parent.call(this)
- 此时 this 是当前 Child 中的 this
相当于强制给实例设置了一个私有的属性,相当于让子类的实例继承了父类的私有的属性,并且也变为了子类私有的属性“拷贝式”
- 只能继承父类中私有的,公有的不行(并且是把父类私有的变成子类私有的)
我们满意的继承方式应该是:父类私有变为子类私有 父类公有变为子类公有
第三种:寄生组合式继承(CALL继承 + 另类原型继承)
function Parent() {
this.x = 100;
}
Parent.prototype.getX = function getX() {
return this.x;
};
function Child() {
Parent.call(this);
this.y = 200;
}
//Child.prototype.__proto__ = Parent.prototype
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.getY = function getY() {
return this.y;
};
let c1 = new Child;
console.log(c1);
上面我们说了 call 继承有个好处:能把父类私有的变成子类私有的;所以 call 继承我们留下来 那现在我们只要想办法让父类中公有的也能变成子类中公有的即可;
我们刚才的原型继承,大体的实现了这个功能;
- 原型继承是创建了父类的一个实例,从而达到子类继承父类的效果;它最终实现的核心点是:Child.prototype.proto === Parent.prototype;
- 如果说我们在原型继承时不想要父类私有的内容,我们只需要:Child.prototype.proto = Parent.prototype;
- 由于新创建的原型没有constructor属性,所以我们自己手动加一个默认的;
第四种:ES6中的类和继承
class Parent {
constructor() {
this.x = 100;
}
// Parent.prototype.getX=function...
getX() {
return this.x;
}
}
// 继承: extends Parent(类似于寄生组合继承)
// 注意:继承后一定要在CONSTRUCTOR第一行加上SUPER
class Child extends Parent {
constructor() {
super(); //=>类似于我们之前的CALL继承 super(100,200):相当于把Parent中的constructor执行,传递了100和200
this.y = 200;
}
getY() {
return this.y;
}
}
let c1 = new Child;
console.log(c1);
// Child(); //=>Uncaught TypeError: Class constructor Child cannot be invoked without 'new' ES6中创建的就是类,不能当做普通函数执行,只能new执行