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