- Class
class MyClass{
//class方法
constructor(){....}
method1(){...}
method2(){...}
//在class里不需要写“,”
}
然后使用 new MyClass()
来创建具有上述列出的所有方法的新对象。
new
会自动调用 constructor()
方法,因此我们可以在 constructor()
中初始化对象。
例如:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// 用法:
let user = new User("John");
user.sayHi();
当 new User("John") 被调用:
1.一个新对象被创建。
2.constructor 使用给定的参数运行,并为其分配 this.name。
……然后我们就可以调用对象方法了,例如 user.sayHi。
类的方法之间没有逗号
对于新手开发人员来说,常见的陷阱是在类的方法之间放置逗号,这会导致语法错误。
不要把这里的符号与对象字面量相混淆。在类中,不需要逗号。
在 JavaScript 中,类是一种函数。
看看下面这段代码:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// 佐证:User 是一个函数
alert(typeof User); // function
class User {...}
构造实际上做了如下的事儿:
- 创建一个名为
User
的函数,该函数成为类声明的结果。该函数的代码来自于constructor
方法(如果我们不编写这种方法,那么它就被假定为空)。 - 存储类中的方法,例如
User.prototype
中的sayHi
。
当 new User
对象被创建后,当我们调用其方法时,它会从原型中获取对应的方法,因此,对象 new User
可以访问类中的方法。
内部接口和外部接口
在面向对象的编程中,属性和方法分为两组:
1.内部接口 —— 可以通过该类的其他方法访问,但不能从外部访问的方法和属性。
2.外部接口 —— 也可以从类的外部访问的方法和属性。在 JavaScript 中,有两种类型的对象字段(属性和方法):
1.公共的:可从任何地方访问。它们构成了外部接口。到目前为止,我们只使用了公共的属性和方法。
2.私有的:只能从类的内部访问。这些用于内部接口。Class-类-私有的和受保护的属性和方法
受保护的属性通常以下划线 _ 作为前缀。
这不是在语言级别强制实施的,但是程序员之间有一个众所周知的约定,即不应该从外部访问此类型的属性和方法。
受保护的字段是可以被继承的
如果我们继承 class MegaMachine extends CoffeeMachine,那么什么都无法阻止我们从新的类中的方法访问 this._waterAmount 或 this._power。
所以受保护的字段是自然可被继承的。与我们接下来将看到的私有字段不同。
- 私有的
私有属性和方法应该以 # 开头。它们只在类的内部可被访问。
例如,这儿有一个私有属性 #waterLimit 和检查水量的私有方法 #checkWater:
class CoffeeMachine {
#waterLimit = 200;
#checkWater(value) {
if (value < 0) throw new Error("Negative water");
if (value > this.#waterLimit) throw new Error("Too much water");
}
}
let coffeeMachine = new CoffeeMachine();
// 不能从类的外部访问类的私有属性和方法
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
在语言级别,# 是该字段为私有的特殊标志。我们无法从外部或从继承的类中访问它。
私有字段与公共字段不会发生冲突。我们可以同时拥有私有的 #waterAmount 和公共的 waterAmount 字段。
例如,让我们使 waterAmount 成为 #waterAmount 的一个访问器:
class CoffeeMachine {
#waterAmount = 0;
get waterAmount() {
return this.#waterAmount;
}
set waterAmount(value) {
if (value < 0) throw new Error("Negative water");
this.#waterAmount = value;
}
}
let machine = new CoffeeMachine();
machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
与受保护的字段不同,私有字段由语言本身强制执行。这是好事儿。
但是如果我们继承自 CoffeeMachine,那么我们将无法直接访问 #waterAmount。我们需要依靠 waterAmount getter/setter:
class MegaCoffeeMachine extends CoffeeMachine {
method() {
alert( this.#waterAmount ); // Error: can only access from CoffeeMachine
}
}
在许多情况下,这种限制太严重了。如果我们扩展 CoffeeMachine,则可能有正当理由访问其内部。这就是为什么大多数时候都会使用受保护字段
,即使它们不受语言语法的支持。
私有字段不能通过 this[name] 访问
私有字段很特别。
正如我们所知道的,通常我们可以使用 this[name] 访问字段:
class User {
...
sayHi() {
let fieldName = "name";
alert(`Hello, ${this[fieldName]}`);
}
}
对于私有字段来说,这是不可能的:this['#name'] 不起作用。这是确保私有性的语法限制。
- 总结
就面向对象编程(OOP)而言,内部接口与外部接口的划分被称为 封装
它具有以下优点:
保护用户,使他们不会误伤自己
想象一下,有一群开发人员在使用一个咖啡机。这个咖啡机是由“最好的咖啡机”公司制造的,工作正常,但是保护罩被拿掉了。因此内部接口暴露了出来。
所有的开发人员都是文明的 —— 他们按照预期使用咖啡机。但其中的一个人,约翰,他认为自己是最聪明的人,并对咖啡机的内部做了一些调整。然而,咖啡机两天后就坏了。
这肯定不是约翰的错,而是那个取下保护罩并让约翰进行操作的人的错。
编程也一样。如果一个 class 的使用者想要改变那些本不打算被从外部更改的东西 —— 后果是不可预测的。
- 可支持性
编程的情况比现实生活中的咖啡机要复杂得多,因为我们不只是购买一次。我们还需要不断开发和改进代码。
- 如果我们严格界定内部接口,那么这个 class 的开发人员可以自由地更改其内部属性和方法,甚至无需通知用户。
如果你是这样的 class 的开发者,那么你会很高兴知道可以安全地重命名私有变量,可以更改甚至删除其参数,因为没有外部代码依赖于它们。
对于用户来说,当新版本问世时,应用的内部可能被进行了全面检修,但如果外部接口相同,则仍然很容易升级。
- 隐藏复杂性
人们喜欢使用简单的东西。至少从外部来看是这样。内部的东西则是另外一回事了。
程序员也不例外。
- 当实施细节被隐藏,并提供了简单且有据可查的外部接口时,总是很方便的。
为了隐藏内部接口,我们使用受保护的或私有的属性:
- 受保护的字段以
_
开头。这是一个众所周知的约定,不是在语言级别强制执行的。程序员应该只通过它的类和从它继承的类中访问以_
开头的字段。 - 私有字段以
#
开头。JavaScript 确保我们只能从类的内部访问它们。
目前,各个浏览器对私有字段的支持不是很好,但可以用 polyfill 解决。