第九章 JavaScript中的类
第1节 ES5中的近类结构
ES5及早期版本中没有类的概念,因此用了一个相近的思路来创建一个自定义类型:首先创建一个构造函数,然后定义另一个方法并赋值给构造函数的原型。例如:
1 function PersonType(name)
2 {
3 this.name = name;
4 }
5
6 PersonType.prototype.sayName = function() {
7 console.log(this.name);
8 }
9
10 var person = new PersonType("zxx");
11 person.sayName(); // "zxx"
12
13 console.log(person instanceof PersonType); //true
14 console.log(person instanceof Object); //true
第2节 类的声明
1. 在ES6中,要声明一个类,先写 class 关键字,然后是类的关键字。例如:
1 class PersonClass {
2 //等价于PersonType构造函数
3 constructor(name) {
4 this.name = name;
5 }
6
7 //等价于PersonType.prototype.sayName
8 sayName() {
9 console.log(this.name);
10 }
11 }
12
13 let person = new PersonClass("zxx");
14 person.sayName(); //"zxx"
15
16 console.log(person instanceof PersonClass); //true
17 console.log(person instanceof Object); //true
18 console.log( typeof PersonClass); //"function"
19 console.log( typeof PersonClass.prototype.sayName); //"function"
2. 自有属性
自有属性是实例中的属性,不会出现在原型上,只能在类的构造函数或方法中创建,此例中的 name 就是一个自有属性。
3. 类与自定义类型的差异:
1)函数声明可以被提升,而类声明与 let 类似,不能被提升;
2)类声明中的所有代码将运行在严格模式下;
3)在类中,所有方法都是不可枚举的;
4)每个类中,都有一个名为 [[Construct]] 的内部方法,通过关键字new调用那些不含 [[Construct]] 的方法回导致程序抛出错误;
5)使用除 new 以外的方式调用类的构造函数会导致程序抛出错误;
6)在类中,修改类名会导致程序报错。
第3节 类表达式
类和函数都有两种存在形式:声明形式和表达形式。声明形式的函数和类都由相应关键字(分别为function,class)进行定义,随后紧跟一个标识符。表达形式的函数和类与之类似,只是不需要再关键字后加标识符。举例:
1 let PersonClass = class {
2 //等价于PersonType构造函数
3 constructor(name) {
4 this.name = name;
5 }
6
7 //等价于PersonType.prototype.sayName
8 sayName() {
9 console.log(this.name);
10 }
11 }
12
13 let person = new PersonClass("zxx");
14 person.sayName(); //"zxx"
15
16 console.log(person instanceof PersonClass); //true
17 console.log(person instanceof Object); //true
18 console.log( typeof PersonClass); //"function"
19 console.log( typeof PersonClass.prototype.sayName); //"function"
第4节 作为一等公民的类
1. 什么是一等公民?
一等公民是指一个可以传入函数,可以从函数返回,并且可以赋值给变量的值。
2. JavaScript中函数、类都是一等公民。
第5节 访问器属性
尽管应该在类构造函数中创建自己的属性,但类也支持直接在原型上定义访问器属性。创建 getter 时,需要在关键字 get 后紧跟一个空格和相应的标识符;创建 setter 时,只需要把 getter 关键字 get 替换为set即可。
第6节 可计算成员名称
1. 类方法和访问器属性也支持使用可计算名称。用方括号包裹一个表达式,即可使用可计算名称。例如:
1 let methodName = "sayName";
2
3 class PersonClass {
4 constructor(name) {
5 this.name = name;
6 }
7
8 [methodName]() {
9 console.log(this.name);
10 }
11 };
12
13 let me = new PersonClass("zxx");
14 me.sayName(); // "zxx"
2. 同样地,在访问器属性中也可以使用可计算名称。例如:
1 let propertyName = "html";
2
3 class CustomHTMLElement {
4 constructor(element) {
5 this.element = element;
6 }
7
8 get [propertyName]() {
9 return this.element.innerHTML;
10 }
11
12 set [propertyName]() {
13 this.element.innerHTML = value;
14 }
15 }
第7节 生成器方法
在类中,也可以通过 “*” 来定义生成器。如果类是用来表示值的集合的,那么为它定义一个默认迭代器会更有用。
第8节 静态成员
ES5、ES6中都有静态成员的语法。下面分别讲一下它们:
1. ES5中,通过直接将方法添加到构造函数中类模拟静态成员。例如:
1 function PersonType(name) {
2 this.name = name;
3 }
4
5 //静态方法
6 PersonType.create = function(name) {
7 return new PersonType(name);
8 };
9
10 //实例方法
11 PersonType.prototype.sayName = function() {
12 console.log(this.name);
13 };
14
15 var person = PersonType.create("zxx");
2. 在ES6中,简化了创建静态成员的语法。在方法或者访问器属性名前使用正式的静态注释(static)即可。例如:
1 class PersonClass {
2 //等价于PersonType的构造函数
3 constructor(name) {
4 this.name = name;
5 }
6
7 //等价于PerosonType.prototype.sayName
8 sayName() {
9 console.log(this.name);
10 }
11
12 //等价于PersonType.create()
13 static create(name) {
14 return new PersonClass(name);
15 }
16 }
17
18 let person = PersonClass.create("zxx");
3. 注意:
1)类中的所有方法和访问器属性都可以用static关键字类定义,唯一的限制是不能将static用于定义构造函数方法。
2)不可在实例中访问静态成员,必须要直接在类中访问静态成员。
第9节 继承与派生类
1. 基本概念介绍
1)使用 extends 关键字可以指定类继承的函数。
2)通过 super() 方法即可访问基类的构造函数。
3)继承自其它类的类被称作派生类。
4)如果不使用构造函数,则当创建新的类实例时会自动调用 super() 并传入所有参数。
5)使用super()需要注意:
a. 只可以在派生类的构造函数中使用super()
b. 在构造函数中,访问 this 之前一定要调用 super(),它负责初始化this
c. 如果不想调用 super(),唯一的方法是让类的构造函数返回一个对象。
2. 类方法遮蔽
派生类中的方法总会覆盖基类中的同名方法。如果仍然想使用基类中的方法,则可以借助 super 关键字来实现。
3. 静态成员继承
派生类继承基类后,基类中的静态成员也可以直接在派生类中使用。
4. 派生自表达式的类
1)只要表达式可以被解析为一个函数,并且具有[[Construct]]属性和原型,那么就可以用extends进行派生。
2)extends强大的功能使得类可以继承自任意类型的表达式,而且可以是不止一个的表达式,这样可以动态地确定类的继承目标,创建不同的继承方法。举例:
1 function Rectangle(length, width)
2 {
3 this.length = length;
4 this.width = width;
5 }
6
7 Rectangle.prototype.getArea = function() {
8 return this.length * this.width;
9 }
10
11 function getBase()
12 {
13 return Rectangle;
14 }
15
16 class Square extends getBase() {
17 constructor(length) {
18 super(length * length);
19 }
20 }
21
22 var x = new Square(3);
23
24 console.log(x.getArea()); //9
25 console.log(x instanceof Rectangle); //true
5. 内建对象的继承。
在ES5中,无法通过继承的方式创建属于自己的特殊数组。而ES6类语法的一个目标是支持内建对象的继承。具体是:先由基类(Array)创建this的值,然后派生类的构造函数(MyArray)再修改这个值。举例:
1 class MyArray extends Array {
2 //空
3 }
4
5 var colors = new MyArray();
6 colors[0] = "red";
7
8 console.log(colors.length); //1
9
10 colors.length = 0;
11 console.log(colors[0]) // undefined
6. Symbol.species属性
内建对象继承的一个实用之处是,原本在内建对象中返回实例自身的方法将自动返回派生类的实例。
通过Symbol.species可以定义当派生类的方法返回实例时,应该返回的值的类型。
第10节 在类的构造函数中使用new.target
类的构造函数必须通过new关键字调用,所以总是在类的构造函数中定义new.target属性。但是其值有时会不同。每个构造函数都可以根据自身被调用的方式改变自己的行为。例如,可以用new.target创建一个抽象基类(不能被直接实例化的类),就像这样:
1 //抽象基类
2 class Shape {
3 constructor() {
4 if (new.target === Shape) {
5 throw new Error("这个类不能被直接实例化");
6 }
7 }
8 }
9
10 class Rectangle extends Shape {
11 constructor(length, width) {
12 super();
13 this.length = length;
14 this.width = width;
15 }
16 }
17
18 var x = new Shape(); // 抛出错误
19
20 var y = new Rectangle(3,4); //没有错误
21 console.log(y instanceof Shape); // true
(本节完)
