【问题标题】:Closures in auto executing functions vs objects自动执行函数和对象的闭包
【发布时间】:2012-01-04 03:03:51
【问题描述】:

假设我有以下内容:

var foo = (function(){
    var bar = 0;
    return {
       getBar: function(){
           return bar;
       },
       addOne: function(){
           bar++;
       },
       addRandom: function(rand){
           bar += rand;
       }
    }
})();

我有以下几点:

var foo2 = function(){
    var bar = 0;
    this.getBar = function(){
           return bar;
       };
    this.addOne = function(){
           bar++;
       };
    this.addRandom = function(rand){
           bar += rand;
       }
};

执行函数的唯一区别是new吗?

alert(foo.getBar()); //0
foo.addOne();
foo.addRandom(32);
alert(foo.getBar()); //33

var foo2_obj = new foo2;
alert(foo2_obj.getBar());//0
foo2_obj.addOne();
foo2_obj.addRandom(32);
alert(foo2_obj.getBar());//33

他们都输出了完全相同的东西。

那么从长远来看有什么区别呢?

一个人能做什么,另一个人不能?

上述的小提琴演示:http://jsfiddle.net/maniator/YtBpe/

【问题讨论】:

  • 一句话:一是对象,二是函数。
  • 从纯粹的程序化角度关注可以完成和不能完成的事情是不正确的。 JavaScript 的语言设计,就像所有的代码一样都是不完美的。特别是考虑到原始设计的匆忙时间表。因此,专注于最佳实践。 new 通常用于构造函数调用模式(Crockford 认为这是一件坏事)。它设置 this = 原型对象。它还以有趣的方式修改方法的返回值。因此,构造函数应该大写以表示其预期用途。
  • 您的示例无关紧要,因为它们没有更大的上下文而存在。如果你想谈论闭包,你必须谈论跨越范围边界以及在上下文中的结果。
  • 'foo' 将运行一次并生成一个对象。 'foo2' 可以与 'new' 一起运行,以创建任意数量的对象。就像布赖恩说的,将 'Foo2' 大写。

标签: javascript object closures


【解决方案1】:

在第一个中,您只能创建一次对象,而在第二个中,您可以创建任意数量的对象。 IE。第一个实际上是一个单例。

请注意,第二个闭包是不行的。每次实例化它时,您都在重新创建函数并浪费大量内存。原型对象旨在解决这个问题,您可以在函数范围之外创建函数,并且不会创建意外的闭包。

function foo2(){
    this._bar = 0;
}

foo2.prototype = {

    constructor: foo2,

    getBar: function(){
        return this._bar;
    },

    addOne: function(){
        this._bar++;
    },

    addRandom:function(rand){
        this._bar += rand;
    }

};

然后:

var a = new foo2, b = new foo2, c = new foo2;

创建三个实例,它们有自己的_bar,但共享相同的功能。

jsperf

您可以将所有这些与 PHP 进行“比较”,有些代码甚至不会运行,但原则上是“等效的”:


var foo = (function(){
    var bar = 0;
    return {
       getBar: function(){
           return bar;
       },
       addOne: function(){
           bar++;
       },
       addRandom: function(rand){
           bar += rand;
       }
    }
})();

在 PHP 中大致“等价于”:

$foo = new stdClass;

$foo->bar = 0;

$foo->getBar = function(){
    return $this->bar;
};

$foo->addOne = function(){
    $this->bar++;
}

$foo->addRandom = function($rand){
    $this->bar += $rand;
}

var foo2 = function(){
    var bar = 0;
    this.getBar = function(){
        return bar;
    };
    this.addOne = function(){
        bar++;
    };
    this.addRandom = function(rand){
        bar += rand;
    }
};

在 PHP 中大致“等价于”:

Class foo2 {


    public function __construct(){
    $bar = 0;

        $this->getBar = function(){
            return $bar;
        };
        $this->addOne = function(){
            $bar++;
        };
        $this->addRandom = function($rand){
            $bar += rand;
        };


    }

}

function foo2(){
    this._bar = 0;
}

foo2.prototype = {

    constructor: foo2,

    getBar: function(){
        return this._bar;
    },

    addOne: function(){
        this._bar++;
    },

    addRandom:function(rand){
        this._bar += rand;
    }

};

在 PHP 中大致“等价于”:

Class foo2 {

    public $_bar;

    public function __construct(){
        $this->_bar = 0;    
    }

    public function getBar(){
        return $this->_bar;    
    }

    public function addOne(){
        $this->_bar++
    }

    public function addRandom($rand){
        $this->_bar += $rand;
    }

}

...并且是上面三个例子中唯一一个接近OOP的例子


【讨论】:

  • 没有单例。在 OP 的帖子中,foo 是一个返回具有三个函数(getBaraddOneaddRandom)的动态对象的函数。您可以根据需要创建该对象的任意多个实例,它不是单例。
  • @FritsvanCampen,在 OP 中仔细查看,该函数会立即执行,因此 foo 只是一个创建一次的对象,而不是创建“动态”对象的函数。您甚至可以按原样在控制台中运行它...
  • +1 用于提及私有变量的共享性质,这使得示例一中的 bar 成为静态变量,而在第二个示例中,它是对象实例独有的私有变量
【解决方案2】:

唯一的区别是foo 将是通用Object,而foo2_obj 在检查其类型时将标识为foo2(即foo2_obj.constructor == foo2 将是true,而@ 上的等价物987654327@ 是foo.constructor == Object)。

当然,foofoo2 之间有一个重要区别 - foo 是一个对象,而 foo2 是一个函数(旨在用作构造函数)。因此,创建尽可能多的foo2 实例(其中foo2_obj 就是其中之一)是微不足道的,而创建foo 的“实例”的想法实际上没有意义——你能做的最好的就是复制(这比调用构造函数更难)。

由于复制/创建实例的区别,第二种方法允许使用原型链进行真正的 OO 编程,而第一种方法使此类事情更加更加困难(并且不明智)。

【讨论】:

  • 这不是真的。 Object.create 允许你用……一切来制作“真正的”OO。
  • @Zirak 第一种方法不使用Object.create。我看不出这与我所说的有什么矛盾。
  • 您还可以使用第一种形式创建出色的继承模式和“强大的构造函数”。
  • @jAndy power constructor 是什么?
  • @Zirak,你只是说一般吗? Object.create 对第一种形式没有任何用处,对第二种形式也没有。第二种形式也不是 OOP,所有函数都是在每次实例化时手动创建的,而不是从原型继承的。也不能延长。 :(
【解决方案3】:

[1]第一,但不重要:效率

function Foo1() {
    var bar = 0;
    return {
        getBar: function () {
            return bar;
        }
    }
}
var o = Foo1();
o.getBar();


function Foo2() {
    var bar = 0;
    this.getBar = function () {
        return bar;
    }
}
var o = new Foo2();
o.getBar();

哪个更快?,看object-literal-vs-new-operate

[2]程序模式:前者没有程序模式,但后者将受益于prototypal inheritance。如果现在我们要添加一个名为“logBar”的方法,

以前的:

1:扩展每个 Foo1 实例:

o.logBar = function () {
    console.log(this.getBar());
}
o.logBar();

不好的方式!

2:查找Foo1定义的位置并添加:

function Foo1() {
    var bar = 0;
    return {
        getBar: function () {
            return bar;
        },
        logBar:function () {
            console.log(this.getBar());
        }
    }
}
var o = Foo1();
o.logBar = o.logBar();

当您想增加更多的方法执行时间时,您想返回执行此操作吗?

后来:

Foo2.prototype.logBar = function () {
    console.log(this.getBar());
}

var o = Foo2();
o.logBar = o.logBar();

这样就好了。

[3] 回到效率: 以Foo1的方式,它在Foo1实例创建时产生logBar函数实例ervey时间。object-literal-vs-new-operate

【讨论】:

  • 我认为使用(原型)继承的能力是最重要的因素。 +1
  • 就是这样!原型是 JS 的重要特性。
  • OP 中没有原型继承,并且通过在构造函数中定义可共享函数,您将原型(或任何类型)继承暴露在脸上。
  • 测试 object-literal-vs-new-operate 使用原型测试对象创建,这不是您或提问者示例所做的。在构造函数内设置 this.property 与在构造函数外设置 foo.prototype.property 不同,也没有相同的效率节省。
  • 对此我感到非常抱歉。我在jsperf 有一个没有prototype 的清晰版本,但是当我更改它以显示prototype 更好时它没有保存旧版本。
【解决方案4】:

我个人认为这两种类型
1- 单例
2-对象

假设我们有一页使用 Object (Second) 的 javascript,并且 有许多使用单例的实用程序(第一个),并且工作正常。

但是有一天我们需要一个通过 AJAX 调用第一页的新页面,这个新页面使用 Object (Second) 的 javascript 和使用单例的相同 utils,但我们在 utils 单例中添加了一些新功能。

事实证明,新页面中的 utils 单例被第一页中加载的 utils 单例覆盖,因此当新页面执行时,其中一些新功能不存在,产生错误 ...

我认为这是我的观点,当你遇到这种情况时,单例会被覆盖,并且在这种情况下会发现错误很难......很难......,与具有唯一实例的对象不同

干杯。

【讨论】:

    【解决方案5】:

    主要区别实际上是foo是一个对象,而foo2是一个函数。

    这意味着您将无法创建另一个对象,例如 foo,它实际上并不是 foo 本身,除非您复制/粘贴其代码。

    另一方面,您可以创建另一个foo2 对象并在将foo2_obj 用于其他目的时对其进行操作。

    简而言之,foo 是一个实例,而foo2 可以看作是一个类(即使它只是一个构造对象的函数)。

    这取决于你想在你的程序中做什么,但我肯定会推荐使用第二种形式,它允许通过创建其他实例来重用你的代码。

    【讨论】:

    • 从对象而不是类继承(例如,通过使用 Douglas Crockford 的 object() 方法 javascript.crockford.com/prototypal.html)实际上是 javascript 中非常标准的做法
    • 我没有提到继承,而是创建另一个对象作为我们最初定义的对象。使用您指出的方法,在复制时第二个对象将是空的,或者与第一个对象具有相同的状态(自初始化以来可能已更改)。
    【解决方案6】:

    foofoo2_obj 是一样的。在这两种情况下,您都有一个创建新对象的函数,在闭包范围内引用一个变量并返回该对象。

    你有 4 样东西

    • 作为“foos”工厂的匿名函数
    • 从匿名工厂创建的对象 foo
    • foo2 是“foo2_objs”的名称工厂
    • 从 foo2 工厂创建的对象 foo2_obj

    如果您不接触 <Function>.prototype,则使用 new 和从函数返回函数字面量之间的确切区别可以忽略不计

    你可能想比较

    var foo2 = function(){
        var bar = 0;
        this.getBar = function(){
               return bar;
           };
        this.addOne = function(){
               bar++;
           };
        this.addRandom = function(rand){
               bar += rand;
           };
    };
    

    var Foo = {
      addOne: function () { this.bar++; },
      addRandom: function (x) { this.bar+=x; }
    };
    
    var foo3 = function () {
      return Object.create(Foo, { bar: { value: 0 } });
    }
    

    foo3 使用原型 OO。这意味着您不必一直重新创建这些函数。

    【讨论】:

    • 那么用new 制作对象有什么意义呢?那我不能自己实例化所有东西吗?
    • @Neal:当然可以。许多优秀和有才华的 js 程序员甚至不使用new。调用Object.create()Object.defineProperties() 可能是一个更好的主意。顺便说一句,你最好希望 Crockford 永远不会看到那个 sn-p。不将使用new 调用的构造函数大写就像要求缓慢死亡:p
    • @Neal new 是原型 OO 的一种机制。构造函数是 ES3 混乱的一部分
    【解决方案7】:

    简单来说,如果你创建了 10 个 foofoo2 的实例,foogetBar 函数将在内存中存在 10 次,而 foo2 的函数将只存在一次。

    此外,现代浏览器(例如带有 V8 编译器的 chrome)会将 js 编译为机器代码...在这种情况下,foo2 将被翻译为本机类对象,并且速度快 20 倍(当您创建说 1000循环中的实例)


    当只需要该类/模块的一次实例时,我通常使用简单对象方法。我遵循的结构是,

    var myInstance = function(){
       var self = {};
       self.bar = null;
       self.gerBar = function(){
          return self.bar
       }
       return self;
    }();
    

    这与foo 方法非常相似,但我发现这种结构更方便。


    我通常遇到的另一个区别(在实际使用中)是当我在类中有 callback 函数或 timeouts 时,

    var foo2 = function(){
        this.doSomething = function(){
            var temp = this;
            $.someAsyncCall(function(){
               // 'this' in current scope is the inline function itself, not the class instance
               // so have to store the instance ref in a local var in outer scope and then use that to get the class instance
               temp.callAfterAsyncCall();
            });
        };
        this.callAfterAsyncCall = function(){
        };
    };
    

    当你有很多这些情况时,你可以看到局部临时变量并不漂亮。

    在另一种方法中,您总是在模块范围内的任何地方引用 self

    var myInstance = function(){
       var self = {};
       self.doSomething = function(){
          $.someAsyncCall(function(){
              self.callAfterAsyncCall();
          });
       }
       self.callAfterAsyncCall = function(){
       };
       return self;
    }();
    

    我不确定它对你是否重要,但我认为值得一提。

    【讨论】:

    • 另外,我很确定这不是真的“如果你创建 10 个 foo 和 foo2 的实例, foo 的 getBar 函数将在内存中存在 10 次,而 foo2 的函数只会存在一次。”如果您使用 foo2.prototype.doSomething,这肯定是正确的,但我认为直接在 foo2 的新实例上设置属性不会有任何这种性质的效率节省(考虑一下,foo2 可以分支并在不同实例上设置不同的属性取决于条件,因此它不能假设所有对象共享相同的方法和属性)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-12
    相关资源
    最近更新 更多