JavaScript中没有类,是通过使用构造函数和原型模式的组合来实现类似其它面向对象编程语言中“类”的功能。ES6引入的关键字class,形式上向其它面向对象编程语言靠拢,其实质只是一个语法糖,绝大部分功能ES5都可以实现。

一、class

  在ES6之前,创建自定义对象最常用的方式是组合使用构造函数和原型模式。而ES6通过class关键字在形式上加以规范。

  1. // ES6之前的写法
  2. function OldStudent (name,age) {
  3. this.name = name
  4. this.age = age
  5. }
  6. OldStudent.prototype.sayMessage = function () {
  7. console.log(`我叫${this.name},今年${this.age}岁。`)
  8. }
  9. // ES6写法
  10. class NewStudent{
  11. constructor (name, age) {
  12. this.name = name
  13. this.age = age
  14. }
  15. sayMessage() {
  16. console.log(`我叫${this.name},今年${this.age}岁。`)
  17. }
  18. }
  19. let LiLei = new OldStudent('LiLei',20)
  20. LiLei.sayMessage() // 我叫LiLei,今年20岁。
  21. let HanMeiMei = new NewStudent('HanMeiMei',18)
  22. HanMeiMei.sayMessage() // 我叫HanMeiMei,今年18岁。
  23. console.log(typeof NewStudent) // function

  由上代码可以看出,通过class关键字定义的代码块也是函数。比较特殊的是通过class定义的函数只能通过new关键字来调用,并且不存在变量提升,只能先定义后使用。另外,class定义的函数内部模式是严格模式。

  class中的constructor方法相当于ES5中的构造函数,在实例化对象时必须调用constructor方法,如果没有显式定义,引擎也会自动添加一个空的constructor方法。constructor方法和构造函数生成对象的规则一样:创建一个新对象,作用方法的执行上下文,执行方法中的代码;如果没有明确return一个对象,则返回新创建的对象。

  class中除了定义constructor方法之外的方法是直接定义在构造函数的原型对象上的。如果该方法前面加了static关键字,则该方法为静态方法,直接定义在构造函数上而不是原型对象上。ES6明确规定class中可以定义静态方法,无法在其中定义静态变量。想要添加静态变量,可以在class之外直接向函数上添加。如下代码所示:

  1. // sayHello为静态方法
  2. class NewStudent{
  3. static sayHello() {
  4. console.log('hello')
  5. }
  6. }
  7. // 添加静态属性
  8. NewStudent.type = 'student'
  9. NewStudent.sayHello() // hello
  10. console.log(NewStudent.type) // student

  注意:通过class关键字向原型对象上只能添加方法而不能添加属性。也许是为了防止在原型对象上添加对象的情况,这样会导致在继承的时候子对象可以修改原型链上对象的数据。但是这样有些矫枉过正了,JavaScript的很大优势在于灵活性,class关键字定义类,外形规范的同时降低了灵活性。

  为了实现数据共享,可以在通过class定义过类之后,再向原型对象中添加属性。或者在class中定义getter方法,这样的话就不能在子对象上直接添加同名属性,要通过Object.defineProperty()方法来定义子对象上的同名属性。如下代码所示:

  1. class Person{
  2. get age () {
  3. return 18
  4. }
  5. }
  6. class Student extends Person{
  7. constructor (){
  8. super()
  9. }
  10. }
  11. let LiLei = new Student()
  12. console.log(LiLei.age) // 18
  13. LiLei.age = 20 // 非严格模式下赋值失败,严格模式下报错
  14. console.log(LiLei.age) // 18
  15. // 通过Object.defineProperty()方法修改
  16. Object.defineProperty(LiLei, 'age', {
  17. value: 20
  18. })
  19. console.log(LiLei.age) // 20

  由上代码所示,ES6的原型属性添加很麻烦,如果想要往原型对象上添加属性,直接往类的prototype属性赋值最为简单。

二、extends

  实现引用类型继承的最佳方式是寄生组合式继承,ES6通过extends关键字来使这种继承方式形式更加优雅,如下代码所示:

  1. // ES6之前的继承
  2. function PersonA (name) {
  3. this.name = name
  4. }
  5. PersonA.prototype.sayName = function () {
  6. console.log(this.name)
  7. }
  8. function StudentA (name,age) {
  9. PersonA.call(this,name)
  10. this.age = age
  11. }
  12. StudentA.prototype = Object.create(PersonA.prototype)
  13. // ES6的继承
  14. class PersonB{
  15. constructor (name){
  16. this.name = name
  17. }
  18. sayName () {
  19. console.log(this.name)
  20. }
  21. }
  22. class StudentB extends PersonB{
  23. constructor (name, age) {
  24. super(name)
  25. this.age = age
  26. }
  27. }
  28. let LiLei = new StudentA('LiLei',20)
  29. LiLei.sayName() // LiLei
  30. let HanMeiMei = new StudentB('HanMeiMei',18)
  31. HanMeiMei.sayName() // HanMeiMei

  子类的constructor中必须调用super方法,在执行super()之前子类不能使用this,这是因为ES6之前的寄生组合式继承是先创建子类的实例,然后通过call或者apply方法将该实例作为执行上下文执行一遍父类构造函数。而ES6正好相反,先创建父构造函数的实例对象,然后将实例对象作为this执行子构造函数。这就导致如果不调用super方法子类中就不能使用this。

  ES6继承中先创建父类实例的特性还带来一个ES5无法实现的功能:继承原生构造函数。如下代码所示:

  1. class MyArray extends Array{
  2. constructor (...args) {
  3. super(...args)
  4. }
  5. }
  6. let number = new MyArray(1,2,3)
  7. console.log(number.length) // 3
  8. number.push(4)
  9. console.log(number.length) // 4

  通过extends子类同时能够继承父类的静态方法。如下代码所示:

  1. class Person{
  2. constructor (){}
  3. static sayHello () {
  4. console.log('hello')
  5. }
  6. }
  7. class Student extends Person{
  8. constructor () {
  9. super()
  10. }
  11. }
  12. Student.sayHello() // hello

  super可以作为方法在子类构造函数中调用,也可以作为对象使用。当super作为对象时,在普通方法中指向父类的原型对象,在静态方法中,指向父类。另外,super在普通方法中指向的是父类的原型对象,无法通过super来访问父类实例中的属性和方法。如下代码所示:

  1. class Person{
  2. constructor (name){
  3. this.name = name
  4. }
  5. static sayStatic () {
  6. console.log('static')
  7. }
  8. }
  9. class Student extends Person{
  10. constructor (name) {
  11. super(name)
  12. }
  13. static sayStatic () {
  14. super.sayStatic()
  15. }
  16. sayName () {
  17. console.log(super.name)
  18. }
  19. }
  20. let LiLei = new Student('LiLei')
  21. Student.sayStatic() // static
  22. LiLei.sayName() // undefined

三、总结

  ES6新增的关键字class、extends在形式上规范了JavaScript对类的模拟,使代码看起来更加优雅。而且能够通过extends来扩展内建的对象类型,比如Array或者RegExp。

  class带来的也不全是好处,将.prototype隐藏起来,使人更加迷惑。实际上JavaScript是没有类的,通过原型链来实现继承更恰当的说法应该是委托。而且仅仅通过class关键定义构造函数和原型对象的混合体灵活性有所降低,比如:在原型对象上只能添加方法,无法添加属性。

如需转载,烦请注明出处:https://www.cnblogs.com/lidengfeng/p/9342084.html

JavaScript夯实基础系列(五):类的更多相关文章

  1. JavaScript夯实基础系列(四):原型

      在JavaScript中有六种数据类型:number.string.boolean.null.undefined以及对象,ES6加入了一种新的数据类型symbol.其中对象称为引用类型,其他数据类 ...

  2. JavaScript夯实基础系列(三):this

      在JavaScript中,函数的每次调用都会拥有一个执行上下文,通过this关键字指向该上下文.函数中的代码在函数定义时不会执行,只有在函数被调用时才执行.函数调用的方式有四种:作为函数调用.作为 ...

  3. JavaScript夯实基础系列(二):闭包

      在JavaScript中函数是一等公民.所谓一等公民是指函数跟其他对象一样,很普通,可以进行把函数存在数组中.作为参数传递.赋值给变量等操作.当函数作为另一个函数的返回值在外部调用时,跟该函数在函 ...

  4. JavaScript夯实基础系列(一):词法作用域

      作用域是一组规则,规定了引擎如何通过标识符名称来查询一个变量.作用域模型有两种:词法作用域和动态作用域.词法作用域是在编写时就已经确定的:通过阅读包含变量定义的数行源码就能知道变量的作用域.Jav ...

  5. 【C++自我精讲】基础系列五 隐式转换和显示转换

    [C++自我精讲]基础系列五 隐式转换和显示转换 0 前言 1)C++的类型转换分为两种,一种为隐式转换,另一种为显式转换. 2)C++中应该尽量不要使用转换,尽量使用显式转换来代替隐式转换. 1 隐 ...

  6. 夯实基础系列四:Linux 知识总结

    前言 前三节内容传送门: 夯实基础系列一:Java 基础总结 夯实基础系列二:网络知识总结 夯实基础系列三:数据库知识总结 现在很多公司项目部署都使用的是 Linux 服务器,互联网公司更是如此.对于 ...

  7. UML基础系列:类图

    类图描述系统中类的静态结构,它不仅定义系统中的类,描述类之间的联系,如关联.依赖.聚合等,还包括类的内部结构(类的属性和操作).类图描述的是静态关系,在系统的整个生命周期中都是有效的.对象图是类图的实 ...

  8. web基础系列(五)---https是如何实现安全通信的

    https是如何实现安全通信的 如果有不正确的地方,还望指出! web基础系列目录 总结几种常见web攻击手段极其防御方式 总结几种常见的安全算法 回顾 总结几个概念(具体描述可以看上一篇文章) 数字 ...

  9. How Javascript works (Javascript工作原理) (十五) 类和继承及 Babel 和 TypeScript 代码转换探秘

    个人总结:读完这篇文章需要15分钟,文章主要讲解了Babel和TypeScript的工作原理,(例如对es6 类的转换,是将原始es6代码转换为es5代码,这些代码中包含着类似于 _classCall ...

随机推荐

  1. JUC中Lock和ReentrantLock介绍及源码解析

    Lock框架是jdk1.5新增的,作用和synchronized的作用一样,所以学习的时候可以和synchronized做对比.在这里先和synchronized做一下简单对比,然后分析下Lock接口 ...

  2. Java 使用PDFBox提取PDF文件中的图片

    今天做PDF文件解析,遇到一个需求:提取文件中的图片并保存.使用的是流行的apache开源jar包pdfbox, 但还是遇到坑了,比如pdfbox版本太高或太低都不能用!!这个包竟然没有很好地做好兼容 ...

  3. 树莓派使用modbus与stm32通信

    树莓派+stm32开发板通信树莓派上使用java+jamod实现.jamod官网stm32使用freemodbus实现 ​

  4. Telerik控件集-2019.R1.SP1.All

    Telerik 专注于微软.Net平台的表示层与内容管理控件,提供高度稳定性和丰富性能的组件产品DevCraft,并可应用在非常严格的环境中.Telerik拥有 Microsoft, HP, Alco ...

  5. 《前端之路》之 JavaScript 高级技巧、高阶函数(一)

    目录 一.高级函数 1-1 安全的类型检测 1-2 作用域安全的构造函数 1-3 惰性载入函数 1-4 函数绑定 1-5 函数柯里化 1-6 反函数柯里化 一.高级函数 1-1 安全的类型检测 想到类 ...

  6. 通过改进团队流程最大限度发挥Scrum的优势

    团队如何最大限度地发挥Scrum和敏捷的优势? 回想一下,Scrum团队在Scrum的框架内定义了自己的流程.这其中包括方法.工具和互动以及如何履行Scrum角色的职责.如何使用工件和事件等. 如何确 ...

  7. 我们为什么要搞长沙.NET技术社区(二)

    我们为什么要搞长沙.NET技术社区(二) 某种意义上讲,长沙和中国大部分内地城市一样,都是互联网时代的灯下黑.没有真正意义上的互联网公司,例如最近发布的中国互联网企业一百强中没有一家湖南或者长沙的公司 ...

  8. Java基础系列-ArrayList

    原创文章,转载请标注出处:<Java基础系列-ArrayList> 一.概述 ArrayList底层使用的是数组.是List的可变数组实现,这里的可变是针对List而言,而不是底层数组. ...

  9. 【转载】Sqlserver数据库备份的几种方式

    在实际的数据库Sqlserver的运维的过程中,很多时候我们需要做到数据的备份操作,可以做到定时备份,也可以进行手动数据库备份.在实际的过程中,有时候因业务需要备份出完整数据库,而有时候又因为实际业务 ...

  10. 【English EMail】2019 Q2 Public Holiday Announcement

    Hi all, According to 2019 public holiday announcement released by Chinese government, this is to ann ...