class 关键字是 ES6 新增的。类(class)是ECMAScript 中新的基础性语法糖,本质上还是一个函数,但实际上它使用的仍然是原型和构造函数的概念。并且类受块级作用域限制。
class Person { }
console.log(Person);// class Person { }
类定义中的代码默认都在严格模式下执行。类包含如下可选内容:
空的类定义照样有效。
class Person {}
const Animal = class {};
在使用 new 操作符创建类的新实例时,会调用 constructor 方法,主要作用是初始化数据。不是必需的,不定义构造函数相当于将构造函数定义为空函数。
使用 new 调用类的构造函数会执行如下操作。
class Animal { }
class Person {constructor(name) {this.name = name;}
}
let animal = new Animal();
let person = new Person('孤城浪人');
console.log(animal); //Animal {}
console.log(person); //Person {name: '孤城浪人'}
默认情况下,constructor 会在执行之后返回当前实例对象。如果 constructor 显示的 return 了一个对象,那么这个对象不会通过 instanceof 操作符检测出跟类有关联,因为这个对象的原型指针并没有被修改。
class Person {constructor(name) {this.foo = name;if (name) {return {name: 'XXXXX'};}}
}
let p1 = new Person(),p2 = new Person('孤城浪人');
console.log(p1); // Person {foo: undefined}
console.log(p1 instanceof Person); // true
console.log(p2); // {name: 'XXXXX'}
console.log(p2 instanceof Person); // false
我们可以调用实例的 constructor 方法,因为他就是普通函数,只是必须使用 new 关键字调用罢了。
class Person {constructor(name) {this.foo = name;}
}
let p1 = new Person('孤城浪人'),p2 = new p1.constructor('XXXXX');
console.log(p1); // Person {foo: undefined}
console.log(p2); // Person {name: 'XXXXX'}
类可以立即调用
const person = new class Person {constructor(name) {this.name = name;}
}('孤城浪人')
console.log(person);// Person {name: '孤城浪人'}
每次通过 new 调用类标识符时,都会执行类构造函数,这也就意味着在 constructor 中定义的成员每个实例都是一份新的,不会共享。但是不是在 constructor 中定义的成员(不能是原始值或对象)是定义在类的原型上,所有类的实例共享。
const person = class Person {constructor(name) {// 不共享this.name = name;}// 定义在类原型上共享sayName() {console.log(this.name);}
}
类定义也支持获取和设置访问器,方法名支持字符串、符号或计算的值。
class Person {set name(newName) {this.name_ = newName;}get name() {return this.name_;}['sa' + 'y']() {console.log('xxxx');}
}
new Person().say();
类可以定义静态方法。它的调用不依赖于实例,静态方法的方法名不能重复。
class Person {static sayHellow() {console.log('Hellow');}static say() {console.log('xxxx');}
}
Person.say();//Hellow
Person.sayHellow();//xxxx
类支持在原型和类本身定义生成器方法,因此可以添加一个默认的迭代器,把类实例变成可迭代对象。
class Person {constructor() {this.names = ['孤城浪人', 'xxxx']}// [Symbol.iterator]() {// return this.names.entries();// }*[Symbol.iterator]() {yield* this.names.entries();}
}
const person = new Person();
for (let item of person) {console.log(item);
}
// [0, '孤城浪人']
// [1, 'xxxx']
这是类最优秀的特性,原生支持继承,但是本质上还是利用了原型链。使用 extends 关键字,就可以继承任何拥有[[Construct]]和原型的对象,前面说过类就是一个语法糖,他本质上是一个函数,所以 extends 不仅可以继承一个类,也可以继承普通的构造函数。并且 this 永远指向当前实例。
function Person() { };
class Father extends Person {say() {console.log(this);}
}
class Son extends Father { }
const son = new Son();
console.log(son instanceof Father);//true
console.log(son instanceof Person);//true
son.say();//Son{}
这个例子可以看到 Father 继承 Person,Son 继承 Father,所以最后 Son 的实例的原型链中有 Father
和 Person 的原对象,因此 instanceof 返回 true。
如果子类需要调用父类的构造函数 constructor 初始化数据,那么只能在子类的构造函数中使用 super 关键字,此外在实例方法和静态方法内部也可以使用 super 关键字。(ES6 给类构造函数和静态方法添加了内部特性 [[HomeObject]] 指向定义该方法的对象。 super 始终会定义为 [[HomeObject]] 的原型)
注意:子类若定义了 constructor 则一定要调用 super,并且不要在调用 super 之前引用 this,否则会抛出 ReferenceError,
class Father {constructor(name) {this.name = name;}say() {console.log(this);}
}
class Son extends Father {constructor() {super('孤城浪人');}sayName() {console.log('sayName')super.say();}
}
const son = new Son();
son.sayName();
// sayName
// Son { name: '孤城浪人' }
如果子类没有构造函数,在实例化子类时会自动调用 super(),而且会把所有传给子类的参数作为 super 的参数传入。
class Father {constructor(name) {this.name = name;}say() {console.log(this);}
}
class Son extends Father {}
const son = new Son('孤城浪人');
son.say();
//Son { name: '孤城浪人' }
抽象基类就是可供其他类继承,但本身不会被实例化的类。可以在构造函数中使用 new.target 属性阻止抽象基类实例化,也可以在其中强制子类要定义某个方法。
class Father {constructor(name) {if (new.target == Father) {throw new Error('不能实例化');}if (!this.say) {throw new Error('缺少say方法');}}
}
class Son extends Father {constructor(name) {super();this.name = name;}say() {console.log(this.name);}
}
const son = new Son('孤城浪人');
son.say();//孤城浪人
new Father()
我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。