ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。class(类) 其实是对ES5 的构造函数 , 继承等进行了封装 , 语法糖等 , 使的构造函数创建,继承等像使用类一样。
类定义
与函数类型相似,定义类也有两种主要方式:类声明和类表达式。这两种方式都使用 class 关键字加大括号。
// 类声明
class Person {} // 不需要小括号
// 类表达式
const Animal = class {};
类表达式
类表达式可以只有变量名, 也可以有类名和变量名
const person = class perClass{}
以这种方法创建的类 , 可以在类里面使用这个类名 , 不能在外部使用一个类名
console.log(perClass) // ReferenceError: perClass is not defined
而如果要获取这个类名
console.log(person.name) // perClass (字符串)
注意 : 类的定义不会被提升
类受块作用域限制 , 即如果是在 IF里面创建的类 , 也是不能被外部引用的
if (true) {
class person {}
}
console.log(person); // person is not defined
类的构成
类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。
类构造函数
constructor 关键字用于在类定义块内部创建类的构造函数。**方法名 constructor 会告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数。**构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。
实例化
使用 new 操作符实例化 Person 的操作等于使用 new 调用其构造函数。并且会将实例化对象以 this 传入 。
class Class1 {
constructor(name) {
console.log(this); // Class1 {} , 类的实例化对象
this.name = name
}
}
const c1 = new Class1('c1')
console.log(c1); // Class1 { name: 'c1' }
console.log(c1 instanceof Class1); // ture
默认情况下,类构造函数会在执行之后返回 this 对象。,如果没有其他地方再使用到这个 this 对象 , 那么它会被销毁。
不过,如果返回的不是 this 对象,而是其他对象,那么这个对象不会通过 instanceof 操作符检测出跟类有关联,因为这个对象的原型指针并没有被修改。
class Class2 {
constructor() {
return { // 返回非 this 的 非空对象
name: 'c2'
}
}
}
const c2 = new Class2 // 无参数时 可以没有括号
console.log(c2.name); // c2
console.log(c2 instanceof Class2); // flase
类构造函数没有什么特殊之处,实例化之后,它会成为普通的实例方法(在实例里)(但作为类构造函数,仍然
要使用 new 调用)。因此,实例化之后可以在实例上引用它.
class Class3 {
constructor(name) {
this.name = name
}
}
const c3 = new Class3('c3')
console.log(c3.constructor); // [Function: Class3] 一个构造函数
const c3c = new c3.constructor('c3c')
console.log(c3c); // Class3 { name: 'c3c' }
console.log(c3c instanceof c3.constructor); //true , 因为 c3c 是 new 的是 constructor 构造函数
console.log(c3c instanceof Class3); //true
把类当成特殊函数
ECMAScript 中没有正式的类这个类型。从各方面来看,ECMAScript 类就是一种特殊函数。
使用说是函数就有 prototype 属性指向原型对象 , 而实例是类 new 出来的 , 使用 实例也有一个[[prototype]] 指向这个原型对象。
并且原型对象也有一个 constructor 指向类函数。
因为类里的 constructor 是间接实例化对象的 , 相当于辅助的 。 new 的是类 。 所以实例化对象不是属于构造函数constructor 的实例化对象 , 也就是
console.log(c3 instanceof Class3.constructor); // false
但是 , 如果把类当做一个普通函数 , 去实例函数内部的 constructor() , 那么就不一样了
const c3cc = new Class3.constructor
console.log(c3cc instanceof Class3.constructor); // true
类可以做参数 , 也可以在创建时同时实例化对象
实例、原型和类成员
类的语法可以非常方便地定义应该存在于实例上的成员、应该存在于原型上的成员,以及应该存在于类本身的成员。
实例成员
通过new调用类标识符时,都会执行类构造函数。然后会执行 constructor 构造函数 , 将新的实例对象(this) 传入 , 在 constructor 里创建实例的独有的属性和方法(即不同实例这些属性和方法是不同的)
原型方法与访问器
为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。
其中 constructor 中创建的属性和方法保存在不同实例里 , 地址不同 。
而类内 , constructor外创建方法 , 会被保存到类的原型对象上 , 用于各实例共享
可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据(ES6是不允许的)
class Class4 {
constructor(name) {
this.name = name
};
fun4() {
console.log('fun4');
};
age = '1'; // 在ES7 ,这个会被当做实例的属性去实例化给新对象
}
const c4 = new Class4('c4')
c4.fun4() // fun4
console.log(Class4.prototype.fun4); // [Function: fun4]
console.log(Class4.prototype.age); // undefined 不在原型上 , 但是在实例上是有的
类方法也支持设置和获取访问器 , 与普通对象使用方法一样
class Person {
set name(newName) {
this.name_ = newName;
}
get name() {
return this.name_;
}
}
let p = new Person();
p.name = 'Jake';
console.log(p.name); // Jake
静态类方法
可以在类上定义静态方法。这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。与原型成员类似;
ES6规定,静态成员每个类上只能有一个,并且必须是方法。
ES7 允许它有静态属性
静态类成员在类定义中使用 static 关键字作为前缀。在静态成员中,this 引用类自身。其他所有约定跟原型成员一样
// 定义在类本身上
static locate() {
console.log('class', this);
}
Person.locate(); // class, class Person {}
// 在原型个实例上是没有 locate() 的
非函数原型和类成员
虽然说不支持在创建类时给类块或原型添加引用值和对象 。 但是并不是不能 。 最好是在外部添加
Class4.afe ='age; // 可以给类添加
Class5.prototype.h = 'h' // 可以给原型添加
**注意的是 , 在类外给类添加的属性和方法是属于静态属性的方法的 , 在实例和原型上是没有的 。 **
**在ES6时 ,是只允许从外部添加静态方法(一个 , 且是方法) ES7 才允许在内部写静态方法和属性(不止一个) **
迭代器与生成器方法
类定义语法支持在原型和类本身上定义生成器方法
class Class5 { *
gen1() { // 定义在原型上
yield 1
yield 2
yield 3
}
static * gen2() {
yield 'a'
yield 'b'
yield 'c'
}
}
const c5 = new Class5
for (const i of c5.gen1()) {
console.log(i); // 1 2 3
}
for (const i of Class5.gen2()) {
console.log(i); // a b c
}
因为类支持生成器,那么可以给实例对象生成一个默认迭代器方法 , 让它可以迭代 , 并且迭代的内容是可以自己设置的;
class Class6 {
constructor() {
this.k1 = 'v1'
this.k2 = 'v2'
this.k3 = 'v3',
this.arr = [1, 2, 3]
}
* [Symbol.iterator]() { // 第一个 * 表示这是一个生成器 ,可以使用 yield
yield* Object.entries(this)
// 第二个 * 和 yield 一起使用 , 后面跟迭代器
}
// 这里也可以只返回迭代器 , return Object.entries(this) , 因为 for of 可以处理迭代器或有默认迭代器的对象的
}
const c6 = new Class6()
for (const i of c6) {
console.log(i);
}
// [ 'k1', 'v1' ]
// [ 'k2', 'v2' ]
// [ 'k3', 'v3' ]
// [ 'arr', [ 1, 2, 3 ] ]
// 相当于
obj = {
k1: 'v1',
k2: 'v2',
k3: 'v3'
}
for (const i of Object.entries(obj)) {
console.log(i);
}
// [ 'k1', 'v1' ]
// [ 'k2', 'v2' ]
// [ 'k3', 'v3' ]