闭包,作用域链,垃圾回收,内存泄露
1、函数创建
创建函数
1、开辟一个堆内存(16进制的内存地址)
2、声明当前函数的作用域(再哪个上下文创建的,它的作用域就是谁)
3、把函数体内的代码当作字符串存储在堆内存当中(所以不执行没有意义)
4、把函数的堆内存地址类似对象一样放到栈中供对象调用
执行函数
1、会形成一个全新的私有上下文(目的是供函数中的代码执行),然后进栈执行
2、在私有上下文中有一个存放私有变量的变量对象 AO(xx)
3、在代码执行之前要做的事情
- 初始化它的作用域链<自己的上下文,函数的作用域>
- 初始化this (箭头函数没有this)
- 初始化Arguments实参集合(箭头函数没有)
- 形参赋值(形参变量是函数的私有变量,需要存储在AO中的)
- 变量提升(在私有上下文中声明的变量都是私有变量)
4、代码执行(把之前在函数中存储的字符串,拿过来在上下文中一次执行)
作用域链查找机制:在代码执行过程中,遇到一个变量,我们首先看一下是否为自己的私有变量,如果是,接下来的操作都是私有的和外界没有直接联系,如果不是私有的,那么按scope-chain,向上级上下文中查找(如果是上级私有的,接下来的操作都是操作上级的变量,。。。一直找,找到EC(G)为止)
5、根据实际情况确定当前上下文是否出栈释放
6、为了保证栈内存的大小,内存优化,一般情况下,如果当前函数执行产生的上下文在进栈且代码执行完成后,会把这次上下文移除栈(上下文释放后,之前上下文中存储的私有变量等信息也就跟着释放了), 全局上下文是在打开页面生成的,只有在页面关闭的时候才能释放掉。 特殊情况:只要上下文中的某些内容被上下文以外的东西占用,那么当前上下文是不会释放的(上下文中存储的变量等信息也保留下来了)
函数第二次执行,会形成一个全新的私有上下文,把之前做过的事,原封不动的在执行一遍,此时形成的上下文和上一次形成的上下文之间没有必要联系。
var x = [12,23];
function fn(y) {
y[0] = 100;
y = [100];
y[1] = 200;
console.log(y)
}
fn(x);
console.log(x)
2、闭包作用域
闭包: 函数运行时产生的一种机制,函数执行会形成一个全新的私有上下文,可以保护里面的私有变量和外界互不干扰(保护机制),但是大家普遍认为的闭包,认为当前上下文不能被出栈释放,这样私有变量及它的值不会被释放掉(保存机制)
作用: 保护和保存 , 保护里面的私有变量不被干扰,保存私有变量不被释放
3、经典面试题分析
// 题一
let x =1;
function A(y){
let x = 2;
function B(z){
console.log(x + y +z)
}
return B;
}
let C = A(2)
C(3)
// 7
分析:
// 题二:
let x = 5;
function fn(x){
return function(y){
console.log(y+(++x))
}
}
let f = fn(6)
f(7)
fn(8)(9)
f(10)
console.log(x);
// 14、18、18、5
分析:
i ++ 和 ++i 的区别 i ++ 是先运算在加一,++i 是先加一在运算
5+(++i)EC(G)——VO(G) => 【 let x = 5 ; fn=>AAAFFF000 , f = fn(6) 执行fn 返回 ,所以 f => BBBFFF000;】
AAAFFF000 => [[scope]] : EC(G) 【 形参 x “return function(y){console.log(y+(++x)) }” 】
fn(6) => AAAFFF000(6) => EC(FN1) 【作用域链<EC(FN1).EC(G)>,形参 x = 6(会被下面修改++) , 代码执行: return BBBFFF000 】
BBBFFF000 => [[scope]] : EC(FN1) 【 形参 y “console.log(y+(++x))” 】
f(7) => BBBFFF000(7) => EC(F1) 【作用域链<EC(F1).EC(FN1)>, 形参 y=7,代码执行:console.log(y+(++x) 】AAAFFF000中的x被修改为了7,不会释放,所以结果为14f(8)(9) => AAAFFF(8) => EC(FN2) 【作用域链<EC(FN1).EC(G)>,形参 x = 8(被下文变9) , 代码执行: return BBBFFF111 】
BBBFFF111 => [[scope]] : EC(FN1) 【 形参 y “console.log(y+(++x))” 】
AAAFFF(8) 临时不被释放,BBBFFF111(9) => 形成EC(NA) => 【作用域链<EC(NA).EC(FN2)>,形参 y = 9 , 代码执行: return BBBFFF111 】,没人用BBBFFF111了会被释放,所以结果为18f(10) => BBBFFF000(10) =>EC(F2) => 【作用域链<EC(F2).EC(FN1)>, 形参 y=10,代码执行:10+ EC(FN1)中的x变为了++7】结果为18
全局上下文中的x还是5
// 题三:
let a = 0,
b = 0;
function A(a){
A = function(b){
alert(a+b++)
};
alert(a++)
}
A(1); // 1
A(2); // 4
// "1" , "4"
分析
4、JS中的内存优化
-
栈内存【执行上下文】
栈内存的优化:一般情况下,函数执行完,所形成的上下文会被出栈释放掉,
特殊情况
1、当前上下文中的某些内容被上下文以外的事物占用了,此时不能出栈释放
2、全局上下文,加载页面创建的,也只有页面关闭才会被释放掉 -
堆内存
浏览器的垃圾回收机制
1、引用计数(IE为主、某些情况下不能正常计数,导致不能被释放——内存泄漏)
2、检测引用(占用)(谷歌为主), 浏览器在空闲时会一次检测所有的堆内存,把没有被任何事物占用的内存释放掉,以此优化内存
手动释放内存一般就是解除占用,解除指针的指向,一般赋值为null (空对象指针) -
闭包的使用
大量使用闭包会造成性能损耗,但是闭包的保护和保存,我们还是需要的,所以开发中要合理使用闭包,使用后需要解除占用 -
闭包的应用
1、实战用途
2、 高阶编程:柯里化函数、惰性函数、compose函数
3、 自己封装插件组件的时候,
4、 源码分析JQ/Lodash/React(Redux/高阶组件/HOOKS)
5、闭包在实际中应用的场景
完成一下点击按钮,完成换肤的效果,循环点击获取当前索引的五种方式
html文件
<style> html, body { padding: 0; margin: 0; height: 100%; } </style>
<body>
<button index="0">红</button>
<button index="1">绿</button>
<button index="2">蓝</button>
<button index="3">黑</button>
<button index="4">粉</button>
</body>
解决方式一
let btns = document.getElementsByTagName("button");
let arr = ["red", "green", "blue", "black", "pink"];
for (var i = 0; i < btns.length; i++) {
// 子执行函数形成一个私有上下文,
(function (i) {
// btn[0].onclick = AAAFFF000([[scope]]:EC(AN))
btns[i].onclick = function () {
var color = arr[i];
document.body.style.backgroundColor = color;
};
})(i); // 把每一轮的实参I传给私有上下文中的形参i,第一轮传递的是0
}
解决方式二
let btns = document.getElementsByTagName("button");
let arr = ["red", "green", "blue", "black", "pink"];
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = (function (i) {
return function () {
var color = arr[i];
document.body.style.backgroundColor = color;
};
})(i);
}
解决方式三
// 每一轮都会形成一个私有的作用域,并且又一个私有变量i,分别存储每一轮的索引
// 循环5轮,会形成6个作用域(加一个for循环的父作用域) —— 也不是全局的
// let的块级作用域是浏览器底层自己实现的,比我们实现的闭包要好一些
let btns = document.getElementsByTagName("button");
let arr = ["red", "green", "blue", "black", "pink"];
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
var color = arr[i];
document.body.style.backgroundColor = color;
};
}
解决方式四
let btns = document.getElementsByTagName("button");
let arr = ["red", "green", "blue", "black", "pink"];
for (var i = 0; i < btns.length; i++) {
btns[i].myIndex = i;
btns[i].onclick = function () {
// => this 为当前操作的按钮
var color = arr[this.myIndex];
document.body.style.backgroundColor = color;
};
}
解决方式五
let btns = document.getElementsByTagName("button");
let arr = ["red", "green", "blue", "black", "pink"];
document.body.onclick = function (ev) {
let target = ev.target,
targetName = target.tagName;
// 当前点了按钮,target事件源就是这个按钮
if (targetName === "BUTTON") {
var index = target.getAttribute("index");
this.style.backgroundColor = arr[index];
}
};
6、写在最后
欢迎访问我的博客rockshang.com