闭包
js在开始运行一段代码时,会在虚拟内存(运行内存)中开辟一个栈内存(stack)和一个堆内存(heap),堆内存默认开辟一个空间用来存放js的全局对象window。而在栈内存中默认开辟一个空间---全局执行上下文栈 [EC(G)] .用来存放全局变量[VO(G)]和运行代码。(使用let、const声明的变量会放在VO(G)中,而使用var定义的会放在存放window的堆空间中)。
在定义一个引用内存时,会先在堆空间中开辟一个空间,这个空间主要用来存放代码和值。如果是函数,还会自动添加一个变量[scope]:xxx,用于存放作用域。(在哪里定义的,作用域就是谁,比如在全局执行上下文栈中定义的,那么它的[scope]就是[EC(G)])...
然后每执行一个函数或者执行“块级上下文”的代码时会在栈内存中开辟一个函数执行上下文[EC(fn)]和块级执行上下文[EC(block)]。然后在这个执行上下文中会首先定义开辟一个地方用来存放变量[VO(Fn)],然后是存放作用域链、参数赋值、变量提升、执行代码、return、释放空间。但是如果存在闭包了,这个空间就不会被释放。
而闭包就是:
- 函数执行产生一个私有上下文,首先这里的私有变量会被保护起来,不受外界的干扰!!
- 而且如果上下文不被释放,则里面的私有变量也会被"保存"下来! 这样其下级上下文就可以操作(访问或修改)这些值了!我们吧这种“保护” + “保存” 的机制称之为闭包。
垃圾回收
堆内存:看当前堆内存空间的地址,是否被别的东西占用
- 如果没有被占用:浏览器会在空闲的时候,释放这个堆
- 如果被占用,则不能被释放
fn = null 清除之前的引用(占用),手动释放无用的内存
两个方法:引用计数、标记清除
栈内存:
-
EC(G)打开页面的时候形成,也只有页面关闭才会释放
-
函数/块级私有上下文:
-
正常情况下,代码执行完,产生的私有上下文会被释放掉
-
特殊情况:如果上下文中创建的某个东西(一般指的是函数),被上下文以外的事物占用了,则不仅这个东西不能被释放,而且当前私有上下文也不能被释放!!
this
我们研究的this,都是研究函数私有上下文中的THIS:
- 因为全局上下文中的this -> window
- 块级上下文中没有自己的this,在此上下文中遇到的this,都是其所处环境(上级上下文)中的this
- ES中的箭头函数和块级上下文类似,也是没有自己的this,遇到的this也是其上级上下文中的
THIS是执行主体:通俗来讲,谁把它执行的,而不是在哪执行,也不是在哪定义的,所以this是谁和在哪执行以及在哪定义都没有直接的关系;想搞定THIS,我们可以按照以下总结的规律去分析!
- @1 给DOM元素进行事件绑定(不论是DOM0还是DOM2),当事件行为触发,绑定的方法执行,方法中的THIS是当前DOM元素本身。
- @2 当方法执行,我们看函数前面是否有“点”
- 有:“点”之前是谁,THIS就是谁
- 没有:THIS就是Window(非严格模式)或者undefined(严格模式 “use strict”)
- 匿名函数(自执行函数或者回调函数等)中的THIS一般都是window/undefined
const fn = function(){
console.log(this)
};
let obj = {
name:'zhufeng',
//fn:fn
fn
}
fn() //window
obj.fn() //obj
// 自执行函数:创建完立即执行
// ~function(){}()
// !function(){}()
// +function(){}()
(function(x){
console.log(this) //window / undefined
})(10);
// 回调函数:把一个函数作为实参值,传递给另一个函数 【在另外一个函数中,把其执行】、
const fn = function(callback){callback()};
fn(function(){})
setTimeout(function(){
console.log(this) // window
},1000)
let arr = [10,20];
let obj = { name:'ly' };
arr.forEach(function(item,index){
//console.log(item,index)
console.log(this); // window/undefined
})
arr.forEach(function(item,index) {
console.log(this) //obj
},obj) //forEach([回调函数],[修改回调函数中的THIS])
题目1
var x = 3 ,
obj = { x:5};
obj.fn = (function(){
this.x *= ++x;
return function(y) {
this.x *= (++x) + y;
console.log(x);
}
})();
var fn = obj.fn;
obj.fn(6);
fn(4);
console.log(obj.x,x)
// 13
// 234
//95 234
解题步骤:
创建全局上下文栈EC(G):
-
EC(G)中有VO(G)用于存放变量
-
存放代码
-
执行代码
-
在VO(G)中创建值3,再创建变量x,赋值
-
创建对象堆区 ,地址为0x001,其中有键值对:x -> 5
-
立即执行函数
-
创建堆区0x002,作用域[[scope]]:EC(G)
-
指向函数0x002,
-
创建函数上下文 EC(AN1)
-
创建AO(AN1),用于存放该函数上下文中的变量以及值
-
创建作用域链:<EC(AN1),EC(G)>
-
参数变量赋值、变量提升
-
执行代码
this.x *= ++x; return function(y) { this.x *= (++x) + y; console.log(x); } -
查找this,由于是匿名函数,this指向window,this.x就是window. x --> 3
-
this.x *= ++x -> this.x = this.x * (++x) -> this.x = 3 * 4 -> this.x = 12 -> window.x = 12
-
所以EC(G)中VO(G)中的x -> 12
-
return 的是函数
-
开辟函数堆0x003
- 作用域[[scope]]:EC(AN1)
- 存放代码
-
this.x *= (++x) + y; console.log(x);
-
-
return 0x003
-
-
代码执行完毕,给0x001对象堆中添加键值对:fn -> 0x003
-
-
执行代码
var fn = obj.fn(省略了变量提升) -
给EC(G)中的VO(G)添加了键值对 fn -> 0x003
-
执行
obj.fn(6)-
obj.fn --- > 0x003
-
开辟函数上下文 EC(AN2)
-
开辟AO(AN2)
-
作用域链 <EC(AN2),EC(AN1)> (上一级的查看0x003的[[scope]])
-
参数赋值 : 存放在AO(AN2) y -> 6
-
执行代码(代码存放在0x003中): this.x *= (++x) + y;
- this.x指的是obj,查看对象obj堆中是否有x,发现有x,x = 5
- this.x = 5 * ((++x) + y);
- 查看AO(AN2)中有没有x,没有,沿着作用域链去上一级上下文(EC(AN1))找。
- 在AO(AN1)中也没找到,继续沿着EC(AN1)函数上下文的作用域链往上找(EC(G))
- 发现在VO(G)中有x,x=12
- 执行 ++x,EC(G)的VO(G)中的x被修改,x=13
- 查看AO(AN2)中有没有y,发现有y,值为6
- this.x = 5 * (13 + 6) -> this.x = 5*19 -> this.x = 95 -> obj.x = 95
-
执行consoel.log(x)
- 查看AO(AN2)中有没有x,没有,沿着作用域链去上一级上下文(EC(AN1))找。
- 在AO(AN1)中也没找到,继续沿着EC(AN1)函数上下文的作用域链往上找(EC(G))
- 发现在VO(G)中有x,x=13
- 输出 13
-
函数执行完毕,该函数上下文被释放
-
-
-
执行
fn(4);-
fn --> 0x003
-
开辟函数上下文EC(AN3)
-
开辟AO(AN3)
-
作用域链 <EC(AN3),EC(AN1)> (上一级的查看0x003的[[scope]])
-
参数赋值 : 存放在AO(AN3) y -> 4
-
执行代码(代码存放在0x003中): this.x *= (++x) + y;
- this.x指的是window,查看window的VO(G)/GO中是否有x,发现有x,x = 13
- this.x = 13 * ((++x) + y);
- 查看AO(AN3)中有没有x,没有,沿着作用域链去上一级上下文(EC(AN1))找。
- 在AO(AN1)中也没找到,继续沿着EC(AN1)函数上下文的作用域链往上找(EC(G))
- 发现在VO(G)中有x,x=13
- 执行 ++x,EC(G)的VO(G)中的x被修改,x=14
- 查看AO(AN3)中有没有y,发现有y,值为4
- this.x = 13 * (14 + 4) -> this.x = 13*18 -> this.x = 234 -> window.x = 234
-
执行consoel.log(x)
- 查看AO(AN3)中有没有x,没有,沿着作用域链去上一级上下文(EC(AN1))找。
- 在AO(AN1)中也没找到,继续沿着EC(AN1)函数上下文的作用域链往上找(EC(G))
- 发现在VO(G)中有x,x=234
- 输出 234
-
函数执行完毕,该函数上下文被释放
-
-
-
执行
console.log(obj.x,x)- obj堆0x001中发现x为95,
- EC(G)的VO(G)中发现x为234
- 输出95 234
-
题2:
let a = 0,
b = 0;
let A = function(a) {
A = function(b) {
alert(a + b++);
};
alert(a++);
}
A(1); // 1
A(2); // 4
执行顺序:
-
开辟全局上下文执行栈EC(G),将代码(字符串)存放找栈中
-
执行代码,发现let声明,ab值为原始值,先创建值0,再创建变量a和b,再进行地址的引用(赋值)
-
发现let 声明A,值为引用类型
-
堆区开辟空间 0x001
- 堆区第一部分存放作用域[[scope]]:EC(G) -> 在哪声明的
- 第二部分存放代码字符串: A = function(b) {alert(a + b++);};alert(a++);
- 第三部分存放键值对:name:'' , length:1 ptototype:xxx [[prototype]]:xxx、
-
再EC(G)中创建变量A
-
将堆区空间地址0x001赋值给A
-
-
执行到A(1)代码,执行函数
要开辟函数上下文EC(A1)
-
第一部分:AO(A1) 存放变量以及值
-
第二部分:作用域链: <EC(A1),EC(G)>
- <1,2> 中1值的是当前作用域,2指的是上级作用域
-
形参赋值:a = 1
- 存放在AO(A1)中
-
变量提升
-
代码执行:A = function(b) {alert(a + b++);};alert(a++);
-
发现函数声明
-
堆区开辟空间0x002
- 堆区第一部分存放作用域[[scope]]:EC(A1)
- 第二部分存放代码字符串: alert(a + b++);
- 第三部分存放键值对:name:'' , length:1 ptototype:xxx [[prototype]]:xxx
-
但是发现A不是自己私有的,就得沿着作用域链向上查找,去EC(G)中查找,找到了A
-
将EC(G)中的A的值改为了0x002
-
-
执行下一句代码 alert(a++)
- 输出 “1”
- a -> 2
A(1)执行完毕,由于0x001没有被使用了,将会被释放掉。但是0x002函数堆被EC(G)中的A占用了,所以0x002会保留,那么0x002的作用域是EC(A1),则EC(A1)也会被保留下来。
-
-
执行代码A(2)
要开辟函数上下文EC(A2)
-
第一部分:AO(A2),存放变量和值
-
第二部分,作用域链<EC(A2) , EC(A1)>
-
形参赋值:b -> 2
- 存放在AO(A2)中
-
变量提升
-
代码执行:alert(a + b++);
-
a本上下文没有,沿着作用域链去上级查找,找到a -> 2
-
b是私有的
-
输出 "4"
- b++,
- AO(A2)中的 b -> 3
-
-
释放掉
-
-
手动释放 A = null
- 函数堆0x002没有被引用了,0x002被释放
- 0x002被释放之后,EC(A1)作用域也就不会被使用了
- EC(A1)释放掉
害,不知道你们看不看得懂,反正我是看得懂,哈哈哈哈哈~