考虑到 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