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

哦哈哈發表於2020-11-12

前言

這本書的電子版我已經在學習總結第一篇已經放了下載連結了,可以去檢視
溫故而,知新篇之《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.

相關文章