
背景
在ES6之前,官方都不支持类的语法,很多第三方类库都通过模拟实现了类似类的用法,最终ES6引入了类
类的介绍
类的声明
ES6之前的写法:称为自定义类型
function Person(name){
this.name = name
}
Person.prototype.sayName = function(){
return this.name
}
ES6的写法:
class Person {
//构造器
constructor(name){
this.name = name //自有属性
}
sayName(){
return this.name
}
}
let person = new Person('Tom')
console.log(person.sayName()) //Tom
console.log(person instanceof Person) //true
console.log(person instanceof Object) //true
console.log(typeof Person) //function
console.log(typeof Person.sayName) //undefined
console.log(typeof Person.prototype.sayName) //function
console.log(typeof Person) //function通过打印结果可以看到,ES6对类的声明类似ES5自定义类型写法的语法糖,本质上class定义的Person仍然是一个函数console.log(typeof Person.sayName) //undefined console.log(typeof Person.prototype.sayName) //function通过打印结果可以看到,ES6类中声明的方法实际也是挂载到原型上的,这个ES5自定义类型中定义的方法是一样的
类表达式:
let Person = class {
//构造器
constructor(name){
this.name = name //自有属性
}
sayName(){
return this.name
}
}
//具名类表达式
let Person = class Person2{
//构造器
constructor(name){
this.name = name //自有属性
}
sayName(){
return this.name
}
}
**这里的Person2只能在类的方法中被访问到**
使用ES6声明类的优势
- 类声明不会被提升,类声明类似于let,在程序执行到达类声明之前,是处于暂时性死区中的,访问会报错。ES5中的函数定义法,函数是可以被提升到当前作用域顶部的
- 类声明中的代码强制在严格模式下运行'use strict',且不能退出
- 类的所有方法不可枚举,ES5的自定义类型只能通过Object.defineProperty()来定义属性描述符规定其不可枚举
- 调用类生成实例时,必须使用new关键词,会自动调用类中的构造器constructor函数。不使用new 调用类会报错;这是自定义类型的函数是没办法规避的
- 类的方法不能使用new 关键词调用,会抛出错误
- 试图在类的方法中重写类名(相当于const类名),会抛出错误;在类外部可以重写类名(相当于let类名)
类是一等公民
在编程中,能被当做值来使用的就称为一等公民,也就是说,就像函数那样,能够作为函数的参数、函数的返回值、能用来给变量赋值等操作
function createClass(classDef){
return new classDef()
}
let obj = createClass(class {
sayHi(){
console.log('Hi')
}
})
obj.sayHi() //Hi
类的访问器属性
class Person {
constructor(name){
this.value = name
}
get name(){
console.log('getter')
return this.value
}
set name(name){
console.log('setter')
this.value = name
}
}
let person = new Person('Tom')
console.log(person.name)
person.name = 'Lisa'
console.log(person.name)
/*
getter
Tom
setter
getter
Lisa
*/
为类添加生成器方法(定义Symbol.iterator)
class Collection {
constructor(){
this.items= []
}
*[Symbol.iterator](){
yield *this.items.values() //this.items.values()是数组内置的迭代器,这里相当于把this.items的生成器合并在类生成器中了
}
}
let collection = new Collection()
collection.items.push(1)
collection.items.push(2)
collection.items.push(3)
for(let value of collection){
console.log(value)
}
/*
1
2
3
*/
静态成员
我们之前已经实验过,在类中定义的方法,是把此方法属性加到类的原型上的,类的实例也都继承了这些方法,可以访问; 如果不希望实例可以访问,只能被类自身访问,则需要使用静态成员
ES5传统的实现写法
function Person(name){
this.name = name
}
//添加在原型上,所有实例都可以访问
Person.prototype.getName = function(){
return this.name
}
静态方法,实例访问不到
Person.sayHi = function(){
console.log('Hi')
}
ES6写法
class Person {
constructor(name){
this.name = name
}
//添加到原型上,所有实例都可以访问
getName(){
return this.name
}
static sayHi(){
console.log('Hi')
}
}
let person = new Person('Tom')
console.log(person.getName()) //Tom
// person.sayHi() // 抛出异常 person.sayHi is not a function
Person.sayHi()//Hi
使用extends关键词完成类的继承
class Rectangle {
constructor(length, width){
this.length = length;
this.width = width;
}
getArea(){
return this.length * this.width
}
}
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
- 继承父类的类叫派生类,派生类如果显式定义了构造器,就必须在访问this之前,要显式地使用super()调用父类的构造器,完成父类初始化this,并通过继承传递给派生类,否则在派生类中访问this时会报错
- 在派生类中才可以使用super关键词,指代父类,super()表示先初始化父类的构造器,然后派生类继承父类的this,所以在派生类中this也就可以访问了
class Rectangle { constructor(){ console.log('call parent') this.length = 10 this.width = 5 } getArea(){ return this.length * this.width } } class Square extends Rectangle { constructor(){ console.log('call child1') super() console.log('call child2') } getValue(){ console.log('length',this.length) console.log('width',this.width) } } let square = new Square() square.getValue() /* call child1 call parent call child2 length 10 width 5 */ - 如果派生类没有显式定义构造器,则系统在初始化的时候会默认调用super(),并使用创建实例时传递的所有参数
class Square extends Rectangle { } // 等价于 class Square extends Rectangle { constructor(..args){ super(...args) } }
注意:如果派生类与父类初始化的参数完全相同,可以选择省略constructor的形式
class Rectangle {
constructor(length, width){
this.length = length;
this.width = width;
}
getArea(){
return this.length * this.width
}
}
class Square extends Rectangle {
}
let square = new Square(3,3)
console.log(square.getArea()) //9
如果派生类与父类的初始化参数不一致,就得自己写构造器constructor,手动调用super(),否则会得到意想不到的结果
class Rectangle {
constructor(length, width){
this.length = length;
this.width = width;
}
getArea(){
return this.length * this.width
}
}
class Square extends Rectangle {
}
let square = new Square(3) //这里只传了1个参数,派生类使用默认的构造器传递参数,与父类构造器参数不一致,导致有些参数为undefined,调用计算方法是肯定为NaN
console.log(square.getArea()) //NaN
静态成员也会被继承
class Person {
constructor(name){
this.name = name
}
static sayHi(){
console.log('Hi')
}
}
class Student extends Person {
}
Person.sayHi()//Hi
Student.sayHi()//Hi