参考视频:https://www.bilibili.com/video/BV1xf4y1R7AH
一、小试牛刀
先来回顾一下JS的两种数据类型:基本数据类型和引用数据类型。
- 基本数据类型:number 、null、boolean、string、undefined、symbol(es6)
- 引用数据类型:object(array)、function
两道小题感受一下
(1)基本数据类型
题:
let a = 22;
let b = a;
b = 33;
console.log(a); //打印22
解析:
先创建基本类型的值22(存在栈内存当中),然后再赋值给a(将a和22关联在一起)。
=
的作用:先创建值,接着创建变量,最后将变量和值关联。
(2)引用数据类型
题:
let a = {
n:12};
let b = a;
b['n'] = 13; //b通过地址找到对应的堆内存,把这个地址的值修改
console.log(a.n);
解析:
先创建一个对象,是引用类型的值(不能存在栈里面,要单独开辟一个堆内存,使用地址去寻找它),把这个地址放到栈里边,供变量来调用。
总结:基本数据类型是存到栈里边直接进行操作,而引用数据类型则是存在堆里面,并提供一个地址供栈内的变量去寻找。
二、大餐
闭包
:当前函数执行,形成一个私有的上下文,函数执行完,当前私有上下文中的某些内容,被上下文以外的内容所占用,那么当前上下文就不能被释放 ==> 闭包。
闭包的作用
:保护和保存。
let a = 0,b = 0;
function A(a){
A = function (b) {
console.log(a + b++);
};
console.log(a++);
}
A(1); //结果:1
A(2); //结果:4
图解:
解析:
- 先有全局执行上下文EC(G),然后有全局变量的环境VO(G),a,b,function A都属于全局的变量。
- 给a、b创建基本数据类型值并赋值,但
A
是个函数,函数是引用类型的值,所以会开辟一个函数堆,有个地址AAAFFF000,然后将这个地址和函数A
关联在一起。 - 创建A函数的时候会声明一个作用域 [[scope]]:EC(G)(在全局作用域下),形参是a,里面有代码字符串…
- A(1)执行。函数执行就是把函数里面的代码执行。那每个函数的执行都会形成一个全新的私有上下文(环境EC(A1))。
AO(A1)
是私有变量对象。函数执行会先初始化作用域链:<EC(A1),EC(G)>
(左边是自己的上下文,右端是A这个函数创建时所在的作用域即为全局作用域EC(G))。在代码执行过程中遇了一个变量,首先看是否是自己的私有变量,如果是自己的私有变量则找私有的,否则就找其函数所在的作用域EC(G)中的全局变量。此时的进行形参赋值 a=1(形参变量也是私有变量,让a和1进行关联)。让代码执行,A = function(b){....}
(这时要创建一个新函数堆内存(BBBFFF000),声明作用域:[[scope]]:EC(A1)。形参是b,代码字符串"console.log(a+b++)"),因为A不是EC(A1)私有的,故去EC(G)里面找,那么就要修改原来A对应的函数地址(将AAAFFF000改为BBBFFF000,即把全局下的A修改为BBBFFF000)。接着执行console.log(a++)
(先输出a=1,然后再++,即做完后修改a的值为2,这里的a是EC(A1)里面的)。执行完毕。(此时EC(A1)不能被释放,因为全局中A的地址为BBBFFF000,占用了EC(A1)中的内容,这就形成了闭包) - A(2)执行。此时的A对应的为BBBFFF000,也就是要进行那个小函数的执行(即执行
console.log(a + b++)
)。这时又会形成一个全新的私有上下文EC(A2)。作用域链:<EC(A2),EC(A1)>
。形参赋值:b=2。代码继续执行:a + b++
,因为当前上下文中没有a,故往上级上下文EC(A1)找,有a,此时a=2。b在自己的上下文EC(A2)中有,所以b=2,故a+b++=4,然后b++,修改当前上下文中的b为3。执行完毕。(因为执行完后,这个EC(A2)上下文中没有东西被外部东西占用,所以默认就会被释放掉)
三、总结
自己的错误点:
A = function (b) {
console.log(a + b++);
};
会把上面那个代码看成是立即执行,其实并不是。老是犯这个错误,谨记!!!