class 关键字定义类

在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类;但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已;

怎么定义一个类:(和 function 关键字很类似)

  • 类声明:class Object {}
  • 类表达式:let Object = class {} ```javascript // 类的声明 class Person { }

// 类的表达式 var Animal = class { }

// es6的类和构造函数非常类似,因为class其实是语法糖,所以类拥有构造函数一切的原型操作 console.log(Person.prototype) // {} console.log(Person.prototype.proto) // [Object: null prototype] {} console.log(Person.prototype.constructor) // [class Person]

// 因为 typeof 中没有类的类型,所以还是识别出是函数 console.log(typeof Person) // function

var p = new Person() console.log(p.proto === Person.prototype) // true

  1. <a name="vk6wR"></a>
  2. # 类的构造函数
  3. class 定义类,我们会发现一个问题。之前构造函数就是类,函数可以接收参数,然后来初始化对象。现在变成 class 了,还怎么接收参数初始化对象呢?<br />为了解决这个问题,所以就在类中设计了一个固定方法`constructor()`,把它当“构造函数”。每次实例化对象都会自动调用这个方法。并且每个类只能有一个`constructor()`函数。这个设计从外表上看和 Java 的构造函数是很像的。
  4. new 实例化 class 定义的类时,会调用这个 constructor 函数,具体的执行细节:(和之前 new 构造函数一模一样)
  5. 1. 在内存中创建一个新的对象(空对象);
  6. 2. 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性;
  7. 3. 构造函数内部的this,会指向创建出来的新对象;
  8. 4. 执行构造函数的内部代码(函数体代码);
  9. 5. 如果构造函数没有返回非空对象,则返回创建出来的新对象;
  10. ```javascript
  11. class Person {
  12. // 构造函数
  13. constructor(name, age) {
  14. this.name = name,
  15. this.age = age
  16. }
  17. }

类中的方法

类的实例方法

es6 之前实例方法需要定义到类的原型上,现在也是一样,只是简化了代码,可以直接写了。

  1. class Person {
  2. // 构造函数
  3. constructor(name, age) {
  4. this.name = name,
  5. this.age = age
  6. }
  7. // 实例方法(Java 中的成员方法)
  8. // 方法直接写,不用再这样赋值 Person.prototype.eat = function() {}
  9. eat() {
  10. console.log(this.name + ' eating');
  11. }
  12. }
  13. new Person('zs', 18).eat() // zs eating
  14. console.log(Person.prototype.eat === new Person().eat); // true

类的访问器方法(setter,getter)

属性描述符时有讲过定义类时可直接添加 setter 和 getter 函数,class 定义的类中也可以,并且语法也一样。

  1. class Person {
  2. constructor(name) {
  3. this.name = name,
  4. this._gender = undefined // _gender 私有
  5. }
  6. // “方法名”被注册为一个向外暴露的实例属性
  7. set gender(value) {
  8. console.log('_gender 被赋值了 ' + value);
  9. this._gender = value
  10. }
  11. get gender() {
  12. console.log('_gender 被读取了');
  13. return this._gender;
  14. }
  15. }
  16. let p = new Person()
  17. p.gender = 'man'
  18. console.log(p.gender)
  19. // _gender 被赋值了 man
  20. // _gender 被读取了
  21. // man

类的静态方法(类方法)

静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义

  1. class Person {
  2. constructor(name) {
  3. this.name = name
  4. }
  5. // 静态方法
  6. static createObj(password) { // 定义一个工厂方法来掩盖 new 产生对象过程
  7. return password == 123 ? new Person('hhh') : '爬爬爬'
  8. }
  9. }
  10. console.log(Person.createObj(123)); // Person { name: 'hhh' }

ES6类的继承 = extends + super

es6 提供了 extends 关键字完成了继承,相当于9. 原型链与继承中封装的inheritPrototype()函数,只是继承了方法,属性继承得用 super 关键字。

super关键字有两个作用:

  1. 在子类的构造函数中调用父类的构造函数,继承属性
  2. 在子类的实例方法或静态方法中调用父类的实例方法或静态方法,复用父类逻辑 ```javascript class Person { constructor(name) { this.name = name }

    eat() { console.log(this.name + ‘ eating’); }

    static happy() { return ‘放开玩吧’ } }

class Student extends Person { constructor(sno, name) { super(name) // 相当于 Person.call(this, name),属性还是定义到了Student实例对象自己身上 this.sno = sno }

// 重写父类方法 eat() { console.log(this.name + ‘今晚吃啥啊’); }

// 复用父类方法逻辑 sleep() { super.eat() // 调用了父类的方法 console.log(‘这个年纪你睡得着觉哒?’); } static unhappy() { console.log(‘来不及了,’ + super.happy()); } }

let stu = new Student(111, ‘zs’)

console.log(stu); // Student { name: ‘zs’, sno: 111 } —拥有了父类的属性

stu.eat() // zs今晚吃啥啊 —调用的是子类重写的方法

stu.sleep() // zs eating,这个年纪你睡得着觉哒?—复用了父类方法的逻辑 Student.unhappy() // 来不及了,放开玩吧

  1. [babel官网](https://babeljs.io) 中 Try it out 模块提供了代码的实时转换,有兴趣可以看看这些继承语法糖是怎么实现的。
  2. 阅读源码可能会遇到的问题:
  3. 1. 浮躁
  4. 2. 看到后面忘记前面的东西
  5. 1. 位置打标记
  6. 2. 函数写注释,提醒参数
  7. 3. 读完一个函数不知道函数要干嘛,读得多了就知道了
  8. 4. debugger
  9. <a name="xjkX9"></a>
  10. ## 继承内置类
  11. 可能会要去增强内置类的功能,这时候就要去继承内置类
  12. ```javascript
  13. class MyArray extends Array {
  14. firstItem() { // 获取数组第一个元素
  15. return this[0]
  16. }
  17. lastItem() { // 获取数组最后一个元素
  18. return this[this.length-1]
  19. }
  20. }
  21. var arr = new HYArray(1, 2, 3)
  22. console.log(arr.firstItem()) // 1
  23. console.log(arr.lastItem()) // 3

类的混入 mixin

JavaScript 的类和 Java 一样只支持单继承:也就是只能有一个父类。当我们想要获得两个类的功能时,就可以使用类的混入。
有些语言原生提供了类的混入能力,而 JavaScript 没有。但我们可以利用一些手段达到目的。比如类C 想要继承 类A 和类B 的部分功能,既然不能同时继承,那就一个一个继承就完了。类C 继承类B,类B 继承类A,这样类C 就能同时拥有另外两个类的功能。
所以JavaScript 实现类的混入,就是在一个方法中设置中间类的继承,并将中间类返回。

  • 这个思想其实和9. 原型链与继承中道格拉斯大佬实现原型式继承的思想一样,利用了一个中间人作转接。 ```javascript // 假设 Student 只能继承 Person,而Person是个第三方类,无法被更改,而Student 又需要 fly 方法 class Person { constructor(name) { this.name = name } eat() { console.log(this.name + ‘ eating’) } }

// 在 Person 类中混入 fly 方法 function mixinFly(BaseClass) { return class extends BaseClass { // 匿名中间类 fly() { console.log(‘flying~’) } } }

// Student 类继承混入 fly 方法后的 Person 类 class Student extends mixinFly(Person) { constructor(name) { super(name) } }

let p = new Student(‘zs’)

p.fly() // flying,子类对象拥有了混入的 fly 的方法 p.eat() // zs eating,也拥有父类的属性和方法

  1. 但是这种方法有个很大的缺点,因为方法内部的继承只是用了 extends,没有用 super,所以只继承了方法,没有继承属性。也就只能混入方法,无法混入属性。
  2. react 的高阶组件中就有类似的设计场景<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22919157/1649260624402-e0c7c0af-daef-4924-9a94-9094df3de11f.png#clientId=uf53578d2-938f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=430&id=ufe4d6b30&margin=%5Bobject%20Object%5D&name=image.png&originHeight=537&originWidth=531&originalType=binary&ratio=1&rotation=0&showTitle=false&size=230571&status=done&style=none&taskId=ub032e3f8-05e3-4ac4-a232-224c694a1d6&title=&width=424.8)
  3. <a name="VzPVG"></a>
  4. # JavaScript 中的多态
  5. 维基百科对多态的定义:多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口,或使用一<br />个单一的符号来表示多个不同的类型。<br />说人话就是:不同的数据类型进行同一个操作,表现出不同的行为,就是多态的体现。
  6. 传统的面向对象语言体现多态,有三个前提
  7. 1. 继承
  8. 2. 子类重写父类的方法
  9. 3. 子类实例由父类类型来引用
  10. ```java
  11. class Shape {
  12. getArea() {
  13. System.out.println(100);
  14. }
  15. }
  16. class Rectangle extends Shape { // 继承
  17. getArea() { // 重写
  18. System.out.println(200);
  19. }
  20. }
  21. class Circle extends Shape { // 继承
  22. getArea() { // 重写
  23. System.out.println(300);
  24. }
  25. }
  26. Shape shape1 = new Rectangle();
  27. Shape shape2 = new Circle();
  28. shape1.getArea(); // 200
  29. shape2.getArea(); // 300
  30. // 不同的数据类型 Rectangle和Circle,进行同一个操作,getArea(),表现出不同的行为,200和300

而 JavaScript 的多态没有那么严格,比较松散,但也能达到多态概念的描述。JavaScript 的多态主要是体现在 js 的函数没有限制参数类型。

  1. function foo(n, m) {
  2. return n + m
  3. }
  4. console.log(foo(1, 2))
  5. console.log(foo('月满', '西楼'))
  6. // 不同的数据类型,数字和字符串,进行同一操作 foo,表现出不同行为,一个相加一个拼接
  1. class Car {
  2. getArea() {
  3. return 100
  4. }
  5. }
  6. class Person {
  7. getArea() {
  8. return 200
  9. }
  10. }
  11. // 核心还是参数不限制类型,这样两个类都能被接收
  12. function calcArea(foo) {
  13. console.log(foo.getArea())
  14. }
  15. var person = new Person()
  16. var car = new Car()
  17. calcArea(person) // 200
  18. calcArea(car) // 100