之前只使用ES5的特性来模拟类似有类(Class)的行为。ES6引入了class关键字具有正式定义类的能力。ES6表面上看起来可以支持正式的面向对象编程,但实际上使用的仍是原型和构造函数的概念。
类的定义
定义类的方式
定义类有两种主要方式:类声明和类表达式。
// 类声明
class Person {
}
// 类表达式
var Animal = class {}
- 类的定义不能提升(也可以这样理解:类提升了,但是在定义之前存放在暂存性死区中,不能使用)
- 函数 VS 类
函数 | 类 | |
---|---|---|
定义方式 | 两种 1、函数声明 2、函数表达式 |
两种 1、类声明 2、类表达式 |
表达式定义 | 提升,在赋值之前不能使用 | 提升,在赋值之前不能使用 |
声明定义 | 提升,在任何地方都可以使用 | 不提升,在声明之后可以使用 |
声明定义的作用域 | 函数声明不受作用域限制 | 类声明受块作用域限制 |
- 类表达式中的名称是可选的。这个名称只能在类表达式作用域中使用。
// 空类
class Foo {}
// 有构造函数
class Bar {
constructor() {
}
}
// 有设置函数或获取函数
class Baz {
get a() {
return "a"
}
}
// 有静态方法
class Qux {
static myQux() {
}
}
类的构成
类可以包含构造函数方法、原型方法、实例方法、获取函数、设置函数和静态方法。但是不必要的。空的类定义同样有效。默认情况下,类定义中的代码都是在严格模式下进行的。
类构造函数
constructor
关键字用于在类定义块内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类的实例时,应该调用此方法。
实例化
使用new
操作符实例化类时,相当于使用new调用其的构造函数。构造流程图如下
// 默认返回
class Person {
constructor(name) {
this.name = name;
}
}
// 设置返回
class Animal {
constructor(type) {
this.type = type
return {}
}
}
let person = new Person("张三");
let monkey = new Animal("猴子");
console.log(person instanceof Person) // true
console.log(monkey instanceof Animal) // false
console.dir(person)
console.dir(monkey)
注意事项
-
constructor
构造函数不是必需的。不定义构造函数时相当于将构造函数定义为空函数。 - 类构造函数与构造函数的区别:调用类的构造必须使用new操作符,而普通构造函数可以使用new,也可以不使用new。
- 类可以认为是特殊的构造函数。typeof操作符检测一个类时,返回的结果为
function
。同时类中有[[Prototype]]
属性,此属性中的constructor
属性指向自身。与构造函数相同。
类 | 构造函数 | |
---|---|---|
使用方式 | 必需使用new操作符 | 可以使用new操作符,也可以直接调用 |
instanceof | 效果相同 | 效果相同 |
typeof | 效果相同(function) | 效果相同(function) |
实例成员
每次通过new调用类标识时,都会执行类的构造函数。在构造函数的内部,可以为新创建的实例(this)添加自有属性。
class Person {
constructor(name) {
this.name = name;
this.friends = ["1", "2", "3"];
this.sayName = () => {
console.log(this.name)
}
}
}
let person1 = new Person("张三");
person1.friends.push("4")
let person2 = new Person("李四");
console.log(person1.friends) // ["1", "2", "3", "4"]
console.log(person2.friends) // ["1", "2", "3"]
console.log(person1.sayName == person2.sayName) // false
注意事项
- 每个实例都有一个唯一的成员对象,所有的成员不会在原型上共享。
- 实例成员定义在类中。
原型方法
类定义语法把在类块中定义的方法叫做原型方法。
let sym = Symbol("now")
class Person {
constructor(name) {
this.name = name;
this.friends = ["1", "2", "3"];
}
sayName() {
console.log(this.name)
}
[sym]() {
console.log("计算属性定义的方法")
}
}
let person1 = new Person("张三");
person1.friends.push("4")
let person2 = new Person("李四");
person1[sym]() // 计算属性定义的方法
console.log(person1.friends) // ["1", "2", "3", "4"]
console.log(person2.friends) // ["1", "2", "3"]
console.log(person1.sayName == person2.sayName) // true
注意事项
1、在类块中定义的普通方法会定义在类的原型中
2、定义类方法的时候,可以使用字符串、符号或计算的值作为键,即可以使用计算属性。
静态方法
在类上定义静态方法,这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。
// 默认返回
class Person {
constructor(name) {
this.name = name;
this.friends = ["1", "2", "3"];
this.sayName = () => {
console.log(this.name)
}
}
show() {
console.log(this)
}
static getPrototype() {
return this;
}
static getNow() {
console.log("getNow")
}
}
let person1 = new Person("张三");
console.dir(person1)
注意事项
1、定义的静态方法会定义在类原型中的constructor
对象中。
2、类中定义的静态方法无法使用实例进行访问。
访问器:获取函数、设置函数
class Person {
constructor(name) {
this.name_ = name;
this.friends = ["1", "2", "3"];
this.sayName = () => {
console.log(this.name)
}
}
show() {
console.log(this)
}
get name() {
return this.name_
}
set name(newName) {
this.name_ = newName;
}
}
let person1 = new Person("张三");
console.dir(person1.name) // 张三
person1.name = "李四"
console.dir(person1.name) // 李四
非函数原型和类成员
类定义并不显式支持在原型或类上添加成员数据,但在类定义的外部,可以手动添加:
class Person {
constructor(name) {
this.name = name;
this.friends = ["1", "2", "3"];
}
type = "type" // 自动到实例中
// 定义在原型中
getNow() {
}
// 定义在原型的constructor中
static getTHIS() {
console.log(this)
}
// 定义在原型的constructor中
static type2 = "type2"
}
Person.type3 = "type3" // 添加到原型的constructor中
Person.prototype.type4 = "type4" // 添加到原型中
let person1 = new Person("张三");
注意事项
1、类中定义的静态方法或属性只能通过类名.
进行访问,无法使用实例进行访问。
2、定义类时,不能在类的原型上直接定义属性,但是可以在定义好类之后操作原型。
迭代器和生成器
class Person {
constructor() {
this.name = "abc";
this.nickname = ["a", "b", "c"]
}
* getIterator() {
yield 1;
yield 2;
yield 3;
}
// 定义迭代器,返回一个迭代器; 返回可迭代对象时,需要使用生成器函数
[Symbol.iterator]() {
return this.nickname.entries();
}
}
let person1 = new Person("张三");
for (let value of person1) {
console.log(value)
}
console.log("-----------")
let iterator = person1.getIterator();
console.log(iterator.next().value) // 1
console.log(iterator.next().value) // 2
console.log(iterator.next().value) // 3
console.log(iterator.next().value) // undefined
注意事项
1、定义迭代器时,定义普通函数,返回一个迭代器;或 定义生成器函数,返回一个可迭代对象。
类的继承
类的继承是ES6新语法,但是底层使用的仍然是原型链。
入门
- ES6支持单继承。使用关键字
extends
。继承内容内容是任何拥有[[Constructor]]和原型的对象。即可以继承类和一个普通的构造函数。 - 派生类通过原型链访问类和原型上定义的方法,同时this表示当前的实例。
class Vehicle {
identProto(id) {
console.log(id, this)
}
static identClass(id) {
console.log(id, this)
}
}
class Bus extends Vehicle {
}
let bus = new Bus();
console.log(bus instanceof Vehicle) // true
console.log(bus instanceof Bus) // true
bus.identProto("bus") // bus Class Bus
Bus.identClass("Bus") // Bus Class Bus
Vehicle.identClass("Vehicle") // Vehicle Class Vehicle
function Person() {
}
class Man extends Person {
}
let man = new Man();
console.log(man instanceof Person) // true
console.log(man instanceof Man) // true
super、HomeObject、构造函数
super
派生类的方法通过super关键字引用他们的原型。仅局限于派生类的构造函数、实例方法和静态方法。
子类有构造函数
class Vehicle {
constructor(type = "car", product_time, width, height) {
this.type = type;
this.product_time = product_time;
this.width = width;
this.height = height;
}
identProto(id) {
console.log(id, this)
}
static identClass(id) {
console.log(id, this)
}
}
// Bus 显示定义构造函数
class Bus extends Vehicle {
constructor(...arg) {
super("bus", ...arg);
this.param = arg;
}
static func() {
console.log("静态方法")
}
}
// Car 未定义构造函数
class Car extends Vehicle {
}
let bus = new Bus(1999, 6, 20);
let car = new Car(2000, 3, 6);
console.dir(bus)
console.dir(car)
[[HomeObject]]
ES6给类的构造函数和静态方法添加了内部特性[[HomeObject]],这个特性是一个指针,指向定义该方法的对象。此指针是自动赋值的,而且只能在JavaScript引擎内部使用。
注意事项
1、super只能在派生类构造函数和静态方法中使用
2、不能单独使用super关键字,要么使用它调用构造函数,要么使用它引用静态方法。
3、调用super()会调用父类的构造函数,并将返回的实例赋值给this。同时也可通过super()进行参数的传递。
4、派生类没有定义构造函数时,实例化的时候会默认的构造函数,只调用父类的构造函数;派生类定义构造函数时,要么调用super()之后在进行其他操作,要么直接return一个对象。
继承内置类型
js支持继承内置类型,这样新创建的类就继承了内置对象的相关方法。
class SubArray extends Array {
static arr() {
}
}
let sub = new SubArray(...[1,2,3,4])
console.dir(sub)
注意事项
1、使用派生类类型调用父类的方法同时此方法有返回对象,返回的对象也属于派生类。
抽象基类
抽象基类,可供其他类继承,但本身不会被实例化。(类似java中的接口)。
- 原理:
new.target
保存了通过new
关键字调用的类或函数。可以通过这个字段进行判断是否是抽象基类。
class Vehicle {
constructor(type = "car",product_time, width, height) {
if(new.target == Vehicle) {
throw new Error("Vehicle 不能实例化")
}
this.type = type;
this.width = width;
this.height = height;
}
}
class Bus extends Vehicle {
constructor(...param) {
super("bus", ...param);
this.param = param
}
}
let bus = new Bus(1999, 6, 20);
console.dir(bus)
let vehicle = new Vehicle(200, 3, 6);
多继承
虽然ES6不支持多继承,但是可以通过现有的特性可以轻松地模拟此行为。
原理:
extends关键字支持在类表达中使用,通过箭头函数实现类的继承。
class Vehicle {
vehicle() {
console.log("vehicle")
}
}
let FooMixin = (Superclass) => class extends Superclass {
foo() {
console.log("foo")
}
}
let BarMixin = (Superclass) => class extends Superclass {
bar() {
console.log("bar")
}
}
let BazMixin = (Superclass) => class extends Superclass {
baz() {
console.log("baz")
}
}
class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {
}
let bus = new Bus();
bus.vehicle(); // vehicle
bus.foo(); // foo
bus.bar(); // bar
bus.baz(); // baz
注意事项
1、Object.assign()
是混合对象,将对象的属性进行混合;而不是方法。