前端学习笔记(十四)-类

227 阅读5分钟

在学习 react 之前。
今天补一下 ES6 的一些语法。学习材料为红宝书。

1.类

1.1 类定义

写法很像其他语言。

class Person {};

和函数不同:

  • 类的声明不会被提升。
  • 类的作用域为块级作用域,和 let,const 一样。
  • 类中的代码默认都是 strcit mode

类可以赋值给变量,赋值后的变量可以通过 name 属性访问类的初始定义名字,且复制后类的初始名字就无法在类定义之外的区域访问了:

let RenamedPerson = class Person {
	identify() {
		console.log(RenamedPerson.name); // 输出 Person
		console.log(Person.name); // 也是输出 Person
						 // 可以访问原 Person 标识符
						 // 并且这个 Person 也有name属性
	}
};

console.log(RenamedPerson.name); // 输出 Person
console.log(Person); // reference 错误

1.2 类构造函数

类也有构造函数。
不过,类有为此专门的关键字 constructor 来定义构造函数。
当实例化这个类的时候,类便会调用构造函数。相当于 python 里的 __init__

class Person {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
}

也可以不含 constructor,这样相当于构造函数为空。

1.2.1 实例化

当使用 new 来实例化类的时候, js 解释器能够感知并调用构造函数来进行实例化。
当执行 let person = new Person(); 的时候,其背后是以下行为。

  1. 内存中创建一个对象(因为此时还没有赋值,现在用 xxx 表示这个对象)。
  2. 把这个对象的 [[prototype]] 指针指向构造函数的 prototype。(从浏览器上来看就是 xxx.__proto__ = Person.prototype
  3. 准备执行构造函数,为了执行构造函数,首先需要把构造函数里的 this 指向这个新对象。
  4. 执行构造函数,给这个对象添加属性(xxx.name = name 这样,因为 this 指的就是 xxx)。
  5. 构造完毕,如果构造函数没有返回值,则返回这个对象(一般情况)。如果构造函数有返回一个对象,则返回这个对象,并且这个对象和类没有任何关系(除非这个对象本身就和类有关系)。
  6. 把新对象赋值给 person(let person = xxx)。

1.2.2 和普通构造函数的区别

  • 使用类时,如果不使用 new,就会报错。
  • 普通构造函数不用 new,就会以 window 作为 this 实例化,很坑。
  • 实例化之后,可以访问 constructor,但是这个 constructor 是类不是类的构造函数
console.log(person.constructor); // 输出 Person
console.log(Person.prototype.constructor); // 还是输出 Person
  • 不过类构造出来的实例属于类的实例,而不是构造函数的实例,这一点要记住。
person instanceof Person; // true
person instanceof Person.constructor; // false

1.3 类仍然是函数

  • Js 中并没有类这个类型,虽然类的定义的语法和函数极其不同,但是一旦声明了之后,类就完全是函数了。使用 typeof 也是输出为函数。
  • 类可以被当做参数传递

1.4 原型成员

为了在实例间共享方法,使用原型。能够共享同一种方法,减少每次构造的时间。
原型方法定义很简单,在类快中所有定义的内容都在原型上:

class Person {
    constructor() {
        // 定义在构造器中,所有实例中都有,不共享。
        this.myFunc = function() {let a = 1;};
    }
    // 定义在类块中,实例中没有,要去原型上找
    myFunc() {
        let a = 1;
    }
}

不能在类块中添加属性,会报错。
原型方法里的 this 指的是这个实例

  • 类定义也支持get,set访问器

1.5 静态类方法

有些方法是定义在类上的,无需实例化即可使用。实例化对象和原型都无法调用这种方法,只有类本身能调用。

class Person {
    ...
    
    static addName(x, y) {
        console.log(x + y);
    }
}
Person.addName("a", "b"); // 输出 "ab"
person1.addName; // 输出 undefined

静态方法里的 this 指的是这个类。(其他两个里 this 都是指实例对象)

静态类方法可以作为实例工厂,在里面实例化该类。

class Person {
    ...
    static createBadGuy() {
        return new Person("张三");
    }
}

1.6 继承

类的继承使用了新的语法,但是背后依然是原型链。

1.6.1 单继承

使用 extends 关键字,就可以继承类,甚至还可以继承普通的构造函数(为了兼容)。

class Person {};
class Teacher extends Person {}

// 也可以继承普通构造函数
function Person() {}
class Teacher extends Person {}

静态方法和原型方法也会被继承。

1.6.2 派生类

在派生类块中使用 super 引用父类构造函数。注意 super 用法非常限定:

class Person {/*假设这里有一堆方法*/}

class Teacher extends Person {
    constructor(x, y, z) {
        // 必须先用 super(),才能引用 this,不然会报错
        super(x, y); // 相当于普通构造函数继承时使用的 Person.call(this, x, y)
        
        this.z = z; // 派生类的专有属性,放在 super 语句前面会报错
        
        console.log(this); // 输出一个Teacher对象,如果不指定返回值,就会返回该对象
    }
    
    myPrototpeFunc() {
        super.personFunc(); // super 的另一个用法,可以调用父类的原型方法。
        
        super.personStatic(); // 报错,只有在静态方法中才能调用父类的静态方法。
        
        Person.personStatic(); // 非要用的话,只能这么用。
    }
    
    static myStatic() {
        super.personStatic(); // 只有在静态方法中才能调用父类的静态方法。
    }
}
  1. 构造函数里必须先使用 super(),才能用 this。
  2. 只有在静态方法里,使用 super.staticFunc(),才能调用父类的静态方法(也只能调用静态方法)。如果要在别的地方用,就得直接调用父类 Person.staticFunc()。
  3. 在构造函数和原型方法里使用 super.xxxFunc(),只能调用父类的原型方法。
  4. 2 和 3 里提到的 super 都可以用 this 代替。
  5. 原型方法和静态方法都会被自动继承。
  6. 构造函数可以不声明,这样子会自动声明 super()。一旦声明了构造函数,就必须加 super()。

1.7 抽象基类

抽象基类为一个类,可以被继承,但是不能被实例化。 js 没有对应语法,但是通过 new.target 可以完成。new.target 表示 new 的对象。

class Person {
    constructor() {
        if (new.target === Person){
            throw new Error("Person 是抽象基类,不能被实例化");
        }
    }
}

1.8 类混入(多类继承)

js 不显示支持类混入,常用方法是:
有 a,b,c 三个类,d 想要继承这三个类,那就得先让 b 继承 c ,然后 a 继承 b ,最后 d 继承 a。

  • 然而很多框架抛弃这种方式,转而使用复合模式。