11

我知道这不是推荐的方法,但是如果我声明以下函数,然后将它们作为构造函数调用,那么生成的对象之间会有什么区别(如果有的话)?

function Something() {
    this.foo = "bar";
}

function something2() {
    var that = {};
    that.foo = "bar";
    return that;
}

var x = new Something();
var y = new something2();
var z = something2();

x即,yz这里有什么不同?

写构造函数不是something2更好的方法吗,因为不管你使用new与否都不会影响函数的结果?

BTW这里应该something2大写吗?(我认为不会因为 Crockford 如此坚持大写,因为函数会破坏全局命名空间......)

4

4 回答 4

15

简而言之:

new something2() instanceof something2 === false

相关地,如果您扩展示例以使用原型属性

Something.prototype.method = function () { };
something2.prototype.method = function () { };

你会发现在后一种情况下原型没有被继承:

typeof (new Something()).method === "function"
type (new something2()).method === "undefined"

真正的答案是你正在利用完全不同的底层机制。调用 withnew调用[[Construct]]机制,该机制涉及根据.prototype构造函数的属性设置 [[Prototype]] 属性。

但是在 [[Construct]] 算法的第 8--10 步中发生了一件有趣的事情:在设置一个新的空对象,然后附加它的 [[Prototype]] 之后,它对实际对象执行 [[Call]]构造函数,使用这个新的空加原型对象作为this. 然后,在第 9 步中,如果结果证明该构造函数返回了一些东西——它会丢弃this它花费所有时间设置的原型绑定、作为对象传递的对象!

注意:您可以通过以下方式访问对象的 [[Prototype]](与构造函数的 不同.prototypeObject.getPrototypeOf

Object.getPrototypeOf(new Something()) === Something.prototype // steps 5 & 6
Object.getPrototypeOf(new something2()) === Object.prototype // default

回答一些元问题:

  • 不,不要大写something2,因为它是工厂函数而不是构造函数。如果某些东西是大写的,那么它应该具有构造函数语义,例如new A() instanceof A.
  • 如果您担心破坏全局命名空间的危险,您应该开始使用严格模式,将其放在"use strict";文件的顶部。严格模式的许多不错的清理之一是this默认为undefined,而不是全局对象,因此例如调用构造函数而不使用new将导致构造函数尝试将属性附加到的那一刻出错undefined
  • 工厂函数(又名“闭包模式”)通常是构造函数和类的合理替代品,只要您 (a) 不使用继承;(b) 不构造该对象的太多实例。后者是因为,在闭包模式中,您将每个方法的新实例附加到每个新创建的对象,这对内存使用不利。IMO 闭包模式的最大收益是使用“私有”变量的能力(这是一件好事,不要让任何人告诉你:P)。
于 2012-04-26T06:49:46.490 回答
2

在第二种情况下,返回的对象没有从构造函数继承任何东西,所以这样使用它没有什么意义。

> var x = new Something();
> var y = new something2();
> var z = something2();

即这里的 x、y 和 z 有什么不同?

x继承自Something,既不继承,y也不z继承自something2

写构造函数的方式不是更好,因为无论你是否使用 new 都不会影响函数的结果?

作为构造函数调用是没有意义的,something2因为它返回的对象不是分配给它this的继承自的新构造的对象something2.prototype,这是其他人在调用时可能期望得到的new something2()

顺便说一句,something2 应该在这里大写吗?(我认为不会因为 Crockford 如此坚持大写,因为函数会破坏全局命名空间......)

不,因为将其称为构造函数有点毫无意义,因此将其描述为构造函数会产生误导。

于 2012-04-26T06:49:19.330 回答
1

我想说最重要的是返回对象的原型。

  function Something() {
       this.foo = "bar";
  }

  Something.prototype = {
    // Something prototype code
    hello: function(){
     //...
    }
  }

  function something2() {
     var that = {};
     that.foo = "bar";
     return that;
  }

  something2.prototype = {
      // something2 prototype code
      greetings : function() {
      //...
      }
  }

  var x = new Something();
  var y = new something2();
  var z = something2();

  typeof x.hello === function // should be true
  typeof y.greetings === undefined // should be true
  typeof z.greetings === undefined // should be true

换句话说,我会说你不是用 something2 来实例化对象,你是在创建从 Object 继承的纯粹的新对象。

  1. 当您使用new关键字时,Something() 将为您提供“Something”类型的新对象。
  2. something2() 将为您提供“Object”类型的新对象,它将立即返回一个新的空对象。
  3. new something2 效率低下,因为您正在创建一个空白范围,从中创建一个新对象

    var that = {};
    

    这相当于

    var that = new Object();
    
于 2012-04-26T07:00:38.123 回答
1

调用函数作为构造函数(即使用 new keyword)运行以下步骤:

  1. 创建一个新对象
  2. 将该对象的原型设置为prototype函数属性中的对象
  3. 在该对象的上下文中执行构造函数(即this是新对象)
  4. 返回该对象(如果构造函数没有return语句)

因此,您的第二个解决方案将只返回一个具有属性“foo”的普通对象。但既不是y也不zinstanceof Something2,也不是从该原型继承。有这样的函数,是的,但它们不应该被称为构造函数(没有大写命名,没有调用new)。它们属于工厂模式。

如果您想要一个可以在没有 new 的情况下执行的构造函数,请使用该代码:

function Something(params) {
    if (! this instanceof Something)
         return new Something(params);
    // else use "this" as usual
    this.foo = "bar";
    ...
 }
于 2012-04-26T06:56:24.810 回答