从广义的角度来说:JavaScript中的函数都是闭包;
从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的变量,那么它是一个闭包; 闭包由两部分组成:函数+可以访问的自由变量 按道理说,foo执行完之后,它的ao对象应该也就销毁了,但是因为闭包,它没有被销毁
1.普通函数执行
var message = 'hello';
function foo(){
var name = 'foo';
var age = 18;
}
function test(){
console.log('test');
}
foo();
test();
1.在执行所有代码之前,引擎会在内存里创建GO对象(ox100),它里面有String对象啦,window对象这些 对象。是被提前创建好的。然后现在去执行代码, GO对象是不会被销毁的
2.创建执行上下文栈
3.然后执行全局代码,创建全局执行上下文,然后创建全局执行上下文的VO,这个Vo指向GO。
4.然后,这个时候解析全局代码,往全局GO里面加东西了,原来的全局里面有Date,window,String等等,加入
message(undefined)foo(oxa00),test(oxboo)等变量。解析foo是函数,就创建一个函数对象 foo(oxa00),里面有函数的父级作用域,也就是全局的GO对象(ox100)。,还有函数执行体(函数代 码)。 解析test是函数,就创建一个函数对象test(oxb00),里面有函数的父级作用域,也就是全局的GO对象 (ox100)。,还有函数执行体(函数代码)
5 接下来执行执行代码,先给message赋值,变成了hello,然后执行函数foo。
6 创建foo函数的函数执行上下文。往里面创建VO对象,VO指向AO对象。创建一个foo函数的AO对象(ox200)。
默认里面没有对象,然后解析函数,里面放入name:undefined,age:undefined
image.png7.然后执行一行一行执行foo里面的代码,同时把AO里面的name赋值'foo',age赋值18;
image.png
8.foo函数执行完之后,栈里面的foo函数执行上下文就会被销毁,一旦销毁,对foo的AO对象的引用将会没有,然后oxaoo就会被销毁。
image.png一般情况下,在函数执行上下文被销毁的同时,函数的AO对象也会被销毁
9.test函数执行完之后也是一样,test的AO对象也会被销毁。代码执行完之后的内存结果就是上图。
2.闭包函数执行
var message = 'hello'
function foo(){
var name = 'foo';
var age = 18;
function bar(){
console.log(name);
console.log(age);
}
return bar
}
var fn = foo();
fn()
执行完var fn = foo();之后
image.png然后foo的执行上下文被销毁,但是bar不会被销毁,因为fn指着它。 然后bar对象不会被销毁,它上面的foo的ao对象也不会被销毁的。因为bar里面有parentScope这个东西,它指向foo的AO对象。
image.png
接下来执行fn()
image.png
然后销毁fn的执行上下文.
image.png
foo的AO对象是没有被销毁的,因为bar的父级作用域是指向foo的AO的。 内存泄漏:这里的bar函数对象一直不会被销毁,也就是foo的AO也一直不会被销毁。 如果执行完一次fn之后,就再也不会执行这个fn了,那么保存的bar和foo的AO也就没有意义了。 该销毁的东西没有被销毁,就是内存泄漏。 fn= null;就可以解决
image.png
虽然这时候bar和foo的AO循环引用,但是根据标记清除法,只要从根对象OG开始能找到的对象就不会被销毁。
但是bar和foo的AO从根对象指不向他们,他们就会被销毁。
image.png
如果也想销毁掉foo这个函数,也是一样,直接foo = null;就可以了;
image.png3. 内存泄漏
image.png image.png
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// var arr = new Array(1024 * 1024).fill(1)
// console.log(arr);
//整数占据四个字节 4Byte 1MB=1024KB
//1024*1024*4 = 1024 * 1Kb * 4B = 4kB * 1024 = 4MB
//现在这个数组占据的空间是4MB
function createFnArray(){
var arr = new Array(1024 * 1024).fill(1);
return function(){
console.log(arr.length);
}
}
var arrayFns = [];
for(var i = 0; i < 100; i++){
//调用100次createFnArray(),就会有100个createFnArray对应的AO。
//返回100个函数对象,每一个函数对象都会引用createFnArray对应的AO。
arrayFns.push(createFnArray());
}
</script>
</html>
4.闭包中没有被使用的属性
function foo(){
var name = 'why';
var age = 18;
function bar(){
debugger
console.log(name);
}
return bar
}
var fn = foo();
fn();
foo的AO对象不会被销毁,里面有name和age两个变量,name因为被bar引用,不会被销毁,但是从引擎优化的角度,发现这里age根本不会被用到,依然保留age的话,会占用内存空间,影响内存性能。一般情况下,js引擎会把多余的属性销毁掉。
5.对象中的函数
var test = "test"
var p1 = {
p1eating(){
// console.log(test);
var p1test = "p1test"
var p2 = {
p2eating(){
var p2test = "p2test"
var p3 = {
p3eating(){
//console.log(test);
console.log(p1test);
//console.log(p2test);
}
}
p3.p3eating()
}
}
p2.p2eating()
}
}
p1.p1eating()
对象中的函数也是存在闭包的,方法中的变量也是向上层作用域中去找的,而且对象不是一个作用域,对象中的方法和全局才是
网友评论