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

tho*_*asc 38 javascript oop private class

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

例如,在矢量绘图应用程序中,工作空间通常在绘图开始时为空:我无法从现有工作空间创建新的"行".更一般地说,动态创建对象的每种情况都需要使用类.

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

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

function ClassA()
{
    var value = 1;
    this.getValue = function()
    {
        return value;
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是"ClassA"的每个实例都有自己的成员函数"getValue"的副本,这是无效的.

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

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

ClassB.prototype.getValue = function()
{
    return this.value;
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是成员变量"value"是公共的.

我不认为这个问题可以轻易解决,因为在创建对象期间需要定义"私有"变量(以便对象可以访问其创建的上下文,而不暴露those值)而基于原型的成员函数定义必须在对象创建后完成,因此原型有意义("this.prototype"不存在,我已经检查过).

或者我错过了什么?


编辑:

首先,感谢您的有趣答案.

我只想为我的初始消息添加一点精度:

我真正想做的是拥有1)私有变量(封装是好的,因为人们只能访问他们需要的东西)和2)有效的成员方法声明(避免拷贝).

似乎简单的私有变量声明只能通过javascript中的闭包来实现,这就是为什么我专注于基于类的方法.如果有一种方法可以用基于原型的方法实现简单的私有变量声明,那对我来说没问题,我不是一个激烈的基于类的方法proponnent.

在阅读答案后,似乎简单的解决方案是忘记私有,并使用特殊的编码约定来阻止其他程序员直接访问"私有"变量...

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

Ben*_*aum 31

嘘,过来!想听到一个秘密吗?

经典继承是经过测试和尝试的方法.

经常实现它在JavaScript中非常有用.在对象很棒之后,类是一个很好的概念,拥有模板来建模我们的世界.

经典继承只是一种模式.如果它是您的用例所需的模式,那么在JavaScript中实现经典继承是完全可以的.

原型继承专注于共享功能,这很棒(dinasaur鼓棒很棒),但在某些情况下,你想要共享数据方案而不是功能.这是一个原型继承根本没有解决的问题.

所以,你告诉我课堂并不像每个人一直告诉我的那样邪恶吗?

不,他们不是.JS社区不赞成的不是类的概念,而是将自己限制在仅用于代码重用的类中.就像语言不强制执行强类型或静态类型一样,它不会强制执行对象结构的方案.

实际上,在场景背后,语言的巧妙实现可以将您的普通对象转换为类似于经典继承类的东西.

那么,类如何在JavaScript中工作

好吧,你真的只需要一个构造函数:

function getVehicle(engine){
    return { engine : engine };
}

var v = getVehicle("V6");
v.engine;//v6
Run Code Online (Sandbox Code Playgroud)

我们现在有一个车辆类.我们不需要使用特殊关键字显式定义Vehicle类.现在,有些人不喜欢用这种方式做事,习惯于更经典的方式.为此,JS通过以下方式提供(愚蠢的imho)语法糖:

function Vehicle(engine){
     this.engine = engine;
}
var v = new Vehicle("V6");
v.engine;//v6
Run Code Online (Sandbox Code Playgroud)

这与上面的例子大致相同.

那么,我们还缺少什么?

继承和私有成员.

如果我告诉你基本的子类型在JavaScript中非常简单怎么办?

JavaScript的输入概念与我们在其他语言中习惯的概念不同.在JS中成为某种类型的子类型意味着什么?

var a = {x:5};
var b = {x:3,y:3};
Run Code Online (Sandbox Code Playgroud)

类型的b子类型是a什么类型?假设它是否符合(强)行为子类型(LSP):

<<<<开始技术部分

  • 子类型中方法参数的逆变 - 在这种继承中完全保留.
  • 子类型中返回类型的协方差 - 在这种继承中完全保留.
  • 子类的方法不应抛出新的异常,除非这些异常本身是超类型方法抛出的异常的子类型. - 完全保留在这种继承中.

也,

所有这些都是由我们来决定的.我们可以保持它们的紧密或松散,我们不需要,但我们当然可以.

事实上,只要我们在实现继承时遵守上述规则,我们就会完全实现强大的行为子类型,这是一种非常强大的子类型形式(参见注释*).

>>>>>结束技术部分

平凡地,人们也可以看到结构子类型成立.

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

function getCar(typeOfCar){
    var v = getVehicle("CarEngine");
    v.typeOfCar = typeOfCar;
    return v;
}
v = getCar("Honda");
v.typeOfCar;//Honda;
v.engine;//CarEngine
Run Code Online (Sandbox Code Playgroud)

不是很难,是吗?私人会员怎么样?

function getVehicle(engine){
    var secret = "Hello"
    return {
        engine : engine,
        getSecret : function() {
            return secret;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

看,secret是一个闭包变量.它完全是"私有的",它的工作方式不同于Java等语言中的私有,但是不可能从外部访问.

如何在职能部门中获得私有化?

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

如果我们想在我们在原型上共享的函数中使用私有变量,我们需要首先了解JS闭包和函数是如何工作的.

在JavaScript中,函数是第一类的.这意味着您可以传递函数.

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

var a = getPerson("thomasc");
a.greet(); //Hello thomasc
Run Code Online (Sandbox Code Playgroud)

到目前为止一切都那么好,但是我们可以将这个函数传递给周围的其他对象!这可以让你做非常松散的去耦,这很棒.

var b = a.greet;
b(); //Hello thomasc
Run Code Online (Sandbox Code Playgroud)

等待!怎么b知道这个人的名字是thomasc?这只是闭包的魔力.太棒了吧?

你可能会担心性能.让我告诉你我是如何学会停止担心并开始喜欢优化JIT的.

在实践中,拥有这样的功能副本不是一个大问题.javascript中的函数都很好,功能齐全!闭包是一个很棒的概念,一旦你掌握并掌握它们,你就会发现它非常值得,而且性能上的打击真的没那么有意义.JS每天都在变得越来越快,不要担心这些性能问题.

如果您认为它很复杂,以下内容也非常合理.与其他开发人员签订的普通合同只是说"如果我的变量开始时_不接触它,我们都同意成年人".这看起来像是这样的:

function getPerson(name){
    var greeter = {
        greet : function() {
            return "Hello" +greeter._name;
        }
    };
    greeter._name = name;
    return greeter;
}
Run Code Online (Sandbox Code Playgroud)

或者是古典风格

function Person(name){
    this._name = name;
    this.greet = function(){
       return "Hello "+this._name;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

function Person(name){
    this._name = name;
}
Person.prototype.greet =  function(){
       return "Hello "+this._name;
}
Run Code Online (Sandbox Code Playgroud)

所以,总结一下:

  • 您可以使用经典继承模式,它们对于共享数据类型很有用

  • 您还应该使用原型继承,它同样有效,并且在您想要共享功能的情况下更多.

  • TheifMaster几乎钉了它.私有私有并不像人们在JavaScript中想的那么大,只要你的代码定义了一个明确的界面,这根本就不成问题.我们都集中在这里的成年人:)

*聪明的读者可能会想:嗯?你不是用历史规则欺骗我吗?我的意思是,属性访问不是封装的.

我说不,我不是.即使您没有将字段显式封装为私有字段,也可以简单地以不访问它们的方式定义合同.通常像TheifMaster建议的那样_.此外,我认为只要我们不改变属性访问处理父对象属性的方式,历史规则在很多这样的场景中并不是那么重要.再次,这取决于我们.

  • 我想指出在js中实现"类"的人经常忽略的东西:你正在限制代码行为的方式,就像任何形式的封装一样.js的核心是原型,因此你可以在其中实现类的事实表明前者更抽象,更强大(在经典语言中实现原型继承要困难得多).Yegge之前说过:http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html (3认同)
  • 你的`getCar()`是组合,而不是继承:-) (2认同)

Aad*_*hah 28

我不想被劝退,因为你似乎是StackOverflow上的一个相当新的成员,但我将不得不一点点在你的脸上,并说,这是一个非常糟糕的主意,试图实现在JavaScript经典传承.

注意:当我说在JavaScript中实现经典继承是一个坏主意时,我的意思是尝试在JavaScript中模拟实际的类,接口,访问修饰符等是一个坏主意.然而,作为JavaScript中的设计模式的经典继承是有用的,因为它只是原型继承的语法糖(例如最大限度的最小类).我一直在我的代码中使用这种设计模式(la augment).

JavaScript是一种原型的面向对象编程语言.不是经典的面向对象编程语言.当然,您可以在JavaScript之上实现经典继承,但在此之前请记住以下事项:

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

为了你的缘故,我将为这个问题提供两个答案.第一个将向您展示如何在JavaScript中进行经典继承.第二个(我推荐)将教你拥抱原型继承.

JavaScript中的经典继承

大多数程序员开始尝试在JavaScript中实现经典继承.甚至像Douglas Crockford这样的JavaScript大师也试图在JavaScript中实现经典继承.我也试图在JavaScript中实现经典继承.

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

我提到这个的唯一原因是因为每个人在某个时间点都是业余爱好者,虽然我更喜欢你没有在JavaScript中使用经典的继承模式,但我不能指望你理解为什么原型继承才有意义.

你无法学习如何骑行而不会摔倒几次.我相信你仍然处于原型继承的学习阶段.您对经典继承的需求就像循环训练轮.

然而,训练轮很重要.如果你想要一些经典的继承库,那么你可以更轻松地在JavaScript中编写代码.一个这样的库是jTypes.当你对自己作为JavaScript程序员的技能充满信心时,请记住取下训练轮.

注意:我个人不喜欢jTypes一位.

JavaScript中的原型继承

我正在写这个部分作为你的里程碑,以便你可以稍后回来,你准备好了解真正的原型继承,知道下一步该做什么.

首先,以下行是错误的:

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

这是错误的,因为:

  1. 您永远不需要在JavaScript中从类创建对象.
  2. 无法在JavaScript中创建类.

是的,可以在JavaScript中模拟经典继承.但是,您仍然从对象而不是类继承属性.例如,ECMAScript Harmony类只是原型继承的经典模式的语法糖.

在同一个上下文中,您提供的示例也是错误的:

例如,在矢量绘图应用程序中,工作空间通常在绘图开始时为空:我无法从现有工作空间创建新的"行".更一般地说,动态创建对象的每种情况都需要使用类.

是的,即使工作区在开头是空的,您也可以从现有线创建新线.您需要了解的是,实际上并未绘制该线.

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

现在您可以通过简单地调用来绘制上述行,line.draw或者您可以从中创建一个新行:

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();
Run Code Online (Sandbox Code Playgroud)

线line2,line3,line4line5形成100x100绘制时正方形.

结论

所以你看到你真的不需要JavaScript中的类.物体就够了.使用功能可以轻松实现封装.

也就是说,如果没有每个实例都有自己的公共函数集,那么每个实例的公共函数都不能访问对象的私有状态.

这不是问题,因为:

  1. 你真的不需要私人国家.你可能认为你这样做,但你真的没有.
  2. 如果你真的想让一个变量私有,那么就像ThiefMaster所提到的那样只是在变量名前加上一个下划线,并告诉你的用户不要弄乱它.


elc*_*nrs 4

好吧,这是我解决这个特定问题的尝试,尽管我认为遵循约定是更好的方法,即。为变量添加前缀_. 在这里,我只是跟踪数组中的实例,然后可以使用方法将它们删除_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
Run Code Online (Sandbox Code Playgroud)