15

Are there any important/subtle/significant differences under the hood when choosing to use one of these four patterns over the others? Object.create()而且,当通过与操作员“实例化”时,它们之间有什么区别new吗?

1)CoffeeScript翻译“类”定义时使用的模式:

Animal = (function() {

  function Animal(name) {
    this.name = name;
  }

  Animal.prototype.move = function(meters) {
    return alert(this.name + (" moved " + meters + "m."));
  };

  return Animal;

})();

2)Knockout似乎促进的模式:

var DifferentAnimal = function(name){

    var self = this;

    self.name = name;

    self.move = function(meters){
        return alert(this.name + (" moved " + meters + "m."));
    };

}

3)我经常看到的类似的简单模式:

var DifferentAnimalWithClosure = function(name){

    var name = name;

    var move = function(meters){

    };

    return {name:name, move:move};

}

4)Backbone推广模式:

var OneMoreAnimal= ClassThatAlreadyExists.extend({

    name:'',
    move:function(){}

});

更新 1:更改模式 #2 并添加模式 #3 以响应 Elias 的响应 // 次要格式

4

2 回答 2

8

需要明确一点:JS 不知道类,只知道对象和自定义的、自定义的构造函数,但这不是重点。
简而言之,回答您的问题:是的,创建您在此处发布的新对象的各种方式之间存在一些小的甚至相当大的差异。

CoffeeScript:
这实际上是创建自己的构造函数的最清晰和最传统的方法,但它已经“优化”,因为它已经准备好使用(可选)闭包变量。
基本上,这段代码的作用是使用 IIFE,将构造函数定义proptotype 方法分配包装在它们自己的私有范围内,返回对新构造函数的引用。它只是干净、简单的 JS,与您自己编写的没有什么不同。

淘汰赛:
现在这让我有点吃惊,因为至少对我来说,您提供的代码片段看起来像是模块模式的一部分,或者是一个强大的构造函数。但是由于您没有使用strict mode,因此省略new仍然会导致危险情况,并且由于整个函数都经历了创建 的新实例的麻烦,DifferentAnimal然后构造第二个对象文字,将 的所有属性分配DifferentAnimal给该辅助对象,我会说你错过了一些东西。因为,说实话,在return {};这里省略最后一句话,可能根本没有任何区别。另外:如您所见,您move在本质上是构造函数中声明了一个方法 ( )。这意味着实例将被分配它自己的函数对象move,而不是从原型中获取它。
简而言之:再仔细看看你从哪里得到这个片段,并仔细检查这是否是完整版本,因为如果是,我只能看到反对这个的论据。

使用在构造函数中定义的变量很简单:一个闭包,假设您的属性具有不同的初始状态,由一些参数确定,并传递给该构造函数:

function MyConstructor(param)
{
     var paramInit = param/2;//or something
     this.p = paramInit;//this property can change later on, so:
     this.reInit = function()
     {//this method HAS to be inside constructor, every instance needs its own method
         this.p = paramInit;//var paramInit can't, it's local to this scope
     };
}
var foo = new MyConstructor(10);
console.log(foo.p);//5
foo.p = 'hi';
console.log(foo.p);//hi
foo.reInit();
console.log(foo.p);//5
console.log(foo.paramInit);//undefined, not available outside object: it's a pseudo-private property

仅此而已,真的。当您看到 ppl usingvar that = this;或其他东西时,通常是创建对在任何地方都可用的主对象的引用,而不必处理令人头疼的问题thisthis引用是什么?当应用于对象以外的对象时,该方法应该做什么?它最初是为了?等等......)

Backbone:
在这里,我们正在处理另一种情况:扩展对象(即:使用现有“类”(构造函数)或特定实例的方法、属性)与简单地创建对象不同。
众所周知,JS 对象可以在任何给定时间分配新属性。这些属性也可以删除。有时,原型属性可以在实例本身上重新定义(掩盖原型行为)等......所以这完全取决于您希望结果对象(新创建的对象,扩展给定实例)看起来像:你希望它从实例中获取所有属性,或者您是否希望两个对象在某个地方使用相同的原型?
这两件事都可以通过使用简单的 JS 来实现,但是他们需要自己编写更多的努力。但是,如果你写,例如:

function Animal(name)
{
    this.name = name;
}
Animal.prototype.eat= function()
{
    console.log(this.name + ' is eating');
};

这可以被视为相当于写作:

var Animal = Object.extend({name:'',eat:function()
{
    console.log(this.name + ' is eating');
}});

短得多,但缺少构造函数。

newvsObject.create
好吧,这很简单:Object.create只是功能更强大new:您可以在需要创建对象时定义原型方法、属性(包括天气与否,它们是否可枚举、可写等),而不是不必编写构造函数和原型,或者创建对象文字并弄乱所有这些Object.defineProperty行。
缺点:有些人还是没有使用兼容 ECMA5 的浏览器(IE8 还没有完全死掉)。以我的经验:一段时间后调试相当大的脚本确实变得相当困难:虽然我倾向于使用强力构造函数而不是常规构造函数,但我仍然在脚本的最顶部定义了它们,具有独特、清晰和非常具有描述性的名称,而对象文字是我只是“即时”创建的东西。使用Object.create,我注意到我倾向于创建实际上有点过于复杂而无法作为实际对象字面量的对象,就好像它们是对象字面量一样:

//fictional example, old:
var createSomething = (function()
{
    var internalMethod = function()
    {//method for new object
        console.log(this.myProperty || '');
    };
    return function(basedOn)
    {
        var prop, returnVal= {};
        returnVal.myProperty = new Date();
        returnVal.getCreated = internalMethod;//<--shared by all instances, thx to closure
        if (!basedOn || !(basedOn instanceof Object))
        {//no argument, or argument is not an object:
            return returnVal;
        }
        for (prop in basedOn)
        {//extend instance, passed as argument
            if (basedOn.hasOwnProperty(prop) && prop !== '_extends')
            {
                returnVal[prop] = basedOn[prop];
            }
        }
        returnVal._extends = basedOn;//<-- ref as sort-of-prototype
        return returnVal;
    };
}());

现在这很冗长,但我已经准备好基本的构造函数,我也可以使用它来扩展现有实例。简单地写下可能看起来不那么冗长:

var createSomething = Object.create(someObject, {getCreated:function()
{
    console.log(this.myProperty);
},
myProperty:new Date()});

但是 IMO,这让你更难跟踪在哪里创建了哪个对象(主要是因为Object.create它是一个表达式,并且不会被提升
嗯,这当然不是一个决定性的论点:两者都有其优点和缺点:我更喜欢使用模块模式、闭包和强大的构造函数,如果你不这样做就好了。

希望这能为您解决一两件事。

于 2012-12-09T18:42:36.633 回答
3

第一个示例将 move 函数放在原型中,该原型将在所有 Animal 实例之间共享。

第二个示例为每个动物实例创建一个新的移动函数。

第三个示例生成一个 Animal 类,其原型中的 move 函数与第一个示例类似,但分配的代码更少。(在您的示例中,名称也在所有实例之间共享,您可能不想要)

将函数放入原型中可以更快地实例化 Animals,并且由于 JIT 引擎的工作方式,甚至函数的执行速度也更快。

于 2012-12-09T17:15:12.877 回答