从作用域和作用域链理解闭包
作用域
作用域就是变量的可用范围
全局作用域
- 不属于任何函数的外部范围称为 全局作用域
- 保存在全局作用域的变量称为全局变量
全局作用域的特点:
- 优点: 可反复使用
- 缺点: 全局污染
函数作用域
- 一个函数内的范围称为函数作用域
- 保存在函数作用域内的变量称为局部变量
函数作用域特点:
- 优点: 不会被污染
- 缺点: 无法反复使用
Js 中只有两种局部变量
- 函数内 var 出来的
- 函数的形参变量
作用域链
其实每个函数在定义时,就已经规划好了自己专属的一个由内向外的查找变量的路线图,称为作用域链
一个函数可用的所有作用域串联起来,就行成了当前函数的作用域链。
在浏览器运行下面这段代码
var a = 10
function fn() {
var a = 100
a++
console.log(a)
}
fun() //?
console.log(a)函数调用过程:
准备阶段
先创建全局变量
a,再创建函数对象fn,此时函数作用域链中只有window这个全局对象调用阶段
一旦函数开始调用,就会临时创建函数作用域对象,保存着局部变量
a,此时函数作用域中有window和函数作用域(AO)两个对 象,接着逐行执行代码调用结束
函数调用结束,相当于解除了对它创建的函数作用域对象的引用,没引用所以被垃圾回收释放了,所以局部变量
a也就不存在了 ,此时函数作用域链恢复到刚开始的只有window这个全局对象的状态
总结:
- 作用域和作用域链本质都是对象结构
- 函数作用域是 js 引擎在调用函数时才临时创建的一个作用域对象。其中保存函数的局部变量。当函数调用完,函数作 用域对象就释 放了,JS 中函数作用域对象,还有个别名"活动对象(Actived Object)"简称, AO。
所有函数在定义时, 作用域链 scopes 中都只有一个全局作用域对象 window 吗?
闭包
浅显的从用法上理解:既重用变量又保护变量不被污染的一种编程方法。
如何使用闭包
- 用外层函数包裹要保护的变量和使用变量的内层函数
- 在外层函数内部返回内层函数
- 调用外层函数,用变量接住返回的内层函数对象
//第1步: 用外层函数包裹要保护的变量和内层函数
function wrapper() {
var total = 1000
//第2步: 返回内层函数对象
return function (p) {
total -= p
console.log('total: ', total)
}
}
//第3步: 调用外层函数,用变量接住内层函数对象
var fn = wrapper()
fn(100)
total = 0
fn(100)浏览器中执行一下
外层函数调用过程:
外层函数准备阶段
创建函数对象
wrapper,此时函数作用域链中只有window这个全局对象外层函数调用阶段
一旦函数开始调用,就会临时创建函数作用域对象,保存着局部变量
total,此时函数作用域中有window和函数作用域(AO)两个 对象,接着逐行执行代码,遇到return function时内层函数准备阶段
创建内层函数对象
fn,因为内层函数fn是在外层函数创建,所以此时内层函数的作用域链除了window还有外层函数的函数作 用域
外层函数调用结束
外层函数函数调用结束,清除对自己创建的临时函数作用域对象的引用,但是却被内层函数的作用域链偷偷引用着,侥幸存活下来了 ,并且只有内层函数知道位置
内层函数调用阶段
创建临时函数作用域对象,保存着形参变量,此时函数作用域中有
window,外层函数作用域和自己的函数作用域,遇 到total变量时,现在自己的函数作用域对象找,没找到去上一层作用域找,找到然后执行代码内层函数调用结束
释放临时函数作用域对象,所有函数调用完只清空作用域链中离自己近的一个作用域
内部函数再次调用
因为上次调用只释放了离自己最近的函数作用域,所以还可以找到
total,并且是同一个内部函数再次创建
因为两次创建是分开的,此时的闭包是外层函数又新创建的临时函数作用域,
total还是 1000
总结:
- 闭包也是一个对象
- 闭包就是每次调用外层函数时,临时创建的函数作用域对象
- 为什么外层函数作用域对象能留下来?因为被内层函数对象的作用域链引用着,无法释放
- 内部函数创建完,就与外层函数再无关系
- 在外层函数里面同时创建的内部函数共用同一个闭包,因为都指向外部函数的函数作用域
- 分开创建的内部函数的闭包互相独立
- 闭包缺点:由于闭包藏得很深几乎找不到,所以极容易造成内存泄漏,解决:及时释放,给保存内部函数的变量赋值为 null
看完有没有理解闭包呢,做几道题试下吧
function fun() {
var i = 999
nAdd = function () {
i++
}
return function () {
console.log(i)
}
}
var getN = fun()
getN() // ?
nAdd() // ?
getN() // ?function mother() {
return function () {
i++
console.log(i)
}
}
var get1 = mother()
var get2 = mother()
get1() //?
get2() //?function fun() {
arr = []
for (var i = 0; i < 3; i++) {
arr[i] = function () {
console.log(i)
}
}
}
fun()
arr[0]() //?
arr[1]() //?
arr[2]() //?