ES6为JavaScript引入了类,但对于复杂的应用程序来说,它们可能过于简单。类字段(也称为类属性)旨在通过私有和静态成员来简化构造函数。该提案目前处于TC39第3阶段:候选阶段,很可能会添加到ES2019(ES10)。Node.js 12、Chrome 74和Babel目前支持私有字段。在我们了解类字段的实现方式之前,回顾一下ES6类很有用。本文于2020年更新。有关更深入的JavaScript知识,请阅读我们的书籍《JavaScript:从新手到忍者,第二版》。
关键要点
ES6类基础
来自C 、C#、Java和PHP等语言的开发人员可能会对JavaScript的面向对象继承模型感到困惑。因此,ES6引入了类。它们主要是语法糖,但提供了更熟悉的面向对象编程概念。类是一个对象模板,它定义了该类型的对象的行为方式。以下Animal类定义了通用动物(类通常用初始大写字母表示,以区别于对象和其他类型):
class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } speak() { console.log(`${this.name} says "${this.noise}"`); } walk() { console.log(`${this.name} walks on ${this.legs} legs`); } }
类声明始终在严格模式下执行。无需添加'use strict'。构造函数方法在创建Animal类型的对象时运行。它通常设置初始属性并处理其他初始化。speak()和walk()是实例方法,它们添加了更多功能。现在可以使用new关键字从此类创建对象:
let rex = new Animal('Rex', 4, 'woof'); rex.speak(); // Rex says "woof" rex.noise = 'growl'; rex.speak(); // Rex says "growl"
Getter和Setter
Setter是仅用于定义值的特殊方法。类似地,Getter是仅用于返回值的特殊方法。例如:
class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } speak() { console.log(`${this.name} says "${this.noise}"`); } walk() { console.log(`${this.name} walks on ${this.legs} legs`); } // setter set eats(food) { this.food = food; } // getter get dinner() { return `${this.name} eats ${this.food || 'nothing'} for dinner.`; } } let rex = new Animal('Rex', 4, 'woof'); rex.eats = 'anything'; console.log( rex.dinner ); // Rex eats anything for dinner.
子类或子类
将一个类用作另一个类的基类通常是可行的。Human类可以使用extends关键字继承Animal类中的所有属性和方法。可以根据需要添加、删除或更改属性和方法,以便更容易和更清晰地创建人类对象:
class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } speak() { console.log(`${this.name} says "${this.noise}"`); } walk() { console.log(`${this.name} walks on ${this.legs} legs`); } }
super指的是父类,因此它通常是在构造函数中调用的第一个调用。在此示例中,Human speak()方法重写了在Animal中定义的方法。现在可以创建Human的对象实例:
let rex = new Animal('Rex', 4, 'woof'); rex.speak(); // Rex says "woof" rex.noise = 'growl'; rex.speak(); // Rex says "growl"
静态方法和属性
使用static关键字定义方法允许在不创建对象实例的情况下在类上调用它。考虑Math.PI常量:在访问PI属性之前,无需创建Math对象。ES6不支持与其他语言相同的静态属性,但可以将属性添加到类定义本身。例如,Human类可以适应保留已创建多少个human对象的计数:
class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } speak() { console.log(`${this.name} says "${this.noise}"`); } walk() { console.log(`${this.name} walks on ${this.legs} legs`); } // setter set eats(food) { this.food = food; } // getter get dinner() { return `${this.name} eats ${this.food || 'nothing'} for dinner.`; } } let rex = new Animal('Rex', 4, 'woof'); rex.eats = 'anything'; console.log( rex.dinner ); // Rex eats anything for dinner.
类的静态COUNT getter会相应地返回人类的数量:
class Human extends Animal { constructor(name) { // 调用Animal构造函数 super(name, 2, 'nothing of interest'); this.type = 'human'; } // 重写Animal.speak speak(to) { super.speak(); if (to) console.log(`to ${to}`); } }
ES2019类字段(新)
新的类字段实现允许在类的顶部、任何构造函数之外初始化公共属性:
let don = new Human('Don'); don.speak('anyone'); // Don says "nothing of interest" to anyone don.eats = 'burgers'; console.log( don.dinner ); // Don eats burgers for dinner.
这等效于:
class Human extends Animal { constructor(name) { // 调用Animal构造函数 super(name, 2, 'nothing of interest'); this.type = 'human'; // 更新Human对象的计数 Human.count++; } // 重写Animal.speak speak(to) { super.speak(); if (to) console.log(`to ${to}`); } // 返回人类对象的个数 static get COUNT() { return Human.count; } } // 类的静态属性本身 - 不是其对象 Human.count = 0;
如果您仍然需要构造函数,则会在其运行之前执行初始化程序。
在上面的示例中,静态属性在定义类对象后以不优雅的方式添加到类定义对象中。使用类字段则无需这样做:
console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 0 let don = new Human('Don'); console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 1 let kim = new Human('Kim'); console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 2
这等效于:
class MyClass { a = 1; b = 2; c = 3; }
ES6类中的所有属性默认都是公共的,可以在类外部检查或修改。在上面的Animal示例中,没有任何东西可以阻止在不调用eats setter的情况下更改food属性:
class MyClass { constructor() { this.a = 1; this.b = 2; this.c = 3; } }
其他语言通常允许声明私有属性。这在ES6中是不可能的,因此开发人员经常使用下划线约定(_propertyName)、闭包、符号或WeakMap来解决这个问题。下划线为开发人员提供了一个提示,但没有任何东西可以阻止他们访问该属性。在ES2019中,私有类字段使用哈希#前缀定义:
class MyClass { x = 1; y = 2; static z = 3; } console.log( MyClass.z ); // 3
请注意,无法定义私有方法、getter或setter。TC39第3阶段:草案提案建议对名称使用哈希#前缀,它已在Babel中实现。例如:
class MyClass { constructor() { this.x = 1; this.y = 2; } } MyClass.z = 3; console.log( MyClass.z ); // 3
立即受益:更简洁的React代码!
React组件通常具有与DOM事件绑定的方法。为了确保这解析为组件,有必要相应地绑定每个方法。例如:
class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } set eats(food) { this.food = food; } get dinner() { return `${this.name} eats ${this.food || 'nothing'} for dinner.`; } } let rex = new Animal('Rex', 4, 'woof'); rex.eats = 'anything'; // 标准setter rex.food = 'tofu'; // 完全绕过eats setter console.log( rex.dinner ); // Rex eats tofu for dinner.
当incCount定义为ES2019类字段时,可以使用ES6 =>胖箭头将其赋值为函数,该函数自动绑定到定义对象。无需再添加绑定声明:
class MyClass { a = 1; // .a是公共的 #b = 2; // .#b是私有的 static #c = 3; // .#c是私有的和静态的 incB() { this.#b++; } } let m = new MyClass(); m.incB(); // 运行正常 m.#b = 0; // 错误 - 私有属性不能在类外部修改
类字段:改进?
ES6类定义过于简单。ES2019类字段需要更少的代码,提高了可读性,并实现了一些有趣的面向对象编程的可能性。使用#表示私有性受到了一些批评,主要是因为它很丑陋,感觉像是一种技巧。大多数语言都实现了private关键字,因此尝试在类外部使用该成员将被编译器拒绝。JavaScript是解释型的。考虑以下代码:
class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } speak() { console.log(`${this.name} says "${this.noise}"`); } walk() { console.log(`${this.name} walks on ${this.legs} legs`); } }
这将在最后一行抛出运行时错误,但这仅仅是尝试设置属性的严重后果。JavaScript故意宽容,ES5允许修改任何对象上的属性。虽然笨拙,但#符号在类定义之外是无效的。尝试访问myObject.#secret可能会抛出语法错误。这场辩论将继续下去,但无论喜欢与否,类字段都已在多个JavaScript引擎中采用。它们将继续存在。
关于JavaScript私有类字段的常见问题解答(FAQ)
JavaScript中的私有类字段提供了一种封装或隐藏类中数据的方法,这是面向对象编程的基本原则。这种封装确保对象的内部状态只能通过类明确定义的方式更改。这导致代码更健壮、更可预测,因为它可以防止外部代码意外地以意外的方式更改对象的状态。此外,私有字段可以帮助简化类的接口,因为它们减少了公开给外部世界属性和方法的数量。
在JavaScript中,私有类字段通过在字段名前添加哈希(#)符号来声明。例如,要在类中声明名为“value”的私有字段,可以编写#value。然后,此字段只能在类的内部方法中访问,而不能从类外部访问。
不可以,JavaScript中的私有类字段无法从类外部访问。这是设计使然,因为私有字段的主要用途之一是从外部世界隐藏内部数据。如果尝试从类外部访问私有字段,JavaScript将抛出语法错误。
可以,JavaScript类可以同时具有私有字段和公共字段。公共字段的声明方式与私有字段相同,但没有哈希(#)前缀。与只能从类内部访问的私有字段不同,公共字段可以从类内部和外部访问和修改。
JavaScript中的私有类字段和私有方法具有相似的用途,它们都提供了一种从外部世界隐藏类内部细节的方法。但是,它们的使用方式不同。私有字段用于存储只能在类内部访问的数据,而私有方法是只能在类内部调用的函数。
私有类字段是JavaScript中相对较新的特性,因此并非所有环境都支持它。在撰写本文时,大多数主要浏览器(包括Chrome、Firefox和Safari)的最新版本都支持私有字段。但是,Internet Explorer不支持它们。如果您需要编写可在所有浏览器中运行的代码,则可能需要使用Babel之类的转换器将代码转换为旧版浏览器可以理解的形式。
要在类的内部方法中使用私有类字段,只需使用其名称(前面带有哈希(#)符号)引用该字段即可。例如,如果您有一个名为“value”的私有字段,则可以在方法中像这样访问它:this.#value。
可以,可以在JavaScript的子类中使用私有类字段。但是,每个子类都有自己独立的一组私有字段,这些字段不与超类或其他子类共享。这意味着如果子类声明的私有字段与超类中的私有字段同名,则这两个字段完全独立,不会相互干扰。
不可以,JavaScript中的私有类字段不能在静态方法中使用。这是因为静态方法与类本身相关联,而不是与类的实例相关联,而私有字段只能在类的实例中访问。
不可以,JavaScript中的私有类字段不包含在对象的属性迭代中。这是设计使然,因为私有字段的主要用途之一是从外部世界隐藏内部数据。如果您需要迭代对象的属性,则应改为使用公共字段或方法。
This response maintains the original image format and placement, and paraphrases the text while preserving the original meaning. The code examples remain largely unchanged as significant alteration would change the meaning.
以上是JavaScript的新私人班级字段以及如何使用它们的详细内容。更多信息请关注PHP中文网其他相关文章!