经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
es6 快速入门 系列 —— 类 (class)
来源:cnblogs  作者:彭加李  时间:2021/3/8 11:50:47  对本文有异议

其他章节请看:

es6 快速入门 系列

类(class)是 javascript 新特性的一个重要组成部分,这一特性提供了一种更简洁的语法和更好的功能,可以让你通过一个安全、一致的方式来自定义对象类型。

试图解决的问题

es5 及早期版本中没有类的概念,通常会编写类似下面这样的代码来自定义类

  1. // 自定义类的思路是:首先创建一个构造函数,然后定义一个方法并赋值给构造函数的原型
  2. function Rectangle(length, width){
  3. this.length = length;
  4. this.width = width;
  5. }
  6. Rectangle.prototype.getArea = function(){
  7. return this.length * this.width
  8. }
  9. let rect = new Rectangle(2, 3)
  10. console.log(rect.getArea()) // 6
  11. console.log(rect instanceof Rectangle) // true

如果还需要实现继承,可能会这么实现:

  1. // 定义类:Rectangle
  2. function Rectangle(length, width){
  3. this.length = length;
  4. this.width = width;
  5. }
  6. Rectangle.prototype.getArea = function(){
  7. return this.length * this.width
  8. }
  9. // 定义类:Square
  10. function Square(length){
  11. // 对象冒充
  12. Rectangle.call(this, length, length)
  13. }
  14. // 指定 Square 的原型
  15. Square.prototype = Object.create(Rectangle.prototype, {
  16. constructor: {
  17. value: Square,
  18. enumerable: true,
  19. writable: true,
  20. configurable: true
  21. }
  22. });
  23. let square = new Square(3)
  24. console.log(square.getArea()) // 9 - getArea() 方法从父类继承
  25. console.log(square instanceof Square) // true
  26. console.log(square instanceof Rectangle) // true

通过自己模拟类、模拟继承,实现起来比较复杂,也非常容易出错。

解决方法

es6引入类(class),让类的创建和继承都更加容易。下面我们重写 Rectangle 的例子:

  1. // 通过 class 定义一个类
  2. class Rectangle{
  3. constructor(length, width){
  4. this.length = length;
  5. this.width = width;
  6. }
  7. // 方法直接写在 class 中
  8. getArea(){
  9. return this.length * this.width
  10. }
  11. }
  12. // 通过 extends 关键字指定类继承的函数,原型会自动调整
  13. class Square extends Rectangle{
  14. constructor(length){
  15. // 通过调用 super() 方法即可访问基类的构造函数
  16. super(length, length)
  17. }
  18. }
  19. let square = new Square(3)
  20. console.log(square.getArea()) // 9
  21. console.log(square instanceof Square) // true
  22. console.log(square instanceof Rectangle) // true

这个版本比我们自定义的要简单很多,如果你先前对其他语言中的继承语法有所了解,那么你应该会觉得很亲切。

有关类(class)的具体使用、其他特性,都可以在下面的补充章节中找到答案

补充

类的创建

类和函数都存在声明形式和表达式形式。

声明类,首先编写 class 关键字,接着是类的名字,其他部分的语法类似对象字面量的简写形式,但类的各元素之间不需要使用逗号。就像这样:

  1. // class 类名
  2. class People{
  3. constructor(name){
  4. this.name =name;
  5. }
  6. // 不需要逗号
  7. sayName(){
  8. console.log(this.name)
  9. }
  10. }
  11. let people = new People('aaron')
  12. people.sayName() // aaron
  13. console.log(typeof People) // function {1} - People 其实还是一个函数

类的表达式语法:

  1. // 直接将一个类赋值给变量 People
  2. let People = class{
  3. constructor(name){
  4. this.name =name;
  5. }
  6. sayName(){
  7. console.log(this.name)
  8. }
  9. }
  10. let people = new People('aaron')
  11. people.sayName() // aaron

类(class)只是自定义类的一个语法糖。用 class 声明的类是一个函数,那么将这个函数赋值给一个变量,当然没问题。

类是一等公民

在程序中,一等公民是指可以传入函数,可以从函数返回,也可以赋值给变量。javascript 函数是一等公民,这是 javascript 中的一个独特之处,es6 也把类(class)设计成了一等公民。例如,将类(class)作为参数传入函数:

  1. function createObject(classDef){
  2. return new classDef()
  3. }
  4. let obj = createObject(class People{
  5. sayName(){
  6. console.log('aaron')
  7. }
  8. })
  9. obj.sayName() // aaron

继承

在”解决方法“章节中,我们已经学会用 class 创建类,通过 extends 关键字指定类继承的函数,以及使用 super() 调用基类的构造函数。

当使用 super() 时切记以下几个关键点

  • 只可以在派生类(使用 extends 声明的类)的构造函数中使用 super(),否则会导致程序抛出错误
  • 在构造函数中访问 this 之前,一定要调用 super(),它负责初始化 this,如果在调用 super() 之前访问 this 会导致程序报错
  • 如果不想调用 super(),唯一的方法是让构造函数返回一个对象,否则也会导致程序报错
  1. class A{}
  2. class B extends A{
  3. constructor(){
  4. }
  5. }
  6. let b = new B() // 报错 - 没有调用super()

内建对象的继承

现在可以通过继承创建自己的特殊数组。请看下面:

  1. class MyArray extends Array{
  2. }
  3. let myArray = new MyArray()
  4. myArray[0] = 11
  5. console.log(myArray.length) // 1
  6. myArray.length = 0;
  7. console.log(myArray[0]) // undefined

MyArray 继承自 Array,其行为与 Array 也很相似。但在以前,用传统的继承方式是无法实现这样的功能。es6 类语法的一个目标是支持内建对象继承,因而 es6 中的类继承模型与 es5 中的稍有不同,主要体现是:

  • es5 先由派生类(例如,MyArray)创建 this,然后调用基类的构造方法(例如 Array.apply()方法)
  • es6 则相反,先由基类创建this,然后派生类的构造函数再修改,所以从一开始可以访问基类的所有内建功能

我们也可以继承自其他内建对象

静态成员

es5 及更早,通过直接将方法添加到构造函数中来模拟静态成员,例如:

  1. function People(){}
  2. People.create = function(){}

es6 简化了这个过程,类(class)通过 static 关键字定义静态方法:

  1. class People{
  2. constructor(name){
  3. this.name = name
  4. }
  5. static create(name){
  6. return new People(name)
  7. }
  8. }
  9. let p1 = People.create('aaron')
  10. console.log(p1.name) // aaron

静态成员也可以被继承,请看示例:

  1. class A{
  2. static say(){
  3. console.log('A say')
  4. }
  5. }
  6. class B extends A{
  7. }
  8. B.say() // A say

派生自表达式的类

es6 中最强大的一面或许是从表达式中导出类了。只要表达式可以被解析为一个函数并且具有[[Constructor]]属性和原型,就可以用 extends 派生。举个例子:

  1. function Rectangle(length, width){
  2. this.length = length;
  3. this.width = width;
  4. }
  5. Rectangle.prototype.getArea = function(){
  6. return this.length * this.width
  7. }
  8. function getBase(){
  9. return Rectangle;
  10. }
  11. // getBase()这个表达式返回 Reactangle, 具有[[Constructor]]属性和原型,因此 Square 类可以直接继承它
  12. class Square extends getBase(){
  13. constructor(length){
  14. super(length, length)
  15. }
  16. }
  17. let square = new Square(3)
  18. console.log(square.getArea()) // 9

extends 强大的功能使得类可以继承自任意类型的表达式,从而创建更多的可能性。

类的构造函数中使用 new.target

  1. class A{
  2. constructor(){
  3. if(new.target === A){
  4. console.log(true)
  5. }else{
  6. console.log(false)
  7. }
  8. }
  9. }
  10. class B extends A{
  11. constructor(){
  12. super()
  13. }
  14. }
  15. new A() // true {1}
  16. new B() // false {2} - B 调用 A 的构造函数,所以当调用发生时 new.target 等于 B

简单情况下,new.target 等于类的构造函数,就像行{1},但有时却会不同(行{2})。理解它(行{2})非常重要,因为每个构造函数可以根据自身被调用的方式改变自己的行为。例如,可以用 new.target 创建一个抽象基类(不能被直接实例化的类),就像这样:

  1. // 抽象基类 BaseClass
  2. class BaseClass{
  3. constructor(){
  4. if(new.target === BaseClass){
  5. throw new Error('这个类不能被实例化')
  6. }
  7. }
  8. sayName(){
  9. console.log(this.name)
  10. }
  11. }
  12. class B extends BaseClass{
  13. constructor(name){
  14. super()
  15. this.name = name
  16. }
  17. }
  18. let b = new B('aaron')
  19. b.sayName() // aaron
  20. new BaseClass() // Error: 这个类不能被实例化

访问器属性和可计算成员名称

类支持直接在原型上定义访问器属性,例如我们用访问器属性封装一下数据 name:

  1. // 数据 name 存储在变量 _name 中,只能通过访问器属性来操作,从而达到数据封装的目的
  2. class People{
  3. constructor(name){
  4. this._name = name;
  5. }
  6. set name(v){
  7. this._name = v;
  8. }
  9. get name(){
  10. return this._name
  11. }
  12. }
  13. let people = new People('aaron')
  14. console.log(people.name) // aaron
  15. people.name = 'lj'
  16. console.log(people.name) // lj

类方法和访问器属性也支持可计算成员名称,请看下面示例:

  1. let methodName = 'sayName'
  2. let getName = 'age'
  3. class People{
  4. constructor(name){
  5. this.name = name;
  6. }
  7. [methodName](){
  8. console.log(this.name)
  9. }
  10. get [getName](){
  11. console.log('get')
  12. }
  13. }
  14. let people = new People('aaron')
  15. people.sayName() // aaron
  16. people.age // get

生成器

在类中,可以将任意方法定义成生成器。但如果你的类是用来表示集合,那么定义一个默认的迭代器会更有用:

  1. class Collection{
  2. constructor(){
  3. this.items = []
  4. }
  5. push(v){
  6. this.items.push(v)
  7. }
  8. *[Symbol.iterator](){
  9. yield *this.items.values()
  10. }
  11. }
  12. let collection = new Collection()
  13. collection.push('11')
  14. collection.push('22')
  15. collection.push('33')
  16. for(let v of collection){
  17. console.log(v) // 11 22 33
  18. }

其他章节请看:

es6 快速入门 系列

原文链接:http://www.cnblogs.com/pengjiali/p/14488228.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号