你可能會用到的
- 堆內存: 存儲引用類型值所在的空間
- 棧內存: 存儲基本類型值和存儲代碼所在空間
- 函數上下文: JS每一個函數在執行的時候都會創建一個執行上下文
1. 堆內存中的數字和字符串都是相等的
let a = {}, b='0', c=0;
a[b] = 'marron';
a[c] = 'Mar'
console.log(a[b]) // Mar
- 第一行代碼, a創建是一個對象,對象在JS中是引用類型,因此會創建一個堆內存來存儲對象
// 堆: AAAFFF00
(此時里面是空的)
- 此時a的值實際上是指向這個堆的地址,即
A = AAAFFF00
- 在執行
a[b] = 'marron'
時,實際上會給堆內存中鍵為’0’賦上值 ‘marron’
// 堆: AAAFFF00
'0': 'marron'
- 在執行
a[c] = 'Mar'
時,由于堆中字符串和數字默認是相等的,此時堆內存中實際的操作是:
// 堆: AAAFFF00
+ '0': 'Mar'
- 因此最后輸出的回收’Mar’
2.對象作為值在堆內存中都會隱式調用toString方法,變為字符串
let a = {},b = {n: '1'},c = {m: '2'}
a[b] = 'marron'
a[c] = 'Mar'
console.log(a[b]); // 'Mar'
// 在堆內存中都是 { '[object object]': 'Mar' }
- 執行
a= {}
時,
// 堆: AAAFFF01
- 此時
a = AAAFFF01
- 執行
a[b] = 'marron'
- 會先隱式調用
b.toString()
,然后將得到的結果存放到 堆AAAFFF01中
// 堆: AAAFFF01
'[Object Object]': 'marron'
- 執行
a[c] = 'Mar'
,同理
// 堆: AAAFFF01
'[Object Object]': 'Mar'
- 因此,最后會輸出 ‘Mar’
3. 閉包問題
var test = (function(i){return function(){alert(i *= 2)}
})(2)
test(5)
3.1 需要了解的
- 函數上下文: JS中每一個函數在執行的時候都會創建一個執行上下文
- 堆內存: JS中每一個引用類型的操作,都對應一個堆內存
3.2 解析
var test = (function(i){...})(2)
,等號右邊是一個自執行函數.執行函數的時候會創建一個執行上下文
// 自執行函數的執行上下文
i = 2;
return function(){}
- 遇到
return function(){}
中的function是一個引用類型,故會創建一個堆內存
// 堆: AAAFFF00
"alert(i *=2)"
...
- 然后將堆內存的地址返回,此時堆內存的上一級作用域是自執行函數的執行上下文
// 自執行函數的 執行上下文
i = 2;
return AAAFFF00
- 此時test的值是堆的內存地址:
test = AAAFFF00
- 之后遇到了 test(5),函數執行會創建一個執行上下文
// test(5)的 執行上下文
-> 堆: AAAFFF11
- 然后順著地址去找到堆AAAFFF11,找到堆AAAFFF11之后,遇到函數代碼字符串.
alert( i *= 2)
,
// test(5)的 執行上下文
"alert( i*= 2)"
-
由于當前堆中沒用i的值,會順著作用域鏈,往上級作用域尋找,找到了 自執行函數的上下文.然后回彈出字符串 “4”,同時堆內存中i的值被改成了4
-
完畢之后,由于
test(5)的執行上下文
中沒用變量被引用,會根據JS的垃圾回收機制,進行銷毀. -
而
自執行函數的 執行上下文
中的變量i被堆AAAFFF11引用,會一直存在,因此形成了閉包.
4. 閉包小練手
var a = 0,b = 0;
function A(a){A = function(b){alert(a + b++);};alert(a++)
}
A(1);
A(2);
- 首先有個全局作用域
// global
a = 0;
b = 0;
A = 堆: FFFAAA00
ctx:A(1)
ctx:A(2)
- 執行到
A(1)
時
// ctx: A(1)
a(局部) = 1
A(全局) = 堆: FFFAAA01
alert(a++) // 會彈出'1',此時局部a = 2
// 由于a被堆: FFFAAA01 引用,因此結束時, ctx: A(1)不會被清除,形成了閉包喲.
A(1)
執行完畢,此時全局作用域
// global
a = 0;
b = 0;
A = 堆: FFFAAA01
ctx: A(2) <-- 執行到這一行
A(2)
開始執行
// ctx: A(2) 傳入參數由b接收
a(ctx(A(1))) = 2;
b(局部) = 2;
alert(a + b++); // 彈出'4', 然后局部b = 3
// 完畢后,作用域銷毀
// 注: A此時執行的是堆: AAAFFF01,堆并未消失
- 綜上所述,會彈出’1’,‘4’
說明: 上面執行了2次A函數,且分別用到了a , b變量…但是在對a,b變量操作完成后.全局變量的a和b的值并未改變.這引出了閉包的第二個作用,保護全局變量.