ES5和ES6中对继承的实现

1.原型链相关知识

  正式学习之前,我们先温习下原型链的相关知识:(图片来源:Javascript的原型链图

这里写图片描述

  图片中有待补充的内容包括:每一个new出来的对象或者函数有一个constructor 属性指向构造函数,例子如下:

var b = new Function();
b.constructor === Function;  //true

  先对照图片对下面的例子做出解释

var a = {};
console.log(a.prototype);  //=> undefined (实例化出来的对象)

var b = function(){};
console.log(b.prototype);  //=> {} (对应Foo.prototype)

var c = 'Hello';
console.log(c.prototype);  //=> undefined (实例出来的字符串)

补充知识点:

var o1 = new Object();
typeof(o1);      //=> 'object'

var o2 = {};
typeof(o2);     //=> 'object'

typeof(Object); //=> 'function'

  我们不难得出一个结论,typeof所的到的对象类型和对象的__proto__类型相同,Function.prototype除外。

typeof Function.prototype.__proto__ ;  //=> 'object'

2.ES5中的继承

(相关阅读:ES5和ES6中的继承/ECMAScript 继承机制实现

2.1 原型链方法

将父类实例当做子类构造函数的原型

//先来个父类,带些属性
function Super(){
    this.flag = true;
}
//为了提高复用性,方法绑定在父类原型属性上
Super.prototype.getFlag = function(){
    return this.flag;
}
//来个子类
function Sub(){
    this.subFlag = false;
}
//实现继承
Sub.prototype = new Super;
//给子类添加子类特有的方法,注意顺序要在继承之后
Sub.prototype.getSubFlag = function(){
    return this.subFlag;
}
//构造实例
var es5 = new Sub;

优点:

  • 父类的方法(getName)得到了复用。

缺点:

  • 原型中属性的改变会反应到所有的实例上
  • 创建子类的实例时,不能向父类的构造函数传递参数

  例子如下:

function Super(){
    this.flag = true;
}//父类
function Sub(){
   this.subFlag = false;
}//子类
Sub.prototype = new Super();//继承
var obj = new Sub();//子类实例1
obj.flag = flase;  //修改之后,由于是原型上的属性,之后创建的所有实例都会受到影响
var obj_2 = new Sub();//子类实例2
console.log(obj.flag)  //false;

2.2 call和apply方法(和对象冒充类似)

call方法,它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。

function sayColor(sPrefix,sSuffix) {
    alert(sPrefix + this.color + sSuffix);
};

var obj = new Object();
obj.color = "blue";

sayColor.call(obj, "The color is ", "a very nice color indeed.");

  在这个例子中,函数 sayColor()在对象外定义,即使它不属于任何对象,也可以引用关键字 this。对象 objcolor属性等于blue。调用call() 方法时,第一个参数是 obj,说明应该赋予sayColor() 函数中的 this 关键字值是 obj。第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数sPrefixsSuffix 匹配,最后生成的消息 “The color is blue, a very nice color indeed." 将被显示出来。

优点:

  • 子类的每个实例都有自己的属性(name),不会相互影响。

缺点:

  • 父类方法没有得到复用,方法存在于每个实例当中而不是来自继承。

2.3 混合方式

function A (color){
    this.color = color
}
A.prototype.sayColor = function(){
    console.log(this.color)
}
function B(sColor,name){
    A.call(this,sColor);
    this.name = name;
}
B.prototype = new A();
B.prototype.sayName = function(){
    console.log(this.name)
}
var b = new B("red","nik")

请注意:
在执行 B.prototype = new A(); 之后,B.prototype.constructor 指向A 。然而constructor的定义是要指向原型属性对应的构造函数的,B.prototypeB构造函数的原型,所以应该添加一句纠正:

B.prototype.constructor = B;

2 ES6中的继承

  相关阅读(Class的继承

2.1 简介

  Class可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

class Point {
}

class ColorPoint extends Point {
}

   以上代码实际等同于下面的代码:

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}

  上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

  子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

  注意:在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

2.2 super 关键字

  super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

  第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

  super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

  上面代码中,new.target指向当前正在执行的函数。可以看到,在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B

  第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

  上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()

  由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

  如下:

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined

  上面代码中,p是父类A实例的属性,super.p就引用不到它。

  如果属性定义在父类的原型对象上,super就可以取到。如下:

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }
}

let b = new B();

ES6 规定,通过super调用父类的方法时,方法内部的this指向子类。

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let b = new B();
b.m() // 2

待补充。。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值