一、简单使用
// 定义一个Animal类
class Animal {
// 构造方法,相当于ES5中的构造函数
// this代表的是实例对象
constructor(kind) {
this.kind = kind;
}
getKind() {
console.log(this.kind);
}
}
- 类必须使用new运算符调用,直接调用会报错
// 类必须使用new运算符来调用,如果直接调用会报错,这点跟ES5的构造函数不一样
const cat = new Animal('cat');
// const cat = Animal('cat'); // TypeError: Class constructor Animal cannot be invoked without 'new'
cat.getKind(); // cat
- 类的数据类型是函数,类本身就指向构造函数
console.log(typeof Animal); // function
console.log(Animal === Animal.prototype.constructor); // true
- 类中的所有方法都是定义在类的prototype属性上的
- 实例的属性除非显式的定义在其本身(即this对象上),否则都是定义在原型上
// kind为实例属性
console.log(Object.getOwnPropertyNames(cat)); // [ 'kind' ]
// constructor、getKind为原型上的方法
console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(cat))); // [ 'constructor', 'getKind' ]
- 类不存在变量提升,因此在继承的时候父类必须在子类之前定义
const foo = new Foo(); // ReferenceError: Foo is not defined
class Foo {}
二、constructor
- constructor是类的默认方法,它相当于ES5中的构造函数,当我们使用new命令生成对象实例时,会自动调用该方法。
- 一个类必须有constructor方法,如果没有显示定义,会默认添加一个空的constructor方法。
class Animal {
}
// 等同于
calss Animal {
constructor() {}
}
- constructor方法默认返回实例对象,不过也可以指定返回另一个对象。
class Foo {
constructor() {
// 返回一个数组
return new Array()
}
print() {
console.log('124')
}
}
// 实例对象不再是Foo类的实例,而是Array的实例
const foo = new Foo();
console.log(foo instanceof Foo); // false
console.log(foo instanceof Array); // true
// 无法调用Foo类中的方法
foo.print(); // TypeError: foo.print is not a function
三、this的指向
类的方法中如果含有this,则默认指向类的实例。但是如果单独使用该方法,可能会报错。
class Foo {
printName() {
this.print('hahah');
}
print(text) {
console.log(text);
}
}
const foo = new Foo();
// 将printName单独提出来
const printName = foo.printName;
// 此时printName中的this指向运行时所在的环境(这里是Window),找不到print方法
printName(); // TypeError: Cannot read property 'print' of undefined
解决方法:
- 在类的构造方法constructor中绑定this,当new一个实例的时候,会执行该方法
class Foo {
constructor() {
// bind方法会创建一个新的绑定函数,绑定函数的执行上下文为this
this.printName = this.printName.bind(this)
}
printName() {
this.print('hahah');
}
print(text) {
console.log(text);
}
}
const foo = new Foo();
const printName = foo.printName;
printName(); // hahah
- 使用箭头函数
class Foo {
constructor() {
// 在实例对象上新增了一个方法printName
this.printName = () => {
this.print('lalal')
}
}
printName() {
this.print('hahah');
}
print(text) {
console.log(text);
}
}
const foo = new Foo();
const printName = foo.printName;
// 调用的是实例的printName,而不是原型上的printName
printName(); // lalal
四、静态方法和静态属性
4.1 静态方法
-
定义:静态方法指的是Class本身的方法,而不是prototype属性上的方法;
-
语法: 在类的方法前加上static关键字;
-
特点:
- 类相当于实例的原型,默认情况下类中的所有方法都会被实例继承,但是静态方法不会被实例继承,而是通过类直接调用;
class Foo { // 静态方法 static sayHi() { console.log('Hi') } } const foo = new Foo(); // 实例调用静态方法会报错 // foo.sayHi(); // TypeError: foo.sayHi is not a function Foo.sayHi(); // Hi
- 父类的静态方法可以被子类的继承;
class Bar extends Foo {} Bar.sayHi(); // Hi
- 静态方法可以通过super调用,因为当super作为对象使用时,在静态方法中指向父类。
class Bar extends Foo { static sayHello() { super.sayHi() } } Bar.sayHello(); // Hi
4.2 静态属性
-
定义:静态属性值类本身的属性,而不是定义在实例对象上(this)的属性
-
语法:
- 目前只能使用Class.propname的写法,因为ES6规定,Class内部只有静态方法,没有静态属性
// 添加静态属性prop Foo.prop = 1; const foo = new Foo(); // 通过类访问 console.log(Foo.prop); // 1 // 不能通过实例访问 console.log(foo.prop); // undefined
- 提案:目前有一个提案,规定可以在Class内部使用static关键字+等式的方法为类添加静态属性
class Foo { // 静态属性 static prop = 1; // 提案也规定了Class的实例属性可以用等式写入类的定义之中 name = 'Lily'; constructor(){ console.log(Foo.prop, this.name); // 1 Lily } }
五、new.target属性
ES6为new运算符添加了new.target属性,(在构造函数中)返回new命令所左右的构造函数。如果构造函数不是通过new命令调用的,那么new.target会返回undefined,因此可以使用该属性确定构造函数是如何调用的。
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用new生成实例')
}
}
const person1 = new Person('Lily');
const person2 = Person('Lily'); // Error: 必须使用new生成实例
class Foo {
constructor() {
console.log(new.target === Foo);
}
}
new Foo(); // true