JavaScript中class类、ES6中实现继承的方法

导读:本篇文章讲解 JavaScript中class类、ES6中实现继承的方法,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.class方式定义类

1.1 class的定义类结构

我们会发现,按照前面的构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解

  • ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类
  • 但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已
  • 所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系;

那么,如何使用class来定义一个类呢

  • 可以使用两种方式来声明类:类声明类表达式

    // 方式一: 类声明
    class Person {
      
    }
    
    // 创建实例对象
    var p1 = new Person();
    var p2 = new Person();
    console.log(p1, p2); // Person {} Person {}
    
    // 方式二: 类表达式(少见)
    var Student = class {
      
    }
    
    // 创建实例对象
    var stu1 = new Student();
    var stu2 = new Student();
    console.log(stu1, stu2); // Student {} Student {}
    

注意: 类结构中定义多个内容, 不需要加逗号进行分隔

1.2 class类中构造函数

如果我们希望在创建对象的时候给类传递一些参数,这个时候应该如何做呢?

  • 每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor
  • 当我们通过new操作符操作一个类的时候会调用这个类的构造函数constructor
  • 每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常

当我们通过new关键字操作类的时候会调用这个constructor函数,并且执行如下操作:

  1. 在内存中创建一个新的对象(空对象)
  2. 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性
  3. 构造函数内部的this,会指向创建出来的新对象
  4. 执行构造函数的内部代码(函数体代码);
  5. 如果构造函数没有返回非空对象,则默认返回创建出来的新对象
class Person {
  // 类中的构造函数
  // 当我们通过new关键字调用一个Person类时, 默认调用class中的constructor
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

var p1 = new Person("coder", 18);

console.log(p1.name, p1.age); // coder 18
// 实例的隐式原型会绑定Person的显式原型
console.log(p1.__proto__ === Person.prototype); // true

1.3 class类中实例方法

在上面我们定义的属性都是直接放到了this上,也就意味着它是放到了创建出来的新对象中:

  • 在前面我们说过对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享;
  • 这个时候我们可以直接在类中定义
class Person {
  // 类中的构造函数
  // 当我们通过new关键字调用一个Person类时, 默认调用class中的constructor
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 类中的实例方法
  running() {
  	console.log("running");
	}
	eating() {
  	console.log("eating");
	}
}

var p1 = new Person("coder", 18);

// 调用实例方法
p1.running();
p1.eating();

类中多个内容之间, 不需要逗号分隔

1.4 类和构造函数异同

这里有一段分别使用function和class定义类的代码:

// function定义类
function Person1(name, age) {
  this.name = name;
  this.age = age;
}
// 类的实例方法
Person1.prototype.running = function () {
  console.log("Person1 running~");
};
Person1.prototype.eating = function () {
  console.log("Person1 eating~");
};

// class定义类
class Person2 {
  // 1.类的构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 2.类的实例方法
  running() {
    console.log("Person2 running~");
  }
  eating() {
    console.log("Person2 eating~");
  }
}

我们来研究一下类和构造函数的一些特性:

  • 你会发现类和我们的构造函数的特性其实是一致的;
// 创建Person1的实例
var p1 = new Person1("coder", 18);

console.log(typeof Person1); // function
console.log(Person1.prototype); // {running: ƒ, eating: ƒ, constructor: ƒ}
console.log(Person1.prototype.constructor); // Person1
console.log(p1.__proto__ === Person1.prototype); // true
// 创建Person2的实例
var p2 = new Person2("kaisa", 19);

console.log(typeof Person2); // function
console.log(Person2.prototype); // {constructor: ƒ, running: ƒ, eating: ƒ}
console.log(Person2.prototype.constructor); // Person2
console.log(p2.__proto__ === Person2.prototype); // true

不同点: function定义的类可以作为普通函数去调用; class定义的类, 不能作为一个普通函数调用

// function定义的类可以作为普通函数调用
Person1("aaa", 11);

// class定义的类不可作为普通函数调用, 会报错
Person2("bbb", 22);

1.5 对象的访问器方法

我们之前讲对象的属性描述符时有讲过对象可以添加setter和getter函数的, 这里我们回顾一下:

  • 我们对一些属性编写的get和set方法, 我们可以称之为getter方法和setter方法

方式一: 通过属性描述符定义访问器

var obj = {
  // 程序员之间的约定: 以下划线开头的属性和方法, 是不在外界访问的
  _name: "kaisa"
}

Object.defineProperty(obj, "name", {
  set: function(value) {
    this._name = value
  },
  get: function() {
    return this._name
  }
})

方式二: 之间在对象定义访问器(很少见,了解)

var obj = {
  _name: "kaisa",
  set name(value) {
    this._name = value;
  },
  get name() {
    return this._name;
  },
};

1.6 class的访问器方法

那么类也是可以添加getter和setter函数

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

  set age(value) {
    this._age = value;
  }

  get age() {
    return this._age;
  }
} 

1.7 class类的静态方法

静态方法通常用于定义直接使用类来执行的方法不需要有类的实例回顾一下我们之前ES5定义类的静态方法(类方法)

function Person() {}
// 添加实例方法
Person.prototype.running = function() {}

// 添加静态方法(类方法)
Person.eating = function() {}

var p1 = new Person()
p1.running() // 调用实例方法
Person.eating() // 调用静态方法

ES6中class, 使用static关键字来定义静态方法(类方法):

class Person {
  // 实例方法
  eating() {}
  running() {}

  // 类方法
  static studying() {}
}
var p1 = new Person();
p1.running(); // 调用实例方法
p1.eating(); // 调用实例方法

Person.studying(); // 调用类方法

注意: 实例方法的this指向实例对象, 静态方法的this指向类

2.ES6实现继承

2.1 extends实现继承

前面我们花了很大的篇幅讨论了在ES5中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的

  • ES6中新增了使用extends关键字,可以方便的帮助我们实现继承

我们来写代码感受一下ES6中的继承吧

  • 先定义一个Student类

    class Student {
      constructor(name, age, id, score) {
        this.name = name;
        this.age = age;
        this.id = id;
        this.score = score;
      }
    
      running() {
        console.log(this.name + "running~");
      }
    
      eating() {
        console.log(this.name + "eating~");
      }
    
      studying() {
        console.log(this.name + "studying~");
      }
    }
    
  • 再定义一个Teacher类

    class Teacher {
      constructor(name, age, title) {
        this.name = name;
        this.age = age;
        this.title = title;
      }
    
      running() {
        console.log(this.name + "running~");
      }
    
      eating() {
        console.log(this.name + "eating~");
      }
    
      teaching() {
        console.log(this.name + "teaching");
      }
    }
    
  • 我们发现两个类之间有许多重复的属性和方法, 我们可以定义一个父类, 使相同的属性和方法放在父类中

    // 定义一个父类, 将相同的属性方法添加到父类中
    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    
      running() {
        console.log(this.name + "running~");
      }
    
      eating() {
        console.log(this.name + "eating~");
      }
    }
    
  • 子类再使用extends关键字, 可以快速实现继承

    // 让Student继承自Person
    class Student extends Person {
      constructor(id, score) {
        // super关键字马上会讲到 这里是调用父类构造函数, 将属性传进去
        super(name, age);
        this.id = id;
        this.score = score;
      }
    
      studying() {
        console.log(this.name + "studying~");
      }
    }
    
    // 让Teacher继承自Person
    class Teacher extends Person {
      constructor(title) {
        // super关键字马上会讲到 这里是调用父类构造函数, 将属性传进去
        super(name, age);
        this.title = title;
      }
    
      teaching() {
        console.log(this.name + "teaching~");
      }
    }
    
  • 这样我们就实现了子类Student和Teacher继承自Person,子类均可访问父类或自身的属性和方法

    var stu1 = new Student("kaisa", 18, 11, 99);
    console.log(stu1); // Student  {name: 'kaisa', age: 18, id: 11, score: 99}
    stu1.eating(); // kaisa eating~
    stu1.studying(); // kaisa studying~
    
    var tec1 = new Teacher("coder", 22, "数学");
    console.log(tec1); // Teacher  {name: 'coder', age: 22, title: '数学'}
    tec1.running(); // coder running~
    tec1.teaching(); // coder teaching~
    

2.2 super关键字

Class为我们的方法中还提供了super关键字:

  • 执行 super.method(…)调用一个父类方法
  • 执行 super(…)调用一个父类constructor(只能在constructor中使用)

我们会发现在上面的代码中我使用了一个super关键字,这个super关键字有不同的使用方式

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

位置一: 上面我们以及在子类构造函数中使用过了

位置二: 实例方法

class Animal {
  running() {
    console.log("running");
  }

  eating() {
    console.log("eating");
  }
}

class Dog extends Animal {
  // 如果子类对继承过来父类的running方法不满足
  // 重新实现父类方法也称之为, 父类方法的重写
  running() {
    // 比如父类的running方法只满足了我们一部分需求
    console.log("未满足的一部分需求");
    // 再通过super调用父类方法
    super.running();
  }
}

var dog = new Dog();
dog.running(); // 未满足的一部分需求   running

位置三: 静态方法

class Animal {
  static sleep() {
    console.log("睡觉");
  }
}

class Dog extends Animal {
  static sleep() {
    console.log("趴着");
    super.sleep();
  }
}

// 子类的静态方法中调用父类的静态方法
Dog.sleep();

2.3 继承内置类

我们也可以让我们的类继承自内置类,比如Array:

  • 我们可以继承自内置类之后, 对内置类进行一些扩展, 例如对Array中扩展一个获取数组中最后一个元素的方法

    // 创建一个新的类, 继承自Array进行扩展
    class MYArray extends Array {
      // 扩展一个方法: 获取数组的最后一个元素
      lastItem() {
        return this[this.length - 1];
      }
      
      // 可以使用get方法扩展成属性: 获取数组的第一个元素
    	get firstItem() {
    	  return this[0];
    	}
    }
    
    var arr = new MYArray(10, 20, 30, 40);
    console.log(arr.lastItem()); // 40
    console.log(arr.firstItem); // 10
    
  • 当然我们也可以直接对Array进行扩展

    // 直接通过原型对象对Array进行扩展
    Array.prototype.lastItem = function() {
      return this[this.length - 1]
    }
    
    var arr = new Array(10, 20, 30, 40);
    console.log(arr.lastItem()); // 40
    

3.类的混入(了解)

JavaScript的类只支持单继承:也就是只能有一个父类

class Animal {
  running() {
    console.log("running")
  }
}

class Flyer {
  flying() {
    console.log("flying")
  }
}

// Bird这个类想要同时继承Animal, Flyer在JavaScript中是不允许的
class Bird extends Animal, Flyer{}

那么在开发中我们我们需要在一个类中添加更多相似的功能时,应该如何来做呢

  • 这个时候我们可以使用混入(mixin)
// 传入一个类
function mixinAnimal(BaseClass) {
  // 返回一个新创建的类继承自传入的那个类, 并在新创建的类中添加running方法
  return class extends BaseClass {
    running() {
      console.log("running~");
    }
  };
}

// 传入一个类
function mixinFlyer(BaseClass) {
  // 返回一个新创建的类继承自传入的那个类, 并在新创建的类中添加flying方法
  return class extends BaseClass {
    flying() {
      console.log("flying");
    }
  };
}

class Bird {
  eating() {
    console.log("eating");
  }
}

// 创建第一个新的类继承自Bird,并且添加方法running, 在创建第二个类,继承自第一个创建的类, 并且添加flying方法
// 再创建第三个类, 接收第二个创建的类, 此时第三个类以及有三个类的方法了
class newBird extends mixinFlyer(mixinAnimal(Bird)) {}
var bird = new newBird();

bird.running();
bird.flying();
bird.eating();

这种写法和react的高阶组件中的写法是非常相似的, 建议了解一下这种思想

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120138.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!