第九章 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
(本节完)