javascript继承

前言:

随着es6的出现,继承方式逐渐被固定为使用extends关键字实现继承。虽然es6实现继承起来非常简单,但本人仍然觉得es5的继承是必须学习的,因为es5的继承方式,描述了JavaScript继承思想的发展历程,已经影响到了es6继承的实现方案。该篇文章打算从es5的多种继承思想开始阐述继承思路,同时也包括es6的继承的方式。希望能够帮助读者或者本人在前端技术快速发展的前提下,不断打下坚实的基础,以期待厚积薄发。

 

一、ES5继承思想

许多面向对象的开发方式中,两种主流的继承方式为:接口继承(只继承方法签名)和实现继承(继承实际的方法)。ECMAScript 是实现继承的方式,依靠原型链实现继承的。

(1)、原型链继承(基本方式)

原型链模式虽然实现了继承,但也存在一些问题。首先,再通过原型来实现继承时,原型实际上会变成另一个对象类型的实例,如果多次继承的情况很容易无法实现继承。其次,在创建子类实例时,不能向父类的构造函数传递参数。因为这种继承方式存在着问题,因此引申出了后面的继承方式。

//父类
function Parent(){
    this.type="parent";
}

Parent.prototype.getType=function(){
    console.log('type:', this.type);
    return this.type;
}

//子类
function Child(){
    this.childType="child";
}

//继承
Child.prototype=new Parent();
Child.prototype.constructor=Parent;//此处用来处理instanceof或isPropertyOf检查类型失败的问题

Child.prototype.getChildType=function(){
    console.log('childType:', this.childType);
    return this.childType;
}


//创建实例
var instance = new Child();
alert(instance.getType());

(2)、借用构造函数

该继承方式解决了原型链继承方式无法向父类传递参数的问题。通过call或者apply的this绑定的功能,实现了此种继承方式。但此种继承方式使方法都在构造函数中定义,无法解决复用问题。同时,父类原型中定义的方法,对子类型而言是看不到的。因此此种继承类型也经常不是单独出现的

//父类
function Parent(){
    this.attr=['name','age','sex'];
}


//子类
function Child(){
    Parent.call(this,arguments);
}

//实例
var instance1=new Child();
instance1.attr.push('salary');
alert(instance1.attr);//name,age,sex,salary

var instance2=new Child();
alert(instance2.attr);//name,age,sex

(3)、组合继承

也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一起,在创造出来的新的继承方式。组合继承是之前最常用的继承模式。同时能够被instanceof和isPrototypeOf所识别。但该模式也存在一个问题,是无论什么情况下都会调用两次父类的构造方法。

//父类
function Parent(name){
    this.name=name;
    this.colors=['red','green','blue'];
} 

Parent.prototype.sayName=function(){
    return this.name;
}

//子类
function Child(name,age){
    Parent.call(this,name);
    this.age=age;
}

//继承
Child.prototype=new Parent();
Child.prototype.sayAge=function(){
    return this.age;
}

var instance1=new Child("张三",29);
instance1.colors.push('black');
alert(instance1.colors);//'red','green','blue','black'
instance1.sayName();//张三
instance1.sayAge();//29

var instance2=new Child("李四",27);
alert(instance2.colors);//'red','green','blue'
instance2.sayName();//李四
instance2.sayAge();//27

(4)、原型式继承

由道格拉斯●克罗克福德提出。此处只是原型式继承概念,下面代码并未真正实现继承,因为他仍然没有解决在创建子类实例时,不能向父类的构造函数传递参数的问题。

//继承方法
function extend(O){
    function F(){};
    F.prototype=O;
    
    return new F();
}

var person={
    name:'张三',
    friends:['李四','王五','赵柳']
};

var anotherPerson=extend(person);
anotherPerson.name="李琦";
anotherPerson.friends.push("赵三");

var anotherPerson1=extend(person);
anotherPerson.name="刘总";
anotherPerson.friends.push("六子");

alert(person.friends);//李四,王五,赵柳,赵三,六子
alert(person.name);//张三

(5)、寄生式继承

此种继承方式与原型式继承紧密相连,是原型式继承思路的实现。使用该方式为对象来添加函数,会由于不能共用相同的方法而造成效率降低,因此不推荐使用.

//继承方法
function extend(O){
    function F(){};
    F.prototype=O;
    
    return new F();
}

//继承对象
function createAnother(original){
    var clone =extend(original);
    clone.sayHi=function(){
        console.log('HI');
    };
    return clone;
}

var person={
    name:'张三',
    friends:['李四','王五','赵柳']
};

var anotherPerson=createAnother(person);
anotherPerson.sayHi();//"HI"

(6)、寄生组合式继承


//父类
function Parent(name){
    this.name=name;
    this.colors=['red','green','blue'];
} 

Parent.prototype.sayName=function(){
    return this.name;
}

//继承方法
function extend(O){
    function F(){};
    F.prototype=O;
    
    return new F();
}

function inherit(parent,child){
    var prototype=extend(parent.prototype);
    prototype.constructor=child;
    child.prototype=prototype;
}

//子类
function Child(name,age){
    Parent.call(this,name);
    this.age=age;
}

//继承
Child.prototype=new Parent();
Child.prototype.sayAge=function(){
    return this.age;
}


二、es6继承方式

随着JavaScript的不断发展,es6、es7等新的JavaScript语法规范逐渐发展起来,但其发展的趋势逐渐接近面向对象编程的语言(如java)。如此发展的趋势,给了前端开发工程师更多面向对象开发语言的思想方式。因此建议读者尝试使用es6等最新标准进行开发,因为能够在开发过程中,学会更多的思想以及乐趣。

(1)、extends关键字与super关键字

es6规范了继承方式,采用extends关键字的方式继承父类,继承后同时拥有父类公开属性以及公开方法。在子类中必须调用super,否则会报错,我们先看一下阮一峰老师的描述:

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

如上描述,可知es6 通过exteds方式实现继承的过程,会创建父类方法以及属性,创建完成后才会创建子类。在子类中调用super是为了创建父类的实例。此处如果仍然存在迷惑,可以想想es5的继承实现方式,首先需要在子类中通过call或者apply方式执行父类的构造函数,但es6和这个过程不同,会优先创建父类。

注意:

1、super虽然代表了父类的构造函数,但是返回的是子类的实例,即super内部的this指的是子类的实例。

2、super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类(如下方的super.p())。

3、由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的(如下方代码中在子类中调用super.pointName,页面将会报错)。

4、在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。

class Point {
  pointColor="#f00";//创建后,会写到实例上,而不是原型上,属于实例属性
  constructor(){
    this.pointName="parent";//创建后,会写到实例上,而不是原型上,属于实例属性
  }
  p() {
    return 2;
  }
  static printPointName(){
    //同一个类中的静态方法和非静态方法可以同名
    console.log('pointName:',this.pointName);
  }
  printPointName(){
    //同一个类中的静态方法和非静态方法可以同名
    console.log('pointName:',this.pointName);
  }
}

class ColorPoint extends Point {
  pointName="child";
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
    
  }
    
  static printName() {
    super.printPointName();
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

ColorPoint.pointName='childName';
ColorPoint.printName();//childName

(2)、new.target

在es6中可以使用new.target方式来判断函数的创建方式,因为new.target指向当前正在执行的函数(指向子类)。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

// 另一种写法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错

(3)、es6继承方式

如下图,使用es6的继承方式时,实例对象和构造器对象在继承上会有不同的原型链。继承后构造器对象子类的__proto__属性指向父类构造函数,子类的prototype.__proto__指向父类的prototype。当通过new方式创建实例属性后,子类的__proto__.__proto__指向父类的__proto__。但同时也可以看出父类的this指向子类,bName成为子类的实例属性。


class A {
	aName="aaaa"
	sayName(){return this.aName}
}

class B extends A {
	bName="bbbb"
	sayName(){return this.bName}
}

let bTarget=new B();

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值