7

我们一直在讨论如何最好地处理我们的 JS 应用程序中的对象,研究 Stoyan Stefanov 的书,阅读关于“新”、“这个”、“原型”、闭包等无休止的 SO 帖子。(事实上有这么多,而且他们有这么多相互竞争的理论,表明没有完全明显的答案)。

所以让我们假设我们不关心私有数据。我们满足于信任用户和开发人员不会在我们定义的方式之外乱搞对象。

鉴于此,这种技术有什么问题(除了它似乎违背了数十年的 OO 风格和历史)?

// namespace to isolate all PERSON's logic
var PERSON = {};

// return an object which should only ever contain data.
// The Catch: it's 100% public

PERSON.constructor = function (name) {
     return {
         name: name
     } 
}

// methods that operate on a Person
// the thing we're operating on gets passed in

PERSON.sayHello = function (person) {
     alert (person.name); 
}

var p = PERSON.constructor ("Fred");
var q = PERSON.constructor ("Me");

// normally this coded like 'p.sayHello()'
PERSON.sayHello(p);
PERSON.sayHello(q);

明显地:

  1. 没有什么可以阻止某人以邪恶的方式变异“p”,或者只是 PERSON 的逻辑最终传播到了整个地方。(规范的“新”技术也是如此)。
  2. 将“p”传递给您想要使用它的每个函数将是一个小麻烦。
  3. 这是一种奇怪的做法

但这些足以驳回它的理由吗?积极的一面:

  1. 它是有效的,因为(可以说)与重复函数声明的闭包相反。
  2. 它看起来非常简单易懂,而不是到处摆弄“这个”。

重点是前面提到的隐私。我知道我会为此受到抨击,但是,寻找任何反馈。干杯。

4

2 回答 2

9

它本质上没有任何问题。但它确实放弃了使用 Javascript 原型系统所固有的许多优点。

  • 除了它是一个对象字面量之外,您的对象对自身一无所知。所以instanceof不会帮助你确定它的来源。只使用鸭子打字你会被卡住。
  • 您的方法本质上是命名空间的静态函数,您必须通过将对象作为第一个参数传入来重复自己。通过拥有一个原型对象,您可以利用动态调度,以便可以根据 Javascript 知道的类型或根据类型p.sayHello()做不同的事情。这是多态的一种形式。您的方法要求您在每次调用方法时命名(并且可能会出错)类型。PERSONANIMAL
  • 您实际上并不需要constructor函数,因为函数已经是对象。您的PERSON变量也可能是构造函数。

您在这里所做的是创建一个模块模式(如命名空间)。

这是另一种模式,可以保留您所拥有的但提供上述优势:

function Person(name)
{
    var p = Object.create(Person.prototype);
    p.name = name; // or other means of initialization, use of overloaded arguments, etc.
    return p;
}
Person.prototype.sayHello = function () { alert (this.name); }

var p = Person("Fred"); // you can omit "new"
var q = Person("Me");
p.sayHello();
q.sayHello();
console.log(p instanceof Person); // true
var people = ["Bob", "Will", "Mary", "Alandra"].map(Person);
        // people contains array of Person objects
于 2013-02-06T01:16:35.203 回答
1

是的,我真的不明白你为什么要避开构造函数方法,或者当构造函数本身是优雅、灵活、以及完全合理的面向对象编程方法,无论像 Crockford 这样的人给出了多少不喜欢它们的蹩脚理由(因为人们忘记使用新关键字 - 认真吗?)。JS 是高度功能驱动的,它的 OOP 机制也不例外。接受这一点比躲避它更好,IMO。

首先,您的积分列在“显然”下

  1. 在 JavaScript 中几乎不值得一提。高度可变性是设计使然。我们不怕我们自己或其他 JavaScript 开发人员。私有与公共范式没有用,因为它可以保护我们免于愚蠢,而是因为它可以更容易地理解其他开发人员代码背后的意图。

  2. 调用的努力不是问题。当你不清楚你为什么做了你在那里做过的事情时,麻烦就来了。我真的看不出你想要实现的核心语言方法对你没有更好的效果。

  3. 这是 JavaScript。多年来,除了 JS 开发人员之外,所有人都觉得很奇怪。如果您找到一种更好的方法来解决给定领域中的问题,而不是更典型的解决方案,请不要担心。在尝试替换它之前,请确保您了解更典型方法的要点,就像许多人从其他语言范式转向 JS 时所做的那样。用 JS 做一些琐碎的事情很容易,但是一旦你到了想要获得更多 OOP 驱动的地步,尽你所能了解核心语言的工作原理,这样你就可以对流行的观点提出更多的怀疑。那些以副业为生的人让 JavaScript 变得比实际情况更可怕,更充满致命的诱杀陷阱。

现在你在“积极的一面”下的观点

  1. 首先,重复的函数定义实际上只是在繁重的循环场景中需要担心的事情。如果您定期以足够快的速度生成足够多的对象,以使非原型公共方法定义成为性能问题,那么无论如何,您可能会在短时间内遇到非平凡对象的内存使用问题。然而,我用过去时说,因为无论哪种方式,它都不再是一个真正相关的问题。在现代浏览器中,由于现代 JIT 编译器的工作方式,在其他函数中定义的函数实际上通常会提高性能。无论您支持什么浏览器,每个对象定义的几个函数都不是问题,除非您期望有数万个对象。

  2. 关于简单易懂的问题,这不是我的问题,因为我看不出你在这里获得了什么胜利。现在,我不必使用一个对象,而是必须同时使用该对象和它的伪构造函数. 如果我是您的代码库的新手,我会浪费大量时间试图弄清楚您为什么这样做以避免破坏我不理解的其他一些问题。

我的问题是:

为什么不首先在构造函数中添加对象字面量中的所有方法呢?那里没有性能问题,而且从来没有真正存在过,所以唯一可能的胜利是您希望能够在使用它创建新对象后向人员添加新方法,但这就是我们在适当的构造函数上使用原型的原因(顺便说一句,原型方法非常适合旧浏览器中的内存,因为它们只定义一次)。

而且,如果您必须不断传递对象以使方法知道属性是什么,那您为什么还要对象呢?为什么不只是期望具有某些属性的简单数据结构类型对象的函数呢?它不再是真正的 OOP。

但我的主要批评点

您错过了 OOP 的要点,这是 JavaScript 在不向人们隐藏方面比大多数语言做得更好。考虑以下:

function Person(name){
    //var name = name; //<--this might be more clear but it would be redundant
    this.identifySelf = function(){ alert(name); }
}

var bob = new Person();
bob.identifySelf();

现在,在不覆盖对象或方法的情况下更改 bob 标识的名称,这两种操作只有在您明确不想使用最初设计和构造的对象时才要做。你当然不能。这让任何看到这个定义的人都清楚地知道,在这种情况下,名称实际上是一个常数。在更复杂的构造函数中,它将确定唯一允许更改或修改名称的是实例本身,除非用户添加了一个非验证的 setter 方法,这将是愚蠢的,因为这基本上会(看着你的 Java Enterprise Beans)谋杀OOP 的主要目的。

分工明确是关键

暂时忘掉他们在每本书中的关键词,想想重点是什么。在 OOP 之前,一切都只是一堆函数和所有这些函数所作用的数据结构。使用 OOP,您通常拥有一组与一组数据捆绑在一起的方法,而这些数据实际上只有对象本身会发生变化。

所以让我们说输出出了点问题:

  • 在我们严格的程序堆函数中,可能会弄乱该数据的手数没有真正的限制。我们可能有很好的错误处理,但是一个函数可能会以这样一种方式分支,以至于原始的罪魁祸首很难追查。

  • 在适当的 OOP 设计中,数据通常位于对象看门人之后,我知道只有一个对象实际上可以使更改负责。

大多数时候暴露所有数据的对象实际上只比旧的程序方法稍微好一点。真正做的只是给你一个名字来分类松散相关的方法。

很多关于“这个”的事

我从来没有理解分配给“this”关键字的过度关注是混乱和混乱的。这真的没什么大不了的。'this' 标识您正在使用的实例。就是这样。如果该方法不作为属性调用,则它不会知道要查找的实例,因此它默认为全局对象。那是愚蠢的(未定义会更好),但是在这种情况下它不能正常工作应该在一种语言中预期,其中函数也像数据一样可移植并且可以很容易地附加到其他对象。在以下情况下在函数中使用“this”:

  • 它被定义为实例的属性并被调用。

  • 它作为事件处理程序传递(它将作为被侦听事物的成员调用它)。

  • 您正在使用 call 或 apply 方法将其作为其他对象的属性临时调用,而不是这样分配它。

但请记住,真正重要的是召唤。将公共方法分配给某个 var 并从该 var 调用将执行全局操作或在严格模式下引发错误。在没有被引用为对象属性的情况下,函数只真正关心它们定义的范围(它们的闭包)以及您传递给它们的参数。

于 2013-02-11T00:31:33.177 回答