一、类Class的概念
1.1 基本概念
Class是ES6中的一个语法糖,底层依然是构造函数,故它所实现的大部分功能ES5都能完成,但是Class可以使对象原型的写法更加清晰,更像面向对象编程的语法。
下面是通过构造函数和class创建实例的对比写法:
// 构造函数的方式
function Phone(brand, price) {
this.brand = brand
this.price = price
}
Phone.prototype.call = function() {
console.log('call me')
}
let huawei = new Phone('华为', 2999)
huawei.call()
console.log(huawei)
// class的方式
class CPhone {
constructor(brand, price) {
this.brand = brand
this.price = price
}
call() {
console.log('call me')
}
}
let onePlus = new CPhone('1+', 1234)
onePlus.call()
console.log(onePlus)
class中的constructor就是构造方法,其中的this指向类的实例对象。
类的数据类型是函数,类本身指向构造函数。
类的所有方法都定义在类的prototype属性上面:
class A {
constructor() {}
toString() {}
toValue() {}
}
// 上面的代码等同于下面的代码
function A () {
// constructor
};
A.prototype.toString = function() {};
A.prototype.toValue = function() {};
在类的实例上调用方法相当于调用类原型上的方法:
let a = new A();
a.constructor === A.prototype.constructor
1.2 class中的constructor
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
class A {
}
// 等同于
class A {
constructor() {}
}
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class A {
constructor() {
return Object.create(null);
}
}
console.log((new A()) instanceof A);
// false
注意:
- 实例的属性除非显示定义在本身(即this对象上),否则都定义在原型上(即class上)
- class声明不存在变量提升
- 类的方法内部如果有this,它默认指向类的实例
二、Class中的静态成员
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。 如果在一个方法前,加上 static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态成员"。
构造函数与class中静态成员的写法差异:
// 构造函数中的静态成员
function Phone() {
}
Phone.name = 'phone'
Phone.change = function() {
console.log('ccccc')
}
Phone.prototype.size = '5.8inch'
let nokia = new Phone()
console.log(nokia.name) // undefined
// nokia.change() // erorr
console.log(nokia.size) // 5.8inch
// class中的静态成员
class CPhone {
static name = 'phone'
static change() {
console.log('ccccc')
}
size = '5.8inch'
}
let nokia1 = new CPhone()
console.log(nokia1.name) // undefined
// nokia.change() // erorr
console.log(nokia1.size) // 5.8inch
如果静态方法包含this关键字,这个this指的是类,而不是实例。
class A {
static classMethod() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
A.classMethod();
// hello
静态方法classMethod调用了this.baz,这里的this指的是A类,而不是A的实例,等同于调用A.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
父类的静态方法,可以被子类继承。
class A {
static classMethod() {
console.log('hello');
}
}
class B extends A {}
B.classMethod() // 'hello'
三、Class中的继承
下面代码通过构造函数和class的方式实现继承:
// 构造函数的方式
function Phone(brand, price) {
this.brand = brand
this.price = price
}
Phone.prototype.call = function() {
console.log('打电话')
}
function SmartPhone(brand, price, color, size) {
Phone.call(this, brand, price)
this.color = color
this.size = size
}
SmartPhone.prototype = new Phone()
SmartPhone.prototype.constructor = SmartPhone
SmartPhone.prototype.photo = function() {
console.log('拍照')
}
SmartPhone.prototype.playGame = function() {
console.log('玩游戏')
}
const cuizi = new SmartPhone('崔子', 2222, 'black', '6.0inch')
console.log(cuizi)
// class中的继承
class Phone{
constructor(brand, price) {
this.brand = brand
this.price = price
}
call() {
console.log('打电话')
}
}
class SmartPhone extends Phone {
constructor(brand, price, color, size) {
super(brand, price)
this.color = color
this.size = size
}
photo() {
console.log('拍照')
}
playGame() {
console.log('打游戏')
}
}
const xiaomi = new SmartPhone('小米', 799, 'white', '5.8inch')
console.log(xiaomi)
值得注意的是,子类必须在constructor中调用super方法,否则新建的实例就会报错,这是因为子类自己的this对象必须通过父类构造函数来塑造,从而得到和父类相同的实例属性和方法,然后再对其进行加工(加上子类独有的实例属性和方法),如果不调用super子类就得不到this对象。
class Animal { /* ... */ }
class Cat extends Animal {
constructor() {
}
}
let cp = new Cat();
// ReferenceError
另一个需要注意的是,es5构造函数在调用父构造函数前可以访问this,但是es6的构造函数在调用父类构造函数(super)前不能访问this对象。
class A {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class B extends A {
constructor(x, y, name) {
this.name = name; // ReferenceError
super(x, y);
this.name = name; // 正确
}
}
如果子类没有显式定义constructor方法,该方法会被默认添加。
class Cat extends Animal {
}
// 等同于
class Cat extends Animal {
constructor(...args) {
super(...args);
}
}
此外,父类的静态方法也会被子类继承。
class A {
static hello() {
console.log('hello world');
}
}
class B extends A {
}
B.hello() // hello world
四、Class中的super
super在class中既可以当作函数调用,又可以当作对象使用。
4.1 super作为函数调用
super作为函数调用时,代表父类的构造函数。
class A {}
class B extends A {
constructor() {
super();
}
}
super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。
class A {
constructor() {
// new.target 指向正在执行的函数
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B。
4.2 super作为对象调用
在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
4.2.1 super对象在普通函数中调用
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代码中,子类B当中的super.p()将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
上面代码中,p是父类A实例的属性,super.p就引用不到它。如果属性定义在父类的原型对象上,super就可以取到。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
4.2.2 super对象在静态方法中调用
用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
const child = new Child();
child.myMethod(2); // instance 2
上面代码中,super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
还需要注意的是,在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
上面代码中,静态方法B.m里面,super.print指向父类的静态方法。这个方法里面的this指向的是B,而不是B的实例。
五、子类对父类方法的重写
class Phone{
constructor(brand, price) {
this.brand = brand
this.price = price
}
call() {
console.log('打电话')
}
}
class SmartPhone extends Phone {
constructor(brand, price, color, size) {
super(brand, price)
this.color = color
this.size = size
}
call() {
console.log('视频电话')
}
}
const xiaomi = new SmartPhone('小米', 799, 'white', '5.8inch')
xiaomi.call() // 视频通话
六、Class中的getter和setter
class Phone{
get price() {
console.log('price属性被读取了')
return '$30'
}
set price(newPrice) {
console.log('price属性被修改了')
}
}
let s = new Phone()
console.log(s.price) // price属性被读取了 $30
s.price = 'free' // price属性被修改了