没有办法在JavaScript中有基于类的对象?

基于JavaScript原型的面向对象编程风格很有趣,但是在很多情况下,你需要能够从一个类创build对象。

例如在一个vector绘图应用程序中,工作区通常在绘图的开始处是空的:我不能从现有的一个创build一个新的“线”。 更一般地说,每个dynamic创build对象的情况都需要使用类。

我已经阅读了很多教程和书“Javascript:好的部分”,但是在我看来,没有办法定义尊重1)封装和2)有效的成员方法声明的类(我的意思是:成员方法被定义一次,并在每个类实例之间共享)。

要定义私有variables,正在使用闭包:

function ClassA() { var value = 1; this.getValue = function() { return value; } } 

这里的问题是,“ClassA”的每个实例都有自己的成员函数“getValue”副本,效率不高。

为了有效地定义成员函数,正在使用原型:

 function ClassB() { this.value = 1; } ClassB.prototype.getValue = function() { return this.value; } 

这里的问题是成员variables“value”是公开的。

我不认为这个问题可以很容易地解决,因为在创build对象的时候需要定义“私有”variables(这样对象可以访问创build的上下文,而不会泄露值),而基于原型的成员函数定义必须在创build对象之后完成,所以原型才有意义(“this.prototype”不存在,我已经检查过)。

还是我错过了什么?


编辑:

首先,感谢你的有趣答案。

我只是想为我的最初信息添加一点精确度:

我真正想要做的是有1)私有variables(封装是好的,因为人们只能访问他们所需要的)和2)有效的成员方法声明(避免副本)。

看来简单的私有variables声明实际上只能通过javascript中的闭包实现,这就是为什么我关注基于类的方法。 如果有一种方法可以通过基于原型的方法实现简单的私有variables声明,那对我来说就没有问题了,我不是一个激烈的基于类的方法proponnent。

阅读答案后,似乎简单的解决办法是忘记私有,并使用特殊的编码惯例,诋毁其他程序员直接访问“私人”variables…

我同意,我的标题/第一句对我想在这里讨论的问题有误导性。

嘘,来这里! 想听到一个秘密?

古典inheritance是一种经过testing和尝试的方法。

经常在JavaScript中实现它有用的。 类是一个很好的概念,有对象真棒后build模我们的世界的模板。

古典inheritance只是一种模式。 在JavaScript中实现经典的inheritance是完全可以的,如果它是你的用例需要的模式的话。

原型inheritance着重于共享function ,这真棒 ( dinasaur鼓棒真棒),但在某些情况下,你想共享一个数据scheme,而不是function。 这是原型inheritance根本无法解决的问题。

那么,你告诉我课程不是像大家一直告诉我的那样邪恶?

不,他们不是。 JS社区所不喜欢的是类的概念,它只是将代码限制在代码重用的类中。 就像语言不强制执行强types或静态types,它不强制对象结构的scheme。

事实上,在这个场景的背后,聪明的语言实现可以把你的普通对象变成类似于经典inheritance类的东西。

那么,JavaScript是如何工作的

那么,你真的只需要一个构造函数:

 function getVehicle(engine){ return { engine : engine }; } var v = getVehicle("V6"); v.engine;//v6 

我们现在有一个车类。 我们不需要使用一个特殊的关键字明确地定义一个Vehicle类。 现在,有些人不喜欢这样做,习惯于更古典的方式。 对于这个JS通过做提供(愚蠢的imho) 语法糖

 function Vehicle(engine){ this.engine = engine; } var v = new Vehicle("V6"); v.engine;//v6 

这与大部分上面的例子是一样的。

那么,我们还缺less什么?

inheritance和私人成员。

如果我告诉你,基本的子types在JavaScript中非常简单?

JavaScript的input概念与我们在其他语言中习惯的概念不同。 什么意思是在JS中的某种types的子types?

 var a = {x:5}; var b = {x:3,y:3}; 

btypes是btypes的子types吗? 比如说, 根据(强)行为分类(LSP):

<<<<开始技术部分

  • 子types中方法参数的反变化 – 完全保留在这种inheritance中。
  • 子types中返回types的协变 – 完全保留在这种inheritance中。
  • 子types的方法不应引发新的exception,除非这些exception本身是超types方法抛出的exception的子types。 – 完全保留在这种inheritance。

也,

  • 先决条件不能在一个子types中加强。
  • 后代不能被削弱在一个亚型。
  • 超types的不变式必须保存在一个子types中。
  • 历史规则

所有这些都是我们要保留的。 我们可以按照自己的意愿保持紧密或松散的状态,我们不必这样做,但我们当然可以。

所以事实上,只要我们遵守上面这些规则来实现我们的inheritance,我们就完全实现了强大的行为子types,这是一种非常强大的子typesforms(参见注释*)。

>>>>>结束技术部分

平凡地说,人们也可以看到结构分型是成立的。

这将如何适用于我们的Car示例?

 function getCar(typeOfCar){ var v = getVehicle("CarEngine"); v.typeOfCar = typeOfCar; return v; } v = getCar("Honda"); v.typeOfCar;//Honda; v.engine;//CarEngine 

不太难,是吗? 私人会员呢?

 function getVehicle(engine){ var secret = "Hello" return { engine : engine, getSecret : function() { return secret; } }; } 

看, secret是一个closuresvariables。 它完全是“私人”的,它的工作方式与Java等语言中的私有不同,但从外部访问是不可能的。

有什么私人function?

啊! 这是一个很好的问题。

如果我们想在一个函数中使用一个私有variables,我们需要先了解JS闭包和函数是如何工作的。

在JavaScript函数中是一stream的。 这意味着你可以传递函数。

 function getPerson(name){ var greeting = "Hello " + name; return { greet : function() { return greeting; } }; } var a = getPerson("thomasc"); a.greet(); //Hello thomasc 

到目前为止这么好,但我们可以将这个函数传递给其他对象! 这可以让你做非常松散的解耦,这是真棒。

 var b = a.greet; b(); //Hello thomasc 

等待! b怎么知道这个人的名字是thomasc? 这只是closures的魔力。 漂亮真棒吧?

你可能担心performance。 让我告诉你我是如何学会停止担忧的,开始喜欢优化的JIT。

在实践中,像这样的function副本不是一个大问题。 在JavaScript中的function都很好,function! closures是一个很棒的概念,一旦你掌握并掌握了它们,你就会发现它是非常值得的,而性能打击并不是那么有意义。 JS每天都变得越来越快,不用担心这种性能问题。

如果你认为这很复杂,那么下面的内容也是非常合理的。 与其他开发人员的共同合同只是说:“如果我的variables以_开头,请勿触摸它,我们都同意成年人”。 这看起来像这样:

 function getPerson(name){ var greeter = { greet : function() { return "Hello" +greeter._name; } }; greeter._name = name; return greeter; } 

或古典风格

 function Person(name){ this._name = name; this.greet = function(){ return "Hello "+this._name; } } 

或者,如果您想在原型上caching该函数,而不是实例化副本:

 function Person(name){ this._name = name; } Person.prototype.greet = function(){ return "Hello "+this._name; } 

所以,总结一下:

  • 您可以使用经典的inheritance模式,它们可用于共享数据types

  • 你也应该使用原型inheritance,它是一样有效的,更多的情况下你想共享function。

  • TheifMaster几乎钉了它。 拥有私有隐私真的不是什么大问题,只要你的代码定义了一个清晰的界面,这应该不会有问题。 我们都在这里集中成人:)

聪明的读者可能会想:呃? 你不是用历史的规则欺骗我吗? 我的意思是,财产访问不封装。

我说不,我不是。 即使您没有明确地将这些字段封装为私有,您也可以简单地以不访问它们的方式来定义您的合同。 通常像TheifMaster用_build议的那样。 另外,我认为历史规则在很多情况下都不是什么大问题,只要我们不改变属性访问方式处理父对象属性的方式。 再次,这取决于我们。

我不想让你灰心,因为你似乎是一个相当新的StackOverflow的成员,但是我将不得不在你的脸上,并说这是一个非常糟糕的主意,试图在JavaScript中实现经典的inheritance

注意:当我说在JavaScript中实现经典inheritance是一个坏主意时,我的意思是试图在JavaScript中模拟实际的类,接口,访问修饰符等是一个坏主意。 尽pipe如此,作为JavaScript中的devise模式的经典inheritance是有用的,因为它只是用于原型inheritance的语法糖 (例如最小类 )。 我始终在代码中使用这种devise模式( 扩展 )。

JavaScript是一种原型的面向对象的编程语言。 不是一个经典的面向对象的编程语言。 当然,你可以在JavaScript之上实现经典的inheritance,但是在你牢记以下几点之前:

  1. 你违背语言的精神,这意味着你将面临问题。 很多问题 – 性能,可读性,可维护性等
  2. 你不需要上课。 托马斯,我知道你确实相信你需要上课,但是相信我。 你没有。

为了你的缘故,我会为这个问题提供两个答案。 第一个会告诉你如何在JavaScript中做经典的inheritance。 第二个(我build议)将教你接受原型inheritance。

JavaScript中的古典inheritance

大多数程序员开始尝试在JavaScript中实现经典的inheritance。 即使像Douglas Crockford这样的JavaScript大师也试图在JavaScript中实现经典的inheritance 。 我也试图在JavaScript中实现经典的inheritance。

首先,我创build了一个名为Clockwork的库,然后进行扩充 。 但是我不build议你使用这些库中的任何一个,因为它们违背了JavaScript的精神。 事实是,当我写这些经典的inheritance库时,我仍然是一个业余的JavaScript程序员。

我提到这一点的唯一原因是因为每个人在某个时间点都是业余爱好者,虽然我宁愿你没有在JavaScript中使用经典的inheritance模式,但我不能指望你明白为什么原型inheritance很重要 。

你无法学习如何循环而不会摔倒几次。 我相信你在原型inheritance方面还处于学习阶段。 对古典inheritance的需求就像循环中的训练轮。

不过,训练轮子很重要。 如果你想有一些经典的inheritance库,应该让你更容易在JavaScript中编写代码。 一个这样的库是jTypes 。 只要记住,当你对自己的JavaScript程序员的技能有信心时,就可以脱下训练轮。

注:就个人而言,我不喜欢jType一点。

JavaScript中的原型inheritance

我将这一部分写成了一个里程碑,以便您可以稍后再回来,在准备好了解真正的原型inheritance 知道下一步该做什么。

首先下面这行是错误的:

基于JavaScript原型的面向对象编程风格很有趣,但是在很多情况下,你需要能够从一个类创build对象。

这是错误的,因为:

  1. 您将永远不需要从JavaScript中的类创build对象。
  2. 没有办法在JavaScript中创build一个类。

是的,可以在JavaScript中模拟经典inheritance。 但是,你仍然inheritance对象而不是类的属性。 例如, ECMAScript Harmony类只是原型遗传的经典模式的语法糖。

在同样的情况下,你给的例子也是错误的:

例如在一个vector绘图应用程序中,工作区通常在绘图的开始处是空的:我不能从现有的一个创build一个新的“线”。 更一般地说,每个dynamic创build对象的情况都需要使用类。

是的,即使工作区在开始时是空的,也可以从现有的一行创build新的一行。 你需要了解的是这条线虽然没有被绘制出来,

 var line = { x1: 0, y1: 0, x2: 0, y2: 0, draw: function () { // drawing logic }, create: function (x1, y1, x2, y2) { var line = Object.create(this); line.x1 = x1; line.y1 = y1; line.x2 = x2; line.y2 = y2; return line; } }; 

现在你可以通过简单地调用line.draw来绘制上面的line.draw ,否则你可以从中创build一个新行:

 var line2 = line.create(0, 0, 0, 100); var line3 = line.create(0, 100, 100, 100); var line4 = line.create(100, 100, 100, 0); var line5 = line.create(100, 0, 0, 0); line2.draw(); line3.draw(); line4.draw(); line5.draw(); 

绘制时,线条line2line5line4line5构成一个100x100正方形。

结论

所以你看到你真的不需要JavaScript中的类。 对象就够了。 使用function可以轻松实现封装。

这就是说,不能让每个实例的公共职能访问该对象的私有状态,而每个实例都没有自己的一套公共职能。

这不是一个问题,因为:

  1. 你并不需要私人的状态。 你可能认为你是这样做的,但是你确实没有。
  2. 如果你真的想做一个私人的variables,那么ThiefMaster提到的只是用一个下划线前缀variables名,并告诉你的用户不要乱它。

Aight,这是我解决这个问题的尝试,虽然我认为遵循惯例这是一个更好的方法。 用_variables的前缀。 在这里,我只是跟踪数组中的实例,然后可以使用_destroy方法删除它们。 我相信这可以改善,但希望它会给你一些想法:

 var Class = (function ClassModule() { var private = []; // array of objects of private variables function Class(value) { this._init(); this._set('value', value); } Class.prototype = { // create new instance _init: function() { this.instance = private.length; private.push({ instance: this.instance }); }, // get private variable _get: function(prop) { return private[this.instance][prop]; }, // set private variable _set: function(prop, value) { return private[this.instance][prop] = value; }, // remove private variables _destroy: function() { delete private[this.instance]; }, getValue: function() { return this._get('value'); } }; return Class; }()); var a = new Class('foo'); var b = new Class('baz'); console.log(a.getValue()); //=> foo console.log(b.getValue()); //=> baz a._destroy(); console.log(b.getValue()); //=> baz 

运行时不需要私有/公共。 这些是可以静态强制执行的。 任何足够强制执行私有属性的项目都不会在外部使用,将会有一个构build/预处理步骤,您可以使用它来validation事实。 即使具有私有/公共语法的语言也可以在运行时访问私有语言。

至于定义基于类的对象,你使用的构造函数+原型是最简单和最有效的方法。 任何一种额外的魔法都会更复杂,性能更差。

虽然可以cachingprototype所以不必重复ClassB.prototype. 每时每刻:

 //in node.js you can leave the wrapper function out var ClassB = (function() { var method = ClassB.prototype; function ClassB( value ) { this._value = value; } method.getValue = function() { return this._value; }; method.setValue = function( value ) { this._value = value; }; return ClassB; })(); 

上面不需要任何库,你可以很容易地为它创build一个macros。

此外,在这种情况下,即使正则expression式也足以validation“私有”属性是否正确使用。 运行/([a-zA-Z$_-][a-zA-Z0-9$_-]*)\._.+/g通过文件,看到第一场比赛总是thishttp://jsfiddle.net/7gumy/

就我所知,没有其他情况影响价值是不可能的,所以如果它是一个常数,你仍然可以把它包装在一个这样的函数中:

 (function( context ) { 'use strict'; var SOME_CONSTANT = 'Hello World'; var SomeObject = function() {}; SomeObject.prototype.sayHello = function() { console.log(SOME_CONSTANT); }; context.SomeObject = SomeObject; })( this ); var someInstance = new SomeObject(); someInstance.sayHello(); 

你可以做的最好的是注释一个属性不应该被触摸通过使用下划线this._value而不是this.value

请注意,通过将函数隐藏在函数中可以实现私有函数:

 (function( context ) { 'use strict'; var SomeObject = function() {}; var getMessage = function() { return 'Hello World'; }; SomeObject.prototype.sayHello = function() { console.log(getMessage()); }; context.SomeObject = SomeObject; })( this ); var someInstance = new SomeObject(); someInstance.sayHello(); 

下面是两个“类”的扩展和互动的例子: http : //jsfiddle.net/TV3H3/

如果你真的想每个实例的私人实体,但仍然想inheritance你的方法,你可以使用下面的设置:

 var Bundle = (function(){ var local = {}, constructor = function(){ if ( this instanceof constructor ) { local[(this.id = constructor.id++)] = { data: 123 }; } }; constructor.id = 0; constructor.prototype.exampleMethod = function(){ return local[this.id].data; } return constructor; })(); 

现在,如果您创build一个new Bundledata值将被locking在内部:

 var a = new Bundle(); console.log( a.exampleMethod() ); /// 123 

然而,现在您正在讨论是否应该在JavaScript中真正拥有私有值。 就我所知,对那些可能需要扩展你的代码甚至是自己来说,对所有事情都有开放访问的人来说总是会更好。

上述模式还有一些隐藏的缺点,除了缺乏可读性,或者在访问“私有”价值方面笨拙。 一个事实是Bundle每个实例都会保留对local对象的引用。 这可能意味着,例如,如果您创build了数千个Bundle,并且删除了其中的一个,那么保存在local的数据将不会被垃圾收集到所创build的所有Bundle。 你必须包括一些解构代码来解决这个问题……基本上使事情变得更加复杂。

所以我build议删除私人实体/属性的想法,无论你决定去哪种模式…基于对象或构造。 JavaScript的好处在于,所有这些不同的方法都是可能的 – 它不会像基于类的语言一样清晰 – 有些人可能认为这会让事情变得混乱,但我喜欢JavaScript的快速和performance力。

关于你在这个问题上的这个陈述:

例如在一个vector绘图应用程序中,工作区通常在绘图开始时是空的:我不能从现有的一个创build一个新的“线”。 更一般地说,每个dynamic创build对象的情况都需要使用类。

你似乎是误解了Javascript中的对象只能通过克隆现有的对象来实现,而这些对象会回溯到“克隆现有对象无法做到的第一个对象”不是任何现有的对象。“

但是,您可以从头开始创build对象,其语法如var object = {}

我的意思是,这是最简单的对象,一个空的对象。 当然,更有用的东西是一个东西:

 var object = { name: "Thing", value: 0, method: function() { return true; } } 

等等,现在你要参加比赛了!

比我回答这个问题有更聪明的人,但是我想特别注意你刚编辑过的一部分 – 私有variables部分。

你可以使用闭包来模拟这个; 一个令人敬畏的构造,允许一个function拥有自己的环境。 你可以这样做:

 var line = (function() { var length = 0; return { setLen : function (newLen) { length = newLen; }, getLen : function () { return length; } }; }()); 

这会将行设置为setLengetLen方法的对象,但是如果不使用这些方法,您将无法手动访问length