一、ES6类
的类并不与其他语言的类完全相同,所具备的独特性正配合了 JS 的动态本质。
二、ES5 中的仿类结构
JS 在 ES5 及更早版本中都不存在类。与类最接近的是:创建一个构造器,然后将方法指派到该构造器的原型上。这种方式通常被称为创建一个自定义类型。
function StudentType(name){
this.name = name
}
StudentType.prototype.sayName = function(){
console.log(this.name)
}
let student = new StudentType('张三')
student.sayName() // 张三
console.log(student instanceof StudentType) // true
console.log(student instanceof Object) // true
此代码中的 PersonType 是一个构造器函数,并创建了单个属性 name 。 sayName() 方法被指派到原型上,因此在 PersonType 对象的所有实例上都共享了此方法。接下来,使用 new运算符创建了 PersonType 的一个新实例 person ,此对象会被认为是一个通过原型继承了PersonType 与 Object 的实例。
这种基本模式在许多对类进行模拟的 JS 库中都存在,而这也是 ES6 类的出发点。
三、基本的类声明
class StudentClass{
// 等价于StudentType构造器
constructor(name){
this.name = name
}
// 等价于StudentType.prototype.sayName
sayName(){
console.log(this.name)
}
}
let student = new StudentClass('kangyun')
student.sayName() // kangyun
console.log(student instanceof StudentClass) // true
console.log(student instanceof Object) // true
console.log(typeof StudentClass) // function
console.log(typeof StudentClass.prototype.sayName) // function
自有属性( Own properties ):该属性出现在实例上而不是原型上,只能在类的构造器或方法内部进行创建。在本例中, name 就是一个自有属性。我建议应在构造器函数内创建所有可能出现的自有属性,这样在类中声明变量就会被限制在单一位置(有助于代码检查)
四、类表达式
类与函数有相似之处,即它们都有两种形式:声明与表达式。函数声明与类声明都以适当的关键词为起始(分别是 function 与 class ),随后是标识符(即函数名或类名)。函数具有一种表达式形式,无须在 function 后面使用标识符;类似的,类也有不需要标识符的表达式形式。类表达式被设计用于变量声明,或可作为参数传递给函数。
// 类表达式
let StudentClass = class {
// 等价于StudentType构造器
constructor(name){
this.name = name
}
// 等价于StudentType.prototype.sayName
sayName(){
console.log(this.name)
}
}
let student = new StudentClass('kangyun')
student.sayName() // kangyun
console.log(student instanceof StudentClass) // true
console.log(student instanceof Object) // true
console.log(typeof StudentClass) // function
console.log(typeof StudentClass.prototype.sayName) // function
五、作为一级公民的类
在编程中,能被当作值来使用的就称为一级公民( first-class citizen ),意味着它能作为参数传给函数、能作为函数返回值、能用来给变量赋值。 JS的函数就是一级公民(它们有时又被称为一级函数),此特性让 JS 独一无二。
ES6 让类同样成为一级公民。这就使得类可以被多种方式所使用。
function createObject(classDef){
return new classDef()
}
let obj = createObject(class{
sayHi(){
console.log('hi')
}
}
)
obj.sayHi()
类表达式的另一个用途是立即调用类构造器,以创建单例( Singleton )。为此,你必须使用 new 来配合类表达式,并在表达式后面添加括号。
let person = new class{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}('kangyun')
person.sayName() // kangyun
六、需计算的成员名
对象字面量与类之间的相似点还不仅前面那些。类方法与类访问器属性也都能使用需计算的名称。语法相同于对象字面量中的需计算名称:无须使用标识符,而是用方括号来包裹一个表达。
let methodName = 'sayName'
class StudentClass{
constructor(name){
this.name = name
}
[methodName](){
console.log(this.name)
}
}
let zs = new StudentClass('zs')
zs.sayName()
七、静态成员
ES5 及更早版本中,直接在构造器上添加额外方法来模拟静态成员.
function StudentType(name){
this.name = name
}
// 静态方法
StudentType.create = function(name){
return new StudentType(name)
}
// 实例方法
StudentType.prototype.sayName = function(){
console.log(this.name)
}
var student = StudentType.create('kangyun')
工厂方法 PersonType.create() 会被认定为一个静态方法,它的数据不依赖 PersonType 的任何实例。 ES6 的类简化了静态成员的创建,只要在方法与访问器属性的名称前添加正式的 static 标注。
class StudentClass{
// 等价于StudentType构造器
constructor(name){
this.name = name
}
// 等价于StudentType.prototype.sayName
sayName(){
console.log(this.name)
}
// 等价于StudentType.create
static create(name){
return new StudentClass(name)
}
}
let student = StudentClass.create('kangyun')
静态成员不能用实例来访问,你始终需要直接用类自身来访问它们。
八、使用派生类进行继承
类让继承工作变得更轻易,使用熟悉的 extends 关键字来指定当前类所需要继承的函数,即可。生成的类的原型会被自动调整,而你还能调用 super() 方法来访问基类的构造器。
// 长方形
class Rectangle{
constructor(length, width){
this.length = length
this.width = width
}
getArea(){
return this.length * this.width
}
}
// 正方形
class Square extends Rectangle{
constructor(length){
super(length, length)
}
}
let square = new Square(3)
console.log(square.getArea()) // 9
console.log(square instanceof Square) // true
console.log(square instanceof Rectangle) // true
此次 Square 类使用了 extends 关键字继承了 Rectangle 。 Square 构造器使用了super() 配合指定参数调用了 Rectangle 的构造器。
继承了其他类的类被称为派生类( derived classes )。如果派生类指定了构造器,就需要使用 super() ,否则会造成错误。若你选择不使用构造器, super() 方法会被自动调用,并会使用创建新实例时提供的所有参数。
在构造器中,你必须在访问 this 之前调用 super() 。由于 super() 负责初始化this ,因此试图先访问 this 自然就会造成错误。
唯一能避免调用 super() 的办法,是从类构造器中返回一个对象。