基本
传统的JavaScript中只有对象,没有类概念,跟面向对象语言差异很大。为了让JavaScript具有更接近面向对象语言的写法,ES6引入了Class(类)的概念,通过class关键字定义类。面向对象是指类(Class),不是真正的对象(Object)
// ES5的写法function Person1(name, age) {// 实例属性this.name = namethis.age = age}// 原型属性Person1.prototype.count = 1// 原型函数Person1.prototype.getName = function () {return this.name}const p1 = new Person1('kingx', 12)console.log(p1.getName()) // kingx// ES6的写法class Person2 {constructor(name, age) {// 实例属性this.name = namethis.age = age}// 实例属性(es7语法,其实并不是真正原型上的属性,这样写只是会给每个实例加上该属性,相当于this.count = 1)count = 1;// 通过属性访问器添加的才是真正添加到原型上的属性Person2.prototypeget count2() {return 2}set count2(newVal) {}// 原型函数(this指向实例)getName() {return this.name}}const p2 = new Person2('kingx', 12)console.log(p2.getName()) // kingxconsole.log(typeof Person2); // functionconsole.log(p2.constructor === Person2.prototype.constructor); // trueconsole.log(p2.getName === Person2.prototype.getName); // true
Class本质上也是一个函数,class中的所有属性和函数都是定义在prototype属性中的,但ES6将prototype相关的操作封装在了class中,避免我们直接去使用prototype属性。
重点
1. constructor()函数
constructor()函数是一个类必须具有的函数,可以手动添加,如果没有手动添加,则会自动隐式添加一个空的constructor()函数。
constructor()函数默认会返回当前对象的实例,即默认的this指向,可以手动修改返回值。
class Person {constructor(name) {this.name = namereturn {} // 修改了this指向为一个空对象{}}getName() {return this.name}}const p = new Person('kingx')console.log(p.getName()) // TypeError: p.getName is not a function
2. 静态属性和函数
静态属性和函数使用static关键字修饰时,静态属性和函数无法被实例访问,只能通过类自身使用。静态函数中的this指向的是类本身,而不是类的实例,也正因为静态函数和实例函数中的this是隔离的,所以同一个类中可以存在函数名相同的静态函数和实例函数。
class Foo {constructor(number) {// 实例属性this.number = number}// 静态属性static classProp = 'staticProp'// 静态函数static testHello() {return 'hello'}// 静态函数中,this指向类本身而不是实例static getNumber() {return this.number}// 原型函数,this指向实例getNumber() {return this.number}}// 类自身可以正常访问静态属性和函数Foo.classProp // 'staticProp'Foo.testHello() // 'hello'Foo.getNumber() // undefinedFoo.number = 60Foo.getNumber() // 60const foo = new Foo(20)// 通过实例访问静态属性,返回undefinedfoo.classProp // undefined// 通过实例访问静态函数,抛出异常foo.testHello() // TypeError: foo.testHello is not a functionfoo.getNumber() // 20
3. ES7 中类的用法
ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义实例属性,并且可以定义静态属性
class Animal {name = "Jack";static num = 42;constructor() {// ...}}let a = new Animal();console.log(a.name); // Jackconsole.log(Animal.num); // 42
4. 不存在变量提升
let关键字和const关键字声明的变量不存在变量提升,class定义的类同样不存在变量提升,因此如果在定义类之前去使用它,会抛出引用异常。
const p = new Person(); // ReferenceError: Person is not definedclass Person {}
5. 不加function关键字
在类中声明函数时,不要加function关键字,否则会抛出语法异常。
class Person {test1 function() { // SyntaxError: Unexpected token functionreturn '111'}function test2() { // SyntaxError: Unexpected identifierreturn '222'}}
6. this指向问题
类内部的this默认指向的是类的实例,在调用实例函数时,一定要注意this的指向性问题。如果单独使用实例函数时,this的指向会发生变化:
class Person {constructor(name) {this.name = name}getName() {return this.name}}const p = new Person('kingx')let { getName } = pgetName() // TypeError: Cannot read property 'name' of undefined
使用解构获取到getName()函数,在全局环境中执行,this指向的是全局环境,而在ES6的class关键字中使用了严格模式。在严格模式下this不能指向全局环境,而是指向undefined,所以getName()函数在执行时,this实际为undefined。解决方法:在构造函数中使用bind关键字重新绑定this。
class Person {constructor(name) {this.name = name// 重新绑定getName()函数中this的指向为当前实例this.getName = this.getName.bind(this)}getName() {return this.name}}const p = new Person4('kingx')let { getName } = pgetName() // kingx
7. 只能与new关键字配合使用
class定义的类只能配合new关键字生成实例,不能像普通函数一样直接调用。
class Person {}const p1 = new Person(); // 正常const p2 = Person(); // TypeError: Class constructor Person cannot be invokedwithout 'new'
继承
父类的静态函数无法被实例继承,但可以被子类继承。
子类在访问时同样是通过本身去访问,而不是通过子类实例去访问。
class Parent {static staticMethod() {return 'hello';}}class Child extends Parent {}// 通过子类本身可以访问到父类的静态函数,输出“hello”console.log(Child.staticMethod());
