JavaScript中这三种“类”定义模式之间有什么区别?

时间:2012-12-09 17:02:25

标签: javascript backbone.js knockout.js coffeescript

当选择使用这四种模式中的一种而不是其他模式时,是否存在任何重要/细微/显着的差异?并且,当通过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的响应//次要格式

2 个答案:

答案 0 :(得分: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

真的,那也是真的。当您使用var that = this;或其他内容看到ppl时,通常会创建对任何地方都可用的主对象的引用,而不必处理this的问题(this 1}}引用?当应用于对象其他时,该方法应该做什么,而不是最初用于它的那个?等等...

骨干:
在这里,我们处理另一种情况:扩展对象(IE:使用方法,现有"类" (构造函数)或特定实例的属性)不是就像创建一个对象一样。
众所周知,可以在任何给定时间为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');
}});

更短,但缺乏构造函数。

new vs Object.create
嗯,这很简单:Object.create只是new更强大:你可以定义原型方法,属性(包括天气与否,它们是可枚举的,可写的等等... )当你需要创建一个对象时,而不是必须编写一个构造函数和一个原型,或者创建一个对象文字并乱用所有这些Object.defineProperty行。
缺点:有些人仍然没有使用符合ECMA5标准的浏览器(IE8仍然没有完全死亡)。根据我的经验:在一段时间后调试大型脚本确实变得非常困难:虽然我倾向于使用power-constructors而不是常规构造函数,但我仍然将它们定义在我的脚本的最顶层,具有清晰,清晰和对象 - 文字是我刚刚创建"即时" 的东西。使用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是一个表达式,不会被提升。)好吧,当然,这远不是一个确凿的论点:他们都有他们的专业人士和对手:我更喜欢使用模块模式,闭包和电源构造器,如果你不这样做的话。 #39; s很好。

希望这能为你清理一件事或两件事。

答案 1 :(得分:3)

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

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

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

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

相关问题