es6 快速入门 系列 —— 类 (class)

  • A+
所属分类:Web前端
摘要

其他章节请看:es6 快速入门 系列类(class)是 javascript 新特性的一个重要组成部分,这一特性提供了一种更简洁的语法和更好的功能,可以让你通过一个安全、一致的方式来自定义对象类型。


其他章节请看:

es6 快速入门 系列

类(class)是 javascript 新特性的一个重要组成部分,这一特性提供了一种更简洁的语法和更好的功能,可以让你通过一个安全、一致的方式来自定义对象类型。

试图解决的问题

es5 及早期版本中没有类的概念,通常会编写类似下面这样的代码来自定义类

// 自定义类的思路是:首先创建一个构造函数,然后定义一个方法并赋值给构造函数的原型 function Rectangle(length, width){     this.length = length;     this.width = width; } Rectangle.prototype.getArea = function(){     return this.length * this.width } let rect = new Rectangle(2, 3) console.log(rect.getArea()) // 6 console.log(rect instanceof Rectangle) // true 

如果还需要实现继承,可能会这么实现:

// 定义类:Rectangle function Rectangle(length, width){     this.length = length;     this.width = width; } Rectangle.prototype.getArea = function(){     return this.length * this.width } // 定义类:Square function Square(length){     // 对象冒充     Rectangle.call(this, length, length) } // 指定 Square 的原型 Square.prototype = Object.create(Rectangle.prototype, {     constructor: {         value: Square,         enumerable: true,         writable: true,         configurable: true     } });  let square = new Square(3)  console.log(square.getArea()) // 9 - getArea() 方法从父类继承 console.log(square instanceof Square) // true console.log(square instanceof Rectangle) // true 

通过自己模拟类、模拟继承,实现起来比较复杂,也非常容易出错。

解决方法

es6引入类(class),让类的创建和继承都更加容易。下面我们重写 Rectangle 的例子:

// 通过 class 定义一个类 class Rectangle{     constructor(length, width){         this.length = length;         this.width = width;     }     // 方法直接写在 class 中     getArea(){         return this.length * this.width     } } // 通过 extends 关键字指定类继承的函数,原型会自动调整 class Square extends Rectangle{     constructor(length){         // 通过调用 super() 方法即可访问基类的构造函数         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 

这个版本比我们自定义的要简单很多,如果你先前对其他语言中的继承语法有所了解,那么你应该会觉得很亲切。

有关类(class)的具体使用、其他特性,都可以在下面的补充章节中找到答案

补充

类的创建

类和函数都存在声明形式和表达式形式。

声明类,首先编写 class 关键字,接着是类的名字,其他部分的语法类似对象字面量的简写形式,但类的各元素之间不需要使用逗号。就像这样:

// class 类名 class People{     constructor(name){         this.name =name;     }     // 不需要逗号     sayName(){         console.log(this.name)     } } let people = new People('aaron') people.sayName() // aaron console.log(typeof People) // function {1} - People 其实还是一个函数 

类的表达式语法:

// 直接将一个类赋值给变量 People let People = class{     constructor(name){         this.name =name;     }     sayName(){         console.log(this.name)     } } let people = new People('aaron') people.sayName() // aaron 

类(class)只是自定义类的一个语法糖。用 class 声明的类是一个函数,那么将这个函数赋值给一个变量,当然没问题。

类是一等公民

在程序中,一等公民是指可以传入函数,可以从函数返回,也可以赋值给变量。javascript 函数是一等公民,这是 javascript 中的一个独特之处,es6 也把类(class)设计成了一等公民。例如,将类(class)作为参数传入函数:

function createObject(classDef){     return new classDef() } let obj = createObject(class People{     sayName(){         console.log('aaron')     } }) obj.sayName() // aaron 

继承

在”解决方法“章节中,我们已经学会用 class 创建类,通过 extends 关键字指定类继承的函数,以及使用 super() 调用基类的构造函数。

当使用 super() 时切记以下几个关键点

  • 只可以在派生类(使用 extends 声明的类)的构造函数中使用 super(),否则会导致程序抛出错误
  • 在构造函数中访问 this 之前,一定要调用 super(),它负责初始化 this,如果在调用 super() 之前访问 this 会导致程序报错
  • 如果不想调用 super(),唯一的方法是让构造函数返回一个对象,否则也会导致程序报错
class A{} class B extends A{     constructor(){             } } let b = new B() // 报错 - 没有调用super() 

内建对象的继承

现在可以通过继承创建自己的特殊数组。请看下面:

class MyArray extends Array{  } let myArray = new MyArray() myArray[0] = 11 console.log(myArray.length) // 1  myArray.length = 0; console.log(myArray[0]) // undefined 

MyArray 继承自 Array,其行为与 Array 也很相似。但在以前,用传统的继承方式是无法实现这样的功能。es6 类语法的一个目标是支持内建对象继承,因而 es6 中的类继承模型与 es5 中的稍有不同,主要体现是:

  • es5 先由派生类(例如,MyArray)创建 this,然后调用基类的构造方法(例如 Array.apply()方法)
  • es6 则相反,先由基类创建this,然后派生类的构造函数再修改,所以从一开始可以访问基类的所有内建功能

我们也可以继承自其他内建对象

静态成员

es5 及更早,通过直接将方法添加到构造函数中来模拟静态成员,例如:

function People(){}  People.create = function(){} 

es6 简化了这个过程,类(class)通过 static 关键字定义静态方法:

class People{     constructor(name){         this.name = name     }     static create(name){         return new People(name)     } } let p1 = People.create('aaron')  console.log(p1.name) // aaron 

静态成员也可以被继承,请看示例:

class A{     static say(){         console.log('A say')     } } class B extends A{   } B.say() // A say 

派生自表达式的类

es6 中最强大的一面或许是从表达式中导出类了。只要表达式可以被解析为一个函数并且具有[[Constructor]]属性和原型,就可以用 extends 派生。举个例子:

function Rectangle(length, width){     this.length = length;     this.width = width; } Rectangle.prototype.getArea = function(){     return this.length * this.width }  function getBase(){     return Rectangle; } // getBase()这个表达式返回 Reactangle, 具有[[Constructor]]属性和原型,因此 Square 类可以直接继承它 class Square extends getBase(){     constructor(length){         super(length, length)     } }  let square = new Square(3) console.log(square.getArea()) // 9 

extends 强大的功能使得类可以继承自任意类型的表达式,从而创建更多的可能性。

类的构造函数中使用 new.target

class A{     constructor(){         if(new.target === A){             console.log(true)         }else{             console.log(false)         }     } }  class B extends A{     constructor(){         super()     } }  new A() // true {1} new B() // false {2} - B 调用 A 的构造函数,所以当调用发生时 new.target 等于 B 

简单情况下,new.target 等于类的构造函数,就像行{1},但有时却会不同(行{2})。理解它(行{2})非常重要,因为每个构造函数可以根据自身被调用的方式改变自己的行为。例如,可以用 new.target 创建一个抽象基类(不能被直接实例化的类),就像这样:

// 抽象基类 BaseClass class BaseClass{     constructor(){         if(new.target === BaseClass){             throw new Error('这个类不能被实例化')         }     }     sayName(){         console.log(this.name)     } } class B extends BaseClass{     constructor(name){         super()         this.name = name     } } let b = new B('aaron') b.sayName() // aaron new BaseClass() // Error: 这个类不能被实例化 

访问器属性和可计算成员名称

类支持直接在原型上定义访问器属性,例如我们用访问器属性封装一下数据 name:

// 数据 name 存储在变量 _name 中,只能通过访问器属性来操作,从而达到数据封装的目的 class People{     constructor(name){         this._name = name;     }     set name(v){         this._name = v;     }     get name(){         return this._name     } }  let people = new People('aaron') console.log(people.name) // aaron people.name = 'lj' console.log(people.name) // lj 

类方法和访问器属性也支持可计算成员名称,请看下面示例:

let methodName = 'sayName' let getName = 'age' class People{     constructor(name){         this.name = name;     }     [methodName](){         console.log(this.name)     }     get [getName](){         console.log('get')     } } let people = new People('aaron')  people.sayName() // aaron people.age // get 

生成器

在类中,可以将任意方法定义成生成器。但如果你的类是用来表示集合,那么定义一个默认的迭代器会更有用:

class Collection{     constructor(){         this.items = []     }     push(v){         this.items.push(v)     }     *[Symbol.iterator](){         yield *this.items.values()     } } let collection = new Collection() collection.push('11') collection.push('22') collection.push('33')  for(let v of collection){     console.log(v) // 11 22 33 } 

其他章节请看:

es6 快速入门 系列