其他章节请看:

es6 快速入门 系列

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

试图解决的问题

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

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

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

// 定义类:Rectangle
function Rectangle(length, width){
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function(){
return this.length * this.width
}
// 定义类:Square
function Square(length){
// 对象冒充
Rectangle.call(this, length, length)
}
// 指定 Square 的原型
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square,
enumerable: true,
writable: true,
configurable: true
}
}); let square = new Square(3) console.log(square.getArea()) // 9 - getArea() 方法从父类继承
console.log(square instanceof Square) // true
console.log(square instanceof Rectangle) // true

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

解决方法

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

// 通过 class 定义一个类
class Rectangle{
constructor(length, width){
this.length = length;
this.width = width;
}
// 方法直接写在 class 中
getArea(){
return this.length * this.width
}
}
// 通过 extends 关键字指定类继承的函数,原型会自动调整
class Square extends Rectangle{
constructor(length){
// 通过调用 super() 方法即可访问基类的构造函数
super(length, length)
}
} let square = new Square(3) console.log(square.getArea()) // 9
console.log(square instanceof Square) // true
console.log(square instanceof Rectangle) // true

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

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

补充

类的创建

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

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

// class 类名
class People{
constructor(name){
this.name =name;
}
// 不需要逗号
sayName(){
console.log(this.name)
}
}
let people = new People('aaron')
people.sayName() // aaron
console.log(typeof People) // function {1} - People 其实还是一个函数

类的表达式语法:

// 直接将一个类赋值给变量 People
let People = class{
constructor(name){
this.name =name;
}
sayName(){
console.log(this.name)
}
}
let people = new People('aaron')
people.sayName() // aaron

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

类是一等公民

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

function createObject(classDef){
return new classDef()
}
let obj = createObject(class People{
sayName(){
console.log('aaron')
}
})
obj.sayName() // aaron

继承

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

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

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

内建对象的继承

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

class MyArray extends Array{

}
let myArray = new MyArray()
myArray[0] = 11
console.log(myArray.length) // 1 myArray.length = 0;
console.log(myArray[0]) // undefined

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

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

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

静态成员

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

function People(){}

People.create = function(){}

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

class People{
constructor(name){
this.name = name
}
static create(name){
return new People(name)
}
}
let p1 = People.create('aaron') console.log(p1.name) // aaron

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

class A{
static say(){
console.log('A say')
}
}
class B extends A{ }
B.say() // A say

派生自表达式的类

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

function Rectangle(length, width){
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function(){
return this.length * this.width
} function getBase(){
return Rectangle;
}
// getBase()这个表达式返回 Reactangle, 具有[[Constructor]]属性和原型,因此 Square 类可以直接继承它
class Square extends getBase(){
constructor(length){
super(length, length)
}
} let square = new Square(3)
console.log(square.getArea()) // 9

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

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

class A{
constructor(){
if(new.target === A){
console.log(true)
}else{
console.log(false)
}
}
} class B extends A{
constructor(){
super()
}
} new A() // true {1}
new B() // false {2} - B 调用 A 的构造函数,所以当调用发生时 new.target 等于 B

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

// 抽象基类 BaseClass
class BaseClass{
constructor(){
if(new.target === BaseClass){
throw new Error('这个类不能被实例化')
}
}
sayName(){
console.log(this.name)
}
}
class B extends BaseClass{
constructor(name){
super()
this.name = name
}
}
let b = new B('aaron')
b.sayName() // aaron
new BaseClass() // Error: 这个类不能被实例化

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

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

// 数据 name 存储在变量 _name 中,只能通过访问器属性来操作,从而达到数据封装的目的
class People{
constructor(name){
this._name = name;
}
set name(v){
this._name = v;
}
get name(){
return this._name
}
} let people = new People('aaron')
console.log(people.name) // aaron
people.name = 'lj'
console.log(people.name) // lj

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

let methodName = 'sayName'
let getName = 'age'
class People{
constructor(name){
this.name = name;
}
[methodName](){
console.log(this.name)
}
get [getName](){
console.log('get')
}
}
let people = new People('aaron') people.sayName() // aaron
people.age // get

生成器

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

class Collection{
constructor(){
this.items = []
}
push(v){
this.items.push(v)
}
*[Symbol.iterator](){
yield *this.items.values()
}
}
let collection = new Collection()
collection.push('11')
collection.push('22')
collection.push('33') for(let v of collection){
console.log(v) // 11 22 33
}

其他章节请看:

es6 快速入门 系列

es6 快速入门 系列 —— 类 (class)的更多相关文章

  1. es6 快速入门 系列 —— 变量声明:let和const

    其他章节请看: es6 快速入门 系列 变量声明:let和const 试图解决的问题 经典的 var 声明让人迷惑 function demo1(v){ if(v){ var color='red' ...

  2. es6 快速入门 系列 —— promise

    其他章节请看: es6 快速入门 系列 Promise Promise 是一种异步编程的选择 初步认识Promise 用 Promise 来实现这样一个功能:发送一个 ajax,返回后输出 json ...

  3. es6快速入门 系列 - async

    其他章节请看: es6 快速入门 系列 async 前文我们已经知道 promise 是一种异步编程的选择.而 async 是一种用于执行异步任务更简单的语法. Tip:建议学完 Promise 在看 ...

  4. es6 快速入门 系列 —— 对象

    其他章节请看: es6 快速入门 系列 对象 试图解决的问题 写法繁杂 属性初始值需要重复写 function createPeople(name, age){ // name 和 age 都写了 2 ...

  5. es6 快速入门 系列

    es6 快速入门(未完结,持续更新中...) 前言 为什么要学习es6 es6对于所有javaScript开发者来说,非常重要 未来,es6将构成javaScript应用程序的基础 es6中很多特性, ...

  6. es6 快速入门 —— 函数

    其他章节请看: es6 快速入门 系列 函数 函数是所有编程语言的重要组成部分,es6之前函数语法一直没什么变化,遗留了许多问题,javaScript开发者多年来不断抱怨,es6终于决定大力度更新函数 ...

  7. vue 快速入门 系列 —— 侦测数据的变化 - [基本实现]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [基本实现] 在 初步认识 vue 这篇文章的 hello-world 示例中,我们通过修改数据(app.seen = false),页面中 ...

  8. 快速入门系列--WebAPI--01基础

    ASP.NET MVC和WebAPI已经是.NET Web部分的主流,刚开始时两个公用同一个管道,之后为了更加的轻量化(WebAPI是对WCF Restful的轻量化),WebAPI使用了新的管道,因 ...

  9. 快速入门系列--WebAPI--03框架你值得拥有

    接下来进入的是俺在ASP.NET学习中最重要的WebAPI部分,在现在流行的互联网场景下,WebAPI可以和HTML5.单页应用程序SPA等技术和理念很好的结合在一起.所谓ASP.NET WebAPI ...

随机推荐

  1. 【LeetCode】617. Merge Two Binary Trees 解题报告

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcod ...

  2. 【LeetCode】337. House Robber III 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  3. Chapter 9 Measurement Bias

    目录 9.1 Measurement Error The structure of measurement error 9.3 Mismeasured confounders 9.4 Intentio ...

  4. matplotlib 进阶之Tight Layout guide

    目录 简单的例子 Use with GridSpec Legend and Annotations Use with AxesGrid1 Colorbar 函数链接 matplotlib教程学习笔记 ...

  5. Android物联网应用程序开发(智慧城市)—— 环境状态值范围设置界面开发

    效果图: 代码: 布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns ...

  6. Android 悬浮窗

    悬浮窗是一种比较常见的需求.例如把视频通话界面缩小成一个悬浮窗,然后用户可以在其他界面上处理事情. 本文给出一个简单的悬浮窗实现.可缩小activity和还原大小.可悬浮在其他activity上.使用 ...

  7. Java 16 中新增的 Stream 接口的一些思考

    这里先提一个题外话,如果想看 JDK 不同版本之间有何差异,增加或者删除了哪些 API,可以通过下面这个链接查看: https://javaalmanac.io/jdk/17/apidiff/11/ ...

  8. C语言 Make命令教程

    make只是一个根据指定的Shell命令进行构建的工具 在linux和unix中,有一个强大的使用程序,叫make,可以用它来管理多模块程序的编译和链接,直至生成可执行文件 make使用程序读取一个说 ...

  9. SQLAlchemy(十)

    ORM操作在实际项目中的应用非常多,涉及到的框架也是根据不同的项目有不同的处理模块,不过操作流程和步骤都是大同小异基本没有什么太大变化,唯一需要注意的就是在实际操作过程中你要使用的ORM框架的处理性能 ...

  10. Django_MVT(二)

    一.MVT简介 M全拼为Model,与MVC中的M功能相同,负责和数据库交互,进行数据处理. V全拼为View,与MVC中的C功能相同,接收请求,进行业务处理,返回应答. T全拼为Template,与 ...