552 let、const、var及其區別,變數提升,前端程式碼中的上下文(作用域),迴圈中的 IIFE、塊級作用域,迴圈繫結事件的優化
阿新 • • 發佈:2020-10-21
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>