原来如此:ES6-class

94 阅读5分钟

有些人一分开就是一辈子

我与她一个职业,我与她不同的城市。

前言

本篇大部分的篇幅都是用来讲类的使用方法的。

建议先去看看原型原型链,js的组合寄生试继承。

他们是一脉下来的,学完了上述这些看class就会得心应手。

作文讲究的是一个凤头,猪肚,豹尾,所以精华全在开头和结尾

没办法现在很多人也像语文阅卷老师一样浮躁(虽然写的确实不带劲)

什么是类?

讲什么是类前我们说回构造函数,他是生成实例对象的传统方法

function Point(x, y) {
 this.x = x;
 this.y = y;
}

Point.prototype.toString = function () {
 return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

其实等同于


class Point {
 constructor(x, y) {
   this.x = x;
   this.y = y;
 }

 toString() {
   return '(' + this.x + ', ' + this.y + ')';
 }
}
var p = new Point(1, 2);

那这么说class类本质是函数喽?那如何验证呢?

很简单,我们看他符不符合构造函数所构造的原型与原型链。


typeof Point // "function"

Point === Point.prototype.constructor // true

p.__proto__ == Point.prototype // true

p.toString == p.__proto__.toSting == Point.prototype.toString //true
//类的方法都是放在了类的原型上
p.constructor === Point.prototype.constructor // true
//Object.assign()方法可以很方便地一次向类添加多个方法。
Object.assign(Point.prototype, {
 toString(){},
 toValue(){}
});

constructor

一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加,且在通过new命令生成对象实例时,自动调用该方法。

我们先回顾一下实例化的过程也就是new关键词做了什么?

下面是一段仿写的代码

// 仿写该函数接收的第一个参数位函数
function objectFactory() {
// 创建新对象
    var obj = new Object(),
// 取出第一个参数也就是函数
    Constructor = [].shift.call(arguments);
// 将新对象的__proto__指向函数的原型
    obj.__proto__ = Constructor.prototype;
// 改变函数内部的this指向,使this指向新对象
    var ret = Constructor.apply(obj, arguments);
// 返回新对象
    return typeof ret === 'object' ? ret : obj;

};

而constructor对应的就是函数本身,也就是上面的Constructor。

如何验证?

对于上述的代码来说我们要想破坏实例的原型结构,我们只需要让ret(函数执行的返回值)是一个不是obj的新对象就可以了。

class同理

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo //false

静态方法static

什么是静态方法?

我们只需要在类的方法前加上一个static,那他就变成了一个静态方法。

但同时加上这个关键字就意味着,该方法不会被实例继承(所以我们不能通过实例调用)

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

关键词super

在构造函数中使用时,super关键字将单独出现,并且必须在使用this关键字之前使用。super关键字也可以用来调用父对象上的函数。

class Polygon {
  constructor(height, width) {
    this.name = 'Rectangle';
    this.height = height;
    this.width = width;
  }
  sayName() {
    console.log('Hi, I am a ', this.name + '.');
  }

}

class Square extends Polygon {
  constructor(length) {
    this.height; // 这样直接 this.heigh t会报错:ReferenceError,因为 super 需要先被调用!

    // 这里,它调用父类的构造函数的,
    // 作为Polygon 的 height, width
    super(length, length);

    // 注意: 在派生的类中, 在你可以使用'this'之前, 必须先调用super()。
    this.name = 'Square';
    // 同时super关键词还可以调用父类的方法
    console.log(super.sayName()); 
  }
}

继承extends

Class 可以通过extends关键字实现继承

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

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

let colorPoint = new ColorPoint

上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。

注意super

子类必须在constructor方法中调用super方法,否则新建实例时会报错。

这是因为子类如果想要继承父类自然就要继承他的属性,super关键词的本质就是运行父类的constructor函数,以此对子类添加父类的属性。

具体实现是在我们let colorPoint = new ColorPoint()此时class ColorPoint中的constructor的this指向为实例对象colorPoint,当执行super调用父亲的constructor的时候,就自然的为实例colorPoint添加了父类的属性。

最后父类的静态方法也会被子类继承

class A {
  static hello() {
    console.log('hello world');
  }
}

class B extends A {
}

B.hello()  // hello world

基本说完了class的一些核心用法,现在我们看下面的代码。

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

Parent.prototype.getName = function () {
   console.log(this.name)
}

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

// 关键的三步
var F = function () {};

F.prototype = Parent.prototype;

Child.prototype = new F();


var child1 = new Child('kevin', '18');

console.log(child1);

这是一段组合寄生试继承的代码。

这种继承的本质就是通过call改变this指向,执行父类函数,使得子类实例本身有父类的属性。

然后通过原型链让子类的原型作为父类函数的实例,进而通过原型链找到父类原型上的方法

class继承的本质上就是组合寄生试继承

class继承.png

我们看一下 colorPoint,不难发现

  1. colorPoint中有父类的属性。
  2. colorPoint.proto.proto == Point.prototype

这两点与组合寄生试继承是一样的。唯一的不同是他们在子类实例上添加父类属性的方法。

对于组合寄生试继承的实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面Parent.apply(this)。

class 的继承机制的实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

当然最终的目的是一样的。

参考

juejin.cn/post/702106…