JavaScript做用域閉包簡述

做用域閉包

  技術通常水平有限,有什麼錯的地方,望你們指正。異步

  做用域就是變量起做用的範圍。做用域包括全局做用域,函數做用域以塊級做用域,ES6中的let和const能夠造成塊級做用域。函數

  除了塊級做用域,在函數外面聲明的變量能夠在任何一個地方被訪問到,這些變量的做用域都是全局做用域,全局做用域中的變量能夠再任何一個地方使用:spa

var a = "zt";
function fn1(){
    console.log(a);
}
function fn2(){
    console.log(a);
}
fn1();
fn2();

  在函數裏面聲明的變量只能在當前函數內使用,這些變量的做用域咱們稱爲函數做用域,只在當前函數內有效:線程

function fn1(){
    var a = "zt";
    console.log(a);
}
function fn2(){
    console.log(a)
}
fn1();
fn2();//報錯提示a沒有定義

  函數內定義的變量只在當前函數內有效,在函數之外的地方是不能被訪問到的,fn2函數內沒有定義a,全局做用域中也沒有a使用一個不存在的變量因此報錯。code

做用域鏈blog

  做用域是能夠嵌套的好比在全局做用域裏面建立一個函數,函數裏面能夠在建立一個函數,這樣就發生了做用域的嵌套,做用域鏈能夠把做用域連接起來。當使用一個變量的時候,會優先在當前做用域內去尋找變量,若是當前做用域內不存在就會去上層做用域去尋找一直到全局做用域,若是還不能找到變量就會報錯。  作用域

var a = "global";
function fn1(){
    console.log(a);
}
fn1();

做用域是靜態的開發

  咱們先看一個例子:io

  var flag = "outer";
  function demo(){
      var flag = "inner";
      function inner(){
          console.log(flag);
      }
      return inner;
  }
  var fn = demo();
  fn();//inner
  var flag = "outer";
  function demo(){
      var flag = "inner";
      fn();
  }
  function fn(){
      console.log(flag);
  }
  demo();//outer

  經過這兩個例子咱們能夠看出函數的做用域是靜態的,一個函數無論在哪被調用,它的做用域都是聲明時的做用域。函數的做用域在聲明時就已經被建立,在調用函數時會去訪問他已經建立的做用域。

閉包

  閉包在MDN中的定義爲:閉包是指那些能夠訪問獨立變量的函數,因此在定義上咱們能夠把全部的函數都看作是閉包。閉包即密閉的空間,咱們能夠很天然的想到函數,由於函數就會生成一個密閉的空間,若是函數想稱爲一個閉包只須要在使用一個外部變量便可(使用外部變量的函數就是閉包)。經過閉包能夠給咱們帶來一些便利,就是能夠在高等級的做用域使用低等級做用域中的變量:

  function demo(){
      var flag = "test";
      return function(){
          console.log(flag);
      }
  }
  demo()();

  咱們把demo函數裏面的函數經過return使其能夠在外部使用,咱們已經說過做用域都是靜態的,這樣咱們在外部使用return的函數時,就能夠看到咱們在全局做用域中調用函數最後輸出了demo函數裏面的"test"。

  這樣咱們能夠作一些更有意義的事:

  var data = [];
  function demo(){
      var data = [];
      return{
          add:function(a){
              data.push(a);
          },
          print:function(){
              console.log(data);
          }
      }
  }
  var tool = demo();
  tool.add(1);
  tool.add(2);
  tool.add(3);
  tool.print();//[1, 2, 3]

  咱們能夠利用demo函數裏面的data來存儲咱們的信息並且不用擔憂它被破壞(demo裏面的data被私有化),並且咱們也能夠在外部在聲明一個同名的data來存儲別的信息,這兩個不會產生任何衝突。

  閉包也能夠幫咱們解決一些小問題:

  for(var i=0;i<4;i++){
      setTimeout(function(){
          console.log(i);
      });
  }

  咱們預期的結果是打印當前循環的i值結果輸出全是4。先解釋一下出現這麼狀況的緣由:JS是一種單線程的語言,而setTimeout是異步的,只有當咱們的代碼執行完成之後setTimeout的處理函數纔會執行,而執行的時候i的值已是4了因此最終的輸出全是4。

  咱們能夠經過閉包來解決這一問題:

  for(var i=0;i<4;i++){
      (function(i){
          setTimeout(function(){
              console.log(i)
          })
      }(i))
  }

  閉包能夠造成一個獨立的做用域這樣每次循環都會有一個獨立的函數做用域,循環完成後雖然i的值仍然是4可是setTimeout的處理函數在尋找i的時候會優先找到做爲參數的i,而每個參數i都表示當次循環的i,利用閉包咱們能夠完美的解決這種問題。

  在咱們實際開發的過程當中,遇到這種狀況咱們就能夠經過閉包來解決,咱們所說的"這種狀況"一般有三個特色:

  1.首先有一個循環

  2.循環裏面會建立函數,而且函數是延後執行的

  3.這些延後執行的函數會使用一個共同的變量,而且這個共同的變量和當前的循環值有關係

  咱們按照這個規律套一下上面的代碼:

  循環有了,每次循環也會生成一個函數,這些函數也都是在循環完成後才能執行,並且每個函數都使用共同的i,而i就是當前的循環值,正好符合咱們的三個特色。咱們經過(function(){}())這種方式(匿名函數自執行)來造成一個閉包達到咱們預期的目的。

  更深層次的瞭解,能夠在網上查閱相關資料。

相關文章
相關標籤/搜索