一篇带给你JavaScript的Class语法介绍

开发 前端
在面向对象的编程中,class 是用于创建对象的可扩展的程序代码模版,它为对象提供了状态(成员变量)的初始值和行为(成员函数或方法)的实现。

[[358553]]

  •  在面向对象的编程中,class 是用于创建对象的可扩展的程序代码模版,它为对象提供了状态(成员变量)的初始值和行为(成员函数或方法)的实现。
  • Wikipedia

在日常开发中,我们经常需要创建许多相同类型的对象,例如用户(users)、商品(goods)或者任何其他东西。

正如我们在 构造器和操作符 "new" 一章中已经学到的,new function 可以帮助我们实现这种需求。

但在现代 JavaScript 中,还有一个更高级的“类(class)”构造方式,它引入许多非常棒的新功能,这些功能对于面向对象编程很有用。

一、“class” 语法

基本语法是:

  1. class MyClass { 
  2.   // class 方法 
  3.   constructor() { ... } 
  4.   method1() { ... } 
  5.   method2() { ... } 
  6.   method3() { ... } 
  7.   ... 

 然后使用 new MyClass() 来创建具有上述列出的所有方法的新对象。

new 会自动调用 constructor() 方法,因此我们可以在 constructor() 中初始化对象。

例如:

  1. class User { 
  2.  
  3.   constructor(name) { 
  4.     this.name = name
  5.   } 
  6.  
  7.   sayHi() { 
  8.     alert(this.name); 
  9.   } 
  10.  
  11.  
  12. // 用法: 
  13. let user = new User("John"); 
  14. user.sayHi(); 

 当 new User("John") 被调用:

  1. 一个新对象被创建。
  2. constructor 使用给定的参数运行,并为其分配 this.name。

……然后我们就可以调用对象方法了,例如 user.sayHi。

类的方法之间没有逗号

对于新手开发人员来说,常见的陷阱是在类的方法之间放置逗号,这会导致语法错误。

不要把这里的符号与对象字面量相混淆。在类中,不需要逗号。

二、什么是 class?

所以,class 到底是什么?正如人们可能认为的那样,这不是一个全新的语言级实体。

让我们揭开其神秘面纱,看看类究竟是什么。这将有助于我们理解许多复杂的方面。

在 JavaScript 中,类是一种函数。

看看下面这段代码:

  1. class User { 
  2.   constructor(name) { this.name = name; } 
  3.   sayHi() { alert(this.name); } 
  4.  
  5. // 佐证:User 是一个函数 
  6. alert(typeof User); // function 

 class User {...} 构造实际上做了如下的事儿:

  1. 创建一个名为 User 的函数,该函数成为类声明的结果。该函数的代码来自于 constructor 方法(如果我们不编写这种方法,那么它就被假定为空)。
  2. 存储类中的方法,例如 User.prototype 中的 sayHi。

当 new User 对象被创建后,当我们调用其方法时,它会从原型中获取对应的方法,正如我们在 F.prototype 一章中所讲的那样。因此,对象 new User 可以访问类中的方法。

我们可以将 class User 声明的结果解释为:

下面这些代码很好地解释了它们:

  1. class User { 
  2.   constructor(name) { this.name = name; } 
  3.   sayHi() { alert(this.name); } 
  4.  
  5. // class 是一个函数 
  6. alert(typeof User); // function 
  7.  
  8. // ...或者,更确切地说,是 constructor 方法 
  9. alert(User === User.prototype.constructor); // true 
  10.  
  11. // 方法在 User.prototype 中,例如: 
  12. alert(User.prototype.sayHi); // alert(this.name); 
  13.  
  14. // 在原型中实际上有两个方法 
  15. alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi 

 三、不仅仅是语法糖

人们常说 class 是一个语法糖(旨在使内容更易阅读,但不引入任何新内容的语法),因为我们实际上可以在没有 class 的情况下声明相同的内容:

  1. // 用纯函数重写 class User 
  2.  
  3. // 1. 创建构造器函数 
  4. function User(name) { 
  5.   this.name = name
  6. // 函数的原型(prototype)默认具有 "constructor" 属性, 
  7. // 所以,我们不需要创建它 
  8.  
  9. // 2. 将方法添加到原型 
  10. User.prototype.sayHi = function() { 
  11.   alert(this.name); 
  12. }; 
  13.  
  14. // 用法: 
  15. let user = new User("John"); 
  16. user.sayHi(); 

 这个定义的结果与使用类得到的结果基本相同。因此,这确实是将 class 视为一种定义构造器及其原型方法的语法糖的理由。

尽管,它们之间存在着重大差异:

  1. 首先,通过 class 创建的函数具有特殊的内部属性标记 [[FunctionKind]]:"classConstructor"。因此,它与手动创建并不完全相同。编程语言会在许多地方检查该属性。例如,与普通函数不同,必须使用 new 来调用它:class User { constructor() {} } alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new'此外,大多数 JavaScript 引擎中的类构造器的字符串表示形式都以 “class…” 开头class User {constructor() {} } alert(User); // class User { ... }还有其他的不同之处,我们很快就会看到。
  2. 类方法不可枚举。 类定义将 "prototype" 中的所有方法的 enumerable 标志设置为 false。这很好,因为如果我们对一个对象调用 for..in 方法,我们通常不希望 class 方法出现。
  3. 类总是使用 use strict。 在类构造中的所有代码都将自动进入严格模式。

此外,class 语法还带来了许多其他功能,我们稍后将会探索它们。

四、类表达式

就像函数一样,类可以在另外一个表达式中被定义,被传递,被返回,被赋值等。

这是一个类表达式的例子:

  1. let User = class { 
  2.   sayHi() { 
  3.     alert("Hello"); 
  4.   } 
  5. }; 

 类似于命名函数表达式(Named Function Expressions),类表达式可能也应该有一个名字。

如果类表达式有名字,那么该名字仅在类内部可见:

  1. // “命名类表达式(Named Class Expression)” 
  2. // (规范中没有这样的术语,但是它和命名函数表达式类似) 
  3. let User = class MyClass { 
  4.   sayHi() { 
  5.     alert(MyClass); // MyClass 这个名字仅在类内部可见 
  6.   } 
  7. }; 
  8.  
  9. new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容 
  10.  
  11. alert(MyClass); // error,MyClass 在外部不可见 

 我们甚至可以动态地“按需”创建类,就像这样:

  1. function makeClass(phrase) { 
  2.   // 声明一个类并返回它 
  3.   return class { 
  4.     sayHi() { 
  5.       alert(phrase); 
  6.     } 
  7.   }; 
  8.  
  9. // 创建一个新的类 
  10. let User = makeClass("Hello"); 
  11.  
  12. new User().sayHi(); // Hello 

 五、Getters/setters

就像对象字面量,类可能包括 getters/setters,计算属性(computed properties)等。

这是一个使用 get/set 实现 user.name 的示例:

  1. class User { 
  2.  
  3.   constructor(name) { 
  4.     // 调用 setter 
  5.     this.name = name
  6.   } 
  7.  
  8.   get name() { 
  9.     return this._name; 
  10.   } 
  11.  
  12.   set name(value) { 
  13.     if (value.length < 4) { 
  14.       alert("Name is too short."); 
  15.       return
  16.     } 
  17.     this._name = value; 
  18.   } 
  19.  
  20.  
  21. let user = new User("John"); 
  22. alert(user.name); // John 
  23.  
  24. user = new User(""); // Name is too short. 

 从技术上来讲,这样的类声明可以通过在 User.prototype 中创建 getters 和 setters 来实现。

六、计算属性名称 […]

这里有一个使用中括号 [...] 的计算方法名称示例:

  1. class User { 
  2.  
  3.   ['say' + 'Hi']() { 
  4.     alert("Hello"); 
  5.   } 
  6.  
  7.  
  8. new User().sayHi(); 

 这种特性很容易记住,因为它们和对象字面量类似。

七、Class 字段

旧的浏览器可能需要 polyfill

类字段(field)是最近才添加到语言中的。

之前,我们的类仅具有方法。

“类字段”是一种允许添加任何属性的语法。

例如,让我们在 class User 中添加一个 name 属性:

  1. class User { 
  2.   name = "John"
  3.  
  4.   sayHi() { 
  5.     alert(`Hello, ${this.name}!`); 
  6.   } 
  7.  
  8. new User().sayHi(); // Hello, John! 

 所以,我们就只需在表达式中写 " = ",就这样。

类字段重要的不同之处在于,它们会在每个独立对象中被设好,而不是设在 User.prototype:

  1. class User { 
  2.   name = "John"
  3.  
  4. let user = new User(); 
  5. alert(user.name); // John 
  6. alert(User.prototype.name); // undefined 

 我们也可以在赋值时使用更复杂的表达式和函数调用:

  1. class User { 
  2.   name = prompt("Name, please?""John"); 
  3.  
  4. let user = new User(); 
  5. alert(user.name); // John 

 八、使用类字段制作绑定方法

正如 函数绑定 一章中所讲的,JavaScript 中的函数具有动态的 this。它取决于调用上下文。

因此,如果一个对象方法被传递到某处,或者在另一个上下文中被调用,则 this 将不再是对其对象的引用。

例如,此代码将显示 undefined:

  1. class Button { 
  2.   constructor(value) { 
  3.     this.value = value; 
  4.   } 
  5.  
  6.   click() { 
  7.     alert(this.value); 
  8.   } 
  9.  
  10. let button = new Button("hello"); 
  11.  
  12. setTimeout(button.click, 1000); // undefined 

 这个问题被称为“丢失 this”。

我们在 函数绑定 一章中讲过,有两种可以修复它的方式:

  1. 传递一个包装函数,例如 setTimeout(() => button.click(), 1000)。
  2. 将方法绑定到对象,例如在 constructor 中。

类字段提供了另一种非常优雅的语法:

  1. class Button { 
  2.   constructor(value) { 
  3.     this.value = value; 
  4.   } 
  5.   click = () => { 
  6.     alert(this.value); 
  7.   } 
  8.  
  9. let button = new Button("hello"); 
  10.  
  11. setTimeout(button.click, 1000); // hello 

 类字段 click = () => {...} 是基于每一个对象被创建的,在这里对于每一个 Button 对象都有一个独立的方法,在内部都有一个指向此对象的 this。我们可以把 button.click 传递到任何地方,而且 this 的值总是正确的。

在浏览器环境中,它对于进行事件监听尤为有用。

九、总结

基本的类语法看起来像这样:

  1. class MyClass { 
  2.   prop = value; // 属性 
  3.  
  4.   constructor(...) { // 构造器 
  5.     // ... 
  6.   } 
  7.  
  8.   method(...) {} // method 
  9.  
  10.   get something(...) {} // getter 方法 
  11.   set something(...) {} // setter 方法 
  12.  
  13.   [Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为 symbol) 
  14.   // ... 

 技术上来说,MyClass 是一个函数(我们提供作为 constructor 的那个),而 methods、getters 和 settors 都被写入了 MyClass.prototype。

 

责任编辑:姜华 来源: 今日头条
相关推荐

2022-04-29 14:38:49

class文件结构分析

2021-06-28 10:04:12

SpringCloudSleuth微服务

2022-11-24 06:58:44

Ansible

2021-10-28 08:51:53

GPIO软件框架 Linux

2022-02-25 15:50:05

OpenHarmonToggle组件鸿蒙

2021-04-14 07:55:45

Swift 协议Protocol

2021-07-08 07:30:13

Webpack 前端Tree shakin

2023-03-13 09:31:04

2021-04-23 08:59:35

ClickHouse集群搭建数据库

2021-05-08 08:36:40

ObjectString前端

2021-06-07 12:06:19

SpringCloud Sleuth微服务

2022-03-02 08:52:49

PostmangRPCAPI调试

2022-03-01 13:55:27

TektonKubernetes集群

2022-03-08 08:32:43

Tekton云原生开源

2022-07-06 07:57:37

Zookeeper分布式服务框架

2021-07-12 06:11:14

SkyWalking 仪表板UI篇

2021-10-14 11:31:28

数组面试题中心下标

2022-07-07 08:02:49

RedisBitMap

2021-04-20 06:12:09

Swift 反射 Mirror反射机制

2021-03-18 08:53:44

MySQL数据库索引
点赞
收藏

51CTO技术栈公众号