18.Class 的基本语法彩民之家高手论坛

2019-11-21 21:03 来源:未知

Class 表达式

与函数同样,类也能够使用表明式的样式定义。

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};

上边代码应用表明式定义了多少个类。要求在乎的是,这些类的名字是MyClass而不是MeMe只在 Class 的中间代码可用,指代当前类。

let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

上边代码表示,Me只在 Class 内部有定义。

倘使类的中间没用到的话,能够总结Me,也便是能够写成上边包车型地铁样式。

const MyClass = class { /* ... */ };

动用 Class 表明式,可以写出当下实践的 Class。

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

person.sayName(); // "张三"

地点代码中,person是八个任何时候试行的类的实例。

Extends 的继续目的

extends重视字背后能够跟五连串型的值。

class B extends A {
}

上边代码的A,只假诺贰个有prototype质量的函数,就能够被B三番两回。由于函数皆有prototype属性(除了Function.prototype函数),因此A能够是任意函数。

上边,研讨三种奇特别情报形。

首先种非常意况,子类继承Object类。

class A extends Object {
}

A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true

这种情形下,A实际上正是构造函数Object的复制,A的实例就是Object的实例。

第两种相当情形,海市蜃楼任何继承。

class A {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true

这种情景下,A作为二个基类(即不设有任何世襲卡塔尔,便是多少个平时函数,所以直接接轨Funciton.prototype。但是,A调用后回到贰个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。

其二种新鲜景况,子类世襲null

class A extends null {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true

这种情况与第三种状态十二分像。A也是贰个日常函数,所以直接接轨Funciton.prototype。可是,A调用后归来的靶子不继续任何格局,所以它的__proto__指向Function.prototype,即实质上实践了下边包车型大巴代码。

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

Class 的静态属性和实例属性

静态属性指的是 Class 本人的品质,即Class.propName,并不是概念在实例对象(this卡塔 尔(阿拉伯语:قطر‎上的品质。

class Foo {
}

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

上面的写法为Foo类定义了三个静态属性prop

现阶段,唯有这种写法可行,因为 ES6 明显规定,Class 内部只有静态方法,未有静态属性。

// 以下两种写法都无效
class Foo {
  // 写法一
  prop: 2

  // 写法二
  static prop: 2
}

Foo.prop // undefined

前段时间有四个静态属性的提案,对实例属性和静态属性都规定了新的写法。

(1卡塔 尔(阿拉伯语:قطر‎类的实例属性

类的实例属性能够用等式,写入类的定义之中。

class MyClass {
  myProp = 42;

  constructor() {
    console.log(this.myProp); // 42
  }
}

上边代码中,myProp就是MyClass的实例属性。在MyClass的实例上,能够读取那一个性格。

原先,大家定义实例属性,只可以写在类的constructor方式里面。

class ReactCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
}

地点代码中,构造方法constructor里面,定义了this.state属性。

有了新的写法今后,能够不在constructor方法里面定义。

class ReactCounter extends React.Component {
  state = {
    count: 0
  };
}

这种写法比早前更明显。

为了可读性的指标,对于那么些在constructor中间早就定义的实例属性,新写法允许直接列出。

class ReactCounter extends React.Component {
  state;
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
}

(2卡塔尔类的静态属性

类的静态属性只要在地点的实例属性写法后边,加上static珍视字就足以了。

class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myStaticProp); // 42
  }
}

平等的,这几个新写法大大有利了静态属性的发布。

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

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

地方代码中,老写法的静态属性定义在类的外界。整个类生成之后,再生成静态属性。那样令人相当轻巧忽略这几个静态属性,也不合乎相关代码应该放在一块儿的代码协会标准。此外,新写法是显式评释(declarative卡塔 尔(英语:State of Qatar),并非赋值管理,语义更加好。

不设有变量提高

Class不设有变量提高(hoist卡塔尔国,这点与ES5完全两样。

new Foo(); // ReferenceError
class Foo {}

地方代码中,Foo类应用在前,定义在后,那样会报错,因为ES6不会把类的扬言升高到代码尾部。这种规定的原故与下文要提到的持续有关,必得保障子类在父类之后定义。

{
  let Foo = class {};
  class Bar extends Foo {
  }
}

地点的代码不会报错,因为Bar继承Foo的时候,Foo现原来就有定义了。不过,借使存在class的晋级,上边代码就能够报错,因为class会被晋级到代码底部,而let一声令下是不晋级的,所以引致Bar继承Foo的时候,Foo还还没概念。

Class 的 Generator 方法

倘使有些方法此前增加星号(*卡塔 尔(阿拉伯语:قطر‎,就象征该措施是一个 Generator 函数。

class Foo {
  constructor(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (let arg of this.args) {
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  console.log(x);
}
// hello
// world

上边代码中,Foo类的Symbol.iterator主意前有三个星号,表示该格局是一个Generator 函数。Symbol.iterator方式重返一个Foo类的默许遍历器,for...of循环会自动调用这么些遍历器。

概述

JavaScript语言的观念方法是通过构造函数,定义并生成新对象。下边是八个例子。

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);

位置这种写法跟守旧的面向对象语言(比方C 和Java卡塔尔国差别超大,十分轻松让新学习这门语言的技士感觉纠葛。

ES6提供了更近似守旧语言的写法,引进了Class(类卡塔尔国这些定义,作为对象的模板。通过class驷不及舌字,能够定义类。基本上,ES6的class能够作为只是一个语法糖,它的多边作用,ES5都足以达成,新的class写法只是让对象原型的写法尤其清晰、更像面向对象编程的语法而已。上边的代码用ES6的“类”改写,就是底下那样。

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

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

地点代码定义了叁个“类”,能够看出此中有叁个constructor主意,那便是构造方法,而this重要字则表示实例对象。也正是说,ES5的构造函数Point,对应ES6的Point类的构造方法。

Point类除却构造方法,还定义了二个toString方法。注意,定义“类”的主意的时候,前面无需加上function以此主要字,直接把函数定义放进去了就足以了。别的,方法之间无需逗号分隔,加了会报错。

ES6的类,完全能够看作构造函数的另生机勃勃种写法。

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

地点代码评释,类的数据类型正是函数,类自个儿就本着构造函数。

利用的时候,也是一直对类使用new指令,跟构造函数的用法完全生龙活虎致。

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"

构造函数的prototype质量,在ES6的“类”上面继续存在。事实上,类的具备办法都定义在类的prototype特性上边。

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

  toString(){
    // ...
  }

  toValue(){
    // ...
  }
}

// 等同于

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

在类的实例上面调用方法,其实就是调用原型上的法子。

class B {}
let b = new B();

b.constructor === B.prototype.constructor // true

上边代码中,b是B类的实例,它的constructor方法正是B类原型的constructor方法。

鉴于类的格局都定义在prototype目的方面,所以类的新格局能够增加在prototype目标方面。Object.assign艺术能够很平价地一遍向类增多八个章程。

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

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

prototype对象的constructor品质,直接针对“类”的本人,那与ES5的表现是千篇后生可畏律的。

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

其余,类的个中装有定义的主意,都以千千万万的(non-enumerable卡塔尔国。

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

地点代码中,toString方法是Point类内部定义的不二秘诀,它是成千上万的。这或多或少与ES5的作为不均等。

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

Point.prototype.toString = function() {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

上边代码应用ES5的写法,toString方法正是可枚举的。

类的属性名,能够选用表达式。

let methodName = "getArea";
class Square{
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}

地点代码中,Square类的方法名getArea,是从表明式获得的。

name 属性

是因为精气神儿上,ES6 的类只是 ES5 的构造函数的生机勃勃层包装,所以函数的累累本性都被Class继承,包括name属性。

class Point {}
Point.name // "Point"

name属性总是回到紧跟在class关键字背后的类名。

Class的继承

类的实例对象

生成类的实例对象的写法,与 ES5 完全等同,也是使用new一声令下。前边说过,假诺忘记加上new,像函数那样调用Class,将会报错。

class Point {
  // ...
}

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);

与 ES5 同样,实例的属性除非显式定义在其自己(即定义在this目的上卡塔 尔(阿拉伯语:قطر‎,不然都以概念在原型上(即定义在class上)。

//定义类
class Point {

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

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

}

var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

地方代码中,xy都是实例对象point笔者的性能(因为定义在this变量上),所以hasOwnProperty艺术重临true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty措施再次回到false。这几个都与 ES5 的一颦一笑保持后生可畏致。

与 ES5 同样,类的有着实例分享三个原型对象。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__
//true

上边代码中,p1p2都是Point的实例,它们的原型都以Point.prototype,所以__proto__属性是相等的。

那也意味,能够透超过实际例的__proto__品质为“类”增多方法。

__proto__ class="Apple-converted-space"> 并非语言本人的特点,那是各大厂商具体达成时增多的村办属性,即使这段日子广大今世浏览器的 JS 引擎中都提供了那几个私有属性,但照样不建议在生育中利用该属性,防止对情形产生信任性。坐褥条件中,大家能够运用 class="Apple-converted-space"> Object.getPrototypeOf class="Apple-converted-space"> 方法来博取实例对象的原型,然后再来为原型加多方法/属性。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__.printName = function () { return 'Oops' };

p1.printName() // "Oops"
p2.printName() // "Oops"

var p3 = new Point(4,2);
p3.printName() // "Oops"

上面代码在p1的原型上增多了一个printName方法,由于p1的原型就是p2的原型,因此p2也足以调用这一个点子。并且,自此新建的实例p3也足以调用那几个艺术。那象征,使用实例的__proto__质量改写原型,必需生龙活虎对风流浪漫谨严,不推荐应用,因为那会改造“类”的原来定义,影响到具有实例。

Class的静态属性和实例属性

静态属性指的是Class本身的质量,即Class.propname,实际不是概念在实例对象(this卡塔尔国上的属性。

class Foo {
}

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

上边的写法为Foo类定义了叁个静态属性prop

近来,唯有这种写法可行,因为ES6显著规定,Class内部唯有静态方法,未有静态属性。

// 以下两种写法都无效
class Foo {
  // 写法一
  prop: 2

  // 写法二
  static prop: 2
}

Foo.prop // undefined

ES7有叁个静态属性的提案,近来Babel转码器支持。

其黄金年代议事原案对实例属性和静态属性,都规定了新的写法。

(1卡塔尔国类的实例属性

类的实例属性能够用等式,写入类的定义之中。

class MyClass {
  myProp = 42;

  constructor() {
    console.log(this.myProp); // 42
  }
}

上面代码中,myProp就是MyClass的实例属性。在MyClass的实例上,能够读取那么些性情。

原先,大家定义实例属性,只可以写在类的constructor措施里面。

class ReactCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
}

地点代码中,构造方法constructor里面,定义了this.state属性。

有了新的写法今后,能够不在constructor措施里面定义。

class ReactCounter extends React.Component {
  state = {
    count: 0
  };
}

这种写法比原先更清晰。

为了可读性的指标,对于那些在constructor里头早就定义的实例属性,新写法允许直接列出。

class ReactCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  state;
}

(2卡塔 尔(英语:State of Qatar)类的静态属性

类的静态属性只要在上头的实例属性写法前边,加上static重大字就足以了。

class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myProp); // 42
  }
}

雷同的,那几个新写法大大有利了静态属性的发布。

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

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

地点代码中,老写法的静态属性定义在类的外表。整个类生成之后,再生成静态属性。那样令人相当的轻易忽略那几个静态属性,也不切合有关代码应该放在一块儿的代码组织法则。其它,新写法是显式表明(declarative卡塔尔国,并不是赋值管理,语义更加好。

简介

JavaScript 语言中,生成实例对象的金钱观方法是通过构造函数。上边是二个事例。

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);

下面这种写法跟守旧的面向对象语言(比如 C 和 Java卡塔尔差距一点都不小,超轻便让新学习这门语言的程序员感到纠缠。

ES6 提供了更近似古板语言的写法,引进了 Class(类卡塔尔国那一个定义,作为靶子的模板。通过class器重字,能够定义类。

基本上,ES6 的class能够作为只是一个语法糖,它的多方职能,ES5 都能够做到,新的class写法只是让对象原型的写法特别明显、更像面向对象编制程序的语法而已。上边包车型大巴代码用 ES6 的class改写,正是底下这样。

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

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

地点代码定义了八个“类”,能够看看里面有多少个constructor情势,那就是构造方法,而this第一字则象征实例对象。也等于说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。

Point类除却构造方法,还定义了贰个toString措施。注意,定义“类”的秘技的时候,后面无需增加function那个首要字,直接把函数定义放进去了就足以了。其它,方法之间不必要逗号分隔,加了会报错。

ES6 的类,完全能够看做构造函数的另生龙活虎种写法。

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

下边代码证明,类的数据类型便是函数,类自个儿就针对构造函数。

运用的时候,也是直接对类使用new指令,跟构造函数的用法完全生机勃勃致。

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"

构造函数的prototype属性,在 ES6 的“类”上边继续存在。事实上,类的拥有办法都定义在类的prototype质量上边。

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

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同于

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

在类的实例上边调用方法,其实就是调用原型上的主意。

class B {}
let b = new B();

b.constructor === B.prototype.constructor // true

上边代码中,bB类的实例,它的constructor方法正是B类原型的constructor方法。

鉴于类的点子都定义在prototype对象方面,所以类的新办法能够增加在prototype指标方面。Object.assign情势能够很有益于地二遍向类增加七个方法。

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

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

prototype对象的constructor特性,直接指向“类”的本人,那与 ES5 的一举一动是均等的。

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

除此以外,类的里边有着定义的艺术,都以成千上万的(non-enumerable)。

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

地点代码中,toString方法是Point类内部定义的点子,它是比比皆已经的。那或多或少与 ES5 的展现差别等。

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

Point.prototype.toString = function() {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

地点代码应用 ES5 的写法,toString主意便是可枚举的。

类的属性名,能够行使表明式。

let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}

地点代码中,Square类的秘籍名getArea,是从表明式得到的。

类的prototype属性和__proto__属性

大超多浏览器的ES5兑现之中,每三个对象都有__proto__质量,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同一时间有prototype属性和__proto__质量,由此同不经常候设有两条世袭链。

(1)子类的__proto__质量,表示构造函数的后续,总是指向父类。

(2)子类prototype属性的__proto__质量,表示方法的继续,总是指向父类的prototype属性。

class A {
}

class B extends A {
}

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

地方代码中,子类B__proto__属性指向父类A,子类Bprototype属性的__proto__天性指向父类Aprototype属性。

那般的结果是因为,类的继续是依据上边包车型大巴方式完成的。

class A {
}

class B {
}

// B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype);
const b = new B();

// B的实例继承A的静态属性
Object.setPrototypeOf(B, A);
const b = new B();

《对象的扩展》黄金时代章给出过Object.setPrototypeOf方法的落到实处。

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

于是,就收获了上边包车型地铁结果。

Object.setPrototypeOf(B.prototype, A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);
// 等同于
B.__proto__ = A;

这两条世襲链,能够这样精晓:作为一个对象,子类(B)的原型(__proto__属性)是父类(A卡塔 尔(英语:State of Qatar);作为二个构造函数,子类(B)的原型(prototype质量卡塔尔是父类的实例。

Object.create(A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;

私妻儿性

与个体方法生龙活虎致,ES6 不援救个体属性。近来,有二个提案,为class加了个体属性。方法是在属性名在此以前,使用#表示。

class Point {
  #x;

  constructor(x = 0) {
    #x =  x; // 写成 this.#x 亦可
  }

  get x() { return #x }
  set x(value) { #x =  value }
}

下边代码中,#x就代表私有属性x,在Point类之外是读取不到这性情情的。还能见到,私有属性与实例的性质是足以同名的(举例,#xget x())。

民用属性能够钦命开端值,在构造函数实行时进行初阶化。

class Point {
  #x = 0;
  constructor() {
    #x; // 0
  }
}

于是要引进二个新的前缀#意味着私有属性,而并未使用private重在字,是因为 JavaScript 是一门动态语言,使用独立的标识就好像是天下无敌的笃定办法,能够规范地区分风流倜傥种属性是不是为私有总体性。此外,Ruby 语言使用@代表私有属性,ES6 未有用那几个符号而使用#,是因为@现已被留下了 Decorator。

该议案只规定了民用属性的写法。可是,很自然地,它也能够用来写个人方法。

class Foo {
  #a;
  #b;
  #sum() { return #a   #b; }
  printSum() { console.log(#sum()); }
  constructor(a, b) { #a = a; #b = b; }
}

this的指向

类的方式内部假如带有this,它私下认可指向类的实例。然而,必需非常当心,黄金年代旦单独行使该措施,相当大概报错。

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

地方代码中,printName措施中的this,暗许指向Logger类的实例。但是,借使将以此点子提收取来单独选取,this会指向该措施运营时所在的条件,因为找不到print办法而导致报错。

叁个比较简单的消灭方法是,在构造方法中绑定this,那样就不会找不到print方法了。

class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}

另大器晚成种减轻办法是选择箭头函数。

class Logger {
  constructor() {
    this.printName = (name = 'there') => {
      this.print(`Hello ${name}`);
    };
  }

  // ...
}

再有黄金时代种缓和方法是利用Proxy,获取情势的时候,自动绑定this

function selfish (target) {
  const cache = new WeakMap();
  const handler = {
    get (target, key) {
      const value = Reflect.get(target, key);
      if (typeof value !== 'function') {
        return value;
      }
      if (!cache.has(value)) {
        cache.set(value, value.bind(target));
      }
      return cache.get(value);
    }
  };
  const proxy = new Proxy(target, handler);
  return proxy;
}

const logger = selfish(new Logger());

Class 的主导语法

原生构造函数的接续

原生构造函数是指语言内置的构造函数,日常用来生成数据结构。ECMAScript的原生构造函数大致有上边那些。

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

早前,这一个原生构造函数是无法持续的,例如,不可能协和定义二个Array的子类。

function MyArray() {
  Array.apply(this, arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
  constructor: {
    value: MyArray,
    writable: true,
    configurable: true,
    enumerable: true
  }
});

上边代码定义了一个继承Array的MyArray类。然而,那个类的表现与Array一起不意气风发致。

var colors = new MyArray();
colors[0] = "red";
colors.length  // 0

colors.length = 0;
colors[0]  // "red"

就此会发出这种气象,是因为子类不可能拿到原生构造函数的内部属性,通过Array.apply()照旧分配给原型对象都特别。原生构造函数会忽视apply措施传入的this,也便是说,原生构造函数的this没辙绑定,引致拿不到中间属性。

ES5是先新建子类的实例对象this,再将父类的品质增多到子类上,由于父类的个中属性不也许获得,引致力所不及持续原生的构造函数。举例,Array构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,那个里面属性无法在子类获取,引致子类的length属性行为不健康。

下边包车型大巴事例中,大家想让四个常备对象继承Error对象。

var e = {};

Object.getOwnPropertyNames(Error.call(e))
// [ 'stack' ]

Object.getOwnPropertyNames(e)
// []

下面代码中,大家想经过Error.call(e)这种写法,让普通对象e具有Error指标的实例属性。不过,Error.call()全盘忽略传入的第二个参数,而是回到多少个新目的,e本人未有其它变化。那注明了Error.call(e)这种写法,不能持续原生构造函数。

ES6同意继续原生构造函数定义子类,因为ES6是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的具有行为都能够继续。下边是叁个接二连三Array的例子。

class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined

地点代码定义了贰个MyArray类,继承了Array构造函数,由此就足以从MyArray生成数组的实例。那意味着,ES6得以自定义原生数据结构(比方Array、String等卡塔尔国的子类,那是ES5无法成功的。

地点那几个事例也验证,extends重大字不只能够用来世袭类,仍为能够用来世袭原生的构造函数。由此得以在原生数据结构的底工上,定义本人的数据结构。上边正是定义了一个带本子效果的数组。

class VersionedArray extends Array {
  constructor() {
    super();
    this.history = [[]];
  }
  commit() {
    this.history.push(this.slice());
  }
  revert() {
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}

var x = new VersionedArray();

x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]

x.commit();
x.history // [[], [1, 2]]
x.push(3);
x // [1, 2, 3]

x.revert();
x // [1, 2]

下面代码中,VersionedArray布局会透过commit艺术,将团结的脚下景况存入history天性,然后经过revert措施,能够废除当前版本,回到上一个版本。除外,VersionedArray照例是三个数组,全体原生的数组方法都足以在它上面调用。

上边是贰个自定义Error子类的事例。

class ExtendableError extends Error {
  constructor(message) {
    super();
    this.message = message;
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}

class MyError extends ExtendableError {
  constructor(m) {
    super(m);
  }
}

var myerror = new MyError('ll');
myerror.message // "ll"
myerror instanceof Error // true
myerror.name // "MyError"
myerror.stack
// Error
//     at MyError.ExtendableError
//     ...

注意,继承Object的子类,有一个行为差别。

class NewObj extends Object{
  constructor(){
    super(...arguments);
  }
}
var o = new NewObj({attr: true});
console.log(o.attr === true);  // false

上边代码中,NewObj继承了Object,不过不可能通过super主意向父类Object传参。那是因为ES6改成了Object构造函数的行为,大器晚成旦发现Object措施不是经过new Object()这种情势调用,ES6鲜明Object组织函数会忽略参数。

constructor 方法

constructor措施是类的暗许方法,通过new命令生成对象实例时,自动调用该形式。贰个类必得有constructor措施,若无显式定义,二个空的constructor方法会被暗许添加。

class Point {
}

// 等同于
class Point {
  constructor() {}
}

地方代码中,定义了叁个空的类Point,JavaScript 引擎会自动为它增添一个空的constructor方法。

constructor措施私下认可重临实例对象(即this卡塔尔国,完全能够钦赐重返其余二个对象。

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

new Foo() instanceof Foo
// false

上面代码中,constructor函数再次回到三个全新的靶子,结果产生实例对象不是Foo类的实例。

类必得运用new调用,不然会报错。那是它跟通常构造函数的一个首要不相同,前者不用new也得以举行。

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

Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'

着力用法

Class之间能够通过extends重在字贯彻持续,那比ES5的经过改变原型链完毕三番两遍,要清晰和有利广大。

class ColorPoint extends Point {}

地点代码定义了二个ColorPoint类,该类通过extends关键字,继承了Point类的有所属性和章程。不过出于没有配置任何代码,所以那三个类完全等同,等于复制了五个Point类。下面,我们在ColorPoint里头加上代码。

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

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

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

子类必需在constructor方法中调用super主意,否则新建实例时会报错。那是因为子类未有本身的this目的,而是继续父类的this对象,然后对其实行加工。假使不调用super格局,子类就得不到this对象。

class Point { /* ... */ }

class ColorPoint extends Point {
  constructor() {
  }
}

let cp = new ColorPoint(); // ReferenceError

地方代码中,ColorPoint继承了父类Point,不过它的构造函数未有调用super办法,引致新建实例时报错。

ES5的三回九转,实质是先创设子类的实例对象this,然后再将父类的章程增加到this上面(Parent.apply(this)卡塔尔。ES6的接轨机制完全不相同,实质是先创立父类的实例对象this(所以必得先调用super措施卡塔 尔(英语:State of Qatar),然后再用子类的构造函数更改this

若果子类未有定义constructor措施,这几个点子会被暗中同意增加,代码如下。也便是说,不管有未有显式定义,任何叁个子类都有constructor方法。

constructor(...args) {
  super(...args);
}

另多少个亟需注意的地点是,在子类的构造函数中,唯有调用super从此,才能够运用this首要字,否则会报错。那是因为子类实例的营造,是依附对父类实例加工,独有super方法才干回到父类实例。

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

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}

地点代码中,子类的constructor形式未有调用super之前,就使用this尤为重要字,结果报错,而坐落super措施之后就是不错的。

下边是生成子类实例的代码。

let cp = new ColorPoint(25, 8, 'green');

cp instanceof ColorPoint // true
cp instanceof Point // true

下面代码中,实例对象cp同时是ColorPointPoint五个类的实例,那与ES5的行为完全风华正茂致。

Class 的为主语法

严俊方式

类和模块的里边,私下认可就是从严形式,所以没有必要利用use strict点名运营方式。只要您的代码写在类或模块之中,就独有严厉方式可用。

思忖到今后享有的代码,其实都是运维在模块之中,所以ES6实在把全副语言进级到了严俊情势。

new.target 属性

new是从构造函数生成实例对象的通令。ES6 为new一声令下引入了三个new.target质量,该属性凉时用在构造函数之中,重回new一声令下功能于的不行构造函数。若是构造函数不是透过new一声令下调用的,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, '张三');  // 报错

地方代码确认保障构造函数只好通过new指令调用。

Class 内部调用new.target,再次回到当前 Class。

class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    this.length = length;
    this.width = width;
  }
}

var obj = new Rectangle(3, 4); // 输出 true

内需在乎的是,子类世襲父类时,new.target会重临子类。

class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    // ...
  }
}

class Square extends Rectangle {
  constructor(length) {
    super(length, length);
  }
}

var obj = new Square(3); // 输出 false

地点代码中,new.target会回到子类。

利用那个特点,可以写出不能够独立运用、必得三番一遍后技能选用的类。

class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error('本类不能实例化');
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确

上边代码中,Shape类不能够被实例化,只好用来后续。

小心,在函数外界,使用new.target会报错。

Class 的 Generator 方法

生机勃勃经有些方法以前拉长星号(*卡塔 尔(阿拉伯语:قطر‎,就象征该办法是三个 Generator 函数。

class Foo {
  constructor(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (let arg of this.args) {
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  console.log(x);
}
// hello
// world

地方代码中,Foo类的Symbol.iterator办法前有三个星号,表示该措施是二个Generator 函数。Symbol.iterator艺术再次来到多少个Foo类的暗中同意遍历器,for...of循环会自动调用这一个遍历器。

this 的指向

类的点子内部借使含有this,它暗中认可指向类的实例。但是,必得十分的小心,意气风发旦单独使用该形式,很恐怕报错。

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

上边代码中,printName主意中的this,暗许指向Logger类的实例。可是,如若将那个主意提收取来单独行使,this会指向该方法运维时所在的情形,因为找不到print办法而变成报错。

一个比较简单的消除办法是,在构造方法中绑定this,那样就不会找不到print方法了。

class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}

另豆蔻梢头种缓和情势是利用箭头函数。

class Logger {
  constructor() {
    this.printName = (name = 'there') => {
      this.print(`Hello ${name}`);
    };
  }

  // ...
}

再有风度翩翩种缓和措施是行使Proxy,获取情势的时候,自动绑定this

function selfish (target) {
  const cache = new WeakMap();
  const handler = {
    get (target, key) {
      const value = Reflect.get(target, key);
      if (typeof value !== 'function') {
        return value;
      }
      if (!cache.has(value)) {
        cache.set(value, value.bind(target));
      }
      return cache.get(value);
    }
  };
  const proxy = new Proxy(target, handler);
  return proxy;
}

const logger = selfish(new Logger());

个体方法

个人方法是平淡无奇供给,但 ES6 不提供,只好通过变通方法模拟完成。

生机勃勃种做法是在命名上加以差别。

class Widget {

  // 公有方法
  foo (baz) {
    this._bar(baz);
  }

  // 私有方法
  _bar(baz) {
    return this.snaf = baz;
  }

  // ...
}

地点代码中,_bar方法前边的下划线,表示那是三个只限于内部选拔的村办方法。但是,这种命名是不保证的,在类的外界,还是得以调用到这些点子。

另黄金年代种情势正是索性将个人方法移出模块,因为模块内部的有所办法都是对外可知的。

class Widget {
  foo (baz) {
    bar.call(this, baz);
  }

  // ...
}

function bar(baz) {
  return this.snaf = baz;
}

地点代码中,foo是国有方法,内部调用了bar.call(this, baz)。这使得bar其实产生了现阶段模块的民用方法。

再有风度翩翩种形式是应用Symbol值的唯后生可畏性,将个人方法的名字命名字为三个Symbol值。

const bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{

  // 公有方法
  foo(baz) {
    this[bar](baz);
  }

  // 私有方法
  [bar](baz) {
    return this[snaf] = baz;
  }

  // ...
};

上边代码中,barsnaf都是Symbol值,导致第三方无法取获得它们,因此高达了民用方法和村办属性的法力。

不设有变量进步

类不设有变量提高(hoist卡塔 尔(阿拉伯语:قطر‎,那一点与 ES5 完全两样。

new Foo(); // ReferenceError
class Foo {}

上边代码中,Foo类应用在前,定义在后,那样会报错,因为 ES6 不会把类的扬言升高到代码底部。这种规定的缘故与下文要涉及的后续有关,必得保障子类在父类之后定义。

{
  let Foo = class {};
  class Bar extends Foo {
  }
}

上面包车型的士代码不会报错,因为Bar继承Foo的时候,Foo曾经有定义了。然而,假如存在class的晋升,下面代码就能够报错,因为class会被提高到代码尾部,而let命令是不升官的,所以引致Bar继承Foo的时候,Foo还没曾定义。

Class 的静态方法

类也正是实例的原型,全体在类中定义的点子,都会被实例世袭。假设在三个方法前,加上static关键字,就表示该办法不会被实例世襲,而是从来通过类来调用,这就叫做“静态方法”。

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

Foo.classMethod() // 'hello'

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

地点代码中,Foo类的classMethod格局前有static第一字,表明该办法是二个静态方法,能够直接在Foo类上调用(Foo.classMethod()卡塔尔国,并不是在Foo类的实例上调用。若是在实例上调用静态方法,会抛出二个荒唐,表示官样文章该模式。

父类的静态方法,能够被子类继承。

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

class Bar extends Foo {
}

Bar.classMethod(); // 'hello'

地点代码中,父类Foo有一个静态方法,子类Bar能够调用那个方法。

静态方法也是能够从super对象上调用的。

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

class Bar extends Foo {
  static classMethod() {
    return super.classMethod()   ', too';
  }
}

Bar.classMethod();

村办方法

民用方法是遍布要求,但 ES6 不提供,只可以通过变通方法模拟完成。

大器晚成种做法是在命名上加以差别。

class Widget {

  // 公有方法
  foo (baz) {
    this._bar(baz);
  }

  // 私有方法
  _bar(baz) {
    return this.snaf = baz;
  }

  // ...
}

地点代码中,_bar措施前面包车型客车下划线,表示这是多个只限于内部使用的私人商品房方法。然而,这种命名是不保障的,在类的外界,还能调用到这么些艺术。

另生机勃勃种艺术正是索性将民用方法移出模块,因为模块内部的具有办法都以对外可以知道的。

class Widget {
  foo (baz) {
    bar.call(this, baz);
  }

  // ...
}

function bar(baz) {
  return this.snaf = baz;
}

地点代码中,foo是公有方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的村办方法。

再有风度翩翩种形式是行使Symbol值的唯大器晚成性,将个人方法的名字命名字为二个Symbol值。

const bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{

  // 公有方法
  foo(baz) {
    this[bar](baz);
  }

  // 私有方法
  [bar](baz) {
    return this[snaf] = baz;
  }

  // ...
};

地点代码中,barsnaf都是Symbol值,导致第三方不能赢获得它们,由此高达了个体方法和个人属性的效率。

super 关键字

super以此重大字,不仅可以够视作函数使用,也能够视作对象使用。在此二种情状下,它的用法完全两样。

先是种情景,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必需实施二遍super函数。

class A {}

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

地点代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。那是必需的,不然JavaScript 引擎会报错。

注意,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 {}

class B extends A {
  m() {
    super(); // 报错
  }
}

上边代码中,super()用在B类的m方法之中,就能够招致句法错误。

其次种状态,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();

地点代码中,属性x是概念在A.prototype上面的,所以super.x能够取到它的值。

ES6 规定,通过super调用父类的艺术时,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

地方代码中,super.print()固然调用的是A.prototype.print(),但是A.prototype.print()会绑定子类Bthis,引致出口的是2,而不是1。也等于说,实际上施行的是super.print.call(this)

出于绑定子类的this,所以假设经过super对有些属性赋值,这时候super就是this,赋值的习性会成为子类实例的性格。

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

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

let b = new B();

地点代码中,super.x赋值为3,此时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以回来undefined

注意,使用super的时候,必得显式钦定是作为函数、照旧作为靶子使用,不然会报错。

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super); // 报错
  }
}

上边代码中,console.log(super)当中的super,不大概见到是用作函数使用,依旧作为对象使用,所以 JavaScript 引擎分析代码的时候就能够报错。这时候,假使能清晰地注解super的数据类型,就不会报错。

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super.valueOf() instanceof B); // true
  }
}

let b = new B();

地点代码中,super.valueOf()表明super是三个对象,因此就不会报错。同期,由于super绑定Bthis,所以super.valueOf()回来的是一个B的实例。

最后,由于指标总是世袭其余对象的,所以可以在大肆一个指标中,使用super关键字。

var obj = {
  toString() {
    return "MyObject: "   super.toString();
  }
};

obj.toString(); // MyObject: [object Object]

Class 的取值函数(getter卡塔尔国和存值函数(setter卡塔尔国

与 ES5 相同,在“类”的中间能够行使getset最重要字,对某些属性设置存值函数和取值函数,拦截该属性的存取行为。

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: ' value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

地点代码中,prop属性有照看的存值函数和取值函数,因而赋值和读取行为都被自定义了。

存值函数和取值函数是设置在品质的 Descriptor 对象上的。

class CustomHTMLElement {
  constructor(element) {
    this.element = element;
  }

  get html() {
    return this.element.innerHTML;
  }

  set html(value) {
    this.element.innerHTML = value;
  }
}

var descriptor = Object.getOwnPropertyDescriptor(
  CustomHTMLElement.prototype, "html"
);

"get" in descriptor  // true
"set" in descriptor  // true

地方代码中,存值函数和取值函数是概念在html本性的叙说对象方面,那与 ES5 完全一致。

类的实例对象

生成类的实例对象的写法,与ES5通通等同,也是使用new一声令下。假使忘记加上new,像函数那样调用Class,将会报错。

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);

与ES5风姿洒脱致,实例的性子除非显式定义在其自己(即定义在this对象上卡塔尔,否则都以概念在原型上(即定义在class上)。

//定义类
class Point {

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

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

}

var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

地点代码中,xy都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty办法再次来到true,而toString是原型对象的天性(因为定义在Point类上),所以hasOwnProperty格局重临false。那一个都与ES5的作为保持意气风发致。

与ES5雷同,类的装有实例分享一个原型对象。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__
//true

地方代码中,p1p2都以Point的实例,它们的原型都以Point.prototype,所以__proto__天性是相等的。

那也意味着,能够通超过实际例的__proto__属性为Class增加艺术。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__.printName = function () { return 'Oops' };

p1.printName() // "Oops"
p2.printName() // "Oops"

var p3 = new Point(4,2);
p3.printName() // "Oops"

上边代码在p1的原型上增加了二个printName方法,由于p1的原型正是p2的原型,因此p2也能够调用这几个点子。并且,从今以后新建的实例p3也得以调用那些艺术。那代表,使用实例的__proto__属性改写原型,必需风度翩翩对黄金时代严谨,不推荐应用,因为这会更正Class的固有定义,影响到持有实例。

Class 的静态方法

类相当于实例的原型,全数在类中定义的措施,都会被实例世襲。要是在贰个情势前,加上static驷马难追字,就意味着该方式不会被实例世襲,而是径直通过类来调用,那就称为“静态方法”。

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

Foo.classMethod() // 'hello'

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

地方代码中,Foo类的classMethod措施前有static第一字,表明该方法是一个静态方法,能够直接在Foo类上调用(Foo.classMethod()卡塔 尔(阿拉伯语:قطر‎,实际不是在Foo类的实例上调用。假使在实例上调用静态方法,会抛出贰个荒谬,表示海市蜃楼该办法。

小心,借使静态方法包涵this关键字,这个this指的是类,并不是实例。

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

Foo.bar() // hello

上边代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。其余,从这几个例子还足以看来,静态方法能够与非静态方法重名。

父类的静态方法,能够被子类世袭。

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

class Bar extends Foo {
}

Bar.classMethod() // 'hello'

下边代码中,父类Foo有一个静态方法,子类Bar能够调用这一个艺术。

静态方法也是能够从super对象上调用的。

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

class Bar extends Foo {
  static classMethod() {
    return super.classMethod()   ', too';
  }
}

Bar.classMethod() // "hello, too"

name属性

鉴于精气神上,ES6的类只是ES5的构造函数的一层包装,所以函数的大队人Matt色都被Class继承,包括name属性。

class Point {}
Point.name // "Point"

name品质总是回到紧跟在class重大字背后的类名。

严格方式

类和模块的此中,暗中认可就是严俊形式,所以无需利用use strict点名运转格局。只要你的代码写在类或模块之中,就唯有从严格局可用。

思考到未来全数的代码,其实都以运营在模块之中,所以 ES6 实际上把一切语言晋级到了适度从紧方式。

Object.getPrototypeOf()

Object.getPrototypeOf主意能够用来从子类上赢得父类。

Object.getPrototypeOf(ColorPoint) === Point
// true

为此,能够应用这些办法推断,贰个类是还是不是继续了另贰个类。

new.target属性

new是从构造函数生成实例的通令。ES6为new一声令下引入了三个new.target属性,(在构造函数中卡塔尔国再次来到new指令作用于的要命构造函数。假设构造函数不是经过new指令调用的,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, '张三');  // 报错

地方代码确保构造函数只好通过new命令调用。

Class内部调用new.target,重回当前Class。

class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    this.length = length;
    this.width = width;
  }
}

var obj = new Rectangle(3, 4); // 输出 true

亟需介怀的是,子类继承父类时,new.target会回到子类。

class Rectangle {
  constructor(length, width) {
    console.log(new.target === Rectangle);
    // ...
  }
}

class Square extends Rectangle {
  constructor(length) {
    super(length, length);
  }
}

var obj = new Square(3); // 输出 false

地点代码中,new.target会回到子类。

行使那一个特性,能够写出不能够独立使用、必需一而再后才具利用的类。

class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error('本类不能实例化');
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确

地点代码中,Shape类不能被实例化,只可以用于后续。

只顾,在函数外界,使用new.target会报错。

Class基本语法

Class

  1. Class基本语法
  2. Class的继承
  3. 原生构造函数的接二连三
  4. Class的取值函数(getter卡塔 尔(阿拉伯语:قطر‎和存值函数(setter卡塔 尔(阿拉伯语:قطر‎
  5. Class 的 Generator 方法
  6. Class 的静态方法
  7. Class的静态属性和实例属性
  8. 类的个人属性
  9. new.target属性
  10. Mixin格局的兑现

Class表达式

与函数相仿,类也能够使用表明式的样式定义。

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};

地方代码应用表明式定义了二个类。须要注意的是,这些类的名字是MyClass而不是MeMe只在Class的内部代码可用,指代当前类。

let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

地点代码表示,Me只在Class内部有定义。

若是类的里边没用到的话,能够简简单单Me,也等于足以写成上面包车型地铁花样。

const MyClass = class { /* ... */ };

运用Class表明式,能够写出立刻施行的Class。

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

person.sayName(); // "张三"

地点代码中,person是三个当即试行的类的实例。

Class的取值函数(getter卡塔 尔(英语:State of Qatar)和存值函数(setter卡塔尔国

与ES5毫发不爽,在Class内部能够接受getset重大字,对某些属性设置存值函数和取值函数,拦截该属性的存取行为。

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: ' value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

地点代码中,prop质量有对应的存值函数和取值函数,因而赋值和读取行为都被自定义了。

存值函数和取值函数是安装在性质的descriptor对象上的。

class CustomHTMLElement {
  constructor(element) {
    this.element = element;
  }

  get html() {
    return this.element.innerHTML;
  }

  set html(value) {
    this.element.innerHTML = value;
  }
}

var descriptor = Object.getOwnPropertyDescriptor(
  CustomHTMLElement.prototype, "html");
"get" in descriptor  // true
"set" in descriptor  // true

地点代码中,存值函数和取值函数是概念在html性格的呈报对象方面,那与ES5完全豆蔻梢头致。

类的个体属性

目前,有一个提案,为class加了私家室性。方法是在属性名以前,使用#表示。

class Point {
  #x;

  constructor(x = 0) {
    #x =  x;
  }

  get x() { return #x }
  set x(value) { #x =  value }
}

下边代码中,#x就表示私有属性x,在Point类之外是读取不到这么些特性的。还足以观望,私有属性与实例的质量是可以同名的(比方,#xget x())。

个体属性能够钦定伊始值,在构造函数实行时开张开头化。

class Point {
  #x = 0;
  constructor() {
    #x; // 0
  }
}

之所以要引进贰个新的前缀#意味着私有属性,而并未有利用private根本字,是因为 JavaScript 是一门动态语言,使用独立的标识仿佛是必定要经过的道路的笃定形式,能够规范地区分后生可畏种本性是私人民居房属性。其余,Ruby 语言使用@代表私有属性,ES6 未有用那个标识而利用#,是因为@早就被留下了 Decorator。

该议事原案只规定了民用属性的写法。但是,很自然地,它也足以用来写个人方法。

class Foo {
  #a;
  #b;
  #sum() { return #a   #b; }
  printSum() { console.log(#sum()); }
  constructor(a, b) { #a = a; #b = b; }
}

constructor方法

constructor主意是类的暗许方法,通过new一声令下生成对象实例时,自动调用该措施。叁个类必需有constructor主意,若无显式定义,叁个空的constructor方式会被暗中认可增加。

constructor() {}

constructor办法私下认可返回实例对象(即this卡塔 尔(英语:State of Qatar),完全能够钦命重回其余一个目的。

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

new Foo() instanceof Foo
// false

地点代码中,constructor函数重临叁个簇新的对象,结果导致实例对象不是Foo类的实例。

类的构造函数,不利用new是不得已调用的,会报错。那是它跟普通构造函数的一个重视分化,后面一个不用new也能够试行。

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

Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'

实例的__proto__属性

子类实例的__proto__属性的__proto__质量,指向父类实例的__proto__属性。约等于说,子类的原型的原型,是父类的原型。

var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

地点代码中,ColorPoint继承了Point,招致前者原型的原型是前者的原型。

因而,通过子类实例的__proto__.__proto__质量,可以改进父类实例的行为。

p2.__proto__.__proto__.printName = function () {
  console.log('Ha');
};

p1.printName() // "Ha"

上边代码在ColorPoint的实例p2上向Point类增多方法,结果影响到了Point的实例p1

Mixin形式的实现

Mixin格局指的是,将三个类的接口“混入”(mix in卡塔尔另二个类。它在ES6的兑现如下。

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

  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }

  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) {
  // ...
}
TAG标签: 前端 ES6
版权声明:本文由彩民之家高手论坛发布于前端知识,转载请注明出处:18.Class 的基本语法彩民之家高手论坛