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

一、class

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

// ES6之前的写法
function OldStudent (name,age) {
this.name = name
this.age = age
}
OldStudent.prototype.sayMessage = function () {
console.log(`我叫${this.name},今年${this.age}岁。`)
} // ES6写法
class NewStudent{
constructor (name, age) {
this.name = name
this.age = age
} sayMessage() {
console.log(`我叫${this.name},今年${this.age}岁。`)
}
} let LiLei = new OldStudent('LiLei',20)
LiLei.sayMessage() // 我叫LiLei,今年20岁。 let HanMeiMei = new NewStudent('HanMeiMei',18)
HanMeiMei.sayMessage() // 我叫HanMeiMei,今年18岁。 console.log(typeof NewStudent) // function

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

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

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

// sayHello为静态方法
class NewStudent{
static sayHello() {
console.log('hello')
}
} // 添加静态属性
NewStudent.type = 'student' NewStudent.sayHello() // hello
console.log(NewStudent.type) // student

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

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

class Person{
get age () {
return 18
}
} class Student extends Person{
constructor (){
super()
}
} let LiLei = new Student()
console.log(LiLei.age) // 18
LiLei.age = 20 // 非严格模式下赋值失败,严格模式下报错
console.log(LiLei.age) // 18 // 通过Object.defineProperty()方法修改
Object.defineProperty(LiLei, 'age', {
value: 20
})
console.log(LiLei.age) // 20

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

二、extends

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

// ES6之前的继承
function PersonA (name) {
this.name = name
} PersonA.prototype.sayName = function () {
console.log(this.name)
} function StudentA (name,age) {
PersonA.call(this,name)
this.age = age
} StudentA.prototype = Object.create(PersonA.prototype) // ES6的继承
class PersonB{
constructor (name){
this.name = name
}
sayName () {
console.log(this.name)
}
} class StudentB extends PersonB{
constructor (name, age) {
super(name)
this.age = age
}
} let LiLei = new StudentA('LiLei',20)
LiLei.sayName() // LiLei
let HanMeiMei = new StudentB('HanMeiMei',18)
HanMeiMei.sayName() // HanMeiMei

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

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

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

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

class Person{
constructor (){}
static sayHello () {
console.log('hello')
}
} class Student extends Person{
constructor () {
super()
}
} Student.sayHello() // hello

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

class Person{
constructor (name){
this.name = name
} static sayStatic () {
console.log('static')
}
} class Student extends Person{
constructor (name) {
super(name)
} static sayStatic () {
super.sayStatic()
} sayName () {
console.log(super.name)
}
} let LiLei = new Student('LiLei')
Student.sayStatic() // static
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. [USACO11JAN]大陆议会The Continental Cowngress_2-sat

    [USACO11JAN]大陆议会The Continental Cowngress_2-sat 题意: 由于对Farmer John的领导感到极其不悦,奶牛们退出了农场,组建了奶牛议会. 议会以“每头 ...

  2. ubuntu ssh 免密码登录

    1 ssh 是什么? ssh 是一种 加密协议,ssh 是两个加密的密码,一个是公钥一个是私钥,公钥加密的信息只有是要才能解密.ssh协议可用于服务之间的通信.例如:登录验证,git的授权等等 2 s ...

  3. python中的异常

    Python提供了两个非常重要的功能来处理异常和错误: 1) 异常处理try-.except 2) 断言assert 异常和断言,可以用于我们调试python程序,跟踪程序执行状态,尽快排查问题. 3 ...

  4. Java调用Javascript、Python算法总结

    最近项目中经常需要将Javascript或者Python中的算法发布为服务,而发布Tomcat服务则需要在Java中调用这些算法,因此就不免要进行跨语言调用,即在Java程序中调用这些算法. 不管是调 ...

  5. 新手教程:不写JS,在MIP页中实现异步加载数据

    从需求谈起:在 MIP 页中异步加载数据 MIP(移动网页加速器) 的 加速原理 除了靠谱的 MIP-Cache CDN 加速外,最值得一提的就是组件系统.所有 JS 交互都需要使用 MIP 组件实现 ...

  6. MongoDB 小记

    之前本人说过一款非关系型数据库的代表 Redis 的 < Redis 小记 >文章,觉得意犹未尽,今天就来介绍一款数据库 MongoDB ,先来看一下 MongoDB是一款基于分布式文件存 ...

  7. 【重学计算机】操作系统D2章:处理器管理

    1. 指令与处理器模式 指令执行周期:取指.译码.执行 指令分类(根据权限) 特权指令:只能被操作系统内核使用(启动IO,置PC值) 非特权指令:所有程序都能使用 处理器模式: 共有四种:0内核模式, ...

  8. .NET Core微服务系列基础文章索引(目录导航Final版)

    一.为啥要总结和收集这个系列? 今年从原来的Team里面被抽出来加入了新的Team,开始做Java微服务的开发工作,接触了Spring Boot, Spring Cloud等技术栈,对微服务这种架构有 ...

  9. 你真的了解字典(Dictionary)吗?

    从一道亲身经历的面试题说起 半年前,我参加我现在所在公司的面试,面试官给了一道题,说有一个Y形的链表,知道起始节点,找出交叉节点. 为了便于描述,我把上面的那条线路称为线路1,下面的称为线路2. 思路 ...

  10. Redis缓存穿透、缓存雪崩和缓存击穿理解

    1.缓存穿透(不存在的商品访问数据造成压力) 缓存穿透,是指查询一个数据库一定不存在的数据.正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并 ...