关于JS继承

x33g5p2x  于2022-03-04 转载在 其他  
字(2.9k)|赞(0)|评价(0)|浏览(158)

关于JS继承

关于JS继承

关于继承这个概念,是由面向对象衍生出来的。而JS身为一种基于面向对象而设计的语言,它和正统的面向对象语言又是有差别的。

面向对象语言

身为面向对象语言需要对开发者提供四种能力:
①:封装 - 把相关的信息(无论数据或方法)存储在对象中的能力;
②:聚集 - 把一个对象存储在另一个对象内的能力;
③:多态 - 编写能以多种方法运行的函数或方法的能力;
④:继承 - 有另一个或多个类得来类的属性和方法的能力;
虽然JS都具备这种能力,然而它却和正统的面向对象语言有差异,具体表现主要在第③、④点。

多态

多态主要有两种实现形式:
①:重载(编译时多态性) - 方法名相同,形参个数或类型不同;
②:重写(运行时多态性) - 在继承中,子类重写父类的方法;
这两种形式上JS和java/C#是表现形式是不一样的。
重载:

// java代码示例
public void sum(int n, float m){

}
public void sum(int n, float m, int t){
    
}
public void sum(int n, float m, String t){
    
}

在java中这sum方法会分别在不同情况下调用。

// js代码示例
function sum(n,m){

}
function sum(n,m,t){

}

在JS中sum方法在前面定义的会被后面定义的覆盖,因此JS用相同方法处理不用情况只能在sum方法内部对数据做识别判断了。
重写:

//java代码示例
public class Main {

    public static void main(String[] args) {
        Student a = new Student();
        a.say();
    }
}

class person{
    public void say(){
        System.out.println("11111");
    }
}

class Student extends person{
    public void say(){
        System.out.println("22222");
    }
}

这里Student继承了person,对say方法进行了重写,虽然重写了say方法,但是父类方法不会被改变。

//js代码示例
function A(m){
    this.m = m;
}
A.prototype.getM = function(){
    console.log(100,this.m)
}

function B(n){
    this.n = n;
}
B.prototype = new A(100);
B.prototype.getN = function(){
    console.log(this.n)
}

let b = new B(200);
b.getM();   //打印 100,100

b.getM = function(){
    console.log(1)
}
b.getM();   //打印 1
A.prototype.getM(); //打印 100,undefined

b.__proto__.__proto__.getM = function(){
    console.log(0,this.m)
}; 
b.getM();   //打印 1
A.prototype.getM(); //打印 0,undefined

而JS对继承的父类的重写就有点不一样了,JS是基于原型链的继承。调用方法或对象时会在原型链中追溯,而当在实例b中添加了getM方法时,其实是拦截了原型链上的getM,因此这种拦截不会改动到A.prototype.getM,然而A.prototype.getM又不是绝对的私密的,在示例中可以看到利用b.proto.proto.getM重写getM是会导致A.prototype.getM也被重写的,所以js原型链继承中是没有完整意义上的私密属性的。

继承

和其他正统的面向对象语言不一样,其他语言的继承一般都是拷贝继承,也就是子类会把父类中的方法和属性拷贝的子类中实现继承,而JS的继承是把父类的原型放在子类的原型链上,利用原型链的查找机制实现继承的。
而且JS的继承方式也是很多的,下面列举一下比较常见的继承方式。

一、原型链继承
function A(m){
    this.m = m;
}
A.prototype.getM = function(){
    console.log(this.m)
}

function B(n){
    this.n = n;
}
B.prototype = new A(100);
B.prototype.getN = function(){
    console.log(this.n)
}

let b = new B(200);
b.getN();   //输出 200
b.getM();   //输出 100

特点:
1、非常纯粹的继承关系,实例是子类的实例,也是父类的实例;
2、父类新增属性或其原型新增属性,子类都能访问;
缺点:
1、子类能重写父类方法,如:b.proto.proto.getM = null,可以改写父类方法;
2、父类的私有属性和公有属性都变成子类的公有属性;
3、继承时方法B原型上原有的方法或属性会丢失,如:constructor构造函数;(解决:B.prototype = B);
4、子类创建实例时无法向父类传参;
5、无法实现多继承;

二、构造函数继承
function A(m, n){ 
    this.m=m; 
    this.n=n;
} 
function B(m, n, t){
    A.call(this, m, n); 
    this.t=t;
} 
var b = new B(1, 2, 3);
console.log(b.m, b.n, b.t);

特点:
1、能在子类创建实例时向父类传递参数;
2、可以实现多继承;
缺点:
1、只能继承父类的属性(不继承原型链);
2、创建的实例携带着父类属性,臃肿;

三、寄生组合继承
function B(n){
    this.n = n;
}
B.prototype.getB = function(name){
    console.log(this.n, name);
}

function A(m){
    B.call(this,200)
    this.m = m;
}

// 我是旧时代的残党,新时代没有适合承载我的船
// (function(){
//   // 创建一个没有实例方法的类
//   var Super = function(){};
//   Super.prototype = B.prototype;
//   //将实例作为子类的原型
//   A.prototype = new Super();
// })();

// ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。
A.prototype = Object.create(B.prototype);

A.prototype.constructor = A;
A.prototype.getA = function(name){
    console.log(this.m, name)
}

var aa = new A(100);
aa.getA("A");
aa.getB("B");

特点:
1、可以继承父类的属性及其原型上的属性;
2、既是子类的实例,也是父类的实例;
3、不存在引用属性共享问题;
4、父类可接收传参;

相关文章