修身养性,知行合一

  • 首页
  • 爱码
    • 系统
    • 数据库
    • JavaScript
    • CSharp
    • Python
  • 生活
    • 文化
    • 美食
  • 杂谈
  • 关于
修身养性,知行合一
码字,杂谈
  1. 首页
  2. 爱码
  3. 前端
  4. JavaScript
  5. 正文

真丶深入理解 JavaScript 原型和原型链(四):ES6中的class

2020年12月14日 2050点热度 0人点赞 0条评论

今天最后总结一下 class 与 原型的关系。

ES6 的语法糖 - 类(class)

ES6 有了更加清晰明确的面向对象的关键字,但其实它们只不过是经过修饰的语法糖。

类的基础概念和语法

我们之前在原型链中创建一个对象,需要使用函数的形式,然后在其原型中添加方法/属性,最后通过 new 关键字来创建实例。

function User(name) {
  this.name = name;
}
User.prototype.show = function () {
  console.log("Hi, " + this.name);
};

let user = new User("jeremyjone");
user.show(); // Hi, jeremyjone

那么在 ES6 之后,我们可以使用类的方式:

class User2 {
  constructor(name) {
    this.name = name;
  }

  show() {
    console.log("Hi, " + this.name);
  }
}

let user2 = new User2("jeremyjone");
user2.show(); // Hi, jeremyjone

看上去确实清晰了很多。需要明确几点:

  • 1、constructor 是一个构造函数,创建对象时会自动调用。即使你不写,它也默认存在。
  • 2、所有写在 constructor 中的属性都是实例属性,是定义在实例中的。那么相对的,在 constructor 之外的属性,都是定义在类中的,也就是原型属性。
  • 3、this 指向的是调用的实例对象,静态方法指向类本身。
  • 4、子类使用构造器时,必须使用 super 关键字来扩展构造器,并且需要先调用 super。
  • 5、子类会覆盖父类同名属性/方法,这与原型优先级一致。如果需要使用父类属性/方法,使用 super 关键字。
  • 6、使用 static 关键字标明类属性/方法,它们无法在实例中使用,而是通过类直接调用的。

类与原型的关系

为了深入理解,首先来看一下它们的原型结构:

原型与类的结构

看上去差不多,只是一个标记为函数,一个标记为类。

测试一下发现:

// 接上例
user2.__proto__ === User2.prototype; // true
User2.prototype.constructor === User2; // true

这也符合我们之前说过的原型方式,所以 class 本质上还是一个函数,只不过是一个语法糖,一个原型的另一种写法而已。

在此基础上,我们甚至可以通过原型的方式来修改/新增方法:

// 接上例
User2.prototype.print = function () {
  console.log("hello, " + this.name);
};

user2.print(); // hello, jeremyjone

实例属性和原型属性的分别

上面提到,constructor 属性内的是实例属性,之外的是原型属性,可以使用之前提到的检测方法来实践:

// 接上例
// 检测自身属性
console.log(user2.hasOwnProperty("name")); // true
console.log(user2.hasOwnProperty("print")); // false

// 检测原型属性
console.log("name" in user2); // true
console.log("print" in user2); // true

可以看到实例中自身只有 name 属性,而 print 方法确实在其原型链中可以被找到。

类的静态方法/属性

通过关键字 static 可以声明一个静态方法/属性。和其他语言一样,静态方法/属性只会挂载到类中,而不会通过类创建的实例调用。

class User {
  static type = "JZ";

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

  show() {
    console.log("show: " + this.name);
  }

  static print() {
    console.log("static print by: " + this.type); // 静态方法里的 this 指向类本身
  }
}

let user = new User("jeremyjone");

// 实例调用类方法
user.print(); // 报错。找不到对象方法

// 使用类方法
User.print(); // static print by: JZ

类的继承

ES6 中通过 extends 关键字来实现类之间的继承。

// 接上例
class Child extends User {} // 最基本的继承

let child = new Child("child jz");
child.show(); // show: child jz

同时,静态属性/方法是会被继承的。

// 接上例
Child.print(); // static print by: JZ

super 关键字

在继承过程中,经常会看到 super 关键字,它有两个作用:

  • 1、子类调用构造函数 constructor 时,必须在构造函数内部先调用 super 关键字,然后才可以使用 this 对象。
  • 2、子类同名方法会覆盖父类方法,这时使用 super 关键字可以调用父类方法。

构造函数中使用 super

// 接上例
// 错误示例
class Child2 extends User {
  constructor() {} // 空
}

// 当子类调用了构造函数,却没有在内部使用 super,新建实例会报错
let child2 = new Child2("c2"); // 报错

构造函数中没有 super

所以需要在使用到 this 地方之前,调用一下 super。

// 接上例
// 正确示例
class Child2 extends User {
  constructor(name) {
    super(name);
  }
}

let child2 = new Child2("c2"); // 正确

调用父级属性/方法

作为对父类的扩展,有时候需要覆写父类,但是又需要用到父类的功能,这时可以在子类中使用 super 调用父类功能作为子类方法的一部分。

// 接上例
class Child3 extends User {
  show() {
    console.log("Blessings from child3");
    super.show();
  }
}

let child3 = new Child3("c3");
child3.show();
// Blessings from child3
// show: c3

super 指向哪里

ES6 给我们提供的 super 会指向父级的原型。所以我们可以通过 super 找到其原型链中的所有属性/方法,但是无法找到 static 方法/属性。

举一个例子,我们可以将上面的例子转换为:

// 修改上例
class Child3 extends User {
  show() {
    console.log("Blessings from child3");
    // super.show();
    // 转换为如下方式:
    User.prototype.show.call(this, this.name);
    // 或者:
    this.__proto__.show.call(this, this.name);
  }
}

let child3 = new Child3("c3");
child3.show();
// Blessings from child3
// show: c3

从上面可以看到,其实 super 就是指向了原型,同时给我们提供了 this 的指向。

总结

到此为止,基于JS的原型和原型链的内容基本就总结完毕了,学习 JS 一定要搞明白原型的内容。JS 的灵活之处就在于原型和原型链,其继承的方式也基于此,之后的类的概念也是在此基础上的。

总之,这段内容还是要多多练习领悟,才能通透。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: JavaScript 原型
最后更新:2020年12月14日

jeremyjone

这个人很懒,什么都没留下

打赏 点赞
< 上一篇
下一篇 >

文章评论

取消回复

文章目录
  • ES6 的语法糖 - 类(class)
    • 类的基础概念和语法
    • 类与原型的关系
    • 实例属性和原型属性的分别
    • 类的静态方法/属性
    • 类的继承
    • super 关键字
  • 总结
最新 热点 随机
最新 热点 随机
关于 *.vue 文件中使用 TypeScript 声明类型报错的解决方案 element table 加载时宽度闪烁问题 windows 无法登录便签、OneNote等应用 vue2 中 vuex 对 ts 的支持 封装一个极简的右键菜单 vue2 使用 @vue/composition-api 的一些问题
Proxy - JavaScript IIS Express 通过IP访问的方法和坑 双十一 京东养红包攻略 MySQL升级之路(5.6-8.0) vue2 使用 @vue/composition-api 的一些问题 IdentityServer4深入使用(五)-- 数据持久化

(っ•̀ω•́)っ✎⁾⁾ 开心每一天

COPYRIGHT © 2021 jeremyjone.com. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS

京ICP备19012859号-1

京公网安备 11010802028585号