【ES6】从Class的基本用法到Class的继承(一举拿下)

222 阅读5分钟

一. Class的基本用法

1. 构造函数和对象: ES5和ES6

1.1 ES5里构造函数和对象是怎么使用的?

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.say = function(words){
    console.log(`${this.name} is saying ${words}.`);
}

let p = new Person('半个头像的女同学', 22);
p.say('hello World');		// 半个头像的女同学 is saying  hello World

1.2 ES6里构造函数的写法

let methodName = 'say';

class Person {
    // 写成这个样子的函数都是在prototype上的。
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    [methodName](words){
        console.log(`${this.name} is saying ${words}.`);
    }
}

let p = new Person('半个头像的女同学', 25);
p.say('hello world');

let p1 = new Person('半个头像的女同学', 27);

1.3 ES6写法与ES5写法的相同点

let methodName = 'say';

class Person {
    // 写成这个样子的函数都是在prototype上的。
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    [methodName](words){
        console.log(`${this.name} is saying ${words}.`);
    }
}

let p = new Person('半个头像的女同学', 25);
p.say('hello world');

let p1 = new Person('半个头像的女同学', 27);

// 1.3 ES6写法与ES5写法的相同点
console.log(Person === Person.prototype.constructor);	// true
console.log(p.constructor === Person.prototype.constructor);	// true
console.log(p.say === Person.prototype.say);	// true

1.4 ES6写法与ES5写法的不同点

1.4.1 用class方式实现的函数,是不可以枚举的。
let methodName = 'say';

class Person {
    // 写成这个样子的函数都是在prototype上的。
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    [methodName](words){
        console.log(`${this.name} is saying ${words}.`);
    }
}

let p = new Person('半个头像的女同学', 25);
p.say('hello world');

let p1 = new Person('半个头像的女同学', 27);

console.log(Object.keys(Person.prototype));		// []

但是我们看ES5的情况,是可以枚举的

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.say = function(words){
    console.log(`${this.name} is saying ${words}.`);
}

let p = new Person('半个头像的女同学', 22);
p.say('hello World');	
console.log(Object.keys(Person.prototype));		// ["say"]
1.4.2 用class方式实现的构造函数,调用时必须使用new,否则报错
let methodName = 'say';

class Person {
    // 写成这个样子的函数都是在prototype上的。
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    [methodName](words){
        console.log(`${this.name} is saying ${words}.`);
    }
}

let p = new Person('半个头像的女同学', 25);
p.say('hello world');

let p1 = new Person('半个头像的女同学', 27);

Person('aaa', 25);		// 这里会报错,没有new

报错

但是我们看ES5可以

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.say = function(words){
    console.log(`${this.name} is saying ${words}.`);
}

let p = new Person('半个头像的女同学', 22);
p.say('hello World');
Person('aaa', 25);		// 半个头像的女同学 is saying hello World.

2. class 表达式

let Person = class {
    
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    say(words){
        console.log(`${this.name} is saying ${words}.`);
    }
}

2.1 表达式可以写成立即执行函数的形式

let person = new class Person{		// 注意:这里有个new
    
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    say(words){
        console.log(`${this.name} is saying ${words}.`);
    }
}('半个头像的女同学', '18');
person.say('hello');	// 半个头像的女同学 is saying hello.

2.2 Person 可以省略(class表达式可以没有class名字)

let person = new class {		// 看:这里的Person可以省略

    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    say(words){
        console.log(`${this.name} is saying ${words}.`);
    }
}('半个头像的女同学', '18');
person.say('hello');	// 半个头像的女同学 is saying hello.

3. function 会提升变量; 但是class不会

所有的class,必须先定义后使用

// 用function不会报错
console.log(Person);
function Person(){

}
// 用class会报错
console.log(Person);		// Person is not defined

class Person {
    constructor(){

    }
}
// 用class写在后面就不会报错
class Person {
    constructor(){

    }
}

console.log(Person);

4. this的指向

我们先看一下下面的代码有什么问题

class Person {

    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    
    say(words){
        console.log(`${this.name} is saying ${words}.`);		// a
    }

}

let p = new Person('半个头像的女同学', 22);

let f = p.say;

f('Hello');

结果会报错:Cannot read property 'name' of undefined

我们经常会遇到把对象里的函数给到另一个变量,后面再使用的情况,但是函数里的this不能正确获取。

上a处获取不到变量,因为this是在调用的时候确定的,而调用f的是windows,window里没有name,所以报错,解决办法:

4.1 用箭头函数

箭头函数里的this是定义的时候确定的,所以name已经确定了用Person对象里的,所以即使在调用f的是windows,但是name还是用Person对象里的name,最后能正常输出。

class Person {

    constructor(name, age){
        this.name = name;
        this.age = age;
        // 箭头函数
        this.say = words => {
            console.log(`${this.name} is saying ${words}.`);		
        }
    }
}

let p = new Person('半个头像的女同学', 22);

let f = p.say;

f('Hello');

4.2 用bind绑定

class Person {

    constructor(name, age){
        this.name = name;
        this.age = age;
        this.say = this.say.bind(this);		// 这里
    }

    
    say(words){
        console.log(`${this.name} is saying ${words}.`);		// a
    }

}

let p = new Person('半个头像的女同学', 22);

let f = p.say;

f('Hello');		// 半个头像的女同学 is saying Hello.

5. getter/setter, 属性

let obj = {
    _a: 0,
    get a(){
        return this._a;
    },
    set a(x){
        this._a = x;
    }
}


console.log(obj.a);		  // 0 	调用get方法
obj.a = 3;				// 调用set方法
console.log(obj.a);			// 3  

6. 静态属性和静态方法

你们看下面的代码有什么错误

class Person {
    // 静态属性
    static staticNumber = 5;

    // 静态方法
    static staticMethod(words){
        console.log(`static say ${words}`);
    }

    constructor(){
    }
}

let p = new Person();
p.staticMethod('hello!');

结果报错:p.staticMethod is not a function

因为静态属性和静态方法不同通过new的方式调用。

正确写法如下:

class Person {
    // 静态属性
    static staticNumber = 5;

    // 静态方法
    static staticMethod(words){
        console.log(`static say ${words}`);
    }

    constructor(){
    }
}

Person.staticMethod('hello!');		// static say hello!
console.log(Person.staticNumber);		// 5

等价于这个写法

class Person {
    constructor(){
    }
}

Person.staticNumber = 5;
Person.staticMethod = words => {
    console.log(`static say ${words}`);
}

Person.staticMethod('hello!');
console.log(Person.staticNumber);

二. Class的继承

1. 继承怎么用?

  1. 通过调用基类构造函数继承
  2. super()代表基类构造函数
  • 2.1 super()必须在构造函数中调用,ES5中,先有this,后调用基类构造函数;ES6中,this是super初始化的。
  • 2.2 在派生类构造函数完成以前,必须调用基类构造函数
  • 2.3 在调用super以前,不能使用this
class Person {

    constructor(name, age){
        this.name = name;
        this.age = age;
        this.say = this.say.bind(this);
    }

    say(words){
        console.log(`${this.name} is saying ${words}.`);
    }
}

class Student extends Person{ // 派生类 派生 自基类,任何派生类的实例都是基类的实例
    constructor(studentId, ...args){
        // 2.1和2.2 必须通过super调用基类构造函数
        super(...args);
        console.log(1, args);
        
        // 2.3 在调用super以前,不能使用this
        this.studentId = studentId;
        this.study = this.study.bind(this);
    }

    study(knowledge){
        console.log(`Student ${this.studentId} is studying ${knowledge}`);
    }
}

let xiaohong = new Student(1, '小红', 13);
xiaohong.say('hello!');
console.log(xiaohong.name);
xiaohong.study('物理');
console.log(xiaohong.studentId);

他们的继承关系是这样的

继承关系 Student.prototype是Person构造出来的

console.log(Object.getPrototypeOf(Student) === Person);		// true
console.log(Student.prototype.__proto__ === Person.prototype);		// true

2.把supper拿下

2.1 super.属性

情况1:在读取值的时候,充当基类的原型对象
// 基类
class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}

// 派生类
class Student extends Person{
    constructor(studentId, ...args){
        super(...args);
        this.studentId = studentId;
    }
    test(){
        console.log(super.name, this.name);		// undefined "半个头像的女同学"
    }
}

let student = new Student(1,'半个头像的女同学',22);
student.test();

派生类里,super.name读取不到基类里的name属性,需要通过this.name才能读取到。

情况2:在赋值的时候,充当派生类this使用
// 基类
class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}

// 派生类
class Student extends Person{
    constructor(studentId, ...args){
        super(...args);
        this.studentId = studentId;
    }
    test(){
    	super.name = '女同学';
        console.log(super.name, this.name);		// 这里输出:undefined "女同学"
    }
}

let student = new Student(1,'半个头像的女同学',22);
student.test();

派生类里,如果用super.name进行赋值,可以当成this.name进行使用。

情况3:如果super后面是方法,调用的是基类原型的方法,但是函数中绑定的this是派生类的
// 基类
class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    f(){
        console.log('in Person');
    }
}

// 派生类
class Student extends Person{
    constructor(studentId, ...args){
        super(...args);
        this.studentId = studentId;
    }
    test(){
        console.log(super.f, this.f);		// 这里输出:in Person 	in Student
    }
    f(){
        console.log('in Student');
    }
}

let student = new Student(1,'半个头像的女同学',22);
student.test();

super.属性是读取不到基类的属性,但是super.方法是可以读取得到基类的方法,但是this.方法是派生类的方法。