基本
传统的JavaScript中只有对象,没有类概念,跟面向对象语言差异很大。为了让JavaScript具有更接近面向对象语言的写法,ES6引入了Class(类)的概念,通过class关键字定义类。面向对象是指类(Class),不是真正的对象(Object)
// ES5的写法
function Person1(name, age) {
// 实例属性
this.name = name
this.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 = name
this.age = age
}
// 实例属性(es7语法,其实并不是真正原型上的属性,这样写只是会给每个实例加上该属性,相当于this.count = 1)
count = 1;
// 通过属性访问器添加的才是真正添加到原型上的属性Person2.prototype
get count2() {
return 2
}
set count2(newVal) {}
// 原型函数(this指向实例)
getName() {
return this.name
}
}
const p2 = new Person2('kingx', 12)
console.log(p2.getName()) // kingx
console.log(typeof Person2); // function
console.log(p2.constructor === Person2.prototype.constructor); // true
console.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 = name
return {} // 修改了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() // undefined
Foo.number = 60
Foo.getNumber() // 60
const foo = new Foo(20)
// 通过实例访问静态属性,返回undefined
foo.classProp // undefined
// 通过实例访问静态函数,抛出异常
foo.testHello() // TypeError: foo.testHello is not a function
foo.getNumber() // 20
3. ES7 中类的用法
ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义实例属性,并且可以定义静态属性
class Animal {
name = "Jack";
static num = 42;
constructor() {
// ...
}
}
let a = new Animal();
console.log(a.name); // Jack
console.log(Animal.num); // 42
4. 不存在变量提升
let关键字和const关键字声明的变量不存在变量提升,class定义的类同样不存在变量提升,因此如果在定义类之前去使用它,会抛出引用异常。
const p = new Person(); // ReferenceError: Person is not defined
class Person {}
5. 不加function关键字
在类中声明函数时,不要加function关键字,否则会抛出语法异常。
class Person {
test1 function() { // SyntaxError: Unexpected token function
return '111'
}
function test2() { // SyntaxError: Unexpected identifier
return '222'
}
}
6. this指向问题
类内部的this默认指向的是类的实例,在调用实例函数时,一定要注意this的指向性问题。如果单独使用实例函数时,this的指向会发生变化:
class Person {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
const p = new Person('kingx')
let { getName } = p
getName() // 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 } = p
getName() // kingx
7. 只能与new关键字配合使用
class定义的类只能配合new关键字生成实例,不能像普通函数一样直接调用。
class Person {}
const p1 = new Person(); // 正常
const p2 = Person(); // TypeError: Class constructor Person cannot be invoked
without 'new'
继承
父类的静态函数无法被实例继承,但可以被子类继承。
子类在访问时同样是通过本身去访问,而不是通过子类实例去访问。
class Parent {
static staticMethod() {
return 'hello';
}
}
class Child extends Parent {}
// 通过子类本身可以访问到父类的静态函数,输出“hello”
console.log(Child.staticMethod());