JavaScript中的原型OO

Ray*_*nos 43 javascript oop prototypal-inheritance node.js ecmascript-5

TL; DR:

我们是否需要原型OO中的工厂/施工人员?我们可以做一个范例切换并完全删除它们吗?

BackStory:

我最近一直在玩JavaScript中的原型OO并发现在JavaScript中完成的99%的OO正在强制使用经典的OO模式.

我对原型OO的看法是它涉及两件事.方法(和静态数据)的静态原型和数据绑定.我们不需要工厂或建造者.

在JavaScript中,这些是包含函数和的对象文字Object.create.

这意味着我们可以将所有内容建模为静态蓝图/原型和数据绑定抽象,最好直接连接到文档样​​式的数据库中.即,从数据库中取出对象,并通过使用数据克隆原型来创建对象.这意味着没有构造函数逻辑,没有工厂,没有new.

示例代码:

一个伪示例是:

var Entity = Object.create(EventEmitter, {
    addComponent: {
        value: function _addComponent(component) {
            if (this[component.type] !== undefined) {
                this.removeComponent(this[component.type]);
            }

            _.each(_.functions(component), (function _bind(f) {
                component[f] = component[f].bind(this);
            }).bind(this));

            component.bindEvents();

            Object.defineProperty(this, component.type, {
                value: component,
                configurable: true
            });

            this.emit("component:add", this, component);
        }
    },
    removeComponent: {
        value: function _removeComponent(component) {
            component = component.type || component;

            delete this[component];

            this.emit("component:remove", this, component);
        }
    }
}

var entity = Object.create(Entity, toProperties(jsonStore.get(id)))
Run Code Online (Sandbox Code Playgroud)

小解释:

特定代码很冗长,因为ES5很冗长.Entity上面是蓝图/原型.任何带有数据的实际对象都将通过使用来创建Object.create(Entity, {...}).

实际数据(在本例中为组件)直接从JSON存储加载并直接注入到Object.create调用中.当然,类似的模式应用于创建组件,并且只有传递的属性Object.hasOwnProperty存储在数据库中.

当第一次创建实体时,它是用空创建的 {}

实际问题:

现在我的实际问题是

  • JS原型OO的开源示例?
  • 这是一个好主意吗?
  • 它是否与原型OOP背后的想法和概念一致?
  • 不会使用任何构造函数/工厂函数咬我的屁股?我们真的可以逃避不使用构造函数.使用上述方法是否有任何限制,我们需要工厂来克服它们.

Rya*_*nal 26

我不认为构造函数/工厂逻辑是必要的,只要你改变你对面向对象编程的看法.在我最近对该主题的探索中,我发现原型继承更多地用于定义一组使用特定数据的函数.对于经典继承训练的人来说,这不是一个外国概念,但遗憾的是这些"父"对象没有定义要操作的数据.

var animal = {
    walk: function()
    {
        var i = 0,
            s = '';
        for (; i < this.legs; i++)
        {
            s += 'step ';
        }

        console.log(s);
    },
    speak: function()
    {
        console.log(this.favoriteWord);
    }
}

var myLion = Object.create(animal);
myLion.legs = 4;
myLion.favoriteWord = 'woof';
Run Code Online (Sandbox Code Playgroud)

因此,在上面的示例中,我们创建了与动物一起的功能,然后创建具有该功能的对象以及完成操作所需的数据.对于任何习惯于经典继承任何时间长度的人来说,这都让人觉得不舒服和奇怪.它没有公众/私人/受保护的成员可见性等级的温暖模糊性,我将是第一个承认它让我感到紧张的人.

另外,我的第一直觉,当我看到myLion对象的上述初始化是为动物创建一个工厂,所以我可以通过简单的函数调用创建狮子,老虎和熊(我的).而且,我认为,对于大多数程序员来说,这是一种自然的思维方式 - 上述代码的冗长是丑陋的,似乎缺乏优雅.我还没有决定是否仅仅是因为经典训练,或者这是否是上述方法的实际错误.

现在,继承.

我一直都认为JavaScript中的内容很难理解.导航原型链的细节并不十分清楚.直到你使用它Object.create,它将所有基于函数的新关键字重定向从等式中取出.

假设我们想扩展上述animal对象,并创造一个人类.

var human = Object.create(animal)
human.think = function()
{
    console.log('Hmmmm...');
}

var myHuman = Object.create(human);
myHuman.legs = 2;
myHuman.favoriteWord = 'Hello';
Run Code Online (Sandbox Code Playgroud)

这创建了一个具有human原型的对象,而原型又具有animal原型.很容易.没有错误的方向,没有"原型等于函数原型的新对象".只是简单的原型继承.这很简单,也很简单.多态性也很容易.

human.speak = function()
{
    console.log(this.favoriteWord + ', dudes');
}
Run Code Online (Sandbox Code Playgroud)

由于原型链的工作方式,myHuman.speak它将human在它被发现之前被发现animal,因此我们的人类是冲浪者,而不仅仅是一个无聊的老动物.

所以,最后(TLDR):

伪经典构造函数功能有点强调JavaScript,使那些受过古典OOP训练的程序员更加舒适.它绝不是必要的,但它意味着放弃经典概念,例如成员可见性和(重复)构造函数.

您获得的回报是灵活性和简单性.您可以动态创建"类" - 每个对象本身就是其他对象的模板.在子对象上设置值不会影响这些对象的原型(即,如果我使用var child = Object.create(myHuman),然后设置child.walk = 'not yet',animal.walk将不受影响 - 真的,测试它).

继承的简单性是诚实的令人难以置信的.我已经阅读了很多关于JavaScript的继承,并编写了许多代码来试图理解它.但它实际上归结为从其他对象继承的对象.就这么简单,所有的new关键字都搞砸了.

这种灵活性很难在最大程度上使用,我确信我还没有这样做,但它就在那里,导航很有意思.我认为它没有用于大型项目的大部分原因是它根本不被理解,而且,恕我直言,我们被锁定在我们所学到的经典继承模式中曾教过C++,Java等.

编辑

我想我已经对构造函数做了很好的判断.但我反对工厂的论点是模糊的.

在进一步思考之后,我多次翻转到栅栏的两侧,我得出的结论是,工厂也是不必要的.如果animal(上面)给出了另一个函数initialize,那么创建和初始化一个继承自的新对象将是微不足道的animal.

var myDog = Object.create(animal);
myDog.initialize(4, 'Meow');
Run Code Online (Sandbox Code Playgroud)

新对象,已初始化并可供使用.

@Raynos - 你完全是书呆子在这个人身上狙击我.我应该做好准备,连续5天没有任何成效.

  • `initialize`与我书中的构造函数/工厂相同.真正的问题是我们是否可以改变对OO设计的态度,以便构造函数不是必需的. (3认同)

dav*_*vin 11

根据你的评论,问题主要是"构建者知识是否必要?" 我觉得是的.

玩具示例将存储部分数据.在内存中的给定数据集上,当持久化时,我可能只选择存储某些元素(为了效率或数据一致性目的,例如,一旦持久存在,值本身就是无用的).让我们来一个会话,我存储用户名和他们点击帮助按钮的次数(缺少一个更好的例子).当我在我的例子中坚持这一点时,我确实没有使用点击次数,因为我现在将它保存在内存中,下次我加载数据(下次用户登录或连接或其他)我将初始化从头开始的价值(大概是0).这个特定的用例是构造函数逻辑的一个很好的候选者.

Aahh,但你总是可以把它嵌入到静态原型中:Object.create({name:'Bob', clicks:0});当然,在这种情况下.但是,如果该值一开始并不总是0,而是需要计算的东西,那该怎么办呢?例如,Uummmm用户以秒为单位(假设我们存储了名称和DOB).同样,一个很少使用的项目仍然存在,因为它无论如何都需要在检索时重新计算.那么如何将用户的年龄存储在静态原型中呢?

显而易见的答案是构造函数/初始化逻辑.

还有很多场景,虽然我觉得这个想法与js oop或特别是任何语言都没有多大关系.实体创建逻辑的必要性是我看待计算机系统模拟世界的方式所固有的.有时我们存储的项目将是一个简单的检索并注入到原型shell之类的蓝图中,有时值是动态的,需要初始化.

UPDATE

好的,我将尝试更真实的例子,并避免混淆假设我没有数据库,不需要保留任何数据.假设我正在制作单人纸牌服务器.每个新游戏(自然)将是Game原型的新实例.我很清楚,这里需要初始化逻辑(还有很多):

例如,我将在每个游戏实例上不仅需要静态/硬编码的卡片组,而且需要随机洗牌.如果它是静态的,则用户每次都会玩同一个游戏,这显然不是很好.

如果玩家用完,我可能还需要启动计时器才能完成游戏.同样,不是可以是静态的东西,因为我的游戏有一些要求:秒数与连接的玩家到目前为止赢得的游戏数量成反比(再次,没有保存的信息,这个连接有多少)并且与洗牌的难度成比例(根据洗牌结果有一种算法可以确定游戏的难度).

你怎么用静电做的Object.create()

  • 我蔑视地看到你的逻辑和论点.这可能会变成一个特定的函数,当你从数据库加载数据时,它会被调用吗?或者,构造函数/初始化程序与硬件耦合到特定原型的数据库类型转换函数之间没有语义差异. (2认同)