每当我们运行代码的时候,代码就会生成执行上下文(可以理解为执行环境)

JavaScript执行环境主要分为三种:

  • 全局环境
  • 函数环境
  • Eval环境

JavaScript用栈处理多个执行上下文

img

作用域链

创建执行上下文分两个阶段

  • 创建阶段

    • 作用域链
      • 包含当前变量对象+所有父级变量对象
    • 变量对象(参数、变量函数声明)
    • this
  • 执行阶段

    • 变量赋值、函数引用等
1
2
3
4
5
6
7
8
function books(){
var book = "书包里面的书本"
return function(){
console.log(book);
}
}
var bag = books();
bag()

上面代码的上下文创建顺序

全局执行上下文 = {作用域链:{全局变量对象}, {变量对象: books, bag}}

books执行上下文 = {作用域链:{books变量对象+全局变量对象},{变量对象:book}}

匿名函数执行上下文 = {作用域链:{匿名函数遍历对象+books变量对象+全局变量对象},{变量对象:}}

一道题:

1
2
3
4
5
6
for(var i = 0;i<5;i++){
setTimeout(function(){
console.log(i++);
},4000)
}
console.log(i);//556789

解析:

顺序是这样的,1. 循环走完 设置完一堆定时器 定时器里的函数并没有执行;2. 执行第6条的log输出第一个5;3. 定时器时间到,开始执行,此时i从5开始计算 输出 5,6,7,8,9;

闭包的应用-数据私有

没有使用闭包

1
2
3
4
5
6
7
8
9
let i = 0;
function fn(){
i++
console.log(i);
}
//另外一个程序
i= 1000

fn()//1001

我们希望i这个变量是私有的,脱离全局上下文环境

1
2
3
4
5
6
7
8
9
10
11
function count(){
let i = 0;
function fn(){
i++
console.log(i);
}
return fn
}
i = 1000
const fun = count()
fun()//1

这样子i就不在全局上下文环境中了,不会被全局上下文中的代码修改掉。

闭包内存泄露

1
2
3
4
5
6
7
8
9
function count(){
let i = 0;
function fn(){
i++
console.log(i);
}
return fn
}
const fun = count()
  1. fun是一个全局变量,代码执行完毕不会立即销毁
  2. fun使用count函数
  3. count函数使用fn函数
  4. fn函数里面有用到i
  5. i被引用就不会被回收,所以一直存在

此时:闭包引起了内存泄漏

不是所有的内存泄露都要手动回收的

比如react里面很多闭包不能回收的