簡體   English   中英

Javascript閉包 - 有什么負面消息?

[英]Javascript Closures - What are the negatives?

問題:閉包似乎有很多好處,但是什么是負面的(內存泄漏?混淆問題?帶寬增加?)? 另外,我對閉包的理解是否正確? 最后,一旦創建了閉包,它們會被銷毀嗎?

我一直在閱讀有關Javascript Closures的一些信息。 我希望有一點知識淵博的人會指導我的斷言,糾正錯誤的地方。

閉包的好處:

  1. 使用內部函數將變量封裝到本地作用域。 該函數的匿名性是微不足道的。

我發現有用的是做一些關於本地/全球范圍的基本測試:

<script type="text/javascript">

   var global_text  = "";
   var global_count = 0;
   var global_num1  = 10;
   var global_num2  = 20;
   var global_num3  = 30;

   function outerFunc() {

      var local_count = local_count || 0;

      alert("global_num1: " + global_num1);    // global_num1: undefined
      var global_num1  = global_num1 || 0;
      alert("global_num1: " + global_num1);    // global_num1: 0

      alert("global_num2: " + global_num2);    // global_num2: 20
      global_num2  = global_num2 || 0;         // (notice) no definition with 'var'
      alert("global_num2: " + global_num2);    // global_num2: 20
      global_num2  = 0;

      alert("local_count: " + local_count);    // local_count: 0

      function output() {
         global_num3++;

         alert("local_count:  " + local_count  + "\n" +
               "global_count: " + global_count + "\n" +
               "global_text:  " + global_text
              );

         local_count++; 
      }

      local_count++;
      global_count++;

      return output;  
   }  

   var myFunc = outerFunc();

   myFunc();
      /* Outputs:
       **********************
       * local_count:  1
       * global_count: 1
       * global_text: 
       **********************/

   global_text = "global";
   myFunc();
      /* Outputs:
       **********************
       * local_count:  2
       * global_count: 1
       * global_text:  global
       **********************/

   var local_count = 100;
   myFunc();
      /* Outputs:
       **********************
       * local_count:  3
       * global_count: 1
       * global_text:  global
       **********************/


   alert("global_num1: " + global_num1);      // global_num1: 10
   alert("global_num2: " + global_num2);      // global_num2: 0
   alert("global_num3: " + global_num3);      // global_num3: 33

</script>

我從中拿出有趣的東西:

  1. outerFunc中的警報只調用一次,即將outerFunc調用分配給myFunc(myFunc = outerFunc())。 這個賦值似乎保持outerFunc打開,我想稱之為持久狀態。

  2. 每次調用myFunc時,都會執行返回。 在這種情況下,返回是內部函數。

  3. 真正有趣的是定義局部變量時發生的本地化。 注意global_num1和global_num2之間的第一個警報的差異,即使在嘗試創建變量之前,global_num1也被認為是未定義的,因為'var'用於表示該函數的局部變量。 - 之前已經討論過,按照Javascript引擎的操作順序,很高興看到這個工作正常。

  4. 仍然可以使用Globals,但局部變量將覆蓋它們。 注意在第三次myFunc調用之前,創建了一個名為local_count的全局變量,但它對內部函數沒有影響,內部函數有一個同名的變量。 相反,每個函數調用都能夠修改全局變量,正如global_var3所注意到的那樣。

發表想法:盡管代碼很簡單,但是對於你們來說它會被警報混亂,所以你可以即插即用。

我知道還有其他閉包的例子,其中許多使用匿名函數與循環結構相結合,但我認為這對於101啟動課程來看效果是好的。

我關心的一件事是關閉對內存的負面影響。 因為它使函數環境保持打開,所以它還將這些變量保存在內存中,這可能/可能沒有性能影響,尤其是關於DOM遍歷和垃圾收集。 我也不確定這會在內存泄漏方面發揮什么樣的作用,我不確定是否可以通過簡單的“刪除myFunc;”從內存中刪除閉包。

希望這有助於某人,

vol7ron

閉包帶來了很多好處......但也有很多問題。 同樣讓它們變得強大的東西也使它們能夠在你不小心的情況下弄得一團糟。

除了循環引用的問題(這不再是一個問題,因為IE6在中國以外很難使用),至少還有一個巨大的潛在負面因素: 它們可能使范圍復雜化。 如果使用得當,它們可以通過允許函數共享數據而不暴露它來提高模塊性和兼容性......但是如果使用不當,即使不是不可能精確地追蹤變量的設置或更改位置也很困難。

沒有閉包的JavaScript有三個*變量范圍:塊級,功能級和全局。 沒有對象級范圍。 如果沒有閉包,您就知道變量要么在當前函數中聲明,要么在全局對象中聲明(因為這是全局變量所在的位置)。

有了封閉裝置,你就不再有這種保證了。 每個嵌套函數都引入了另一個范圍的范圍,在該函數中創建的任何閉包都會( 大多數 )看到與包含函數相同的變量。 最大的問題是每個函數都可以隨意定義自己的變量來隱藏外部變量。

正確使用閉包要求您(a)了解閉包和var如何影響范圍,以及(b)跟蹤變量所在的范圍。否則,變量可能會被意外共享(或偽變量丟失!),以及各種古怪都可以隨之而來。


考慮這個例子:

function ScopeIssues(count) {
    var funcs = [];
    for (var i = 0; i < count; ++i) {
        funcs[i] = function() { console.log(i); }
    }
    return funcs;
}

簡短,直截了當......幾乎肯定會破碎。 看:

x = ScopeIssues(100);

x[0]();   // outputs 100
x[1]();   // does too
x[2]();   // same here
x[3]();   // guess

數組中的每個函數都輸出count 這里發生了什么? 您將看到將閉包與對封閉變量和范圍的誤解相結合的影響。

創建閉包時,他們在創建閉包時不使用i的值來確定要輸出的內容。 他們使用變量 i ,它與外部函數共享並且仍在變化。 當它們輸出它時,它們輸出的值就是它被調用的時間。 這將等於count ,導致循環停止的值。

為了解決這個問題,你需要另一個閉包。

function Corrected(count) {
    var funcs = [];
    for (var i = 0; i < count; ++i) {
        (function(which) {
            funcs[i] = function() { console.log(which); };
        })(i);
    }
}

x = Corrected(100);

x[0]();  // outputs 0
x[1]();  // outputs 1
x[2]();  // outputs 2
x[3]();  // outputs 3

另一個例子:

value = 'global variable';

function A() {
    var value = 'local variable';
    this.value = 'instance variable';
    (function() { console.log(this.value); })();
}

a = new A();  // outputs 'global variable'

thisarguments是不同的; 幾乎與其他所有東西不同,它們不是在封閉邊界上共享的 每個函數調用都重新定義它們 - 除非你調用函數

  • obj.func(...)
  • func.call(obj, ...)
  • func.apply(obj, [...]) ,或
  • var obj_func = func.bind(obj); obj_func(...)

指定this ,那么你會得到默認值this :全局對象。 ^

解決this問題最常見的習慣是聲明一個變量並將其值設置this值。 我見過的最常見的名字是thatself

function A() {
    var self = this;
    this.value = 'some value';
    (function() { console.log(self.value); })();
}

但這使得self成為一個真正的變量,帶來所有潛在的奇怪之處。 幸運的是,很少想要在不重新定義變量的情況下改變self的值......但是在嵌套函數中,重新定義self當然會為嵌套在其中的所有函數重新定義它。 你做不了類似的事情

function X() {
    var self = this;
    var Y = function() {
        var outer = self;
        var self = this;
    };
}

因為吊裝 JavaScript有效地將所有變量聲明移動到函數的頂部。 這使得上面的代碼相當於

function X() {
    var self, Y;
    self = this;
    Y = function() {
        var outer, self;
        outer = self;
        self = this;
    };
}

selfouter = self運行之前已經是一個局部變量,因此outer獲取本地值 - 此時這是undefined 你剛剛失去了對外在self引用。


*自ES7起。 以前只有兩個,變量甚至更容易追蹤。 :P

使用lambda語法聲明的函數(ES7的新增功能)不會重新定義thisarguments 這可能使問題更加復雜化。

^較新的解釋器支持所謂的“嚴格模式”:一種選擇加入功能,旨在使某些不確定的代碼模式完全失敗或造成更少的損害。 在嚴格模式下, this默認為undefined ,而不是全局對象。 但它仍然是你通常打算搞砸的其他一些價值。

你可能得到一大堆好的答案。 一個肯定的是Internet Explorer循環引用內存泄漏。 基本上,JScript不會將對DOM對象的“循環”引用識別為可收集的。 使用閉包創建IE認為是循環引用很容易。 第二個鏈接提供了幾個示例。

在IE6中,回收內存的唯一方法是終止整個過程。 在IE7中,他們對其進行了改進,以便當您離開相關頁面(或關閉它)時,內存將被回收。 在IE8中,JScript可以更好地理解DOM對象,並按照您的預期收集它們。

IE6的建議解決方法(除了終止進程!)不是使用閉包。

閉包可能會導致內存泄漏,但Mozilla已嘗試優化其垃圾收集引擎以防止此情況發生。

我不確定Chrome如何處理關閉。 我認為他們與Mozilla相提並論,但我不想肯定地說。 IE8肯定比早期版本的IE改進了 - 它幾乎是一個全新的瀏覽器,仍然有一些細微差別。

您還應該對代碼進行基准測試,以確定速度是否有任何改進。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM