作用域的分类
1.局部作用域
(1)函数作用域
特点:
- 函数内部声明的变量只能在函数内部使用,函数外部无法访问,不同函数间内部声明的变量无法互相访问
- 函数参数也是函数内部的局部变量
- 函数中声明的所有变量都将被提升至函数顶部(var,let,const),但是var与后两个略有不同
- 函数执行后由于垃圾回收机制变量被清空
(2)块级作用域(被{}包裹)
if(){},for(){}等 let,const声明的变量具有块级作用域,var没有,也有变量提升
2.全局作用域
html文件中的 script标签和js文件,全局作用域中声明的变量可以被其他作用域所访问,同样有变量提升
作用域链
在定义一个函数A时,js会保存一个作用域链,当函数A被调用时,js将创建一个新的对象来存储A中的局部变量,并把这个新创建的对象添加至保存的作用域链上,同时创建一个更长的作用域链
简单来说作用域链就是作为一种变量查找的机制, 当函数执行时会优先查到当前作用域内的变量,如果该函数作用域中用到了某个变量但是没有在该作用域内申明,就沿着作用域链往父级作用域中查找,直到找到全局作用域(一种从下往上的查找机制)
这里要注意的是函数的作用域链是在函数被定义时就已经确定的,和函数的执行上下文无关
因此子作用域可以访问父作用域中的变量,而父作用域不能访问子作用域中的变量
3.闭包
(1)什么是闭包
一个可以读取使用其他函数内部变量的函数
闭包的构成简单来看可以看成一个内部函数+使用外部变量
function a(){
let a = 1
function b(){
console.log(a);
}
b()
}
a()

(2)闭包的作用
- 闭包使一个函数内部的变量可以被读取
- 让变量的值始终保存在内存中直到关闭页面
function counter(){
let n = 0
return {
count:function(){
return n++
},
reset:function(){
n = 0
}
}
}
let a = counter(),b = counter()//创建两个计数器
a.count()//0
b.count()//0
//以上两个互不干扰
a.reset()
a.count()//0 c被重置
b.count()//1
- 这段代码中这两个方法都可以访问相对于count和reset函数的外部私有变量n,因此是一个闭包
- 每次调用counter都会创建一个新的作用域链和一个新的私有变量,所以调用两次会得到两个计数器对象,不会相互影响
闭包的缺点:导致内存泄漏,如果闭包中引用的外部变量是一个对象,而这个闭包又长时间存在于内存中,那么标记清除法就始终会"到达"这个对象,因此就不会被垃圾回收机制所回收
- 闭包会导致我们持有不再需要的函数引用,会导致与函数相关联的词法环境无法被垃圾处理器所销毁,导致内存泄漏
- 当多个函数共享一个词法环境时,会导致词法环境膨胀,从而导致出现无法触及到的也无法被回收的内存空间, 比如说如果只有一个函数用到了词法环境中的一个内容,而最后这个函数又没被返回给后续的使用,返回了其他的函数,这样相关的内容就不会被清理,导致内存泄漏