ES6之Class(类)
Class 定义
ES6 中引入了 class(类) 的概念。 通过 class 关键字,我们可以定义 类
。
class 可以看做是一个 语法糖,它的绝大部分功能,都可以用 ES5 做到, 它只是让 对象原型(prototype) 的写法 更加清晰、更像面向对象编程 的语法而已。
类的本质还是一个函数
,类就是构造函数的另一种写法。
function Person(){}
console.log(typeof Person); //function
class Person{}
console.log(typeof Person); //function
Class 写法
ES5定义构造函数,一般来说是这样:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
return '我叫' + this.name + ',今年' + this.age + '岁';
}
let p1 = new Person('张三', 18);
console.log(p1.say()); // 我叫张三,今年18岁
ES6 引入了 Class(类)这个概念,提供了更接近传统语言的写法,上述代码用 ES6 实现就是:
class Person {
// 构造方法,this代表实例对象
constructor(name, age) {
this.name = name;
this.age = age;
}
// 这里的方法都是定义在prototype原型对象上的方法
say() { // 定义在原型上
return '我叫' + this.name + ',今年' + this.age + '岁';
}
}
let p1 = new Person('张三', 18);
let p2 = new Person('李四', 20);
console.log(p1.say()); // 我叫张三,今年18岁
// 类的所有方法都定义在类的prototype属性上面
console.log(p1.say === p2.say); //true
注意:
- constructor就是原来构造函数的写法,
类中的 this 指向实例对象
。 - 定义类的方法(
say(){}
)的时候,并不需要加逗号,也不需要加function。 - class不存在变量提升现象,必须要先声明后使用。必须先有类,才可以实例化
- 添加的私有属性都在构造函数中添加
- 类的所有方法都定义在类的
prototype原型对象上
constructor
constructor 方法是类的构造方法
- 是一个默认方法,通过 new 命令创建对象实例时,
自动调用
该方法。 this
代表实例对象- 一个类必须有 constructor 方法,如果没有显式定义,一个默认的 consructor 方法会被默认添加。所以即使你没有添加构造函数,也是会有一个默认的构造函数的,如下例:
// ES6
class Person {}
// 等同于
class Person {
constructor() {}
}
// ES5
function Person () {}
一般 constructor 方法返回实例对象 this ,但是也可以指定 constructor 方法返回一个全新的对象,让返回的实例对象不是该类的实例。
get 和 set
在 class 的内部可以使用 get 和 set 关键字,对某个属性设置 存值函数(setter) 和 取值函数(getter),拦截 该属性的 存取行为。
class Point {
constructor() {
// ...
}
get x() {
console.log('getter');
}
set x(val) {
console.log(`setter: ${val}`)
}
}
const p = new Point();
p.x = 1; // setter: 1
p.x // getter
静态方法和属性
类是实例的原型,所有类的方法都会被实例继承。但是,如果在一个方法前面加上static
关键字,就代表这是一个静态方法,这个 静态方法不会被实例继承,而是通过类直接调用
class Person {
constructor() {}
static say() { // 静态方法
return 'Hello World';
}
}
var p = new Person();
// 只能通过类直接调用
console.log(Person.say()); // 'Hello World'
// 静态方法是不可以被实例继承
console.log(p.say()); // 报错 p.say is not a function
继承
extends 关键字
Class 的继承是通过extends
关键字实现的
class Cat extends Animal{}
这里的子类 Cat
,通过 extends
关键字,继承了父类 Animal
所有的属性和方法
class Animal {
constructor(name, age) {
this.name = name
this.age = age
}
sayName() {
return "it's name is " + this.name
}
}
class Cat extends Animal {
constructor(name, age, color) {
super(name, age) // super关键字调用父元素的方法
this.color = color
}
sayColor() {
return "the color is " + this.color
}
}
var bosimiao = new Cat("bosi", 2, 'yellow');
console.log(bosimiao.sayName()); // it's name is bosi
console.log(bosimiao.sayColor()); // the color is yellow
super 关键字
- 在子构造函数中,必须用
super
方法,否则新建对象的时候会报错 - 子构造函数中,使用
this
前必须先用super
,否则会报错
这是和ES6的继承机制有关:先创建父类的实例对象,然后在构建子类的实例,再修改父类中的this对象
。
super 既可以充当函数,也可以充当对象
1. 当做函数使用
充当函数时,只能在子类的构造函数中使用,且必须放在第一行调用。
指向父类的构造函数,只有super调用之后,子类才可以使用 this 关键字,指向子类的实例对象
class A {}
class B extends A {
constructor() {
super(); // ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错。
}
}
注:在 constructor 中必须调用 super 方法,super 代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是子类B
,因此 super() 在这里相当于 A.prototype.constructor.call(this, props)
。
class A {
constructor() {
console.log(new.target.name); // new.target 指向当前正在执行的函数
}
}
class B extends A {
constructor() {
super();
}
}
new A(); // A
new B(); // B
可以看到,在 super() 执行时,它指向的是 子类 B 的构造函数,而不是父类 A 的构造函数。也就是说,super() 内部的 this 指向的是子类
。
2. 当做对象使用
充当对象时,放在普通函数中,super 指向父类的原型 prototype
class A {
c() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.c()); // 2
}
}
let b = new B();
上面代码中,子类 B 当中的 super.c(),就是将 super 当作一个对象使用。这时,super 在普通方法之中,指向 A.prototype,所以 super.c() 就相当于 A.prototype.c()。
通过 super 调用父类的方法时,super 会绑定子类的 this。
class A {
constructor() {
this.x = 1;
}
s() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.s();
}
}
let b = new B();
b.m(); // 2
上面代码中,super.s() 虽然调用的是 A.prototytpe.s(),但是 A.prototytpe.s()会绑定子类 B 的 this,导致输出的是 2,而不是 1。也就是说,实际上执行的是 super.s.call(this)。