在学习 react 之前。
今天补一下 ES6 的一些语法。学习材料为红宝书。
1.类
1.1 类定义
写法很像其他语言。
class Person {};
和函数不同:
- 类的声明不会被提升。
- 类的作用域为块级作用域,和 let,const 一样。
- 类中的代码默认都是 strcit mode
类可以赋值给变量,赋值后的变量可以通过 name 属性访问类的初始定义名字,且复制后类的初始名字就无法在类定义之外的区域访问了:
let RenamedPerson = class Person {
identify() {
console.log(RenamedPerson.name); // 输出 Person
console.log(Person.name); // 也是输出 Person
// 可以访问原 Person 标识符
// 并且这个 Person 也有name属性
}
};
console.log(RenamedPerson.name); // 输出 Person
console.log(Person); // reference 错误
1.2 类构造函数
类也有构造函数。
不过,类有为此专门的关键字 constructor 来定义构造函数。
当实例化这个类的时候,类便会调用构造函数。相当于 python 里的 __init__
class Person {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
也可以不含 constructor,这样相当于构造函数为空。
1.2.1 实例化
当使用 new 来实例化类的时候, js 解释器能够感知并调用构造函数来进行实例化。
当执行 let person = new Person(); 的时候,其背后是以下行为。
- 内存中创建一个对象(因为此时还没有赋值,现在用 xxx 表示这个对象)。
- 把这个对象的
[[prototype]]指针指向构造函数的 prototype。(从浏览器上来看就是xxx.__proto__ = Person.prototype) - 准备执行构造函数,为了执行构造函数,首先需要把构造函数里的 this 指向这个新对象。
- 执行构造函数,给这个对象添加属性(
xxx.name = name这样,因为 this 指的就是 xxx)。 - 构造完毕,如果构造函数没有返回值,则返回这个对象(一般情况)。如果构造函数有返回一个对象,则返回这个对象,并且这个对象和类没有任何关系(除非这个对象本身就和类有关系)。
- 把新对象赋值给 person(
let person = xxx)。
1.2.2 和普通构造函数的区别
- 使用类时,如果不使用 new,就会报错。
- 普通构造函数不用 new,就会以 window 作为 this 实例化,很坑。
- 实例化之后,可以访问 constructor,但是这个 constructor 是类不是类的构造函数
console.log(person.constructor); // 输出 Person
console.log(Person.prototype.constructor); // 还是输出 Person
- 不过类构造出来的实例属于类的实例,而不是构造函数的实例,这一点要记住。
person instanceof Person; // true
person instanceof Person.constructor; // false
1.3 类仍然是函数
- Js 中并没有类这个类型,虽然类的定义的语法和函数极其不同,但是一旦声明了之后,类就完全是函数了。使用 typeof 也是输出为函数。
- 类可以被当做参数传递
1.4 原型成员
为了在实例间共享方法,使用原型。能够共享同一种方法,减少每次构造的时间。
原型方法定义很简单,在类快中所有定义的内容都在原型上:
class Person {
constructor() {
// 定义在构造器中,所有实例中都有,不共享。
this.myFunc = function() {let a = 1;};
}
// 定义在类块中,实例中没有,要去原型上找
myFunc() {
let a = 1;
}
}
不能在类块中添加属性,会报错。
原型方法里的 this 指的是这个实例
- 类定义也支持get,set访问器
1.5 静态类方法
有些方法是定义在类上的,无需实例化即可使用。实例化对象和原型都无法调用这种方法,只有类本身能调用。
class Person {
...
static addName(x, y) {
console.log(x + y);
}
}
Person.addName("a", "b"); // 输出 "ab"
person1.addName; // 输出 undefined
静态方法里的 this 指的是这个类。(其他两个里 this 都是指实例对象)
静态类方法可以作为实例工厂,在里面实例化该类。
class Person {
...
static createBadGuy() {
return new Person("张三");
}
}
1.6 继承
类的继承使用了新的语法,但是背后依然是原型链。
1.6.1 单继承
使用 extends 关键字,就可以继承类,甚至还可以继承普通的构造函数(为了兼容)。
class Person {};
class Teacher extends Person {}
// 也可以继承普通构造函数
function Person() {}
class Teacher extends Person {}
静态方法和原型方法也会被继承。
1.6.2 派生类
在派生类块中使用 super 引用父类构造函数。注意 super 用法非常限定:
class Person {/*假设这里有一堆方法*/}
class Teacher extends Person {
constructor(x, y, z) {
// 必须先用 super(),才能引用 this,不然会报错
super(x, y); // 相当于普通构造函数继承时使用的 Person.call(this, x, y)
this.z = z; // 派生类的专有属性,放在 super 语句前面会报错
console.log(this); // 输出一个Teacher对象,如果不指定返回值,就会返回该对象
}
myPrototpeFunc() {
super.personFunc(); // super 的另一个用法,可以调用父类的原型方法。
super.personStatic(); // 报错,只有在静态方法中才能调用父类的静态方法。
Person.personStatic(); // 非要用的话,只能这么用。
}
static myStatic() {
super.personStatic(); // 只有在静态方法中才能调用父类的静态方法。
}
}
- 构造函数里必须先使用 super(),才能用 this。
- 只有在静态方法里,使用 super.staticFunc(),才能调用父类的静态方法(也只能调用静态方法)。如果要在别的地方用,就得直接调用父类 Person.staticFunc()。
- 在构造函数和原型方法里使用 super.xxxFunc(),只能调用父类的原型方法。
- 2 和 3 里提到的 super 都可以用 this 代替。
- 原型方法和静态方法都会被自动继承。
- 构造函数可以不声明,这样子会自动声明 super()。一旦声明了构造函数,就必须加 super()。
1.7 抽象基类
抽象基类为一个类,可以被继承,但是不能被实例化。 js 没有对应语法,但是通过 new.target 可以完成。new.target 表示 new 的对象。
class Person {
constructor() {
if (new.target === Person){
throw new Error("Person 是抽象基类,不能被实例化");
}
}
}
1.8 类混入(多类继承)
js 不显示支持类混入,常用方法是:
有 a,b,c 三个类,d 想要继承这三个类,那就得先让 b 继承 c ,然后 a 继承 b ,最后 d 继承 a。
- 然而很多框架抛弃这种方式,转而使用复合模式。