變量提升:
ECStack:
EC(G):
VO(G):
a=12;
fn=AAAFFF111 fn[[scope]]-VO(G)
1、在當前執行上下文代碼執行之前,首先會把所有帶var或者function關鍵字的聲明或者定義(帶var的只是提前聲明,帶function會提前的聲明+定義)
console.log(a); //undefined
a=12;
console.log(a);//12
代碼:
console.log(a); //undefined
fn(); //ok
var a =12;
function fn(){
console.log('ok');
}
以上就是變量提升。
ES6:
let 定義的變量,不存在變量提升。
console.log(a); //Uncaught ReferenceError: Cannot access ‘a’ before initialization
let a =12;
//不存在變量提升
現在項目中創建函數,一般都是基於函數表達式來實現,這樣防止其提前變量提升:推薦使用這種方式。
let fn = function(){
console.log('hello world')
}
fn();
demo:
fn(); //5
function fn() {console.log(1)};
fn();//5
function fn(){console.log(2)};
fn();//5
var fn = function (){
console.log(3); //變量提升時只聲明,沒賦值,在此賦值AAAFFF333
}; //表達式的方式不會在變量提升時定義
fn();//3
function fn() {console.log(4)};
fn();//3
function fn(){console.log(5)};
fn();//3
以上代碼解析:
EC(G):
VO(G):
聲明變量fn,並賦值爲 fn = AAAFFF111;
=AAAFFF222; //不需要重新聲明,只賦值
=AAAFFF444; //不需要重新聲明,只賦值
=AAAFFF555;(變量提升階段完成,fn=AAAFFF555;)
代碼執行:
值爲555333;
2、全局變量對象VO(G)中聲明的變量(用var聲明的),也會給全局對象GO中增加一個對應的屬性,但是用let聲明的變量不存在這個特點。
var x =12;
console.log(window.x) //12
let x = 12;
console.log(window.x) //undefined
function fn(){
//EC(FN):
//AO(FN):
// x=100;
var x =100;
console.log(x)//undefined 僅限於全局有創建全局變量也相當於給全局對象設置屬性有這個特點,私有的執行上下文中就是私有變量
}
fn();
function fn() {
//此時的x不是AO(FN)中的私有變量,則向全局找,此處相當於給全局VO(G)變量對象中設置了一個x的全局變量,也相當於給全局對象GO 設置了一個x的屬性。
x =100;
}
fn();
console.log(window.x) //100
Node下執行,不存在設置全局屬性。
代碼執行階段:
1、編譯階段(編譯器):
詞法解析 => AST抽象語法樹(給瀏覽器引擎運行)
2、引擎執行:
ECStack => EC(G) => VO(G)…
3、帶var的是可以重複聲明(詞法解析可以審覈過),執行階段遇到聲明過,不會再重複聲明,只是重新賦值。但let 不同,若遇到重複聲明,詞法解析階段過不去,會直接報錯,不存在引擎去執行代碼階段。
var x = 12;
var x = 13;
console.log(x); //13
let:
let x = 12;
let x = 14;
console.log(x); //Uncaught SyntaxError: Identifier 'x' has already been declared
//10MS後連續輸出5個5
for (var i = 0 ; i<5; i++){
//定時器是異步操作:不用等定時器到時間,繼續下一輪循環。所以最後i=4,i++後變成5
setTimeout(_ => {
//在此的i不是私有的,向上級查找,全局的i在未執行setTimeout操作時已經變成了5
console.log(i)
},10);
}
//想要10MS後輸出01234
for (var i = 0 ; i<5; i++){
//每一輪循環都執行自執行函數,形成全新的執行上下文EC
//並且把每一輪循環的全局i的值,當做實參賦值給私有上下文中的私有變量i(形參變量)
//10MS定時器觸發執行,用到的i都是私有EC上下文中的保留下來的i,用到的就是閉包機制(保存,保護)
//setTimeout中的內容“_ => {console.log(i)}”屬於私有堆,被全局的window.setTimeout佔用,不能銷燬
//這麼處理是不好的,因循環多少次就形成多少個不銷燬的EC,棧內存就那麼大,裏面上下文越多,空間就越小,可執行上下文就越少
~ function (i){ //自執行函數中的i是私有的
/*EC(自執行):
*AO(自執行)
* i = 0~4
* 創建一個匿名函數_=>{...}AAAFFF000
* AAAFFF000[[scope]]:AO(自執行)
* window.setTimeout(AAAFFF000,10);
*/
setTimeout(_ => {
/*
*EC(AAAFFF000):
* AO(AAAFFF000) [[scopeChain]]:<AO(AAAFFF000),AO(自執行)>
*/
console.log(i)
},10);
}(i)//將全局的i當做實參傳進去
}
因閉包寫法不好理解性能不太好,佔用棧的內存,執行速率會慢,在項目中應減少閉包的使用。
可以使用let
let存在塊級作用域,var沒有
for(let i = 0; i<5; i++){
setTimeout(_ =>{
console.log(i)
},10)
}
執行過程:
{
// => 父塊作用域
let i = 0;
//第一次循環
{
// => 子塊作用域
let i = 0 ; //從父級傳過來
setTimeout(_=>{console.log(i)},10);
}
i++; //i=1
//第二輪循環
{
// => 子塊作用域
let i = 1;
setTimeout(_=>{console.log(i)},10);
}
// ...
}
for(let i = 0; i<5; i++){
setTimeout(_ =>{
console.log(i)
},10)
}
console.log(i) //VM53229:6 Uncaught ReferenceError: i is not defined 用來累計的i也是父塊作用域中的i,也不是全局的,全局還是不能用
塊級作用域:
if(1===1){
let x =100;
console.log(x)
}
console.log(x) ; //Uncaught ReferenceError: x is not defined
let a =100;
switch (a) {
case 100:
let x=200; //沒有break,繼續往下走
case 200:
//let x = 300; //Uncaught SyntaxError: Identifier ‘x’ has already been declared同一個塊下不能重複定義聲明
break;
}
console.log(x) //VM54324:8 Uncaught ReferenceError: x is not defined switch的case會形成塊作用域。
try {
let x = 100;
console.log(x); //100
console.log(a);
} catch (e) {
let y =200;
console.log(y) //200
}
console.log(x); //Uncaught ReferenceError: x is not defined try中是塊級作用域
console.log(y);//Uncaught ReferenceError: y is not defined catch中也是塊級作用域
除了對象的{},剩下的{}都是塊作用域
console.log(typeof a) //undefined JS的暫時性死區(暫時沒解決的BUG)
console.log(typeof a); //Uncaught ReferenceError: Cannot access ‘a’ before initialization
let a;
let 和const區別:
let創建的變量,可以更改它的指針指向的(也就是可以重新賦值)。但是const聲明變量是不允許改變指針指向的。
let x =100; //創建變量x,創建值100,然後進行關聯,將變量x指針指向100
x = 200;
console.log(x); //200
const y =100;
y =200;
console.log(y); //Uncaught TypeError: Assignment to constant variable.
計算機底層賦值,包括堆和棧裏面的變量都是通過指針賦值的。