ES6中的类——class

303 阅读7分钟

概述

    在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。

ES6 提供了更接近传统语言的写法,新引入的class关键字具有正式定义类的能力。类(class)是ECMAScript中新的基础性语法糖结构,虽然ECMAScript 6类表面上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念,让对象原型的写法更加清晰、更像面向对象编程的语法。

    类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。

严格模式

类和模块的内部,默认就是严格模式,所以不需要使用 use strict 指定运行模式

创建一个类的语法格式如下:

class ClassName { constructor() { ... } }

实例:

class Runoob {
    constructor(name, url) {
        this.name = name;
        this.url = url; 
        }
    }
    
   // 创建对象时会自动调用构造函数方法 constructor()。

一、类的定义

  • 定义类也有两种主要方式:类声明和类表达式。这两种方式都使用class关键字加大括号:
// 类声明
class Person {}
 
// 类表达式
const TestPerson = class {}
  • 函数声明和类声明之间的一个重要区别是函数声明会提升,类声明不会。需要先进行声明,再去访问,否则会报错。
var person= new Person()
class Person {
	constructor(x, y) {
    this.x = x
    this.y = y
  }
}
// Person  is not defined
  • 另一个跟函数声明不同的地方是,函数受函数作用域限制,而类受块作用域限制:
{
    function FunctionDeclaration () {}
    class ClassDeclaration {}
    // 使用var 声明
    var VarClass = class {}
    // 使用let/const 声明
    let LetClass = class {}
}
 
console.log(FunctionDeclaration) // FunctionDeclaration () {}
console.log(ClassDeclaration) // ReferenceError: ClassDeclaration is not defined
console.log(VarClass) // class {}
console.log(LetClass) // ReferenceError: letClass is not defined

类声明不可以重复

class Person {}
class Person {}
// TypeError Identifier 'Person' has already been declared
  • 类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,就是后者不用 new 也可以执行
class Person {
	constructor(x, y) {
    this.x = x
    this.y = y
  }
}
Person()
// TypeError Class constructor Person cannot be invoked without 'new'
  • 类表达式可以是被命名的或匿名的
/* 匿名类 */ 
let Person = class {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
}
 
/* 命名的类 */ 
let Person = class Person {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
}
  • class 类完全可以看成构造函数的另一种写法,这种写法可以让对象的原型属性和函数更加清晰。
class Person {}
 
console.log(typeof Person) // function
console.log(Person === Person.prototype.constructor) // true

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

二、类构造函数

首先我们要了解什么是构造方法

构造方法是一种特殊的方法:

  • 构造方法名为 constructor()。
  • 构造方法在创建新对象时会自动执行。
  • 构造方法用于初始化对象属性。
  • 如果不定义构造方法,JavaScript 会自动添加一个空的构造方法。

constructor 方法

    constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法(默认返回实例对象 this)。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。一个类只能拥有一个名为 “constructor” 的特殊方法,如果类包含多个 constructor 的方法,则将抛出 一个 SyntaxError 。

如果没有定义"constructor"构造函数,class 会默认添加一个空的"constructor"构造函数。
constructor 方法也是一个特殊的方法,这种方法用于创建和初始化一个由`class`创建的对象。
通过 new 关键字生成对象实例时,自动会调用该方法。
class Person {
   constructor(x, y) {
    this.x = x    // 默认返回实例对象 this
    this.y = y
  }
  toString() {
    console.log(this.x + ', ' + this.y)
  }
}

注意:

  • 在类中声明方法的时候,方法前不加 function 关键字
  • 方法之间不要用逗号分隔,否则会报错
  • 类的内部所有定义的方法,都是不可枚举的(non-enumerable)
  • 一个类中只能拥有一个 constructor 方法

静态方法

    静态方法可以通过类名调用,不能通过实例对象调用,否则会报错

class Person {
    static sum(a, b) {
        console.log(a + b)
    }
}
var p = new Person()
Person.sum(1, 2)  // 3
p.sum(1,2)     //  TypeError p.sum is not a function

原型方法

    类的所有方法都定义在类的 prototype 属性上面,在类的实例上面调用方法,其实就是调用原型上的方法

    原型方法可以通过实例对象调用,但不能通过类名调用,会报错

class Person {
	constructor() {
		// 默认返回实例对象 this
	}
    sum() {
    	
    }
    toString() {
    	console.log('123456')
  	}
}
// 给 Person 的原型添加方法
Person.prototype.toVal = function() {
	console.log('I am is toVal')
}
 
// 等同于
Person.prototype = {
  constructor() {},
  sum() {},
  toString() {}
}
 
var p = new Person()
p.toString()       // 123456
p.toVal()          // I am is toVal
Person.toString()  // TypeError Person.toStringis not a function
Person.toVal()  // TypeError Person.toVal is not a function

实例方法

    实例方法也可以通过实例对象调用,但同样不能通过类名调用,会报错

class Person {
    constructor() {
        this.sum = function(a, b) {
            console.log(a + b)
        }
    }
}
var p = new Person()
p.sum(1,2)       // 3
Person.sum(1,2)  // TypeError Person.sum is not a function

继承 extends

  • 解决代码的复用
  • 使用extends关键字实现继承
  • 子类可以继承父类中所有的方法和属性
  • 子类只能继承一个父类(单继承),一个父类可以有多个子类
  • 子类的构造方法中必须有super() 来指定调用父类的构造方法,并且位于子类构造方法中的第一行
  • 子类中如果有与父类相同的方法和属性,将会优先使用子类的(覆盖)

使用 extends 关键字,让子类继承父类的属性和方法。

class Person {
    num = 1
    text = 'person'
 
    getNum = () => console.log(this.num, this)
 
    addNum () {
        console.log(++this.num, this)
    }
}
 
// 继承
class Child extends Person {
    constructor () {
        super()
        this.getText()
    }
 
    getText = () => console.log(this.text, this)
}
 
const a = new Child() // output: person  Child {}
 
console.log(a instanceof Child) // output: true
console.log(a instanceof Person) // output: true
 
a.getText() // output: person Child {}
a.getNum() // output: 1 Child {}
a.addNum() // output: 2 Child {}
a.getNum() // output: 2 Child {}
 
a.text // person
a.num // 2

从上面代码中,我们可以看出Child 类 继承了 Person 的属性及方法,在Child 中也是可以调用Person的方法及属性,注意 this 的值会反映调用相应方法的实例或者类。子类中(Child)如果设置了 constructor 方法 就必须调用 super() ,否则就会出现新建实例时报错,如果没有 constructor 构造函数,在实例化继承类时会调用 super() ,而且会传入所有传给继承类的参数(后面会详细讲解)。

class Person {
    static staticText = 'staticText';
    #private = 'private'
 
    static staticMethods1 (person) {
        console.log('staticMethods1', this)
        person.#privateMethods()
    }
 
    #privateMethods () {
        console.log('#privateMethods', this)
    }
}
 
// 使用表达式格式 也是可以使用 extends 继承
const Child = class extends Person {
    methods () {
        console.log('methods', Child.staticText)
    }
}
 
const a = new Child()
 
a.methods() // output: methods staticText
 
Child.staticMethods1(a)
// output: staticMethods1  class Child {}
// output: #privateMethods Child {}
 
Person.staticMethods1(a)
// output: staticMethods1  class Person {}
// output: #privateMethods Child {}

super 关键字可以作函数使用,也可以作对象使用,但是其只能在继承类中使用,且只能在继承类的constructor 构造函数、实例方法和静态方法中使用。作为函数时是在 继承类的constructor 构造函数中使用,根据要求如果继承类中定义了constructor构造函数就必须要调用super方法(调用父类的constructor),否则就会报错。

class Person {}
 
class Child extends Person {
    constructor () {
        // 如果不调用 super() 就会报错
        // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor  
        super() // 调用父级的constructor
        console.log(this) // Child {}  
    }
}

注意: constructor() 中必须super() 顶部首段执行代码,否则也是一样报错;

在使用 super() 时应该注意下面几个问题:

  1. super只能在继承类构造函数和静态方法中使用。
class Person {
    constructor () { 
        // 在非继承类 的constructor 中使用super 会报错
        super() //  SyntaxError: 'super' keyword unexpected here
    }
 
    methods () {
        console.log(super.text) // undefined
    }
 
    static staticMethods () {
        console.log(super.text) // undefined
    }
}
  1. 不能单独引用super关键字,要么用它调用构造函数,要么用它引用静态方法。
class Person {}
 
class Child extends Person {
    
    constructor () {
        super // SyntaxError: 'super' keyword unexpected here
    }
 
    methods () {
        console.log(super) // SyntaxError: 'super' keyword unexpected here
    }
 
    static staticMethods () {
        console.log(super) // SyntaxError: 'super' keyword unexpected here
    }
}
  1. 调用super()会调用父类构造函数,并将返回的实例赋值给this
class Person {}
class Child extends Person {
    constructor () {
        super()
        console.log(this instanceof Person) // output: true
    }
}
 
new Child()
  1. super() 的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。
class Person {
    constructor (text) {
        this.text = text
    }
}
 
class Child extends Person {
    constructor (text) {
        super(text)
    }
}
 
// 这里注意 其text 会设置到Child 中
const a = new Child('设置 text') // Child { text: '设置 text' }
 
console.log(a.text) // output: 设置 text
  1. 如果没有定义类构造函数,在实例化继承类时会调用super(),而且会传入所有传给继承类的参数。
class Person {
    constructor (text) {
        this.text = text
    }
}
 
class Child extends Person {}
 
const a = new Child('设置 text'); // Child { text: '设置 text' }
 
// 上面提到过 会默认 生成 constructor (...arge) {super(...arge)}
  1. 如果在继承类中显式定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象。
class Person {
    methods () {}
}
 
class Child1 extends Person {}
 
class Child2 extends Person {
    constructor () {
        super()
    }
}
 
class Child3 extends Person {
    constructor () {
        return {}
    }
}
 
const a = new Child1() // Child1 {}
 
const b = new Child2() // Child2 {}
 
const c = new Child3() // {} 指向 实例函数 返回的对象 

今天关于class的总结就到此结束吧!