老生常談—Javascript做用域、變量提高、閉包

淺談做用域

  當咱們新建一個能夠儲存變量的值,怎麼才能讀取到這個變量呢?能訪問到這個變量,就說明符合做用域規則,做用域規則就能夠說是js引擎讀取變量的規則。
  在js中變量分爲兩種,全局變量和局部變量,全局變量(擁有全局做用域)能夠在整個js應用中被全部代碼訪問到,從程序開始分配內存直至結束纔會被釋放(出於對代碼的性能、可讀性、以及潛在衝突考慮應該減小使用)。
  局部變量在函數中聲明的變量,函數的參數(做用域是局部性的),在函數體外,或者說的當前做用域的上層是沒法直接讀取的。
  下面再說下做用域嵌套,產生做用域鏈的事,在咱們實際編寫代碼的時候,好比:閉包

var a = 'a';
function demo(b) {
  let c = 'c';
  function print(c) {
    console.log(a + ' ' + b + ' ' + c);
  }
  print(c);
}
demo('b');

執行結果:clipboard.png函數

  在print方法執行的時候,會從當前的執行做用域開始查找變量,若是找不到,就繼續向上一級繼續查找,若是找到則會中止,若是沒有就會繼續直至當最後到達全局做用域時,此時不管找到仍是沒找到,都會結束查找。性能

st=>start: 當前做用域 
io1=>start: 上層做用域
io2=>start: ...
cond=>end: 全局做用域

st->io1->io2->cond

  (簡單寫了個流程圖方便腦補spa

  接下來仍是這段代碼讓咱們來作一個小小的修改。code

var a = 'a';
function demo(b) {
  let c = 'c',
      a = 'd';
  function print(c) {
    console.log(a + ' ' + b + ' ' + c);
  }
  print(c);
}
demo('b');

執行結果:clipboard.pngip

  結果是輸出了上一層的變量a,這也就是做用域查找的機制,做用域始終從運行時所處的最內部做用域開始,逐漸向外層查找,當遇到第一個符合條件的結果就會返回,中止查找。內存

變量提高

  先簡單上一段代碼作用域

a = 'a';
  var a;
  console.log(a);

執行結果:clipboard.png
  爲何會有這種結果呢?聲明在後,反而打印出前面聲明得值,其實js在編譯的時候會先進行聲明操做,以後再進行賦值操做,也就是隻要是在做用域中的聲明一出現,就將在代碼自己被執行前優先進行處理。也能夠將這個過程理解爲全部的聲明(變量和函數)都會被「提高」到做用域的最頂端。(雖然函數聲明和變量聲明都會被提高,可是函數優先級要高於變量開發

在ES6引入了let和const能夠用來聲明建立塊做用域。for循環頭部定義強烈建議用let,感興趣的人能夠去看看js函數做用域和塊做用域it

閉包

  提到閉包不少人腦殼裏都會感受模模糊糊的,閉包在概念上定義是可以讀取其餘函數內部變量的函數,在中,只有函數內部的子函數才能讀取這個函數局部變量,這時候前面說的做用域起到做用了,因此咱們如今能夠這樣理解,定義在一個函數內部的函數,這個函數能夠訪問其餘函數的變量,就能夠稱爲「閉包」
  下面來聊聊閉包的用處,主要就是。1.可以讀取到函數內部的變量,2.可讓變量始終保存在內存中(這點要注意後面解釋),下面上代碼。

function demo() { 
  let a = 'a';
  function print() { 
    console.log(a);
  }
  return print; 
}
var clo = demo();
clo();

執行結果:clipboard.png
  這樣咱們就在外層取得了demo函數內部局部變量的a,也就是閉包實現從外部讀取局部變量的能力。
  下面說一說讓變量始終保存在內存中這點.

function demo() { 
  let a = 1;
  addA = () => a+=1;
  function print() { 
    console.log(a);
  }
  return print; 
}
var clo = demo();
clo();
addA();
clo();

執行結果:clipboard.png
  閉包能夠阻止,demo內部做用域都被垃圾清理機制回收銷燬,能夠看到局部變量a第二次執行比第一次+1,這就說明第一次運行結束後啊,沒有被回收,由於print()仍在使用這個做用域因此demo不會被回收。
閉包會使得對應的變量一直保存在內存中,不會被清理回收,對內存消耗大,別濫用

閉包在實際開發中的使用

  1.原生的setTimeout傳遞的函數不能帶參數

//會報錯
function func(param){
  console.log(param);
}
var test = func(1);
setTimeout(test, 1000);


//經過閉包實現傳參效果
function func(param){
  return function(){
    console.log(param);
  }
}

var test = func(1);
setTimeout(test, 1000);

  2.封裝變量

相關文章
相關標籤/搜索