2013-07-14 82 views
70

一個以前的海報問Function.bind vs Closure in Javascript : how to choose?爲什麼綁定比閉包慢?

並獲得這個答案部分,這似乎表明綁定應該比一個封閉速度快:

範圍遍歷意味着,當你伸手抓住一個值 (變量,對象),因此 增加了額外開銷(代碼執行速度變慢)。

使用綁定,您正在調用具有現有作用域的函數,以便不發生範圍遍歷。

兩個jsperfs表明綁定實際上比很多,比closure慢得多。

這被張貼上述

評論而且,我決定寫my own jsperf

那麼,爲什麼這麼綁定慢得多(70 +%的鉻)?

既然它不是更快,關閉可以達到同樣的目的,應該避免綁定?

+10

」應該避免綁定「---除非您每千頁時間做一頁 - 您不應該關心它。 – zerkms

+0

從小塊中組裝異步複雜任務可能需要與nodejs完全相同的東西,因爲回調需要以某種方式對齊。 – Paul

+0

我想這是因爲瀏覽器沒有盡最大努力來優化它。請參閱Mozilla的代碼(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility)以手動實現它。瀏覽器只是在內部做這些事情,這比快速關閉更有效。 – Dave

回答

127

Chrome 59更新:正如我在下面的答案中預測的那樣,使用新的優化編譯器,bind不再會變慢。以下是具體細節的代碼:https://codereview.chromium.org/2916063002/

大多數情況下並不重要。

除非你正在創建一個應用程序,其中.bind是瓶頸我不會打擾。在大多數情況下,可讀性比純粹的性能要重要得多。我認爲使用本機.bind通常會提供更易讀和可維護的代碼 - 這是一個很大的優勢。

但是,是的,當它的事項 - .bind較慢

是,.bind相當比一個封閉慢 - 至少在Chrome中,至少它在v8實施的電流的方式。我個人不得不在Node.JS中多次切換性能問題(更一般的情況是,在性能密集的情況下,閉包速度很慢)。

爲什麼?因爲.bind算法比使用其他函數包裝函數並使用.call.apply複雜得多。 (有趣的是,它還返回一個函數,toString設置爲[native函數])。

從規範的角度以及從實現的角度來看,有兩種方法來看待這個問題。我們來觀察兩者。

首先,讓我們look at the bind algorithm defined in the specification

  1. 我們的目標是這個值。
  2. 如果IsCallable(Target)爲false,則引發TypeError異常。
  3. 設A是按順序在thisArg(arg1,arg2等)之後提供的所有參數值的新(可能爲空)內部列表。

...

(21.調用F與論據 「論據」 的[[DefineOwnProperty]]內部方法,PropertyDescriptor的{[[獲取]:運動員,[設置]:運動員,[可枚舉]:假的,[配置]:。假},假

(22返回F.

似乎很複雜,不僅僅是一個包裝多很多

秒ond,讓我們看看how it's implemented in Chrome

讓我們來看看FunctionBind在V8(Chrome的JavaScript引擎)的源代碼:

function FunctionBind(this_arg) { // Length is 1. 
    if (!IS_SPEC_FUNCTION(this)) { 
    throw new $TypeError('Bind must be called on a function'); 
    } 
    var boundFunction = function() { 
    // Poison .arguments and .caller, but is otherwise not detectable. 
    "use strict"; 
    // This function must not use any object literals (Object, Array, RegExp), 
    // since the literals-array is being used to store the bound data. 
    if (%_IsConstructCall()) { 
     return %NewObjectFromBound(boundFunction); 
    } 
    var bindings = %BoundFunctionGetBindings(boundFunction); 

    var argc = %_ArgumentsLength(); 
    if (argc == 0) { 
     return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2); 
    } 
    if (bindings.length === 2) { 
     return %Apply(bindings[0], bindings[1], arguments, 0, argc); 
    } 
    var bound_argc = bindings.length - 2; 
    var argv = new InternalArray(bound_argc + argc); 
    for (var i = 0; i < bound_argc; i++) { 
     argv[i] = bindings[i + 2]; 
    } 
    for (var j = 0; j < argc; j++) { 
     argv[i++] = %_Arguments(j); 
    } 
    return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc); 
    }; 

    %FunctionRemovePrototype(boundFunction); 
    var new_length = 0; 
    if (%_ClassOf(this) == "Function") { 
    // Function or FunctionProxy. 
    var old_length = this.length; 
    // FunctionProxies might provide a non-UInt32 value. If so, ignore it. 
    if ((typeof old_length === "number") && 
     ((old_length >>> 0) === old_length)) { 
     var argc = %_ArgumentsLength(); 
     if (argc > 0) argc--; // Don't count the thisArg as parameter. 
     new_length = old_length - argc; 
     if (new_length < 0) new_length = 0; 
    } 
    } 
    // This runtime function finds any remaining arguments on the stack, 
    // so we don't pass the arguments object. 
    var result = %FunctionBindArguments(boundFunction, this, 
             this_arg, new_length); 

    // We already have caller and arguments properties on functions, 
    // which are non-configurable. It therefore makes no sence to 
    // try to redefine these as defined by the spec. The spec says 
    // that bind should make these throw a TypeError if get or set 
    // is called and make them non-enumerable and non-configurable. 
    // To be consistent with our normal functions we leave this as it is. 
    // TODO(lrn): Do set these to be thrower. 
    return result; 

這裏,我們可以在執行看到一堆昂貴的東西。即%_IsConstructCall()。這當然需要遵守規範 - 但它在很多情況下也比簡單的包裝要慢。


在另一方面,呼籲.bind也略有不同,使用Function.prototype.bind創建的規範說明「函數對象沒有prototype屬性或[編號],[FormalParameters] ,[[Scope]]內部屬性「

+0

如果f = g.bind(stuff);應該f()比g(stuff)慢嗎?我可以很快地發現這一點,我只是好奇,如果每次我們調用一個函數時都會發生同樣的事情,不管這個函數實例化了什麼,或者它依賴於函數的來源。 – Paul

+4

@Paul以一些懷疑態度回答我的回答。所有這些都可能在未來版本的Chrome(/ V8)中進行優化。我很少發現自己在瀏覽器中避免使用'.bind',在大多數情況下,可讀和可理解的代碼更重要。至於綁定函數的速度 - [是的,綁定函數現在會保持較慢](http://jsperf.com/bind-vs-native-bind-run),特別是當'this'值不用於部分。你可以從基準,從規範和/或獨立實現[(基準)](http://jsperf.com/bind-vs-native-bind-run)中看到這一點。 –

+4

感謝您的詳細解答。它超出了預期。 – Paul