【js(3)】执行上下文/作用域链/垃圾回收与内存泄漏/闭包
执行上下文/作用域链/垃圾回收与内存泄漏/闭包
- ?
- 一、执行上下文
- 二、作用域链
- 举例
- 三、垃圾回收与内存泄漏
- 1.垃圾回收的概念
- 2.回收机制
- 3.垃圾回收的方式
- (1)标记清除
- (2)引用计数
- 4.内存泄漏
- 四、闭包
- 1.闭包的理解
- 2.闭包的用途
- 3. 举例
?
- 为什么几乎所有的编程语言代码都是从上往下执行
- 为什么所有的编程语言函数外部都访问不到函数内部的变量
一、执行上下文
- 执行上下文通俗来说就是
代码执行所处的当前容器
。- 在js中,大到浏览器,小到一个函数、一个代码块都有自己的上下文
不同的js宿主环境也有不同的全局上下文
(一个程序中只有一个全局执行上下文)。例如在浏览器中全局上下文是window,在node中全局上下文是global- 用浏览器举例,全局上下文会在你打开一个页面时创建,在退出页面或者关闭浏览器时销毁。
- 在函数中(当一个函数被调用,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个):当代码执行流进入到函数,该函数的上下文被推到上下文栈中,等到函数执行完之后,上下文栈会弹出该函数的上下文,将控制权返还给之前的上下文
二、作用域链
- 代码在
上下文栈中执行
的时候,还伴随着作用域链的创建
。- 作用域链决定了
上下文中的代码访问变量的顺序
以及权限
- 在任意上下文中用var定义的一个变量,js会将改变量添加到作用域链中,还会自动挂载为当前上下文对象的一个属性
举例
1.anotherColor和color并不是swapColors函数中的变量,为什么在当前函数上下文中也能访问到呢?
因为js在访问变量的时候,会优先在当前上下文中查找,如果找不到就沿着作用域链去深层寻找,一直找到全局上下文中都没有的话,就输出underfined。这里的两个变量是可以在作用域链中找到的。
三、垃圾回收与内存泄漏
1.垃圾回收的概念
JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收
2.回收机制
- Javascript 具有
自动垃圾回收机制
,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存- JavaScript中存在两种变量:局部变量和全局变量。
全局变量
的生命周期会持续要页面卸载;而局部变量
声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值
,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。- 局部变量被外部函数使用时,其中一种情况就是
闭包
,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
3.垃圾回收的方式
(1)标记清除
- 标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放
(2)引用计数
- 跟踪记录每个值被引用的次数
- 这种方法会引起循环引用的问题。循环引用中,引用次数永远不可能为0.
4.内存泄漏
程序在运行过程中,分配了内存空间,但由于某些原因,这些内存无法被释放或回收。
四、闭包
1.闭包的理解
- 闭包是指
有权访问另一个函数作用域中变量的函数
2.闭包的用途
- 闭包的第一个用途是使我们在
函数外部能够访问到函数内部的变量
。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。- 闭包的另一个用途是使
已经运行结束的函数上下文中的变量对象继续留在内存中
,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收
function foo() {var a = 1; // a 是一个被 foo 创建的局部变量function bar() { // bar 是一个内部函数,是一个闭包console.log(a); // 使用了父函数中声明的变量}return bar();
}
foo(); // 1
foo() 函数中声明了一个内部变量 a , 在函数外部是无法访问的,bar() 函数是 foo() 函数内部的函数,此时 foo 内部的所有局部变量,对 bar 都是可见的,反过来就不行,bar 内部的局部变量,对 foo 就是不可见的。这就是javaScript特有的”作用域链“。
function foo() {var a = 1; // a 是一个被 foo 创建的局部变量function bar() { // bar 是一个内部函数,是一个闭包console.log(a); // 使用了父函数中声明的变量}return bar;
}
const myFoo = foo();
myFoo();
foo() 执行后,将其返回值(也就是内部的 bar 函数)赋值给变量 myFoo 并调用 myFoo(), 实际上只是通过不同的标识符引用调用了内部的函数 bar()。
foo() 函数执行后,正常情况下 foo() 的整个内部作用域被销毁,占用的内存被回收。但是现在的 foo的内部作用域 bar() 还在使用,所以不会对其进行回收。bar() 依然持有对改作用域的引用,这个引用就叫做闭包。
3. 举例
for (var i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i)}, i * 1000)
}//输出6 6 6 6 6 6
//(1)var是函数作用域,var i是在整个函数中的声明,所有的setTimeout回调函数都共享一个i
//(2)setTimeout是异步的,它等主程序执行完了之后才执行,等他执行的时候i已经=6了
解决一:let
for (let i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i);}, i * 1000);
}
//let 有块级作用域,每次循环都会创建一个新的 i,每个 setTimeout 都绑定到当前循环的 i
解决二:闭包
for (var i = 1; i <= 5; i++) {;(function (j) {//自执行函数,接收参数j,每次循环都会调用一次这个函数,j的值是当前循环中的isetTimeout(function timer() {console.log(j);}, j * 1000);})(i);//将当前的i作为参数传入
}