java学习网 java教程视频 java论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 375|回复: 69
收起左侧

JavaScript 类完整指南

  [复制链接]

506

主题

551

帖子

3439

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3439
发表于 2020-7-23 09:55:47 | 显示全部楼层 |阅读模式
JavaScript 使用原型继承:每个对象都从其原型对象继承属性和方法。
在 JavaScript 中不存在 Java 或 Swift 等语言中所使用的作为创建对象 蓝图的传统类,原型继承仅处理对象。
原型继承可以模仿经典类的继承。为了将传统类引入 JavaScript,ES2015 标准引入了 class 语法:基于原型继承上的语法糖。
本文使你熟悉 JavaScript 类:如何定义类,初始化实例,定义字段和方法,了解私有字段和公共字段,掌握静态字段和方法。
目录
1.定义:class 关键字2.初始化:constructor()3. 字段3.1 公共实例字段3.2 私有实例字段3.3 公共静态字段3.4 私有静态字段4. 方法4.1 实例方法4.2 Getter 和 Setter4.3静态方法5. 继承:extends5.1 父构造函数:constructor() 中的 super()5.2 父实例:方法中的 super6. 对象类型检查:instanceof7. 类和原型8. 类功能的可用性9. 总结

1.定义:class 关键字
用特殊关键字 class 在 JavaScript 中定义一个类:
1class User {
2  // The body of class
3}
上面的代码定义了一个类  User。大括号  { } 界定了类的主体。请注意,此语法称为 类声明
你没有义务指明 class 的名称。通过使用类表达式 ,你可以将类分配给变量:
1const UserClass = class {
2  // The body of class
3};
可以轻松地将类导出为 ES2015 模块的一部分。这是 默认导出 的语法:
1export default class User {
2 // The body of class
3}
还有一个 命名导出
1export class User {
2  // The body of class
3}
当你创建类的 实例(instance) 时,该类将变得很有用。实例是一个包含类描述的数据和行为的对象。
JavaScript类实例
new 运算符可在 JavaScript 中实例化该类:instance = new Class()。
例如,你可以用 new 运算符实例化 User 类:
1const myUser = new User();
new User() 创建 User 类的实例。
2.初始化:constructor()
constructor(param1,param2,...) 是类中初始化实例的特殊方法。在这里你可以设置字段的初始值或针对对象进行任何类型的设置。
在以下示例中,构造函数设置了字段 name 的初始值:
1class User {
2  constructor(name) {    this.name = name;  }}
User 的构造函数只有一个参数 name,用于设置 this.name 字段的初始值。
在构造函数中,this 值等于新创建的实例。
用于实例化类的参数成为构造函数的参数:
1class User {
2  constructor(name) {
3    name; // => 'Jon Snow'    this.name = name;
4  }
5}
6
7const user = new User('Jon Snow');
构造函数中的 name 参数的值为 'Jon Snow'。
如果你没有为该类定义构造函数,则会创建一个默认的构造函数。默认构造函数是一个空函数,它不会修改实例。
同时,一个 JavaScript 类最多可以有一个构造函数。
3. 字段
类字段是用来保存信息的变量。字段可以附加到 2 个实体:
  • 类实例上的字段
  • 类本身的字段(又称为静态)

这些字段还具有 2 级可访问性:
  • 公共(public):该字段可在任何地方访问
  • 私有(private):只能在课程正文中访问该字段

3.1 公共实例字段
让我们再次看一下之前的代码片段:
1class User {
2  constructor(name) {
3    this.name = name;  }
4}
表达式 this.name = name 创建一个实例字段 name,并为其分配一个初始值。
稍后,你可以使用属性访问器来访问 name 字段:
1const user = new User('Jon Snow');
2user.name; // => 'Jon Snow'
name 是一个公共字段,你可以在 User 类主体之外访问它。
当像在前面场景中那样在构造函数内部隐式创建字段时,可能很难掌握字段列表。你必须从构造函数的代码中解密它们。
更好的方法是显式声明类字段。无论构造函数做什么,实例始终具有相同的字段集。
类字段提案允许你在类主体内定义字段。另外,你可以立即指示初始值:
1class SomeClass {
2  field1;  field2 = 'Initial value';
3  // ...
4}
让我们修改 User 类,并声明一个公共字段 name:
1class User {
2  name;  
3  constructor(name) {
4    this.name = name;
5  }
6}
7
8const user = new User('Jon Snow');
9user.name; // => 'Jon Snow'
类中的 name; 声明了一个公共字段 name。
以这种方式声明的公共字段有很好的表现力:通过查看字段声明就能够了解该类的数据结构。
而且,可以在声明时立即初始化类字段。
1class User {
2  name = 'Unknown';
3  constructor() {
4    // No initialization
5  }
6}
7
8const user = new User();
9user.name; // => 'Unknown'
类中的 name ='Unknown' 声明一个字段 name 并用值 'Unknown' 初始化它。
对公有字段的访问或更新没有任何限制。你可以读取它们的值并将其分配给构造函数、方法内部以及类外部的公有字段。
3.2 私有实例字段
封装是一个重要的概念,可让你隐藏类的内部细节。使用封装类的人仅涉及该类提供的公共接口,而不会耦合到该类的实现细节。
当实现细节被更改时,考虑封装性的类更易于更新。
使用私有字段是隐藏对象内部数据的一种好方法。这是只能在它们所属的类中读取和修改的字段。该类的外部不能直接更改私有字段。
私有字段 仅可在类的正文中访问。
在字段名前加上特殊符号 # 使其私有,例如 #myField。每次使用该字段时,都必须保留前缀 #:不管是声明、读取还是修改。
确保在实例初始化时可以一次设置字段 #name:
1class User {
2  #name;
3  constructor(name) {
4    this.#name = name;
5  }
6
7  getName() {
8    return this.#name;
9  }
10}
11
12const user = new User('Jon Snow');
13user.getName(); // => 'Jon Snow'
14
15user.#name;     // SyntaxError is thrown
#name 是一个私有字段。你可以在 User 主体内访问和修改  #name。方法 getName()可以访问私有字段 #name。
如果尝试在用户类主体之外访问私有字段 #name,则会引发语法错误:SyntaxError: Private field '#name' must be declared in an enclosing class。
3.3 公共静态字段
你还可以在类本身上定义字段:静态字段 。它有助于定义类常量或存储特定于类的信息。
要在 JavaScript 类中创建静态字段,请使用特殊关键字 static ,后跟字段名称:static myStaticField。
让我们添加一个新的字段 type 来指示用户类型:admin 或 Regular。静态字段 TYPE_ADMIN 和 TYPE_REGULAR 是常量,可以方便的区分用户类型:
1class User {
2  static TYPE_ADMIN = 'admin';  static TYPE_REGULAR = 'regular';
3  name;
4  type;
5
6  constructor(name, type) {
7    this.name = name;
8    this.type = type;
9  }
10}
11
12const admin = new User('Site Admin', User.TYPE_ADMIN);
13admin.type === User.TYPE_ADMIN; // => true
静态 TYPE_ADMIN 和静态 TYPE_REGULAR 定义了 User 类中的静态变量。要访问静态字段,你必须使用类,后面跟字段名称:User.TYPE_ADMIN和User.TYPE_REGULAR。
3.4 私有静态字段
有时甚至静态字段也是你要隐藏的实现细节。在这方面,你可以将静态字段设为私有。
要使静态字段成为私有字段,请在字段名称前添加特殊符号 #:static #myPrivateStaticField。
假设你想限制 User 类的实例数量。要隐藏有关实例限制的详细信息,可以创建私有静态字段:
1class User {
2  static #MAX_INSTANCES = 2;  static #instances = 0;  
3  name;
4
5  constructor(name) {
6    User.#instances++;
7    if (User.#instances > User.#MAX_INSTANCES) {
8      throw new Error('Unable to create User instance');
9    }
10    this.name = name;
11  }
12}
13
14new User('Jon Snow');
15new User('Arya Stark');
16new User('Sansa Stark'); // throws Error
静态字段 User.#MAX_INSTANCES 用来设置允许的最大实例数,而 User.#instances 静态字段则计算实际的实例数。
这些私有静态字段只能在 User 类中访问。外部世界都不会干其扰限制机制:这就是封装的好处。
4. 方法
这些字段用了保存数据。但是修改数据的能力是由属于类的特殊函数执行的:方法
JavaScript 类支持实例方法和静态方法。
4.1 实例方法
实例方法可以访问和修改实例数据。实例方法可以调用其他实例方法以及任何静态方法。
例如,让我们定义一个方法 getName() ,该方法返回 User 类中的名称:
1class User {
2  name = 'Unknown';
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  getName() {    return this.name;  }}
9
10const user = new User('Jon Snow');
11user.getName(); // => 'Jon Snow'
getName() { ... } 是 User 类中的一种方法。user.getName()  是方法调用:它执行该方法并返回计算出的值(如果有的话)。
在类方法以及构造函数中,this 的值等于类实例。使用 this 来访问实例数据:this.field,也可以调用其他方法:this.method()。
让我们添加一个新方法 name Contains(string),该方法有一个参数并调用另一个方法:
1class User {
2  name;
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  getName() {
9    return this.name;
10  }
11
12  nameContains(str) {    return this.getName().includes(str);  }}
13
14const user = new User('Jon Snow');
15user.nameContains('Jon');   // => true
16user.nameContains('Stark'); // => false
nameContains(str) { ... }  是 User 类的一种方法,它接受一个参数 str。不仅如此,它还通过执行实例this.getName() 的另一种方法来获取用户名。
方法也可以是私有的。可以通过前缀使方法私有,其名称以#开头。
让我们将 getName() 方法设为私有:
1class User {
2  #name;
3
4  constructor(name) {
5    this.#name = name;
6  }
7
8  #getName() {    return this.#name;  }
9  nameContains(str) {
10    return this.#getName().includes(str);  }
11}
12
13const user = new User('Jon Snow');
14user.nameContains('Jon');   // => true
15user.nameContains('Stark'); // => false
16
17user.#getName(); // SyntaxError is thrown
#getName() 是私有方法。在方法 nameContains(str) 内,你可以这样调用一个私有方法:this.#getName()。
作为私有变量,不能在 User 类主体之外调用 #getName()。
4.2 Getter 和 Setter
getter 和 setter 模仿常规字段,但是对如何访问和修改字段有更多控制。
在尝试获取字段值时执行 getter,而在尝试设置值时使用 setter。
为了确保 User 的 name 属性不能为空,让我们将私有字段 #nameValue 包装在一个 getter 和 setter 中:
1class User {
2  #nameValue;
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  get name() {    return this.#nameValue;
9  }
10
11  set name(name) {    if (name === '') {
12      throw new Error(`name field of User cannot be empty`);
13    }
14    this.#nameValue = name;
15  }
16}
17
18const user = new User('Jon Snow');
19user.name; // The getter is invoked, => 'Jon Snow'
20user.name = 'Jon White'; // The setter is invoked
21
22user.name = ''; // The setter throws an Error
当你访问字段 user.name 的值时,将执行 get name() {...}  getter。
在  set name(name){...} 字段 user.name ='Jon White' 更新时执行。如果新值是一个空字符串,则 setter 将引发错误。
4.3静态方法
静态方法是直接附加到类的函数。它们具有与类相关的逻辑,而不是与类的实例相关的逻辑。
要创建静态方法,请使用特殊关键字 static,后跟常规方法语法:static myStaticMethod() { ... }。
使用静态方法时,要记住两个简单的规则:
  • 静态方法 可以访问 静态字段
  • 静态方法 无法访问 实例字段。

让我们创建一个静态方法来检测是否已经使用了具有特定名称的 User。
1class User {
2  static #takenNames = [];
3
4  static isNameTaken(name) {    return User.#takenNames.includes(name);  }
5  name = 'Unknown';
6
7  constructor(name) {
8    this.name = name;
9    User.#takenNames.push(name);
10  }
11}
12
13const user = new User('Jon Snow');
14
15User.isNameTaken('Jon Snow');   // => true
16User.isNameTaken('Arya Stark'); // => false
isNameTaken() 是一种静态方法,它使用静态私有字段 User.#takenNames 来检查采用的名称。
静态方法可以是私有的:static #staticFunction(){...}。它们同样遵循私有规则:只能在类主体中调用私有静态方法。
5. 继承:extends
JavaScript 中的类用 extends 关键字支持单继承。
在表达式 class Child extends Parent { }  中,子类 child 从父类继承构造函数\字段和方法。
例如,让我们创建一个新的子类 ContentWriter, 来扩展父类 User。
1class User {
2  name;
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  getName() {
9    return this.name;
10  }
11}
12
13class ContentWriter extends User {  posts = [];
14}
15
16const writer = new ContentWriter('John Smith');
17
18writer.name;      // => 'John Smith'
19writer.getName(); // => 'John Smith'
20writer.posts;     // => []
ContentWriter 从 User 继承构造函数,getName() 方法和 name 字段。同样,ContentWriter 类声明一个新字段 posts。
注意,父类的私有成员不会被子类所继承。
5.1 父构造函数:constructor() 中的 super()
如果你想在子类中调用父构的造函数,则需要使用子构造函数中提供的特殊功能 super()。
例如让 ContentWriter 构造函数调用 User 的父构造函数,并初始化 posts 字段:
1class User {
2  name;
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  getName() {
9    return this.name;
10  }
11}
12
13class ContentWriter extends User {
14  posts = [];
15
16  constructor(name, posts) {
17    super(name);    this.posts = posts;
18  }
19}
20
21const writer = new ContentWriter('John Smith', ['Why I like JS']);
22writer.name; // => 'John Smith'
23writer.posts // => ['Why I like JS']
子类 ContentWriter 中的 super(name) 执行父类 User 的构造函数。
注意,在子构造函数内部,必须在使用 this 关键字之前执行 super()。调用 super() 确保父级构造函数初始化实例。
1class Child extends Parent {
2  constructor(value1, value2) {
3    // Does not work!
4    this.prop2 = value2;    super(value1);  }
5}
5.2 父实例:方法中的 super
如果你想在子方法中访问父方法,则可以使用特殊的快捷方式 super。
1class User {
2  name;
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  getName() {
9    return this.name;
10  }
11}
12
13class ContentWriter extends User {
14  posts = [];
15
16  constructor(name, posts) {
17    super(name);
18    this.posts = posts;
19  }
20
21  getName() {
22    const name = super.getName();    if (name === '') {
23      return 'Unknwon';
24    }
25    return name;
26  }
27}
28
29const writer = new ContentWriter('', ['Why I like JS']);
30writer.getName(); // => 'Unknwon'
子类 ContentWriter 的 getName() 直接从父类 User 访问方法 super.getName()。
此功能被称为方法覆盖。
请注意,你也可以将 super 与静态方法一起使用,来访问父级的静态方法。
6. 对象类型检查:instanceof
object instanceof Class 是确定 object 是否为 Class 的实例的运算符。
让我们来看看 instanceof 运算符的作用:
1class User {
2  name;
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  getName() {
9    return this.name;
10  }
11}
12
13const user = new User('Jon Snow');
14const obj = {};
15
16user instanceof User; // => true
17obj instanceof User; // => false
user 是 User 类的实例, user instanceof User 的计算结果为 true。
空对象 {} 不是 User 的实例,对应的 obj instanceof User 是 false。
instanceof 是多态的:操作符将一个子类检测为父类的实例。
1class User {
2  name;
3
4  constructor(name) {
5    this.name = name;
6  }
7
8  getName() {
9    return this.name;
10  }
11}
12
13class ContentWriter extends User {
14  posts = [];
15
16  constructor(name, posts) {
17    super(name);
18    this.posts = posts;
19  }
20}
21
22const writer = new ContentWriter('John Smith', ['Why I like JS']);
23
24writer instanceof ContentWriter; // => true
25writer instanceof User;          // => true
writer 是子类 ContentWriter 的一个实例。操作符 writer instanceof ContentWriter 的评估结果为 true。
同时 ContentWriter 是 User 的子类。因此 writer instanceof User 也将评估为 true。
如果你想确定实例确切的类怎么办?可以用 constructor 属性并直接与该类进行比较:
1writer.constructor === ContentWriter; // => true
2writer.constructor === User;          // => false
7. 类和原型
我必须说,JavaScript 中的类语法在从原型继承中进行抽象方面做得很好。为了描述 class 语法,我甚至没有使用术语原型
但是这些类是建立在原型继承之上的。每个类都是一个函数,并在作为构造函数调用时创建一个实例。
以下两个代码段是等效的。
类版本:
1class User {
2  constructor(name) {
3    this.name = name;
4  }
5
6  getName() {
7    return this.name;
8  }
9}
10
11const user = new User('John');
12
13user.getName();       // => 'John Snow'
14user instanceof User; // => true
使用原型的版本:
1function User(name) {
2  this.name = name;
3}
4
5User.prototype.getName = function() {
6  return this.name;
7}
8
9const user = new User('John');
10
11user.getName();       // => 'John Snow'
12user instanceof User; // => true
如果你熟悉 Java 或 Swift 语言的经典继承机制,则可以更轻松地使用类语法。
不管怎样,即便是你在 JavaScript 中使用类语法,我也建议你对原型继承有所了解。
8. 类功能的可用性
本文中介绍的课程功能涉及 ES2015 和第 3 阶段的提案。
在2019年底,class 功能分为以下两部分:
  • 公共和私有实例字段是类字段建议的一部分
  • 私有实例方法和访问器是类私有方法建议的一部分
  • 公共和私有静态字段以及私有静态方法是类静态功能建议的一部分
  • 其余部分是 ES2015 标准的一部分。

9. 结论
JavaScript 类用构造函数初始化实例,定义字段和方法。你甚至可以使用 static 关键字在类本身上附加字段和方法。
继承是使用 extends 关键字实现的:你可以轻松地从父级创建子级。super 关键字用于从子类访问父类。
要使用封装,请将字段和方法设为私有来隐藏类的内部细节。私有字段和方法名称必须以 # 开头。
JavaScript 中的类正在变得越来越易于使用。

回复

使用道具 举报

0

主题

20

帖子

29

积分

新手上路

Rank: 1

积分
29
发表于 2020-8-15 13:53:01 | 显示全部楼层
VIP会员办对了
回复

使用道具 举报

0

主题

28

帖子

41

积分

终身VIP会员

Rank: 5Rank: 5

积分
41
发表于 2020-8-16 17:49:54 | 显示全部楼层
深圳java培训
回复

使用道具 举报

0

主题

14

帖子

20

积分

终身VIP会员

Rank: 5Rank: 5

积分
20
发表于 2020-8-17 04:37:05 | 显示全部楼层
感谢分享,快点来学习。
回复

使用道具 举报

0

主题

26

帖子

38

积分

终身VIP会员

Rank: 5Rank: 5

积分
38
发表于 2020-8-17 05:58:12 | 显示全部楼层
谢谢分享
回复

使用道具 举报

0

主题

12

帖子

17

积分

终身VIP会员

Rank: 5Rank: 5

积分
17
发表于 2020-8-17 09:07:03 | 显示全部楼层
java培训学校
回复

使用道具 举报

0

主题

14

帖子

20

积分

终身VIP会员

Rank: 5Rank: 5

积分
20
发表于 2020-8-17 16:31:25 | 显示全部楼层
java程序设计实用教程第四版
回复

使用道具 举报

0

主题

14

帖子

20

积分

终身VIP会员

Rank: 5Rank: 5

积分
20
发表于 2020-8-18 05:05:39 | 显示全部楼层
学java有哪些方向
回复

使用道具 举报

0

主题

22

帖子

32

积分

终身VIP会员

Rank: 5Rank: 5

积分
32
发表于 2020-8-18 13:11:46 | 显示全部楼层
java哪里
回复

使用道具 举报

0

主题

22

帖子

32

积分

终身VIP会员

Rank: 5Rank: 5

积分
32
发表于 2020-8-19 12:39:38 | 显示全部楼层
c语言与java的区别
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

顶部qrcode底部
请扫码本站微信公众号

QQ|Archiver|手机版|sitemap.xml|java教程视频 ( 豫ICP备2020032868号 )

GMT+8, 2020-12-4 20:09

Powered by www.javaj.cn

Copyright © 2001-2020.

快速回复 返回顶部 返回列表