ES5 中的类是通过 构造函数 + 原型对象 来实现的,如下:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.add = function() {
console.log(this.x + this.y);
}
let p = new Point(1, 2);
一、class 语法
class 只是一个语法糖,大部分功能 ES5 也能实现。
- 写法:(1)class (2)constructor(3)直接方法名。
- 调用:类可以看作构造函数的另一种写法(大部分行为也与ES5一致),和构造函数一样用 new,并且
typeof Point === 'function'。
class Point {
constructor(x, y) { // 构造方法
this.x = x;
this.y = y;
}
// 方法之间不需要逗号分隔
add() { // 无 function 前缀
console.log(this.x + this.y);
}
}
let p = new Point(1, 2);
p.add();
// 3
注意,类的所有方法都定义在类的 prototype 属性上面,包括 constructor。
// 调用实例p的方法,实际上就是调用原型上的方法
p.constructor === Point.prototype.constructor // true
// 原型的 constructor 指向构造函数
Point.prototype.constructor === Point // true
1. constructor
constructor 是类的默认方法,new 命令自动调用,如果没有显式定义,会默认添加一个空的 constructor 方法。
class Point {
constructor() {}
}
另外,constructor 默认返回实例对象,因此也可以指定返回另一个对象。
2. 类的一些注意事项
-
和普通构造函数的区别:类必须 new 调用、否则报错,但普通构造函数不用 new 也可以执行(此时会变成了普通函数,不会生成实例对象,并且内部的this指向全局对象)。类的所有方法都是不可枚举的。
-
类和模块的内部,默认就是严格模式,不用 use strict 声明。
-
不存在变量提升,所以不能先使用后定义。
-
如果某个方法前有
*,就表示该方法是一个 Genarator 函数。 -
类的方法内部的this,默认指向类的实例。但如果该方法单独使用,this会指向 undefined(因为严格模式)。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
console.log(this.x + this.y);
}
}
let p = new Point(1, 2);
add(); // Error:add 没有定义
有两种解决方法,绑定this
// 在构造函数中使用 bind 绑定
this.add = this.add.bind(this);
// 在构造函数中使用 箭头函数
this.add = () => this
3. 静态方法
方法前加上 static,表示 (1)该方法不会被实例继承,(2)通过类直接调用。(3)如果静态方法中包含this,那么this指向类。(4)父类的静态方法,可以被子类继承。
4. new.target
ES6 为new命令引入了 new.target 属性,用于构造函数中,返回new命令作用于的那个构造函数,来确定构造函数是怎么调用的。
二、class 继承
1. extends 继承
class Point { // 父类方法
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
return this.x + this.y; // 要用return返回
}
}
class NumPoint extends Point {
constructor(x, y, num) {
super(x, y); // 父类
this.num = num;
}
add() {
console.log(super.add() + this.num); // 父类
}
}
let p = new NumPoint(2, 2, 3);
p.add()
// 7
子类必须先在 constructor 方法中调用 super()。
为什么子类的构造函数,一定要调用`super()`?
ES5的继承机制是先生成子类的实例对象,再将父类的方法添加到这个对象上。
而ES6则是先将父类的属性、方法加到一个空对象上,然后再将该对象作为子类的实例。
super() 在这里表示父类的构造函数,调用后会运行父类构造函数,生成继承父类的this对象。(父类构造函数)
(类只是将构造函数和原型对象做了一层封装,constructor还是构造函数,其他方法还是定义在原型上的方法)
2. super()
1. super 当作函数使用
此时 super 代表父类的constructor构造函数,只能用在子类的构造函数中。类继承时,子类的构造函数必须执行一次 super 函数。
class A {}
class B extends A {
constructor() {
super();
}
}
super虽然表示父类构造函数,但返回的是子类实例,内部的this指向的是子类实例。
2. super 当作对象使用
- 在子类普通方法中,指向
父类的原型对象。所以定义在父类constructor上的属性或方法,都不能通过super调用。而除了父类constructor构造方法之外的其他方法,都是父类原型对象上的方法,可以通过super调用。
class Parent {
add() {
return 2;
}
}
let p = new Parent();
Parent.constructor.prototype === p.add.__proto__ // true
Parent.constructor === p.add.__proto__.constructor // true
另外,在子类普通方法通过super调用父类方法时,方法内部的this指向当前的子类实例。
class Parent {
constructor() {
this.x = 1
}
add() {
console.log(this.x);
}
}
class Child extends Parent {
constructor() {
super();
this.x = 2;
}
print() {
super.add(); // super中的this执行当前实例
}
}
let ch = new Child();
ch.print(); // 2
- 在子类静态方法中,super将指向父类。另外,在子类的静态方法通过super调用父类方法时,方法内部的this指向当前的子类,即子类的constructor(不是子类实例)。
3. 类的prototype 和 __proto__ 属性
在ES5 中,每个实例对应的 __proto__ 都指向构造函数的 prototype 属性。而在Class 同时有 prototype 和 __proto__ 两条继承链。
- 子类的
__proto__指向父类。 - 子类的prototype属性的
__proto__指向父类的prototype属性。
Child.__proto__ === Parent // true
Child.prototype.__proto__ === Parent.prototype // true
类的继承是按照下面的模式实现的:
class Parent {}
class Child {}
// 方法继承,子类的实例继承父类的实例
Object.setPrototypeOf(Child.prototype, Parent.prototype);
// 构造函数继承,子类继承父类的静态属性
Object.setPrototypeOf(Child, Parent);
- 子类的
__proto__属性,表示构造函数的继承,指向父类。 - 子类的
prototype属性的__proto__属性,表示方法的继承,指向父类的prototype属性。