什么闭包
在全局上下文中,在浏览器加载页面会把代码放到栈内存(ECStack)中执行,函数进栈执行会产生一个私有的上下文(EC),此上下文能 保护 里面的私有变量(AO)不受外界干扰,并且如果当前上下文中的某些内容(一般是一个堆)被上下文以外的内容所占用,当前上下文是不会出站释放,这样可以 保存 里面的变量和变量值,我们把函数执行形成私有上下文,来保存和保存私有变量的机制称之为“闭包”,闭包是一种机制。
- 创建函数
- 开辟一个堆内存。
- 把函数体中的代码当做字符串存储进去。
- 把堆内存的地址赋值给函数名。
- 函数在哪儿创建的,那么它执行的时候做需要查找的上级作用域就是谁。
- 函数执行
- 形成一个全新的私有作用域、执行上下文,私有栈内存(执行一次形成一个,多个之间也不会产生影响)。
- 形参赋值&变量提升。
- 代码执行(把所属堆内存中的代码字符串拿出来一行一行执行)。
- 遇到一个变量,首先看它是否为私有变量(形参和在私有作用域中声明的变量),是私有的就操作自己的变量即可,不是私有的则向上级作用域中查找…一直找到全局作用域为止 => 作用域链查找机制。
- 私有变量和外界变量变量没有必然关系,可以理解为被私有栈内存保护起来了,这种机制其实是闭包的保护机制。
闭包的两大作用
保护
- 私有变量和外界没有关系。
- 划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量和其他区域中的变量不会有任何的冲突(防止变量污染)。
保存
- 形成不销毁的上下文,里面的私有变量等信息保存下来了。
- 如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文调取使用 。
- 我们把函数执行形成私有上下文,来保存和保存私有变量的机制称之为“闭包”,闭包是一种机制。
练习题
let a = 0,
b = 0;
function A(a){
A = function(b){
alert(a+b++)
};
alert(a++)
};
A(1);
A(2);
图形解析
关于堆栈内存释放问题
以谷歌webkit内核为例子,函数执行就会形成栈内存【执行上下文/作用域】(从内存中分配出的一块空间),如果内存都不销毁释放,很容易就会导致栈内存溢出(内存爆满,电脑就卡死了),堆栈内存的释放问题是学习JS的核心知识之一。
堆内存释放问题
- 堆内存:只要有一个引用类型值,就有一个堆内存。
- 如果当前创建的堆内存不被其他东西占用了(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收掉释放掉),所以说JS中的内存释放问题是浏览器自己完成的,我们写代码,浏览器帮我们创建一个堆内存,同样的,浏览器也会帮我们把不被占用的内存回收。
let obj = {name:"zhangsan"};
let oop = obj;
//此时obj和oop都占用着对象的堆内存,想要释放对内存,需要手动解除变量和值的关系(null:空对象指针,不占内存)
obj = null;
oop = null;
栈内存释放问题
- 全局上下文:
- 打开浏览器页面形成的全局作用域是栈内存。
- 关掉页面的时候才会销毁。
- F5刷新是把上一次的释放,再形成一个新的。
- 私有占内存:
- 手动执行函数形成的私有作用域是栈内存。
- ES6中的LET/CONST会产生的块级作用域也是栈内存。
- 默认函数执行完毕,形成的私有栈内存会被销毁释放掉。
- 一旦栈内存中的某个东西 (一般都是堆内存) 被私有作用域外的事物给占用了,不仅被占内容不能被释放,当前私有栈也不能被释放。
- 特点:私有上下文(作用域)中的私有变量等信息也保留下来了。
- 闭包是一种机制。
练习题1:
// 默认函数执行形成栈内存,执行完成栈内存销毁
function fn(){};
fn();
function x(){ return function(){} };
// f占用了x执行形成的栈内存中的一个东西(返回小函数对应的堆),则x执行形成的内存不能被释放了。
let f = x();
//如果想要X可以被释放,则让f不占用X中的东西(返回小函数对应的堆),断开连接,即可释放X执行形成的栈内存
//这是最佳办法,也可以关联别的类型值,但是都是要占用内存的
f = null;
练习题2:
var i = 5;
function fn(i){// AF0[i]
return function(){//BF0
console.log(n+(++i));
}
};
var fn = fn(1);//return AF0(1)
fn(2);//4
fn(3)(4);//8
fn(5)(6);//12
fn(7);//10
console.log(i);//5
图形解析
浏览器垃圾回收机制
- 浏览器垃圾回收机制(GC):浏览器自己会在空闲的时候,把所有未被占用的内容释放掉,以此来优化空间;对于前端开发来讲,应该尽可能减少内存的开辟,并把一些无用的内存取消对其的占用。
- 常用方法:
- 引用计数
- 标记清除
引用计数
- [谷歌等高版本浏览器是“基于引用查找”来进行垃圾回收的]。
- 开辟的堆内存,浏览器会在空闲的时候查找所有内存引用,把那些不被引用的释放掉。
- 开辟的栈内存(上下文)一般在代码执行完都会出站释放,如果遇到上下文中的东西被外部占用,则不会释放。
标记清除
- [IE浏览器是“基于计数器”机制来进行内存管理的]。
- 创建的内存被引用一次,则计数1,再被引用一次,计数2… 移除引用减去1…当减为0的时候,浏览器会把内存释放掉。
- 真实项目中,某些情况导致技术规则会出现一些问题,造成很多内存不能被释放,产生"内存泄漏";
- 查找引用的方式如果行程相互引用,也会性导致"内存泄漏"。
练习题1
let x = 5;
const fn = function fn(x){
return function(y){
console.log(y+(++x));
};
};
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
答案解析:
练习题2:
let a = 0,
b = 0;
let A = function(a){
A = function(b){
alert(a+b++);
};
};
A(1);
A(2);
答案解析: