class基本语法与继承

32次阅读

共计 4132 个字符,预计需要花费 11 分钟才能阅读完成。

一丶 Class 的基本语法

1. 简介

基本上,ES6 的 class 可以看作只是一个 语法糖 ,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让 对象原型 的写法更加清晰、更像面向对象编程的语法而已.

来个例子对比一下:
ES6 之前,生成实例对象的传统方法是通过构造函数。

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)

上面的代码用 ES6 改写:

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

  toString(){return '('+this.x+','+this.y+')';
  }
}

ES6 的类,完全可看作构造函数的另一种写法(在类的实例上面调用方法,其实就是调用原型上的方法),这两句话用代码说明:

##demo1
class liumz(){}
typeof liumz //"function"
liumz === liumz.prototype.constructor //true

##demo2
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true

构造函数的 prototype 属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的 prototype 属性上面。

class Point {constructor() {// ...}

  toString() {// ...}

  toValue() {// ...}
}

// 等同于

Point.prototype = {constructor() {},
  toString() {},
  toValue() {},
};

由于类的方法都定义在 prototype 对象上,所以类的新方法可以添加在 protype 对象上面:

class Point {constructor(){// ...}
Object.assign(Point.protype,{toValue(){},
  toString(){}
  })
}

现在看到上面一堆的代码与定义文字说明, 回顾上面到底说了个啥:

ES6 新引进了 Class 这个概念,这个概念的引入只是让对象原型的写法更清晰(Class 的绝大部分功能在 ES5 中都可以实现),ES5 中的构造函数相当于 Class 中的构造方法。类的数据类型就是函数,类本身指向构造函数。
构造函数是有 prototype 属性的,这个属性在 ES6 的类上是继续存在的,只是换了一种写法.

在充分理解了上面的内容时,我们继续深入,ES6 的类,内部定义的方法,都是不可枚举的 , 看代码:

class Point{constructor(x,y){}
  toString(){}
}
Object.keys(Point.prototype) //[]
Object.getOwnPropertyNames(Point.prototype)//["constructor","toString"]

2. 静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”

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

Foo.classMethod() // 'hello'

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

注意,如果静态方法包含 this 关键字,这个 this 指的是类,而不是实例

class Foo {static bar() {this.baz();
  }
  static baz() {console.log('hello');
  }
  baz() {console.log('world');
  }
}

Foo.bar() // hello

3. 实例属性的新写法

实例属性除了定义在 constructor()方法里面的 this 上面,也可以定义在类的最顶层。写法对比:

// 实例属性 this._count 定义在 constructor()方法里面
class IncreasingCounter {constructor() {this._count = 0;}
  get value() {console.log('Getting the current value!');
    return this._count;
  }
  increment() {this._count++;}
}
// 属性定义在类的最顶层,其它不变
class IncreasingCounter {
  _count = 0;
  get value() {console.log('Getting the current value!');
    return this._count;
  }
  increment() {this._count++;}
}

这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。

4. 静态属性

静态属性指的是 Class 本身的属性,即 Class.propName,而不是定义在实例对象(this)上的属性。

class Foo {
}

Foo.prop = 1;
Foo.prop // 1

目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性法的前面,加上 static 关键字。

// 老写法
class Foo {// ...}
Foo.prop = 1;

// 新写法
class Foo {static prop = 1;}

二丶 Class 的继承

1. 简介

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

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

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

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

这是因为子类自己的 this 对象,必须通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后对其加工,加上子类自己的实例属性和方法,如果不调用 super 方法,子类就得不到 this 对象。
2️⃣、父类的静态方法,也会被子类继承

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

class B extends A {
}

B.hello()  // hello world

上面代码中,hello()是 A 类的静态方法,B 继承 A,业绩承了 A 的静态方法。

2.Object.propotypeOf()


Object.propotypeOf 方法可以用来从子类上获取父类。

Object.propotypeOf(colorPoint) === Point
//true

因此,使用这个方法判断,一个类是否继承了另一个类,

3.super 关键字


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

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

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

4. 类的 prototype 属性和 _proto_ 属性


大多数浏览器的 ES5 实现中,每一个对象都有一个 _prop_ 属性,指向相应的构造函数的 prototype 属性。
Class 作为构造函数的语法糖,同时有 prototype 属性和 _prop_ 属性,因此同时存在两条继承链。

  • 子类的 _prop_ 属性,表示构造函数的继承,总是指向父类。
  • 子类 prototype 属性的 _prop_ 属性,表示方法的继承,总是指向父类的 prototype 属性。
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

5.Mixin 模式的实现


Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下

const a = {a: 'a'};
const b = {b: 'b'};
const c = {...a, ...b}; // {a: 'a', b: 'b'}

上面代码中,c 对象是 a 对象和 b 对象的合成,具有两者的接口。

下面是一个更完备的实现,将多个类的接口“混入”(mix in)另一个类。

function mix(...mixins) {class Mix {}

  for (let mixin of mixins) {copyProperties(Mix.prototype, mixin); // 拷贝实例属性
    copyProperties(Mix.prototype, Reflect.getPrototypeOf(mixin)); // 拷贝原型属性
  }

  return Mix;
}

function copyProperties(target, source) {for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"
      && key !== "prototype"
      && key !== "name"
    ) {let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

上面代码的 mix 函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。

class DistributedEdit extends mix(Loggable, Serializable) {// ...}

正文完
 0