JavaScript编码之路【ES6新特性之Class类】

前言

嘟三~ 嘟三~ 今日份广播题目:“怎么让JavaScript越来越6”。接下来,小菜鸡本人将和大家一起来探讨ES6-ES13的那些酷酷的新特性,从这次广播开始,你也可以炫耀:“这个ES新特性我都用得溜溜的!”

引子

ES6就像一盘全新研发出炉的九转大肠,它不仅保留了原来的味道,还加入了山量的新鲜元素,味道更加可口,并且设计得更加优美。同样,ES6增加了很多有用的新特性,比如类(Class)。类是什么呢?为什么要有它呢?类是一种可以实例化为对象或者继承为子类的模板,简洁明快,极大提高了代码的维护性。

一. ES6定义类

1.1. 认识class定义类

按照构造函数形式创建,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。因此在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类,但是,其实这只不过是一个糖衣炮弹【原名:语法糖】,其核心元素是构造函数和原型链。所以学好了构造函数、原型链更有利于去理解类的概念和i其继承关系。就像你要了解为什么芥末能让你眼泪直流,你就得先知道它从哪儿来,和它是怎么做出来的。

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

想象一下,在ES6之前创建一个类,感觉就像驾驶一辆没有自动挡和助力转向的老爷车。有了ES6,你可以使用class关键字来定义一个类,这就像获得了自动挡和助力转向,让全过程简便易行。

可以使用两种方式来助力咱们

类声明

class Person {

}

类表达式

var Student = class {
  
}

接着我们就可以使用new操作符调用类

var p1 = new Person
var p2 = new Person
console.log(p1, p2)

研究一下类的一些特性吧

【探究历程】

小菜鸡创建了一个Person类的实例p, 其实也就是实例化了一个’Person

var p = new Person

首先,小菜鸡打开了持有’Person’手提箱,看见标签上写着"[class Person]"。这标签其实就像是一纸身份证明,告诉俺们’Person’是一个类。

console.log(Person) // [class Person]

接着小菜鸡翻开’Person’的后背,发现了一把钥匙,这就是我们的prototype。此时,钥匙看起来非常空洞,只有它的形状,并没有任何其他的钥匙牙,比如方法或者属性。这就是因为我们的’Person’类还没定义任何方法或属性,所以它的prototype现在看起来是个洁白的 {}

console.log(Person.prototype) // {}

接下来,侦探从钥匙上发现了一个指纹,也就是constructor,这个指纹将我们的钥匙链接回了Person类,证明了这个原型是属于Person类的。

console.log(Person.prototype.constructor) // [class Person]

然后,看最后的打印结果,这就好比小菜鸡召唤了克隆科技,制造出了Person的克隆人。克隆人p虽然拥有自己的生命,但是他的基因,或者说__proto__还是指向他的克隆源Personprototype,表示p是从这里继承属性和方法的。

console.log(p.__proto__ === Person.prototype) // true

最后,小菜鸡拿出神秘的魔镜对Person进行了鉴定。魔镜告诉他,Person其实是一个 ‘function’ 的 disguise,因为在JavaScript中,类实质上还是函数

console.log(typeof Person) // function

【本次探究告一段落】

1.2. 类的构造函数

如果你曾经在游戏角色选择界面里,享受过为你的角色设定名字、性别、属性值的乐趣,那么你那时候其实就对于类的构造函数有一些直观的理解了。构造函数constructor就好比是你创建游戏角色的大脑,可以让定制角色的一些基本属性。

【注意】

  • 每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor

  • 当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor

  • 每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常

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

var p1 = new Person("HuCE", 19, 1.50)
console.log(p1)

开始了,游戏在内存中创建了一个全新的角色(空对象)【第一步】。然后,新角色的继承链([[prototype]]属性)会链接到角色原型(即:Person.prototype)【第二步】。接下来,游戏操作界面(即:this)都指向你的新角色【第三步】。游戏开始运行(执行构造函数的内部代码)【第四步】。最终,如果你没有设定任何属性(也就是构造函数没有返回非空对象),那么角色就会当做一个全新的角色(空对象)返回【第五步】。

每个类只能有一个构造函数。会不会有人问:“哈?我不能让我的角色一次拥有两种个性吗?”答案是,当然不能。试图在一个类中设定两个构造函数,就像是你试图同时控制一个拥有两种个性的角色一样,这将引发冲突,最后会导致异常。

1.3. 类的方法定义

1.3.1. 实例方法

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

对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享,这个时候我们可以直接在类中定义

在这里插入图片描述

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

  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }
}

var p1 = new Person("why", 18, 1.88)
console.log(p1)

p1.running()
p1.eating()
// [ 'constructor', 'running', 'eating' ]
console.log(Object.getOwnPropertyNames(Person.prototype))

如果我们检查类的属性描述符,就像是查看社区规定,会发现它们的enumerable都是false,也就是说,这些方法默认是不会出现在for...in循环中的。

console.log(Object.getOwnPropertyDescriptors(Person.prototype))
1.3.2. 访问器方法

还记得对象属性描述符吗?对象可以添加setter和getter函数的,其实类也是可以的

setter?getter?What?

突然回忆起很小的时候,我躺在门前的稻谷堆里读着《哈利波特 》,忘了读到了什么,只记得太阳暖暖的,然后太阳下山了,天空红扑扑的,外婆这个时候就喊我吃饭了,真是怀念呀。
在这里插入图片描述

settergetter可以让我们精细地控制类的属性

  • setter其实就像是一个警卫,它坐在赋值操作的大门,检查每一个通过大门的值。就算你穿上上哈利波特的隐形衣走到setter前面,setter也不会让你直接通过。它会先确认你是不是合适的值,然后亲自把你带进来。

  • getter就像一个守门人,每当你想访问某个属性,getter就会把它安全地送到你的手上。

class Person {
  constructor(name) {
    this._name = name
  }

  set name(newName) {
    console.log("调用了name的setter方法")
    this._name = newName
  }

  get name() {
    console.log("调用了name的getter方法")
    return this._name
  }
}

var p = new Person("why")
console.log(p.name)
p.name = "kobe"
console.log(p.name)

如上,Person每次被赋值和被访问的时候,settergetter方法都会被调用。

但在类里,这把魔法钥匙(setter和getter)其实是挂在原型的钩子上的,所有的实例都可以使用这把钥匙。这也就是说,在类中定义的settergetter方法是放到原型上的,因此可以被所有实例共享。

var p = new Person("why")
console.log(p.name)
p.name = "kobe"
console.log(p.name)

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

console.log(obj)
console.log(Object.getOwnPropertyDescriptors(p.__proto__))

如上,创建了一个Person类和一个普通对象obj,它们都有同样的gettersetter,但objgettersetter是直接在对象上,而Persongettersetter是在原型上。这就是他们的区别。

1.3.3. 静态方法

【想象时间】

有一个神奇的机器,这个机器只需要录入一个"create"命令,无需其他任何条件,就会自动生成一个Person。而我们的静态方法就是这个神奇的机器。

静态方法通常用于声明跟具体实例无关的方法,也就是说,它们常常与类的实例化对象无关。这些方法,我们一般直接用类去调用,而不是类的实例

JavaScript的类中,我们用static关键字来声明静态方法。

class Person {
  constructor(age) {
    this.age = age
  }

  static create() {
    return new Person(Math.floor(Math.random() * 100))
  }
}

for (var i = 0; i < 10; i++) {
  console.log(Person.create())
}

我们为Person类定义了一个静态方法create。这个方法会调用Person的构造函数,创建一个新的Person实例,其age属性是一个0100的随机数。

可以看到,尽管我们没有创建任何Person的实例,但是我们仍然可以通过Person.create()方法创建Person的实例。而且不管我们调用多少次Person.create(),都不会影响到其他的Person实例,每次调用都会得到一个全新的Person实例。

这就像我们有一个人口生成器,不需要任何条件,只需要启动,就会自动生成一个人口。这种造人的能力,就是静态方法为我们提供的。

二. ES6类的继承

2.1. extends关键字

假设有一种神奇的魔法,它能在父母(Person类)和孩子(Student类)之间架起一座神秘的桥梁。这座桥允许孩子从父母那里获得并流传至子孙的知识(属性)、技能(方法)。这就是ES6为俺们引入的extends关键字【其实在ES5中也是可以实现继承的方案,但是过程却依然是非常繁琐的】:

class Person {

}

class Student extends Person {
  
}

Student类通过extends关键字借由魔法桥Person类那里继承特性能力

接下来,小菜鸡就把这个故事讲细一点:

每个人都有自己的姓名和年龄,同样地,每个Person实例也有自己的nameage属性。他们可以跑步和吃东西,因此有runningeating方法。

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

  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }
}

然后有一种特别的人,他们是学生。他们继承了人的特性,但他们还有自己的特殊之处,比如他们有学生号,所以Student类有自己的sno属性。他们不仅能跑步和吃东西,还需要学习,所以有studying方法。但最神奇的魔法就在于,他们继承了他们的父辈的所有特性和能力。

class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

  studying() {
    console.log(this.name + " studying~")
  }
}

var stu = new Student("why", 18, 111)

Student构造函数中,魔法师调用了super函数【下面会详细介绍】。这个super就如同一个魔法传送门,它将孩子带到父母那里,让孩子的属性和父母一样。如果你的类继承了其他类,那么super就必须在this之前被调用,因为它实际上会创建出this【换句话说,super负责创建this,然后你才能对this做赋值操作。这就是为什么在constructor方法中,你必须首先调用super,然后才能使用this关键字。如果你试图在调用super之前使用thisJavaScript会抛出一个引用错误】。

看,我们已经成功创建了一个Student实例,他的名字是why18岁,学号是111。他能跑步吃东西,还能学习,因为他继承了Person类,并添加了自己的特性studying

2.2. super关键字【魔法棒】

这是一段来自 CodeWhy 王元红老师的温馨提示⚡
【注意:在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!】

super的使用位置有3

  • 子类的构造函数

  • 实例方法

  • 静态方法

想象一下,super关键字是一个拥有神奇力量的魔法棒。一旦你挥舞它,神奇的事情就会发生:你的子类就能像父亲一样,拥有父类的属性和方法。这可真是其非凡之处!

首先,让我们看一下super怎么使用。一般情况下,我们会在子类的构造函数、实例方法、静态方法中找到它。

// 调用 父对象/父类 的构造函数
super([arguments])

// 调用 父对象/父类 上的方法
super.functionOnParent([arguments])

如果你在子类的构造函数中使用this,或者没有调用super就要返回对象,那你将会得到一个错误信息。一定要记得,使用super关键字才会调用父类构造函数并且创建出this。

class Person {

}

class Student extends Person {
  constructor(sno) {
    // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
  }
}

var stu = new Student()

其中,super在实例方法或静态方法中调用父类方法的能力,使我们可以很方便地实现方法的重写。

class Person {
  eating() {
    console.log(this.name + " eating~")
  }

  static create() {
    console.log("Person create")
  }
}

class Student extends Person {
  // 方法的重写
  eating() {
    console.log("做完作业~")
    super.eating()
  }

  static create() {
    super.create()
    console.log("Student create")
  }
}

var stu = new Student()
stu.eating()

Student.create()
--- 控制台 ---
做完作业~
undefined eating~
Person create
Student create

如上为Student类重写了eatingcreate方法。但在新方法中,我们依然调用了Person中的eatingcreate方法。这就好像孩子在观察从父亲那里学到的技能之后,找到自己独特的使用方式。

2.3. 继承内置类

在我们已经知道如何继承自定义的类之后,我们同样也可以选择继承JavaScript内置的类,例如Array。这就像我们有能力把超级英雄的超能力赋予给我们的角色,让他能够飞翔、变身或者制造能量光束。

class HYArray extends Array {
  lastItem() {
    return this[this.length-1]
  }
}

var array = new HYArray(10, 20, 30)
console.log(array.lastItem())

array.filter(item => {
  console.log(item)
})

如上创建了一个新类HYArray,它继承自Array类。这就好像我们的角色继承了超级英雄的能力。在HYArray增加了一个新方法lastItem,能够返回数组的最后一个元素。这么做,咱们的角色除了能做超级英雄能做的事情,还能做一些超级英雄做不到的事情。

同时,咱们可以继续改进HYArray类,令它使用filter方法返回的数组还是原来的Array,而不是HYArray。只需要在HYArray类中添加一个Symbol.species方法而已。这个方法返回该构造函数用来创建派生对象的默认构造函数。

class HYArray extends Array {
  lastItem() {
    return this[this.length-1]
  }

  static get [Symbol.species]() {
    return Array
  }
}

var array = new HYArray(10, 20, 30)
console.log(array.lastItem())

var newArr = array.filter(item => {
  console.log(item)
})

console.log(newArr instanceof HYArray) // false
console.log(newArr instanceof Array) // true

现在HYArray就如同一个超级英雄,他不仅继承了父类的能力,还能够做一些其他超级英雄做不到的事情!

小结

参考代码全部来自 CodeWhy 王元红老师 【来源微信公众号 CodeWhy 】,感谢我的引路人王老师。

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一小池勺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值