博客 / 詳情

返回

温故而知新篇之《JavaScript忍者秘籍(第二版)》學習總結(三)——閉包和作用域

前言

這本書的電子版我已經在學習總結第一篇已經放了下載鏈接了,可以去查看
温故而,知新篇之《JavaScript忍者秘籍(第二版)學習總結(一)——函數篇

你自律用在什麼地方,什麼地方就會成就你。要記住當你快頂不住的時候,磨難也快頂不住了。

加油吧,兄弟們


先來一個自增函數看看

var addnum= function(){
  var num=0; // 閉包內 參數私有化
  return  function(){
    return num++
  }
}
const Addnum= addnum()
Addnum()
console.log(Addnum()) // 1
console.log(Addnum()) // 2

封裝私有變量

function Ninja() {
 var feints = 0;
 this.getFeints = function() {
  return feints;
  };
  this.feint = function() {
   feints++;
  };
}
var ninja1 = new Ninja();
ninja1.feint();

通過執行上下文來跟蹤代碼

具有兩種類型的代碼,那麼就有兩種執行上下文:全局執行上下文和函數執行上下文。二者最重要的差別是:

  • 全局執行上下文只有一個,當JavaScript程序開始執行時就已經創建了全局上下文;
  • 而函數執行上下文是在每次調用函數時,就會創建一個新的。

定義變量的關鍵字與詞法環境

關鍵字var

var globalNinja = "Yoshi";  //⇽--- 使用關鍵字var定義全局變量

function reportActivity() {
  var functionActivity = "jumping";  //⇽--- 使用關鍵字var定義函數內部的局部變量

  for (var i = 1; i < 3; i++) {
     var forMessage = globalNinja + " " + functionActivity;   //⇽--- 使用關鍵字var在for循環中定義兩個變量
     console.log(forMessage === "Yoshi jumping",
         "Yoshi is jumping within the for block");  //⇽--- 在for循環中可以訪問塊級變量,函數內的局部變量以及全局變量
     console.log(i, "Current loop counter:" + i);
  }

  console.log(i === 3 && forMessage === "Yoshi jumping",
      "Loop variables accessible outside of the loop");  //⇽--- 但是在for循環外部,仍然能訪問for循環中定義的變量
  }

reportActivity();
console.log(typeof functionActivity === "undefined"
   && typeof i === "undefined" && typeof forMessage === "undefined",
   "We cannot see function variables outside of a function");  //⇽--- 函數外部無法訪問函數內部的局部變量”

這源於通過var聲明的變量實際上總是在距離最近的函數內或全局詞法環境中註冊的,不關注塊級作用域。

使用let與const定義具有塊級作用域的變量

const GLOBAL_NINJA = "Yoshi";  //⇽--- 使用const定義全局變量,全局靜態變量通常用大寫表示

function reportActivity() {
 const functionActivity = "jumping";   //⇽--- 使用const定義函數內的局部變量

  for (let i = 1; i < 3; i++) {
     let forMessage = GLOBAL_NINJA + " " + functionActivity;   //⇽--- 使用let在for循環中定義兩個變量
     console.log(forMessage === "Yoshi jumping",
         "Yoshi is jumping within the for block");
     console.log(i, "Current loop counter:" + i);   //⇽--- 在for循環中,我們毫無意外地可以訪問塊級變量、函數變量和全局變量
  }

  console.log(typeof i === "undefined" && typeof forMessage === "undefined",
      "Loop variables not accessible outside the loop");  //⇽--- 現在,在for循環外部無法訪問for循環內的變量
}

reportActivity();
console.log(typeof functionActivity === "undefined"
   && typeof i === "undefined" && typeof forMessage === "undefined",
   "We cannot see function variables outside of a function");  //⇽--- 自然地,在函數外部無法訪問任何一個函數內部的變量

與var不同的是,let和const更加直接。let和const直接在最近的詞法環境中定義變量(可以是在塊級作用域內、循環內、函數內或全局環境內)。我們可以使用let和const定義塊級別、函數級別、全局級別的變量。

在詞法環境中註冊標識符

const firstRonin = "Kiyokawa";
check(firstRonin);
function check(ronin) {
 assert(ronin === "Kiyokawa", "The ronin was checked! ");
}

先執行了check函數,後聲明。卻沒有報錯,因為什麼呢?
JavaScript代碼的執行事實上是分兩個階段進行的。

  • 在第一階段,沒有執行代碼,但是JavaScript引擎會訪問並註冊在當前詞法環境中所聲明的變量和函數。JavaScript在第一階段完成之後
  • 開始執行第二階段,具體如何執行取決於變量的類型(let、var、const和函數聲明)以及環境類型(全局環境、函數環境或塊級作用域)。

JavaScript易用性的一個典型特徵,是函數的聲明順序無關緊要。

函數重載

console.log(fun); // function
var fun =3;
function fun(){

}
console.log(fun); // 3

是函數變量提升了,但是我們需要通過詞法環境對整個處理過程進行更深入的理解。
JavaScript的這種行為是由標識符註冊的結果直接導致的。

  • 在處理過程的第2步中,通過函數聲明進行定義的函數在代碼執行之前對函數進行創建,並賦值給對應的標識符;
  • 在第3步,處理變量的聲明,那些在當前環境中未聲明的變量,將被賦值為undefined。
  • 示例中,在第2步——註冊函數聲明時,由於標識符fun已經存在,並未被賦值為undefined。這就是第1個測試fun是否是函數的斷言執行通過的原因。之後,執行賦值語句var fun = 3,將數字3賦值給標識符fun。執行完這個賦值語句之後,fun就不再指向函數了,而是指向數字3。

小結

  • 通過閉包可以訪問創建閉包時所處環境中的全部變量。閉包為函數創建時所處的作用域中的函數和變量,創建“安全氣泡”。通過這種的方式,即使創建函數時所處的作用域已經消失,但是函數仍然能夠獲得執行時所需的全部內容。
  • 我們可以使用閉包的這些高級功能:
通過構造函數內的變量以及構造方法來模擬對象的私有屬性。
處理回調函數,簡化代碼。
  • JavaScript引擎通過執行上下文棧(調用棧)跟蹤函數的執行。每次調用函數時,都會創建新的函數執行上下文,並推入調用棧頂端。當函數執行完成後,對應的執行上下文將從調用棧中推出。
  • JavaScript引擎通過詞法環境跟蹤標識符(俗稱作用域)。
  • 在JavaScript中,我們可以定義全局級別、函數級別甚至塊級別的變量。
  • 可以使用關鍵字var、let與const定義變量:
關鍵字var定義距離最近的函數級變量或全局變量。
關鍵字let與const定義只能賦值一次的變量。
  • 閉包是JavaScript作用域規則的副作用。當函數創建時所在的作用域消失後,仍然能夠調用函數。”

摘錄來自: [美] John Resig Bear Bibeault Josip Maras. “JavaScript忍者秘籍(第2版)。” iBooks.

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.