ES6类
ECMAScript6中终于引入了类的特性,在此之前只能通过其他方法定义并关联多个相似的对象,当然了,ES6中的类与其他语言中的还是不太一样,其语法的设计实际上借鉴了JavaScript的动态性,本文档简单介绍一下ES6及其新特性。
类的声明
ES6中声明一个类,首先编写class关键字,紧跟着是类的名字,其他部分的语法类似于对象字面量方法的简写形式,但是不需要子类的各元素之间使用逗号分隔,请看下面这段简单的类声明代码:
class PersonClass{
constructor(name){
this.name = name
}
sayName(){
return this.name
}
}
let person = new PersonClass('xiaoMing')
console.log(person.sayName()) // 'xiaoMing'
console.log(person instanceof PersonType) //true
console.log(person instanceof Object) //true
console.log(typeof PersonClass) // function
console.log(typeof PersonClass.prototype.sayName) // function
对比类声明语法定义PersonClass的行为与ES5之前用构造函数构建近似类过程相似,但是仍有很多差异:
1>函数声明可以被提升,而类声明与let声明类似,不能被提升;真正执行声明语句之前,他们会一直存在于临时死区中。
2>类声明中的所有代码将自动运行在严格模式下,而且无法强行让代码脱离严格模式执行
3>在自定义类型,需要通过Object.defineProperty()方法手工指定某个方法为不可枚举,而在类中,所有的方法都是不可枚举的
4>每个类都有一个名为[Constructor]]的内部方法,通过关键字new调用方那些不含[Constructor]]的方法会导致程序抛出错误
5>使用关键字new以外的方式调用类的构造函数会导致程序抛出错误
6>在类中修改类名会导致程序报错
类不仅仅可以通过声明实现,还可以使用类表达式 ,类表达式又分为匿名类表达式和命名类表达式,具体语法如下所示:
//匿名类表达式
let PersonClass = class{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}
//命名表达式
let PersonClass = class PersonClass2{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}
console.log(typeof PersonClass) // function
console.log(typeof PersonClass2) // undefined
两者除了命名表达式需在关键字class后添加一个标识符外,并无其他差别,但是注意typeof PersonClass2 输出的是undefined,这是因为PersonClass2只存在于类定义中,而在类的外部,其实并不存在一个名为PersonClass2的绑定。下边我们用一段没有使用关键字class的等价声明来了解这背后的原理:
let PersonClass = (function () {
const PersonClass2 = function (name) {
if (typeof new.target === "undefined") { //类与构造函数差异的第五条
throw new Error("必须通过new关键字调用构造函数")
}
this.name = name
}
Object.defineProperty(PersonClass2.prototype, 'sayName', {
value: function () {
if (typeof new.target !== "undefined") { //类与构造函数差异的第四条
throw new Error("不可使用new关键字调用该方法")
}
console.log(this.name)
},
enumerable: false,
writable: true,
configurable: true,
})
return PersonClass2
}())
外部作用域中的PersonClasss是用let声明的,而立即表达式中的PersonClass2是用const声明的,这也说明了为什么可以在外部修改类名而内部却不可修改,且PersonClass2只能在类内部使用。
类的特点
下边简单介绍一下类中的几个小特性
-(+) 一级公民类
在编程中,能被当做值使用的就成为一级公民,意味着它可以被当做参数传给函数,可以作为函数返回值,能用来赋值给变量,JS的类也可以被当成值使用,就是一级公民类
-(+) 访问属性
自有属性需要在类构造器中创建,而类还允许你在原型上定义访问器属性。为了创建一个getter ,要使用 get 关键字,并要与后方标识符之间留出空格;创建 setter 用相同方式,只是要换用 set 关键字。
-(+) 需计算成员名
类方法与类访问器属性也都能使用需计算的名称。语法相同于对象字面量中的需计算名称:无须使用标识符,而是用方括号来包裹一个表达式。
-(+)生成器方法
可以使用 Symbol.iterator 来定义生成器方法,从而定义出类的默认迭代器
-(+*) 静态成员
ES6 类的静态成员的创建,只要在方法与访问器属性的名称前添加正式的 static 标注。你能在类中的任何方法与访问器属性上使用 static 关键字,唯一限制是不能将它用于 constructor 方法的定义。
继承与派生类
首先我们来看下ES6中是怎么实现继承的:
class Rectangle(){
constructor(length,width){
this.length = length
this.width = width
}
getArea(){
return this.length * this.width
}
}
class Square extends Rectangle {
constructor(length){
//等价于Rectangle.call(this,length,length)
super(length,length)
}
}
var square = new Square(3)
console.log(square.getArea()) //9
console.log(square instanceof Suqare) //true
console.log(square instanceof Rectangle) //true
ES5中Square继承自Rectangle,为了这样做,必须创建自Rectangle.prototype的新对象重写Square.prototype并调用Rectangle。call()方法,这些步骤很容易让人感到困惑,并在这里犯错。ES6类的出现让我们轻松实现了继承功能,使用extends关键字可以指定类继承的函数,继承自其他类的类被称作派生类,如果在派生类中指定了构造函数则必须要调用super(),通过调用super()方法即可访问基类的构造函数,原型会自动调整,如果不这样做,程序就会报错。如果选择不适用构造函数,则当创建新的类实例时会自动调用super()并传入所有参数。举个例子,一下两个类完全相同:
class Square extends Rectangle{
//没有构造函数
}
clas Square extends Rectangle{
constructor(...args){
super(...args)
}
}
派生类的特性
派生类除了上述的特征外,还有一些其他的特性:
1>派生类中的方法总会覆盖基类中的同名方法
2>如果基类中有静态成员,那么这些静态成员在派生类中也可用
3>只要表达式可以被解析为一个函数并且具有[[constructor]]属性和原型,就可以用extends进行派生
4>ES6支持内建对象的继承