出现的目的

什么是面向对象编程
ES6提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

  • 按照前面的构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。在ES6 ( ECMAScript2015)新的标准中使用了class关键字来直接定义类;
  • 但是类本质上依然是构造函数、原型链的语法糖而已;
  • 所以前面的构造函数、原型链更有利于理解类的概念和继承关系;

    1、类定义

    声明

    1. // 类声明
    2. // 注意:类名需要首字母大写。
    3. class Demo {
    4. // 构造函数 定义属性
    5. constructor() {
    6. this.name = '类的名称'
    7. }
    8. // 定义方法
    9. fn () {
    10. console.log('定义的方法1');
    11. }
    12. }
    13. const demo = new Demo();
    14. demo.fn1()
    15. // 类表达式
    16. const demo = class {};
    特点:
    1、函数声明可以提升(在定义前提前使用),但类定义不能
    2、函数受函数作用域限制,而类受块作用域限制

    类和构造函数的异同

    ```javascript class Person { constructor() { } } // function Person () {

// } console.log(Person.prototype); // Object console.log(Person.prototype.proto); // Object console.log(Person.prototype.constructor); // class Person {…} console.log(typeof Person); // function const p = new Person(); console.log(p.proto == Person.prototype); // true

  1. 发现,和构造函数是一样的,因此class只是构造函数的语法糖
  2. <a name="Zhk8w"></a>
  3. ## 2、构造函数 constructor
  4. <a name="LFm2M"></a>
  5. ### 构造函数 *
  6. constructor 关键字用于在类定义块内部创建类的构造函数,告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数
  7. - 当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作︰
  8. - 在内存中创建一个新的对象(空对象)
  9. - 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性
  10. - 构造函数内部的this,会指向创建出来的新对象
  11. - 执行构造函数的内部代码(函数体代码)
  12. - 如果构造函数没有返回非空对象,则返回创建出来的新对象
  13. 这个属性不是必须的<br />下面代码演示了,一般情况通过函数创建对象,以及通过class的方式创建对象,对比,实际上两个方式是一模一样的。
  14. ```javascript
  15. // ============ 一般的函数 ============
  16. function Person1(name,age){
  17. // 1、定义属性
  18. this.name = name
  19. this.age = age
  20. }
  21. // 2、定义方法
  22. Person1.prototype.running = function(){
  23. console.log(this.name + "正在跑步")
  24. }
  25. var p1 = new Person1("张三",18)
  26. p1.running() //张三正在跑步
  27. // ============ 类的构造方法 ============
  28. class Person2 {
  29. // 1、定义属性
  30. constructor(name,age) { // 类构造函数,这个名字是固定的,而且不能写多个;new 创建一个实例时,会执行里面的代码
  31. this.name = name
  32. this.age = age
  33. }
  34. // 2、定义方法
  35. // 实际也是给原型链添加方法,和上面一样的
  36. running(){
  37. console.log(this.name + "正在跑步")
  38. }
  39. }
  40. var p2 = new Person2("张三",18)
  41. p2.running() //张三正在跑步

new 操作符

类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操作符。

普通构造函数如果不使用 new 调用,那么就会以全局的 this(通常是 window)作为内部对象。
调用类构造函数时如果忘了使用 new 则会抛出错误。

  1. function Person() {}
  2. class Animal {}
  3. // 把 window 作为 this 来构建实例
  4. let p = Person();
  5. let a = Animal();
  6. // TypeError: class constructor Animal cannot be invoked without 'new'
  7. p.constructor();
  8. // TypeError: Class constructor Person cannot be invoked without 'new'
  9. // 使用对类构造函数的引用创建一个新实例
  10. let p2 = new p.constructor();

任意地方定义

  1. // 类可以像函数一样在任何地方定义,比如在数组中
  2. let classList = [
  3. class {
  4. constructor(id) {
  5. this.id_ = id;
  6. console.log(`instance ${this.id_}`);
  7. }
  8. }
  9. ];
  10. function createInstance(classDefinition, id) {
  11. return new classDefinition(id);
  12. }
  13. let foo = createInstance(classList[0], 1234); // instance 1234

立刻实例化

  1. let p = new class Foo {
  2. constructor(x) {
  3. console.log(x);
  4. }
  5. }('bar'); // bar
  6. console.log(p); // Foo {}

3、实例

每次通过new调用类标识符时,都会执行类构造函数。

在这个函数内部,可以为新创建的实例(this)添加“自有”属性。

至于添加什么样的属性,则没有限制。

而且在每个实例的属性都不共享,都是属于他们自己的

属性和方法

  1. class Person {
  2. constructor(name) {
  3. // 添加到 this 的所有内容都会存在于不同的实例上
  4. // 通过 实例.属性 访问
  5. this.name = name
  6. // 通过 实例.方法() 访问
  7. this.locate = () => console.log('instance');
  8. }
  9. // 在类块中定义的所有内容都会定义在类的原型上
  10. //共享属性
  11. // 1、通过 类名.prototype.属性 访问
  12. // 2、通过 实例名.__proto__.属性 访问
  13. otherName = 'human'
  14. //共享方法
  15. // 1、通过 类名.prototype.方法() 访问
  16. // 2、通过 实例名.__proto__.方法() 访问
  17. locate() {
  18. console.log('prototype');
  19. }
  20. }
  21. const p = new Person();
  22. p.locate(); // instance
  23. Person.prototype.locate() // prototype
  24. console.log(p.otherName); // human

访问器 get set *

可以给一个属性设置get和set方法,表示这个属性被读取时(get)执行什么,以及被设置(set)时执行什么。

类似于给对象的属性设置特性,可以查看:
JS原生对象

  1. class Person2 {
  2. constructor(name,age) {
  3. this.name = name
  4. this.age = age
  5. this._city = "成都"
  6. }
  7. running(){
  8. console.log(this.name + "正在跑步")
  9. }
  10. // 访问器(可以拦截对象属性的读取和写入的操作)
  11. get city(){ // 访问这个属性时执行
  12. console.log("读取了我所在的城市")
  13. return this._city
  14. }
  15. set city(newCity){ // 重新设置这个属性时执行
  16. console.log("重新设置了我所在的城市")
  17. this._city = newCity
  18. }
  19. }
  20. var p2 = new Person2("张三",18)
  21. p2.city = "重庆" // 输出:重新设置了我所在的城市

4、继承

继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。

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

继承基础 extends

继承任何拥有[[Construct]]和原型的对象。
这意味着不仅可以继承一个类,也可以继承普通的构造函数

  1. class Vehicle {}
  2. // 继承类
  3. class Bus extends Vehicle {
  4. // Bus类的属性和方法
  5. }
  6. // 或 let Bus = class extends Vehicle { // Bus类的属性和方法 }
  7. let b = new Bus();
  8. console.log(b instanceof Bus); // true
  9. console.log(b instanceof Vehicle); // true
  10. function Person() {}
  11. // 继承普通构造函数
  12. class Engineer extends Person {
  13. // Engineer类的属性和方法
  14. }
  15. let e = new Engineer();
  16. console.log(e instanceof Engineer); // true
  17. console.log(e instanceof Person); // true

继承父类的 this

指向调用相应方法的实例或者类

  1. class Vehicle {
  2. identifyPrototype(id) {
  3. console.log(id, this);
  4. }
  5. static identifyClass(id) {
  6. console.log(id, this);
  7. }
  8. }
  9. class Bus extends Vehicle {}
  10. let v = new Vehicle();
  11. let b = new Bus();
  12. b.identifyPrototype('bus'); // bus, Bus {}
  13. v.identifyPrototype('vehicle'); // vehicle, Vehicle {}
  14. Bus.identifyClass('bus'); // bus, class Bus {}
  15. Vehicle.identifyClass('vehicle'); // vehicle, class Vehicle {}

引用父类super *

注意∶在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!
super的使用位置有三个:子类的构造函数、实例方法、静态方法;

  1. class Person {
  2. constructor(name, age) {
  3. this.name = name;
  4. this.age = age
  5. }
  6. }
  7. // Student 称之为子类 (派生类)
  8. // 直接继承,this会报错
  9. class Student extends Person {
  10. constructor(name, age, sno) {
  11. this.name = name
  12. this.age = age
  13. this.sno = sno
  14. }
  15. }
  16. let stu = new Student('why', 18, 111)
  17. // Must call super constructor in derived class before accessing 'this' or returning from derived constructor

引用错误,必须在子类里,访问子类的this之前,通过super( )调用父类的构造方法;或者在return 之前,通过super( )调用父类的构造方法
正确写法

  1. class Person {
  2. constructor(name, age) {
  3. this.name = name;
  4. this.age = age
  5. }
  6. }
  7. // Student 称之为子类 (派生类)
  8. // 直接继承,this会报错
  9. class Student extends Person {
  10. constructor(name, age, sno) {
  11. super(name,age)
  12. this.sno = sno
  13. }
  14. }
  15. let stu = new Student('why', 18, 111)

类的模块化

文件1:

  1. class a {
  2. // 类a 的属性和方法
  3. }
  4. export {a}

文件2:

  1. import {a} from '路径/文件1.js'
  2. let b = new a()

因此可以把类专门写在一个js文件中,通过es6模块化的方式引入到业务文件中,直接new 创建实例来使用,避免业务文件臃肿,也利于其他业务复用。

babel转换成ES5代码

有的工具如webpack的babel,会把ES6的类转成ES5代码,为了可以适配IE10和更低的浏览器
image.png
这里多写了一个函数 _classCallCheck,目的是检查这个类class,不能让他可以直接被调用 Person( ),调用的话如果this不是她自己的实例(直接调用this就是window),就会抛出错误。除去则函数,可以看到class 就是构造函数的简写