资讯专栏INFORMATION COLUMN

ES6 class extends

Big_fat_cat / 2301人阅读

摘要:解决方案借用构造函数组合继承寄生组合式继承原型链继承欠图一张从来说,实现对象的继承,还是相当麻烦的。分析基类创建的值,然后派生类的构造函数再修改这个值。

继承 inherit

class 是对原型继承的一种语法糖的包装。那相对于原型继承,它有什么优点呢?
我们来先看一个典型的基于原型链继承的例子。部分内容来自“Javascript高级程序设计”

function SuperType() {
    this.property = true;
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
}

function SubType() {
    this.subProperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
    return this.subProperty;
}

var instance = new SubType();
console.log(instance.getSuperValue());  // true
console.log(instance instanceof Object);  // true
console.log(instance instanceof SuperType);  // true
console.log(instance instanceof SubType);  // true

问题,当包含引用类型的值。

function SuperType() {
    this.colors = ["red", "blue", "green"];
}

function SubType() {
   
}

SubType.prototype = new SuperType();


var instance = new SubType();
instance.colors.push("black");
var instance1 = new SubType();
instance1.colors.push("white");
console.log(instance.colors);  // [ "red", "blue", "green", "black", "white" ]
console.log(instance1.colors);  // [ "red", "blue", "green", "black", "white" ]

解决方案:

借用构造函数

function SuperType() {
    this.colors = ["red", "blue", "green"];
}

function SubType() {
   SuperType.call(this);
}

SubType.prototype = new SuperType();


var instance = new SubType();
instance.colors.push("black");
var instance1 = new SubType();
instance1.colors.push("white");
console.log(instance.colors);
console.log(instance1.colors);

组合继承

function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {    
    console.log(this.name);
}

function SubType(name, age) {
   SuperType.call(this, name);
   this.age = age;
}

SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
    console.log(this.age);
}

寄生组合式继承

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType) {
    let prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {    
    console.log(this.name);
}

function SubType(name, age) {
   SuperType.call(this, name);
   this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
    console.log(this.age);
}
var instance = new SubType("Tom", 70);
instance.colors.push("black");
var instance1 = new SubType("Jerry", 69);
instance1.colors.push("white");
console.log(instance.colors);
console.log(instance.sayName());
console.log(instance.sayAge());
console.log(instance1.colors);
console.log(instance1.sayName());
console.log(instance1.sayAge());

MDN 原型链继承
(欠图一张)

extends

从es5来说,实现对象的继承,还是相当麻烦的。而extends 关键字的出现,使继承变得简单,原型会自动进行调整,super()/super关键字可以访问父类的构造方法和属性。

class Animal { 
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(this.name + " makes a noise.");
  }
}

class Dog extends Animal {
  speak() {
    console.log(this.name + " barks.");
  }
}

var d = new Dog("Mitzie");
d.speak();// "Mitzie barks."

分析:Dog类没有构造函数,这样合理吗?

// 等价于上个类定义
class Dog extends Animal {
  constructor(name) {
    super(name)
  }
  speak() {
    console.log(this.name + " barks.");
  }
}

super()方法调用注意:

只可在以extends 实现的派生类中的constructor方法中调用,在非派生类或方法中直接调用,会报错。

在constructor中访问this之前,一定要先调用super(),因为它负责初始化this,如果在super()调用之前尝试访问this,会报错。

如果不想调用super(),则唯一的方法是让类的constructor()返回一个对象。

类方法遮蔽

说明:派生类中的方法总会覆盖基类中的同名方法。

class Animal { 
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(this.name + " makes a noise.");
  }
}

class Dog extends Animal {
  speak() {
    console.log(this.name + " barks.");
  }
}
// 基类中的speak()方法被覆盖
静态类成员继承

说明:如果基类有静态成员,那么这些静态成员在派生类中也可用。

class Animal { 
    constructor(name) {
      this.name = name;
    }
    
    speak() {
      console.log(this.name + " makes a noise.");
    }
    static create(name) {
        return new Animal(name);
    }
  }
  
  class Dog extends Animal {
    speak() {
      console.log(this.name + " barks.");
    }
  }

  let a1 = Animal.create("Monkey");
  let a2 = Dog.create("BeijinDog");
  console.log(a1 instanceof Animal);  // true
  console.log(a2 instanceof Animal);  // true
  console.log(a2 instanceof Dog);  // false 这个是不是很意外?
派生自表达式的类

由ES6的class定义可以知道,是function的语法糖,但为实现原型继承,提供了方便的实现。JS的强大的一点就是函数可以返回函数,那如果返回类的定义呢?是否支持继承?返回对象是个函数,并且有[[Constrcutor]]属性和原型,就能满足extends实现。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + " makes a noise.");
  }
}

function getBase() {
  return Animal;
}
class Dog extends getBase() {
  speak() {
    console.log(this.name + " barks.");
  }
}

const dog = new Dog("Tom");
dog.speak();

如果这个例子基于class的实现,有点取巧的意思,那看另一个例子。

const SerializableMixin = {
  serialize() {
    return JSON.stringify(this);
  }
}

const AnimalMixin = {
  speak() {
    console.log(this.name + " barks.");
  }
}

function mixin(...mixins) {
  const base = function() {};
  Object.assign(base.prototype, ...mixins);
  return base;
}

class Dog extends mixin(AnimalMixin, SerializableMixin) {
  constructor(name){
    super(name);
    this.name = name;
  }
}

const dog = new Dog("Tom");
dog.speak();  // Tom barks.

关于function,class,extends,mixin,是否有新的理解呢?

内建对象继承

在ES6之前,内建对象很难实现继承的,更多用has-a思想,实现对内建对象的处理。ES6中,大量内建对象的内部实现得以暴漏,也使得继承内建对象变成了可能。

class ColorsArray extends Array {
}
const colors = new ColorsArray();
colors[0] = "red";
console.log(colors.length); // 1

colors.length = 0;
console.log(colors[0]); // undefined

分析:基类(Array)创建 this 的值,然后派生类的构造函数(ColorsArray)再修改这个值。所以一开始可以通过this访问基类的所有内建功能,然后再正确地接收所有与之相关的功能。这与Array.apply/call 这种方法实现继承的this处理方式正好相反。这也是extends特殊的地方。

Symbol.species
class ColorsArray extends Array {
}
const colors = new ColorsArray("red", "green", "blue");
const subColors = colors.slice(0,1);
console.log(colors instanceof ColorsArray);  // true
console.log(subColors instanceof ColorsArray);  // true

通常来讲,slice 方法继承自 Array ,返回的应该是Array的实例,但在这个示例中,却返回的是ColorsArray的实例,这是为什么呢?这是ES6中Symbol.species的功劳。Symbol.species MDN 详细说明

class MyArray extends Array {
  // Overwrite species to the parent Array constructor
  static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true

注意:重写实现的时候,使用getter+static,可以返回想用的类型,也可以返回 this,是的,你没看错,在static getter中使用了this,它指向的是MyArray的构造函数。

constructor中new.target

new.target是es6中新添加的元属性,只有通过new操作创建对象的时候,new.target才会被指向类/方法本身,通过call/apply操作,new.target为undefined。可以通过判断new.target,来确实函数是否允许new操作。MDN new.target 说明
惯例,再加个代码示例,偷懒,直接从MDN上拷了。

function Foo() {
  if (!new.target) throw "Foo() must be called with new";
  console.log("Foo instantiated with new");
}

new Foo(); // logs "Foo instantiated with new"
Foo(); // throws "Foo() must be called with new"

又是先说function,不是已经升级到ES6,使用class了吗?始终要有一个清楚的认识,class,是function实现原型继承的语法糖,但有自己的特性存在的(不然,也不用引入class了)。

class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A { constructor() { super(); } }

var a = new A(); // logs "A"
var b = new B(); // logs "B"

class C { constructor() { console.log(new.target); } }
class D extends C { constructor() { super(); } }
 
var c = new C(); // logs class C{constructor(){console.log(new.target);}}
var d = new D(); // logs class D extends C{constructor(){super();}}

这个就是类的了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105822.html

相关文章

  • ES6class与面向对象编程(8)

    摘要:接下来我们看下类的写法,这个就很接近于传统面向对象语言了。如果你想了解传统面向对象语言,这里是一个好切入点。作为对象时,指向父类的原型对象。这些就是为将来在中支持面向对象的类机制而预留的。 在ES5中,我们经常使用方法或者对象去模拟类的使用,并基于原型实现继承,虽然可以实现功能,但是代码并不优雅,很多人还是倾向于用 class 来组织代码,很多类库、框架创造了自己的 API 来实现 c...

    wangjuntytl 评论0 收藏0
  • [译]在 React.js 中使用 ES6+

    摘要:如果是在中,我们也许只能这样做但是,在中,我们不仅可以在对象字面量属性的定义中使用表达式,还有使用使用字符串模板析构扩展运算符我们在编写组件的过程中,经常遇到要从父组件要把自己的很多属性多传给子组件的情况。 原文地址: http://babeljs.io/blog/2015/06/07/react-on-es6-plus/ showImg(http://7xiyp1.com1.z0.g...

    Enlightenment 评论0 收藏0
  • ES6 class继承与super关键词深入探索

    摘要:请看对应版本干了什么可知,相当于以前在构造函数里的行为。这种写法会与上文中写法有何区别我们在环境下运行一下,看看这两种构造函数的有何区别打印结果打印结果结合上文中关于原型的论述,仔细品味这两者的差别,最好手动尝试一下。 ES6 class 在ES6版本之前,JavaScript语言并没有传统面向对象语言的class写法,ES6发布之后,Babel迅速跟进,广大开发者也很快喜欢上ES6带...

    jubincn 评论0 收藏0
  • [译]React ES6 class constructor super()

    摘要:当我们在写时候会用到中的语法比较常见的情况如下这里有两个问题是否有必要在中调用函数调用和有何区别解答只有当你有一个时候调用才是必须的看代码上述代码完全符合规定所以你其实并没有必要去为你创建的每个调用话分两头如果你的代码中有你就必须调用出现上 当我们在写React时候 会用到ES6中的class语法 ,比较常见的情况如下: class MyClass extends React.Comp...

    justjavac 评论0 收藏0
  • ES6 系列之 Babel 是如何编译 Class 的(下)

    摘要:以上的代码对应到就是调用父类的值得注意的是关键字表示父类的构造函数,相当于的。举个例子这是因为作为构造函数的语法糖,同时有属性和属性,因此同时存在两条继承链。子类的属性,表示构造函数的继承,总是指向父类。 前言 在上一篇 《 ES6 系列 Babel 是如何编译 Class 的(上)》,我们知道了 Babel 是如何编译 Class 的,这篇我们学习 Babel 是如何用 ES5 实现...

    endiat 评论0 收藏0

发表评论

0条评论

Big_fat_cat

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<