继承
前言
继承是面向对象语言的一大特性,用来让一个类继承另外一个类,以此来复用代码。
在这里需要明白一个关系:类与实例。
继承是类与类之间的一种关系。
而一个类要想使用,首先是要进行实例化。
一个类就如同汽车设计图,实例则是一个真实的汽车,汽车设计图是有用的,但是,你不能拿汽车设计图来履行,所以需要实例化,也就是根据设计图来生成一辆真正的汽车,一个真正的汽车就是一个实例。
当然,一个设计图理论上可以生成无限个汽车。
背景
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