你可能会用到的
- 堆内存: 存储引用类型值所在的空间
- 栈内存: 存储基本类型值和存储代码所在空间
- 函数上下文: 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的值并未改变.这引出了闭包的第二个作用,保护全局变量.