一些JavaScript 类(class)中需要了解的知识

JavaScript 使用原型继承:每个对象都从原型对象继承属性和方法。

JavaSwift等语言中使用的传统类作为创建对象的蓝图,在 JavaScript 中不存在,原型继承仅处理对象。

原型继承可以模拟经典类继承。为了将传统的类引入JavaScript, ES2015 标准引入了class语法,其底层实现还是基于原型,只是原型继承的语法糖。

本篇文章主要让你熟悉 JavaScript 类:如何定义类,初始化实例,定义字段和方法,理解私有和公共字段,掌握静态字段和方法。

1. 定义:类关键字

使用关键字class可以在 JS 中定义了一个类:

class User {
  // 类的主体
}

上面的代码定义了一个User类。 大括号{}里面是类的主体。 此语法称为class 声明。

如果在定义类时没有指定类名。可以通过使用类表达式,将类分配给变量:

const UserClass = class {
  // 类的主体
}

还可以轻松地将类导出为 ES6 模块的一部分,默认导出语法如下:

export default class User {
  // 主体
}

命名导出如下:

export class User {
  // 主体
}

当我们创建类的实例时,该类将变得非常有用。实例是包含类所描述的数据和行为的对象。

1.jpg

使用new运算符实例化该类,语法:instance = new Class()

例如,可以使用new操作符实例化User类:

const myUser = new User();

new User()创建User类的一个实例。

2. 初始化:constructor()

constructor(param1, param2, ...)是用于初始化实例的类主体中的一种特殊方法。 在这里可以设置字段的初始值或进行任何类型的对象设置。

在下面的示例中,构造函数设置字段name的初始值

class User {
  constructor(name) {
    this.name = name;
  }
}

User的构造函数有一个参数 name,用于设置字段this.name的初始值

在构造函数中,this 值等于新创建的实例。用于实例化类的参数成为构造函数的参数:

class User {
  constructor(name) {
    name; // => 'Jon Snow'
    this.name = name;
  }
}

const user = new User('Jon Snow');

构造函数中的name参数的值为'Jon Snow'。如果没有定义该类的构造函数,则会创建一个默认的构造函数。默认的构造函数是一个空函数,它不修改实例。

同时,一个JavaScript 类最多可以有一个构造函数。

3.字段

类字段是保存信息的变量,字段可以附加到两个实体:

  1. 类实例上的字段
  2. 类本身的字段(也称为静态字段)

字段有两种级别可访问性:

  1. public:该字段可以在任何地方访问
  2. private:字段只能在类的主体中访问

3.1 公共实例字段

让我们再次看看前面的代码片段:

class User {
  constructor(name) {
    this.name = name;
  }
}

表达式this.name = name创建一个实例字段名,并为其分配一个初始值。

然后,可以使用属性访问器访问name字段

const user = new User('Jon Snow');
user.name; // => 'Jon Snow'

name是一个公共字段,因为你可以在User类主体之外访问它。

当字段在构造函数中隐式创建时,就像前面的场景一样,可能获取所有字段。必须从构造函数的代码中破译它们。

class fields proposal 提案允许我们在类的主体中定义字段,并且可以立即指定初始值:

class SomeClass {
  field1;
  field2 = 'Initial value';

  // ...
}

接着我们修改User类并声明一个公共字段name

class User {
  name;
  
  constructor(name) {
    this.name = name;
  }
}

const user = new User('Jon Snow');
user.name; // => 'Jon Snow'

name;在类的主体中声明一个公共字段name

以这种方式声明的公共字段具有表现力:快速查看字段声明就足以了解类的数据结构,而且,类字段可以在声明时立即初始化。

class User {
  name = 'Unknown';

  constructor() {
    // No initialization
  }
}

const user = new User();
user.name; // => 'Unknown'

类体内的name ='Unknown'声明一个字段名称,并使用值'Unknown'对其进行初始化。

对公共字段的访问或更新没有限制。可以读取构造函数、方法和类外部的公共字段并将其赋值。

3.2 私有实例字段

封装是一个重要的概念,它允许我们隐藏类的内部细节。使用封装类只依赖类提供的公共接口,而不耦合类的实现细节。

当实现细节改变时,考虑到封装而组织的类更容易更新。

隐藏对象内部数据的一种好方法是使用私有字段。这些字段只能在它们所属的类中读取和更改。类的外部世界不能直接更改私有字段。

私有字段只能在类的主体中访问。

在字段名前面加上特殊的符号#使其成为私有的,例如#myField。每次处理字段时都必须保留前缀#声明它、读取它或修改它。

确保在实例初始化时可以一次设置字段#name

class User {
  #name;

  constructor(name) {
    this.#name = name;
  }

  getName() {
    return this.#name;
  }
}

const user = new User('Jon Snow');
user.getName(); // => 'Jon Snow'

user.#name;     // 抛出语法错误

#name是一个私有字段。可以在User内访问和修改#name。方法getName()可以访问私有字段#name

但是,如果我们试图在 User 主体之外访问私有字段#name,则会抛出一个语法错误:SyntaxError: Private field '#name' must be declared in an enclosing class

3.3 公共静态字段

我们还可以在类本身上定义字段:静态字段。这有助于定义类常量或存储特定于该类的信息。

要在 JavaScript 类中创建静态字段,请使用特殊的关键字static后面跟字段名:static myStaticField

让我们添加一个表示用户类型的新字段type:adminregular。静态字TYPE_ADMINTYPE_REGULAR是区分用户类型的常量:

class User {
  static TYPE_ADMIN = 'admin';
  static TYPE_REGULAR = 'regular';

  name;
  type;

  constructor(name, type) {
    this.name = name;
    this.type = type;
  }
}

const admin = new User('Site Admin', User.TYPE_ADMIN);
admin.type === User.TYPE_ADMIN; // => true

static TYPE_ADMINstatic TYPE_REGULARUser类内部定义了静态变量。 要访问静态字段,必须使用后跟字段名称的类:User.TYPE_ADMINUser.TYPE_REGULAR

3.4 私有静态字段

有时,我们也想隐藏静态字段的实现细节,在时候,就可以将静态字段设为私有。

要使静态字段成为私有的,只要字段名前面加上#符号:static #myPrivateStaticField

假设我们希望限制User类的实例数量。要隐藏实例限制的详细信息,可以创建私有静态字段:

class User {
  static #MAX_INSTANCES = 2;
  static #instances = 0;
  
  name;

  constructor(name) {
    User.#instances++;
    if (User.#instances > User.#MAX_INSTANCES) {
      throw new Error('Unable to create User instance');
    }
    this.name = name;
  }
}

new User('Jon Snow');
new User('Arya Stark');
new User('Sansa Stark'); // throws Error

静态字段User.#MAX_INSTANCES设置允许的最大实例数,而User.#instances静态字段则计算实际的实例数。

这些私有静态字段只能在User类中访问,类的外部都不会干扰限制机制:这就是封装的好处。

4.方法

字段保存数据,但是修改数据的能力是由属于类的一部分的特殊功能实现的:方法

JavaScript 类同时支持实例和静态方法。

4.1 实例方法

实例方法可以访问和修改实例数据。实例方法可以调用其他实例方法,也可以调用任何静态方法。

例如,定义一个方法getName(),它返回User类中的name

class User {
  name = 'Unknown';

  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

const user = new User('Jon Snow');
user.getName(); // => 'Jon Snow'

getName() { ... }User类中的一个方法,getname()是一个方法调用:它执行方法并返回计算值(如果存在的话)。

在类方法和构造函数中,this值等于类实例。使用this来访问实例数据:this.field 或者调用其他方法:this.method()

接着我们添加一个具有一个参数并调用另一种方法的新方法名称nameContains(str)

class User {
  name;

  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }

  nameContains(str) {
    return this.getName().includes(str);
  }
}

const user = new User('Jon Snow');
user.nameContains('Jon');   // => true
user.nameContains('Stark'); // => false

nameContains(str) { ... }User类的一种方法,它接受一个参数str。 不仅如此,它还执行实例this.getName()的方法来获取用户名。

方法也可以是私有的。 为了使方法私有前缀,名称以#开头即可,如下所示:

class User {
  #name;

  constructor(name) {
    this.#name = name;
  }

  #getName() {
    return this.#name;
  }

  nameContains(str) {
    return this.#getName().includes(str);
  }
}

const user = new User('Jon Snow');
user.nameContains('Jon');   // => true
user.nameContains('Stark'); // => false

user.#getName(); // SyntaxError is thrown

#getName()是一个私有方法。在方法nameContains(str)中,可以这样调用一个私有方法:this.#getName()

由于是私有的,#getName()不能在用User 类主体之外调用。

4.2 getters 和 setters

gettersetter模仿常规字段,但是对如何访问和更改字段具有更多控制。在尝试获取字段值时执行getter,而在尝试设置值时使用setter

为了确保Username属性不能为空,我们将私有字段#nameValue封装在gettersetter中:

class User {
  #nameValue;

  constructor(name) {
    this.name = name;
  }

  get name() {
    return this.#nameValue;
  }

  set name(name) {
    if (name === '') {
      throw new Error(`name field of User cannot be empty`);
    }
    this.#nameValue = name;
  }
}

const user = new User('Jon Snow');
user.name; // The getter is invoked, => 'Jon Snow'
user.name = 'Jon White'; // The setter is invoked

user.name = ''; // The setter throws an Error

get name() {...} 在访问user.name会被执行。而set name(name){…}在字段更新(user.name = 'Jon Snow')时执行。如果新值是一个空字符串,setter将抛出错误。

4.3 静态方法

静态方法是直接附加到类的函数,它们持有与类相关的逻辑,而不是类的实例。

要创建一个静态方法,请使用特殊的关键字static和一个常规的方法语法:static myStaticMethod() { ... }

使用静态方法时,有两个简单的规则需要记住:

  1. 静态方法可以访问静态字段。
  2. 静态方法不能访问实例字段。

例如,创建一个静态方法来检测是否已经使用了具有特定名称的用户。

class User {
  static #takenNames = [];

  static isNameTaken(name) {
    return User.#takenNames.includes(name);
  }

  name = 'Unknown';

  constructor(name) {
    this.name = name;
    User.#takenNames.push(name);
  }
}

const user = new User('Jon Snow');

User.isNameTaken('Jon Snow');   // => true
User.isNameTaken('Arya Stark'); // => false

isNameTaken()是一个使用静态私有字段User的静态方法用于检查已取的名字。

静态方法可以是私有的:static #staticFunction() {...}。同样,它们遵循私有规则:只能在类主体中调用私有静态方法。

5. 继承: extends

JavaScript 中的类使用extends关键字支持单继承。
class Child extends Parent { }表达式中,Child类从Parent继承构造函数,字段和方法。

例如,我们创建一个新的子类ContentWriter来继承父类User

class User {
  name;

  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class ContentWriter extends User {
  posts = [];
}

const writer = new ContentWriter('John Smith');

writer.name;      // => 'John Smith'
writer.getName(); // => 'John Smith'
writer.posts;     // => []

ContentWriter继承了User的构造函数,方法getName()和字段name。同样,ContentWriter类声明了一个新的字段posts

注意,父类的私有成员不会被子类继承。

5.1 父构造函数:constructor()中的super()

如果希望在子类中调用父构造函数,则需要使用子构造函数中可用的super()特殊函数。

例如,让ContentWriter构造函数调用User的父构造函数,以及初始化posts字段

class User {
  name;

  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class ContentWriter extends User {
  posts = [];

  constructor(name, posts) {
    super(name);
    this.posts = posts;
  }
}

const writer = new ContentWriter('John Smith', ['Why I like JS']);
writer.name; // => 'John Smith'
writer.posts // => ['Why I like JS']

子类ContentWriter中的super(name)执行父类User的构造函数。

注意,在使用this关键字之前,必须在子构造函数中执行super()。调用super()确保父构造函数初始化实例。

class Child extends Parent {
  constructor(value1, value2) {
    // Does not work!
    this.prop2 = value2;
    super(value1);
  }
}

5.2 父实例:方法中的super

如果希望在子方法中访问父方法,可以使用特殊的快捷方式super

class User {
  name;

  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class ContentWriter extends User {
  posts = [];

  constructor(name, posts) {
    super(name);
    this.posts = posts;
  }

  getName() {
    const name = super.getName();
    if (name === '') {
      return 'Unknwon';
    }
    return name;
  }
}

const writer = new ContentWriter('', ['Why I like JS']);
writer.getName(); // => 'Unknwon'

子类ContentWritergetName()直接从父类User访问方法super.getName(),这个特性称为方法重写

注意,也可以在静态方法中使用super来访问父类的静态方法。

6.对象类型检查:instanceof

object instanceof Class是确定object 是否为Class实例的运算符,来看看示例:

class User {
  name;

  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

const user = new User('Jon Snow');
const obj = {};

user instanceof User; // => true
obj instanceof User; // => false

userUser类的一个实例,user instanceof User的计算结果为true

空对象{}不是User的实例,相应地obj instanceof Userfalse

instanceof是多态的:操作符检测作为父类实例的子类。

class User {
  name;

  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class ContentWriter extends User {
  posts = [];

  constructor(name, posts) {
    super(name);
    this.posts = posts;
  }
}

const writer = new ContentWriter('John Smith', ['Why I like JS']);

writer instanceof ContentWriter; // => true
writer instanceof User;          // => true

writer是子类ContentWriter的一个实例。运算符writer instanceof ContentWriter的计算结果为true

同时ContentWriterUser的子类。因此writer instanceof User结果也为true

如果想确定实例的确切类,该怎么办?可以使用构造函数属性并直接与类进行比较

writer.constructor === ContentWriter; // => true
writer.constructor === User;          // => false

7. 类和原型

必须说 JS 中的类语法在从原型继承中抽象方面做得很好。但是,类是在原型继承的基础上构建的。每个类都是一个函数,并在作为构造函数调用时创建一个实例。

以下两个代码段是等价的。

类版本:

class User {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

const user = new User('John');

user.getName();       // => 'John Snow'
user instanceof User; // => true

使用原型的版本:

function User(name) {
  this.name = name;
}

User.prototype.getName = function() {
  return this.name;
}

const user = new User('John');

user.getName();       // => 'John Snow'
user instanceof User; // => true

如果你熟悉JavaSwift语言的经典继承机制,则可以更轻松地使用类语法。

8. 类的可用性

这篇文章中的类的一些特性有些还在分布第三阶段的提案中。在2019年底,类的特性分为以下两部分:

9. 总结

JavaScript 类用构造函数初始化实例,定义字段和方法。甚至可以使用static关键字在类本身上附加字段和方法。

继承是使用extends关键字实现的:可以轻松地从父类创建子类,super关键字用于从子类访问父类。

要利用封装,将字段和方法设为私有以隐藏类的内部细节,私有字段和方法名必须以#开头。

你对使用#前缀私有属性有何看法,欢迎留言讨论?

原文地址:https://dmitripavlutin.com/javascript-classes-complete-guide/

为了保证的可读性,本文采用意译而非直译

Image placeholder
前端答疑
未设置
  49人点赞

没有讨论,发表一下自己的看法吧

推荐文章
JS 中一定要了解的数据类型和数据转换

数据类型 前言 Js中的类型只有6种,其中基本数据类型有5种分别为string,number,boolen,null,undefined,引用类型有一种,就是object,object是一个大的综合

JS 中一定要了解的数据类型和数据转换

Js数据类型 前言 Js中的类型只有6种,其中基本数据类型有5种分别为string,number,boolen,null,undefined,引用类型有一种,就是object,object是一个大的

这 20 多个高并发编程必备的知识点,你都会吗?

转载自并发编程网–ifeve.comhttp://ifeve.com/%e9%ab%98%e5... 一、前言借用Java并发编程实践中的话”编写正确的程序并不容易,而编写正常的并发程序就更难了”,相

可视化的JavaScript:JavaScript引擎运行原理

JavaScript很酷,但是JavaScript引擎是如何才能理解我们编写的代码呢?作为JavaScript开发人员,我们通常不需要自己处理编译器。然而,了解JavaScript引擎的基础知识并了解

前端不得不了解的 Flex 布局

背景 又双叒叕被老大拉来顶替前端小姐姐撸代码,接触到了Flex布局,以前只听过没用过,碰巧这次要揭露她的面纱,就记录一下。接触前端的同学都应该知道网页布局是CSS的一个重点,布局的传统方案都是基于盒

前端不得不了解的 Flex 布局

背景 又双叒叕被老大拉来顶替前端小姐姐撸代码,接触到了Flex布局,以前只听过没用过,碰巧这次要揭露她的面纱,就记录一下。接触前端的同学都应该知道网页布局是CSS的一个重点,布局的传统方案都是基于盒

关于工业物联网,你应该了解的3件事

世界各地的基础设施日益紧密。我们应该感谢工业物联网(IIoT)带来的这种连通性,它将交通、制造业、医疗保健等行业的机器和设备连接起来,并远远超出了预期范围。IIoT的潜力仍然很高,但风险也是如此。你需

深入了解JavaScript async/await !

Asyncfunctions让我们以async这个关键字开始。它可以被放置在任何函数前面,像下面这样:asyncfunctionf(){ return1; }在函数前面的「async」这个单词表达了一

JavaScript 安全知识: CORS 简明教程

概述浏览器会强制同源策略以禁止不同源的网站获得响应; 『同源策略』不会阻止对其他来源的请求,但是会禁用JavaScript对响应内容的读取。 -CORS标头允许访问跨域响应。 -CORS与凭证一起需要

看《长安十二时辰》可以了解哪些算法知识

最近,小吴在追一部古装剧—-《长安十二时辰》。故事讲得是在上元节前夕,长安城混入可疑人员,身陷囹圄的张小敬临危受命,与少年天才李必携手在十二时辰内破除隐患。该剧的一大亮点就在于 时间很紧迫,需要在二十

云原生时代,分布式系统设计必备知识图谱(内含22个知识点)

作者|杨泽强(竹涧)阿里云技术专家我们身处于一个充斥着分布式系统解决方案的计算机时代,无论是支付宝、微信这样顶级流量产品、还是区块链、IOT等热门概念、抑或如火如荼的容器生态技术如Kubernetes

Vuex的一些常用知识点介绍

一、为什么要使用Vuex1、多个组件依赖同一个状态,使用组件之间通信方法会非常繁琐,例如多层嵌套组件。2、需要全局保存的数据,例如用户、权限信息,全局系统设置二、Vuex的五个核心属性1、state:

javascript怎么清除CSS样式?

javascript怎么清除CSS样式?一、使用setAttribute方法清除样式dom结构helloworldjavascriptp.setAttribute('style','');二、使用re

IOS APP开发需要学什么知识和技能?

IOSAPP开发需求学什么学问和技艺?目前IOS系统的APP和Android系统的APP,是手机APP开发的两大主流。就拿IOS系统的APP开发来说,需求和安卓完整不同的开发言语和工具。本文着重来讨论

你需要的前端知识收集好了,请查收!

github地址记录学习成长收获的知识,不断进步,Front-end-go-on目前包含的模块有 HTML基础 CSS基础 JS基础 数据结构基础 Http基础 JS代码练习 算法代码练习 CSS代码

前端培训-中级阶段(31)- Class 的基本语法、Class 的继承(2019-12-26期)

前端最基础的就是HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知

喊话 JavaScript 开发者:玩 DOM 也要专业范儿

别再害怕DOM了,让我们充分挖掘DOM的潜力,你会真的爱上它。 2008年,当我刚成为一名专业Web开发人员参加工作时,我了解一些HTML、CSS和PHP的知识。那时我也在学习JavaScript

JavaScript 的数据结构和算法

现在有个还不是好的项目,未来会成为好的项目的项目想介绍给大家。传送门https://github.com/MasterShu/JavaScript-Da...这个是本人在维护的一个项目。主要是使用Ja

一起来学 TypeScript

鉴于JavaScript社区正式更名为F2E前端,我就大胆的把我另外一个项目也放上来。😄这个项目是关于TypeScript,是个人记录TypeScript的学习历程以及各个常用库的TypeScript

5种用于前端开发的JavaScript替代方案

JavaScript虽然是很受欢迎的语言,但是并不适合所有人,那么有哪些替代方案呢?本文将分析5种JavaScript替代方案。1995年,Netscape(网景通信公司)聘请BrendanEich为

javascript如何判断是不是整数?

方式一、使用取余运算符判断任何整数都会被1整除,即余数是0。利用这个规则来判断是否是整数。functionisInteger(obj){ returnobj%1===0 } isInteger(3);

9 个顶级的JavaScript图表库

数据可视化技术在过去十年中一直在不断改进,现在许多高级图表库可供消费者使用。在2000年代初期,图表生成主要由服务器端图像位图构成。诸如Flash和Silverlight之类的插件提供了更具交互性的图

JavaScript闭包基础指南

闭包是函数创建时范围内所有变量的集合。要使用闭包,请在另一个称为嵌套函数的函数内创建一个函数。内部函数将有权访问外部函数范围中的变量(Closure有助于访问外部函数范围),即使在返回外部函数之后也是

先学php还是javascript?

javascript是前台的东西,PHP是后台的东西,两者先学谁都是一样的。两者之间没有啥实质性的必然联系。Javascript就是浏览器执行的脚本语言,控制页面内容。php就是服务器端执行的语言,读

JavaScript中对“this”的简单理解

1.this的奥秘很多时候,JS中的this对于咱们的初学者很容易产生困惑不解。this的功能很强大,但需要一定付出才能慢慢理解它。对Java、PHP或其他标准语言来看,this表示类方法中当前对象的