一、ES6 类的定义
ES5 构造函数的写法:
function Point(x, y) { this.x = x; this.y = y; }
ES6 引入了 Class(类),通过class
关键字,可以定义类。
class Point { constructor(x, y) { this.x = x; this.y = y; } }
这里,ES6 的 Point 类的构造方法对应前面 ES5 的构造函数 Point,代码中的 constructor
是构造方法。
关于 constructor
constructor
是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有
constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加:class Point { } // 等同于 class Point { constructor() {} }
二、ES6 类的实例
生成类的实例与 ES5 一样,也是用 new
命令。
但构造函数的实例不用 new 也可以执行,而类必须用 new,否则会报错:
class Point { // ... } // 报错 var point = Point(2, 3); // 正确 var point = new Point(2, 3);
三、ES6 类的继承
1、Object.getPrototypeOf()
Object.getPrototypeOf 方法可以用来从子类上获取父类,判断一个类是否继承了另一个类
Object.getPrototypeOf(foo) === Foo // true
2、extends 关键字实现继承
class Parent{ constructor(lastName='Liu'){ this.lastName=lastName; } } class Child extends Parent{ constructor(lastName){ super(lastName); } } console.log(new Child('Chen')); // 输出结果

3、super 关键字
① super 作为函数调用时,代表父类的构造函数(ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错)
class A {} class B extends A { constructor() { 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 的时必须显式指定是作为函数、还是作为对象使用,否则会报错
class A {} class B extends A { constructor() { super(); console.log(super); // 报错 } }
上面代码中,console.log(super) 当中的 super,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错
super 关键字扩展
① 类的继承
② 类的重写(覆盖)
③ 类重写后使用 super 关键字重新调用父类方法
四、getter 和 setter
与 ES5 一样,在“类”的内部可以使用 get 和 set 关键字,对某个属性设置取值函数和存值函数,拦截该属性的存取行为
1、getter(取值函数)
class Parent{ constructor(name='Winnie'){ this.name=name; } get longName(){ return 'Liu'+this.name; } } let getterName=new Parent(); console.log(getterName.longName); // LiuWinnie
2、setter(存值函数)
class Parent{ constructor(name='Winnie'){ this.name=name; } get longName(){ return 'Liu'+this.name; } set longName(value){ this.name=value; } } let setterName=new Parent(); setterName.longName='Honey'; console.log(setterName.longName); // LiuHoney
五、public、private 和 protected
1、public
允许在类的内外被调用
2、private
只允许在类的内部被调用
3、protected
允许在类的内部及继承的子类中调用
六、静态方法/属性
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
但如果在一个方法前加上 static 关键字,则该方法不会被实例继承,而是直接通过类来调用,这种方法称为静态方法。
1、静态方法:使用 static 关键字
class Parent{ static tell(){ console.log('hello'); } tell(){ console.log('world'); } } Parent.tell(); // hello
以上代码还可以看出静态方法可以与非静态方法重名
2、静态属性:直接用 .属性名
class Parent { } Parent.lastName = 'Liu'; console.log(Parent.lastName); // Liu
上面代码为 Parent 类定义了一个静态属性 lastName
七、name 属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被 Class 继承,包括 name 属性
class Point {} Point.name // "Point"
name 属性总是返回紧跟在 class 关键字后面的类名
let foo = class Foo { constructor(){ this.name = 'Leo' } } let child = new foo(); console.log(foo.name); // Foo
八、new.target 属性
new 是从构造函数生成实例对象的命令。ES6 为 new 命令引入了一个 new.target 属性,该属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数。如果构造函数不是通过 new 命令或 Reflect.construct() 调用的,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 命令调用
需要注意的是,子类继承父类时,new.target 会返回子类