继承

203 阅读5分钟

继承

前言

继承是面向对象语言的一大特性,用来让一个类继承另外一个类,以此来复用代码。

在这里需要明白一个关系:类与实例。

继承是类与类之间的一种关系。

而一个类要想使用,首先是要进行实例化。

一个类就如同汽车设计图,实例则是一个真实的汽车,汽车设计图是有用的,但是,你不能拿汽车设计图来履行,所以需要实例化,也就是根据设计图来生成一辆真正的汽车,一个真正的汽车就是一个实例。

当然,一个设计图理论上可以生成无限个汽车。

背景

JS严格的来说不是面向对象的语言,没有像面向对象语言(比如JAVA)自身实现了继承,JS语言需要使用特定的技术(也就是特定的代码编写方式)来实现继承。

JS中没有JAVA等面向对象语言的那种真正的类的概念,但是,JS是很灵活的,我们一般使用构造函数来模拟一个类。比如:

function Person(name){
        this.name=name
        this.say= function(){
            console.log('this's name is '+this.name)
        }
}

如上,便是 一个 Person类。

再看一下java中定然的类

public  class Person{
	String name;
	public Person(String myName) { 
        name = myName; 
    } 
	void say(){
		System.out.print(name);
	}
	

}

如上,也是定义了一个Person类,内部有一个name是字符串类型的。一个构造函数Person(该函数会在类被实例化的时候自动调用,用来初始化赋值),一个方法say

虽然他们的样子不一样,但是,js的构造函数是能够用来模拟一个类的。

好了,基础的信息说完,我们就来说js中三种经典的继承技术。

首先我们先提供一个父类

    function Parent(name){
        this.name=name
        this.say= function(){
            console.log('this's name is '+this.name)
        }
    }
    Parent.prototype.say= function(){
            console.log('this\'s name is '+this.name)
    }

原型链继承

原型链继承的核心是基于JS语言的 proto 查找机制。

举个🌰 我们有一个子类

function Child(age){
    this.age=age
}

我们让上边的子类 Child 继承 父类 Parent

	Child.prototype=new Parent('原型链继承')

如上,我们将子类 Child 的原型指向父类 Parent的一个实例。 如此一来,子类 Child就能访问到父类Parent的属性和方法。

如下:

   
    
    var c1 = new Child(12)
	c1.name//'原型链继承'
    
    c1 instanceof Parent  //true
    

子类Child的实例c1就能访问到父类 Parent的属性 name 和方法 say

语法规则是:

CHILD.prototype = new PARENT()

CHILD.prototype.construct = CHILD

注意:

之所以要加 CHILD.prototype.construct = CHILD是因为

原因:

constructor 是 函数的 prototype 的一个属性,这个属性指向函数自身身。

Parent.prototype.constructor == Parent  // true

而在我们让 Child 类 继承 Parent 的时候改变了 Child 的 prototype(Child.prototype=new Parent('原型链继承'))。

所以 Child类的实例 c1.constructor 等价于 Child.prototype.constructor ==>new PARENT().constructor===>Parent.prototype.constructor==>Person.

所以,我们需要纠正这个错误。

CHILD.prototype.construct = CHILD

注意:

我们说过,只有函数才有可能有 prototype属性。

c1.constructor 其实并不是说c1 也有prototype属性,c1.constructor 其实是等价于 c1.proto.constructor.

而 c1.prtoto == Child.prototype(对象的原型属性__proto__ 指向其构造函数的原型对象prototype)

特点

function Parent(list){
    this.NBMan= list
}
Parent.prototype.say= function(){
    console.log(this.NBMan)
}
function Child(age){
    this.age=age
}
Child.prototype = new Parent(['成龙','李连杰'])
var c1 = new Child(12)
c1.NBMan //['成龙','李连杰']
var c2 = new Child(12)
c2.NBMan.push('吴京')
c1.say()// ["成龙", "李连杰", "吴京"]

如上所示,子类Child的实例都可以访问到父类Parent的属性NBMan ,而且都能对其进行修改。

这就会为以后的开发埋下祸患,不经意间一个子类实例修改了父类属性,那么所有其他的子类实例访问到的父类属性都变了,这明显是有问题的。

缺陷:本应该是私有的属性被公开了。

call继承

语法:

PARENT.call(this,[arguments])

举个🌰

function Parent(list){
    this.NBMan= list
}
Parent.prototype.say= function(){
            console.log( this.NBMan)
}

function Child2(age){
    Parent.call(this,['秦始皇','汉武帝','唐太宗'])
     this.age = age
}

var c1 = new Child2(1000)
var c2 = new Child2(200)

看一下c1

    c1.say()//Error
	c1.NBMan//["秦始皇", "汉武帝", "唐太宗"]
	c1.age //1000
    

看一下c2

    c2.say()//Error
	c2.NBMan//["秦始皇", "汉武帝", "唐太宗"]
	c2.NBMan.push('宋太祖')
	c2.age //200
    

再来看一下c1

	c1.NBMan//["秦始皇", "汉武帝", "唐太宗"]

子类Child2 的实例都会有属于自己的父类属性,随便的修改是不会影响到其他的子类实例的。

解释:

简单的来说就是,将父类 当做普通函数来调用,只是将this指向了CHILD的实例。

这种继承方式的特点

1 只继承了父类构造函数的私有属性,而没有继承父类的原型属性(因为是将PARENT当做普通函数来执行了,没有影响到PARENT的prototype属性)。

2 没有继承PARENT的prototype上公有的属性。

寄生组合继承

语法:

PARENT.call(this,[arguments]) CHILD.prototype = Object.create(PARENT.prototype) CHILD.prototype.constructor = CHILD

原型链继承将父类的私有属性当做公有的继承了过来,公有的属性也继承了。

call继承是将父类的私有属性,变为自己的私有属性,但是没有继承公有属性。

寄生组合继承则是结合两者的优势。

    function Child2 (title){
        Parent.call(this,['李白','杜甫','王维'])
        this.title = title
    }
    Child2.prototype=Object.create(Parent.prototype)
    Child2.prototype.constructor = Child2
    

    

先看看 c1

	var c1 = new Child2('大唐')
	c1.NBMan//['李白','杜甫','王维']
	c1.say()//['李白','杜甫','王维']
    

再看看 c2

	var c2 = new Child2('诗人')
	c2.NBMan//['李白','杜甫','王维']
	c2.say()//['李白','杜甫','王维']
	c2.NBMan.push('汪伦')
	c2.say()//['李白','杜甫','王维','汪伦']

再看看 c1

	c1.say()//'李白','杜甫','王维']

ES6中js语言的继承

核心就一个词 extends。

语法规则 :

Class A extends Class B{}

如此,A类就继承了B类。 其核心的原理依旧是使用了寄生组合式的继承,extends只是es6下的语法糖。

举个🌰

    class A {
        
        constructor(name,age){
            this.name = name
            this.age = age
        }
        getName(){
            console.log(this.name,this.age)
        }
    }
    
    class B extends A {
        
        constructor(y){
            super('张三',23)
            this.y = y
        }
        getY(){
            console.log(this.y)
        }
    }
    
    var b = new B(222)
    console.log(b)

最终的b如下所示

    {name: "张三", age: 23, y: 222}
        name: "张三"
        age: 23
        y: 222
            __proto__: A
            constructor: class B
            getY: ƒ getY()
                __proto__:
                constructor: class A
                getName: ƒ getName()
                __proto__: Object