JavaScript 中的对象、原型和类

考虑到JavaScript中的几乎所有东西都是对象,面向对象的JavaScript代码与其他面向对象的语言非常不同。

来自C++背景,我意识到面向对象的编程范式,并对Objects 和 Classes **应该如何工作的非常坚实的想法。

首先,JavaScript对象的创建方式非常不同. 对象实例可以使用 新操作员创建:

1let Reptile = new Object() {
2 // ...
3}

或具有函数构建器

1function Reptile() {
2 // ...
3}

其次,JavaScript对象是非常灵活的,而经典面向对象的语言只允许属性更改或属性插槽,JavaScript允许对象更改其属性和方法,即JavaScript对象有属性和方法插槽。

我的第一个想法是是的,自由!但这带来了成本 - 需要了解JavaScript中的原型属性。

所有JavaScript对象都是从Object构建器创建的:

1var Reptile = function(name, canItSwim) {
2  this.name = name;
3  this.canItSwim = canItSwim;
4}

原型使我们能够为物体构造者添加新的方法,这意味着以下方法现在在所有爬行动物的例子中都存在。

1Reptile.prototype.doesItDrown = function() {
2  if (this.canItSwim) {
3    console.log(`${this.name} can swim`);
4  } else {
5    console.log(`${this.name} has drowned`);
6  }
7};

现在可以创建爬行动物的对象实例:

1// for this example consider alligators can swim and crocs cannot
2let alligator = new Reptile("alligator", true);
3alligator.doesItDrown(); // alligator can swim
4
5let croc = new Reptile("croc", false); 
6croc.doesItDrown(); // croc has drowned

爬行动物对象的原型现在是继承的基础,DoesItDrown方法既可供alligatorcroc访问,因为爬行动物原型有这种方法。

现在,由于有方法插槽和一个共同的原型实例属性在所有实例中共享,一些非常干净的技巧是可能的,这对C++的人来说是非常奇怪的:

1croc.__proto__.doesItDrown = function() {
2  console.log(`the croc never drowns`);
3};
4
5croc.doesItDrown(); // the croc never drowns
6alligator.doesItDrown(); // the croc never drowns

改变一个实例的原型属性或方法,对象的所有实例都会受到影响,这意味着我们也可能正在删除物品。

1delete croc.__proto__.doesItDrown
2alligator.doesItDrown();
3
4//TypeError: alligator.doesItDrown
5// is not a function

现在没有人能游泳。

这只是一个愚蠢的例子来展示JavaScript中的原型对Object系统的根本性,以及它对来自其他面向对象语言的人来说是多么令人不安。

在 ES6 语法中,JavaScript 提供了创建类的功能。

然而,真实类的概念在JavaScript中并不存在,而是通过原型模拟而成,类语法只是围绕它的语法糖,因此,理解这种行为对于实现ES6类的便利性和局限性至关重要。

使用新的语法,爬行动物将被定义为:

 1class Reptile {
 2  constructor (name, canItSwim) {
 3    this.name = name;
 4    this.canItSwim = canItSwim;
 5  }
 6
 7  doesItDrown () {
 8   if(this.canItSwim) 
 9    console.log(`${this.name} can swim`);
10   else
11    console.log(`${this.name} has drowned`);
12  }
13}
14
15let alligator = new Reptile("alligator", true);
16alligator.doesItDrown(); //alligator can swim

这并不意味着它不会为原型用户带来任何新鲜事物,一些陷阱可以通过使用ES6类来避免,例如使创建实例的关键字是强制性的。

1let croc = Reptile("croc", false);
2//TypeError: Class constructor Reptile cannot be invoked without 'new'

这实际上是件好事,因为它防止在使用对象属性和方法时访问错误的背景,这通常是全球范围或窗口对象。

结论

虽然JavaScript目前确实缺乏像真正私人成员这样的功能,它已经通过类语法创建了对象,而不是原型类似于其他OO语言(如C++/Java)的类。


PS. 对于在 JavaScript 类中创建真正的私人会员,TC39 提出了一项建议,你可以跟随它(https://tc39.github.io/proposal-class-fields)并贡献你的意见。

1class Foo {
2  #a; #b; // # indicates private members here
3  #sum = function() { return #a + #b; };
4}
5
6// personally this format reminds me of $variable in PHP.
7// I'm not sure if that's a good thing
Published At
Categories with 技术
Tagged with
comments powered by Disqus