考虑到 JavaScript 中几乎所有东西都是对象这一事实,面向对象的 JavaScript 代码与其他支持对象的语言非常不同。 JS 对象系统更像是一个基于原型的对象系统。
来自 C++ 背景,我了解面向对象的编程范式,并且对对象和类 应该 如何工作有一个非常严格的想法。 接触像 Java 这样的其他语言似乎只是进一步确立了这个想法。 虽然这些语言对对象和类的工作方式有自己的语义; Javascript,对于新用户来说,是一个很大的启示。
首先,JavaScript 对象的创建方式非常不同。 对班级没有要求。 可以使用 new 运算符 创建对象实例:
let Reptile = new Object() {
// ...
}
或使用函数构造函数
function Reptile() {
// ...
}
其次,JavaScript 对象 非常 灵活。 经典的面向对象语言只允许修改属性或属性槽,而 JavaScript 允许对象修改其属性和方法; IE JavaScript 对象既有属性槽也有方法槽。
我的第一个想法是“是的,自由!” 但这是有代价的——需要了解 JavaScript 中的原型属性。 对于希望在 JavaScript 中实现任何面向对象系统外观的开发人员来说,原型知识是必不可少的。
所有 JavaScript 对象都是从 Object 构造函数创建的:
var Reptile = function(name, canItSwim) {
this.name = name;
this.canItSwim = canItSwim;
}
prototype 允许我们向对象构造函数添加新方法,这意味着以下方法现在存在于 Reptile 的所有实例中。
Reptile.prototype.doesItDrown = function() {
if (this.canItSwim) {
console.log(`${this.name} can swim`);
} else {
console.log(`${this.name} has drowned`);
}
};
现在可以创建 Reptile 的对象实例:
// for this example consider alligators can swim and crocs cannot
let alligator = new Reptile("alligator", true);
alligator.doesItDrown(); // alligator can swim
let croc = new Reptile("croc", false);
croc.doesItDrown(); // croc has drowned
Reptile 对象的 prototype 现在是继承的基础,alligator 和 croc 都可以访问 doesItDrown 方法,因为Reptile的prototype有这个方法。 prototype 属性在其所有实例之间共享,并且可以通过特定实例的 __proto__ 属性访问。
现在,由于方法槽的存在和一个公共的 prototype 实例属性在所有实例之间共享,一些非常巧妙的技巧是可能的,这对 C++ 人来说是非常奇怪的:
croc.__proto__.doesItDrown = function() {
console.log(`the croc never drowns`);
};
croc.doesItDrown(); // the croc never drowns
alligator.doesItDrown(); // the croc never drowns
更改一个实例的 prototype 属性或方法,对象的所有实例都会受到影响。 这意味着我们也可以删除内容。 厌倦了溺水的鳄鱼可能会这样做:
delete croc.__proto__.doesItDrown alligator.doesItDrown(); //TypeError: alligator.doesItDrown // is not a function
现在没有人会游泳了。
这只是一个愚蠢的例子,展示了 prototype 对 JavaScript 中的对象系统有多么重要,以及它对其他面向对象语言的人来说是多么的不和谐。
使用 ES6 语法,JavaScript 提供了创建类的功能。
然而,真正的类的概念在 JavaScript 中并不存在,而是通过 prototype 模拟出来的,类语法只是围绕它的语法糖。 因此,理解这种行为对于实现 ES6 类的便利性和局限性很重要。
使用新的 class 语法,Reptile 将被定义为:
class Reptile {
constructor (name, canItSwim) {
this.name = name;
this.canItSwim = canItSwim;
}
doesItDrown () {
if(this.canItSwim)
console.log(`${this.name} can swim`);
else
console.log(`${this.name} has drowned`);
}
}
let alligator = new Reptile("alligator", true);
alligator.doesItDrown(); //alligator can swim
这并不意味着它没有为 prototype 用户提供任何新功能,使用 ES6 类可以避免一些陷阱,例如强制使用 new 关键字来创建实例。
let croc = Reptile("croc", false);
//TypeError: Class constructor Reptile cannot be invoked without 'new'
这实际上是一件好事,因为它可以防止在使用对象属性和方法时访问错误的上下文,这通常是全局范围或窗口对象。
综上所述
尽管 JavaScript 现在确实缺少像真正私有成员这样的特性。 它通过类语法创建对象,而不是原型非常类似于来自其他 OO 语言(如 C++/Java)的类。
PS。 有一个 TC39 提议在 JavaScript 类中创建真正私有的成员,你可以在 这里 关注它并发表你的意见。 如果它被包含在下一个版本中,那么我们会有类似的东西:
class Foo {
#a; #b; // # indicates private members here
#sum = function() { return #a + #b; };
}
// personally this format reminds me of $variable in PHP.
// I'm not sure if that's a good thing