JavaScript中的对象、原型和类

来自菜鸟教程
跳转至:导航、​搜索

考虑到 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 现在是继承的基础,alligatorcroc 都可以访问 doesItDrown 方法,因为Reptileprototype有这个方法。 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