一、堆栈内存
example1
let a = {},
b = '0',
c = 0;
a[b] = '珠峰';
a[c] = '培训';
console.log(a[b]); //培训
解析:答案是培训
。对于引用类型,它的处理方案一定是堆内存来处理的
创建了变量a,给a赋的是一个空对象,对象是引用类型值,得是一个堆内存。
- 堆内存——用来存储引用类型值的空间
- 栈内存——存储基本类型值,执行代码的空间
a
会指向一个16进制内存地址,右侧框框表示一个堆内存。
b='0', c=0,
都是正常声明变量并赋值,
a['0'] = '珠峰'
,此时在堆内存地址中放入'0' : 珠峰
,
又由于堆内存中,属性名是不可以重复的,且数字属性名0与字符串属性名’0’是一样的,于是 a[0] = '培训'
, 会覆盖掉珠峰
,所以a[b]
也就是 a[0]
的结果是培训
。
本小问补充提问:JS中对象和数组的区别
example2
let a = {},
b = Symbol('1'),
c = Symbol('1');
a[b] = '珠峰';
a[c] = '培训';
console.log(a[b]); //珠峰
解析:有了example1题目的讲解之后,这题我们主要就是要讨论:
对于a这个引用类型对象而言,b和c是不是同一个属性名
此处的b = Symbol('1')
和c = Symbol('1')
看起来是一样的,
但是要知道:
Symbol是用来创建唯一值的
Symbol('1') === Symbol('1') //false
那既然b与c不是同一个属性名,显然a[b]
就是'珠峰'
注:对象的属性名不一定只能是字符串。比如可以是Symbol这种,还可以是布尔值、null、undefined。但一定要注意,数字和字符串数字属于一样的。
可以看到,a是有两个“看起来一样”的属性名和值的。
本小问补充提问:自己实现Symbol
example3
let a = {},
b = { n:'1' },
c = { m:'2' };
a[b] = '珠峰';
a[c] = '培训';
console.log(a[b]); //珠峰
解析:对象引用类型值,都要存成字符串。
let obj = {};
let n = { name: 'xxx'}
obj[n] = 100
dir(obj)
--> [object object] : 100
------------------------------
/*引用类型值存储的时候,都会直接默认调用toString方法,
调用toString方法后就变成"[Object Object]"了*/
({name:'xxx'}).toString() //"[Object Object]"
({age:12}).toString() //"[Object Object]"
那么对于这道题中的b = { n:'1' }
和 c = { m:'2' }
,在a中都会存成 "[Object Object]"
,那么显然,培训
会把珠峰
覆盖掉.
a[b]
的值是 培训
本小问补充提问:
关于Object.prototype.toString()它的项目中的应用
valueof原始值 与 toString() 的区别
二、闭包作用域
example1
遇到会的题不要马虎,‘4’ 和 4是不一样的
注意!!!alter弹出来的结果,都要toString()
转成字符串。 console.log()是正常的,是啥就是啥。
比如 alert({});
var test =
(function(i){
return function () {
alert ( i *= 2 );
}
})(2);
test(5); // 字符串'4'
解析: var test = 一个立即执行的自定义函数
浏览器一加载界面就形成了栈内存,而我们每个函数执行,叫,把一个执行上下文压缩到栈内存去执行。每次函数执行,都会形成一个全新的执行上下文(ECstack——Execution Context Stack,执行环境栈)
图解描述:
(function(i){ ... })(2);
这个立即执行函数,会形成一个执行上下文,里面存着形参i- 接下来代码开始执行,return了一个
function(){ alert(i*=2) }
这样的函数,而函数也是引用类型的,而引用类型是由堆内存存储的。 - 存储这个函数的堆,有一个16进制的内存地址,假设为AAAFFF111。 作为函数,在堆中存储代码(字符串)。对象堆里存的都是键值对,函数也是对象,所以这些东西都是作为键值对存储的。
- 所以return到的仅仅是, 函数所存储的堆的内存地址AAAFFF111
- 于是 ,当前
function(){ alert(i*=2) }
这个函数,它的上级作用域,就是(function(i){ … })(2);`这个函数的执行上下文所在的作用域。这样就产生了作用域链这样的概念。 - 所以
(function(i){ ... })(2);
这个自执行函数的返回结果是一个堆内存的地址AAAFFF111。 - 所以
var test = AAAFFF111
- test(5); 函数执行,又会形成一个执行上下文,压栈执行。
- test是哪个函数呢,test其实是
function(){ alert(i*=2) }
这个函数,而这个函数是没有形参的,则不需要形参赋值。 - 但此时这个蓝色的执行上下文中没有
i
, 而这个执行上下文又是隶属于AAAFFF111这个函数的,所以去这个函数的上级作用域去找,找到i=2
. - 所以结果alert(i*=2)是’4’
销毁逻辑:
- 这个执行上下文执行完了之后,这里面的东西没有被其他任何人占用,所以它要销毁。
- 这个就不能被回收,因为它还是AAAFFF111那个函数的上级作用域,如果它没了,绿色的堆内存也就没了,那个函数也就不存于堆内存了。但这个函数又被
var test
占住了,它不能够消失,所以这个执行上下文不能被销毁。
这就是整个的闭包机制, 自执行函数形成的上下文叫闭包的上下文。
闭包是叫:内存不销毁。(不是内存泄漏:内存泄漏是指该销毁的都没销毁,而闭包的不销毁是本就不能销毁)
example2
var a = 0;
b = 0;
function A(a) {
A = function(b) {
alert( a + b++ );
};
alert(a++);
}
A(1); // '1'
A(2); // '4'
图解描述:
-
全局的对象(GO)
- a = 0
- b = 0
- 函数A,存于一个堆内存 记为 A = AAAFFF000
函数堆内存里存着代码字符串A = function(b) { alert( a + b++);}
-
A(1) ; 执行, 形成一个执行上下文,而函数A里含有另一个
function(b) = alert( a + b++ ); }
-
这个函数又形成了一个堆,内存地址记为BBBFFF000,这个内存里存着
alert( a + b++ )
这个代码字符串 -
在A(1) 的执行上下文里,A = BBBFFF000
-
而A(1) 的执行上下文的上级作用域是全局作用域,里面有了一个A = AAAFFF000,所以全局的A会被覆盖为BBBFFF000
-
此时A的指向就变了,A指向BBBFFF000这个地址存的
function(b) = alert( a + b++ ); }
这个函数了。重写了全局方法指向。 -
A(1) 继续执行,
alert(a++)
先输出结果’1’, 再自增为2,并覆盖掉A(1) ECStack里原有的 a = 1, a目前为2
- A(2) ; 执行, 形成一个执行上下文,注意!!此时A还是
function(b) = alert( a + b++ ); }
这个函数。 - 形参b = 2,
- alert(a+b++) 输出’4’ 这个结果,b再自增为3
- ==> a + b = 2+2 = 4
- ==> b++ , b变为3
- 而此时,全局的a,b不会受到任何影响,还是0,0; 其实全局的a,b完全没参与这一场闭包哈哈哈
- 所以,闭包/函数执行的作用除了保存,还有一个作用就是保护