1. 程式人生 > 實用技巧 >552 let、const、var及其區別,變數提升,前端程式碼中的上下文(作用域),迴圈中的 IIFE、塊級作用域,迴圈繫結事件的優化

552 let、const、var及其區別,變數提升,前端程式碼中的上下文(作用域),迴圈中的 IIFE、塊級作用域,迴圈繫結事件的優化

let、const、var及其區別,變數提升

程式碼獲取到後:

詞法解析(AST):把程式碼拆成對應的字元,並且識別成瀏覽器可以解析的物件。

上下文 --> 【初始化】作用域鏈、【初始化】this、形參賦值......【最後】變數提升 --> 程式碼執行

/*

  • JS中宣告變數或者函式的方式
  • 【傳統】
  • var n = 10;
  • function func(){} -> var func=function(){};
  • 【ES6】
  • let n = 10;
  • const m = 20;
  • let func = () => {};
  • import xxx from 'xxx';
    */

/*
const設定的是常量,儲存的值不能被改變 【不對】
const建立的變數,它的指標指向一旦確定,不能再被修改【正確】
let設定的是變數,儲存的值可以改變 【正確】
*/

const n = 10;
n = 20; // => Uncaught TypeError: Assignment to constant variable.
console.log(n);



const obj = {
  name: 'xxx'
};
obj.name = "哈哈嘿嘿";
console.log(obj);  // =>  {name:"哈哈嘿嘿"}



// ------------------------------



/*
 * let 和 var 的區別?
 *    =>  let不存在變數提升
 *    =>  let不允許重複宣告
 *    =>  let會產生塊級作用域
 *    =>  暫時性死區的問題
 */

// 變數提升:在當前上下文程式碼自上而下執行之前,會把所有帶var/function關鍵字的進行提前的宣告或者定義(帶var是隻宣告,帶function是宣告+定義(賦值)都完成了)
/*
 * EC(G)
 *   變數提升: var a;  func=AAAFFF000;
 *   程式碼執行:
 */
console.log(a); // => undefined
func(); // => "OK"
var a = 12;
function func() {
  console.log('OK');
}


// ------------------------


/*
 * EC(G)
 *    變數提升:--
 *    程式碼執行
 */

console.log('STRAT');
console.log(a); // => Uncaught ReferenceError: Cannot access 'a' before initialization 程式碼執行中遇到輸出a,檢測到下面有基於let宣告的操作,則提示不允許在宣告之前使用這個變數
func();
let a = 12;
let func = () => {
  console.log('OK');
};



// ------------------------


// Uncaught SyntaxError: Identifier 'a' has already been declared 重複宣告的檢測和報錯,不是發生在程式碼執行階段,發生在詞法解析階段(不論基於什麼宣告的變數,只要上下中有這個變數,都不能再基於let重複聲明瞭)
console.log('START');
var a = 12;
let a = 13;
console.log(a);


// ------------------------



/*
 * 暫時性死區(瀏覽器的BUG)
 * 《深入理解ES6》:使用 let 或 const 宣告的變數,在達到宣告處之前都是無法訪問的,試圖訪問會導致一個引用錯誤,即使在通常是安全的操作時(例如使用 typeof 運算子),也是如此。
 */
console.log(a); //Uncaught ReferenceError: a is not defined
console.log(typeof a); // => 檢測一個未被宣告的變數,不會報錯,結果是"undefined"
typeof window !== "undefined"  // => 說明當前環境下存在window(瀏覽器環境) JQ原始碼中也是基於這樣的方式處理的

console.log(typeof a); // => Uncaught ReferenceError: Cannot access 'a' before initialization
let a;

前端程式碼中的上下文(作用域)

/*
 * 前端程式碼中的上下文(作用域)
 *    1. 全域性上下文
 *    2. 函式執行形成的私有上下文
 */

var a = 12;
if (1 == 1) {
  console.log(a); // 12
  var a = 13;
  console.log(a); // 13
}
console.log(a); // 13



// ------------------------


// 如果程式碼塊中出現了 let、const、function,則當前程式碼塊會產生一個 塊級上下文(詞法、塊級作用域)  =>  私有的上下文
let a = 12;
if (1 == 1) {
  // console.log(a); // => Uncaught ReferenceError: Cannot access 'a' before initialization
  let a = 13;
  console.log(a); // 13
}
console.log(a); // 12


// ------------------------


// 即使混在一起,跨級作用域只對let、const、function生效,對var不生效
var n = 12;
let m = 13;
if (1 == 1) {
  var n = 120;
  let m = 130;
  console.log(n, m); // 120 130
}
console.log(n, m); // 120 13



// ------------------------



// 迴圈
for (var i = 0; i < 5; i++) {
  // i 都是全域性的
}
console.log(i); // => 5

for (let i = 0; i < 5; i++) {
  // 私有的塊級上下文
  // 迴圈幾次會產生幾個塊級上下文 【上下文是平級的,不巢狀。】
}
console.log(i); // => Uncaught ReferenceError: i is not defined

迴圈中的 IIFE、塊級作用域

// 設定5個1s後執行的定時器
for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
// i是全域性變數
// 第一輪迴圈  i=0  設定定時器 1000MS => {} 【任務佇列】  i++
// 第二輪迴圈  i=1  ...
// 迴圈結束 i=5
// -----
// 達到時間後,依次把任務佇列中的五個定時器到時候後要做的事情去執行
// ()  =>  { console.log(i); }  i不是自己私有上下文中的變數,則找其上級上下文(全域性),但是此時全域性的 i = 5


// ------------------------


// 設定5個1s後執行的定時器
for (var i = 0; i < 5; i++) {
  // 每一輪迴圈:把自執行函式執行
  // EC(AN1) [不會釋放]
  // 作用域鏈:<EC(AN1),EC(G)>
  // 形參賦值:i=0
  (function (i) {
    // 設定一個定時器(非同步任務:任務佇列)
    setTimeout(() => {
      console.log(i);
    }, 1000);
  })(i);
}


// ------------------------


function func(i) {
  return function anonymous() {
    console.log(i);
  }
}

for (var i = 0; i < 5; i++) {
  // 第一輪迴圈:i=0  設定定時器的時候,把func函式執行,傳遞0進去,把返回的匿名函式anonymous設定給定時器  這樣func執行形成的上下文是不被釋放的(形參i=0也是不釋放的)
  setTimeout(func(i), 1000); // 執行func(i)返回函式anonymous
}
// 1000MS後執行的是繫結的anonymous 


// ------------------------


for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}

迴圈繫結事件的優化

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
  <title>哈哈哈</title>
  <!-- IMPORT CSS -->
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    html,
    body {
      height: 100%;
      overflow: hidden;
    }

    button {
      padding: 5px 10px;
      cursor: pointer;
    }
  </style>
</head>

<body>
  <button value="pink">紅</button>
  <button value="yellowgreen">綠</button>
  <button value="skyblue">藍</button>
</body>

</html>
<script>
  // 補充:var 和 function 的提升順序
  // 結論:先var a = undefined;然後 a = 函式a的地址 0x111
  console.log(a) // 函式a
  var a = 11
  function a() {
    console.log(22)
  }

  console.log(b) // 函式b
  function b() {
    console.log(66)
  }
  var b = 55
</script>

<script>
  // 基於事件委託實現多元素的事件繫結,要比傳統迴圈一個個的給元素進行事件繫結,效能提高40%~60%
  document.body.onclick = function (ev) {
    var target = ev.target;
    if (target.tagName === "BUTTON") {
      // this -> body
      this.style.background = target.value;
    }
  };


  // ------------------------------


  var body = document.querySelector('body')
  var buttons = document.querySelectorAll('button')
  var arr = ['pink', 'yellowgreen', 'skyblue']

  for (var i = 0; i < buttons.length; i++) {
    var item = buttons[i];
    item.myIndex = i; // => 在迴圈的時候,把每一個按鈕的索引賦值給當前按鈕(元素物件)的myIndex自定義屬性
    item.onclick = function () {
      // this  =>  當前點選的這個按鈕
      body.style.background = arr[this.myIndex];
    };
  }



  // ------------------------------


  // 都是利用閉包的機制去解決的
  for (var i = 0; i < buttons.length; i++) {
    buttons[i].onclick = (function (i) {
      // IIFE中必須 return一個函式,作為IIFE的返回值
      return function anonymous() {
        body.style.background = arr[i];
      };
    })(i);
  }


  // ------------------------------


  for (let i = 0; i < buttons.length; i++) {
    buttons[i].onclick = function () {
      body.style.background = arr[i];
    };
  }
</script>