这一章的目标是:理解闭包、掌握闭包的工作原理
闭包的好处:
- 可以实现JavaScript没有的私有变量
- 可以通过减少代码数量和复杂度来添加高级特性
什么是闭包呢?(自己的理解)
内部函数调用外部函数的变量可以形成闭包
理解闭包
允许函数访问并操作函数外部的变量
var globe_name = ''
function fn() {
var name = 'tom'
function fn_name() {
return name
}
globe_name = fn_name()
}
fn()
console.log(globe_name);// tom
以上代码就是一个闭包
按理说,fn方法执行完之后,这个作用域应该消失,不会再获取到内部的变量,但是现在执行内部函数时候内部变量仍然存在
当在外部函数中申明内部函数时,不仅仅定义 函数的声明,而且还创建了一个闭包。改闭包不仅包含了函数的声明,还包含了函数声明时该作用域中所有的变量。当最终执行内部函数是,尽管声明时的作用域已经消失了,但是通过闭包,仍然能访问到原始作用域
谨记
每一个通过闭包访问变量的函数都具有一个作用域链,作用域链包含闭包的全部信息
虽然表报是非常有用的,但是不能过度使用。使用闭包时,所有的信息都会存储在内存中,知道JavaScript引擎确保这些信息不再使用(可以安全的进行垃圾回收)或页面卸载时,才会清理这些信息
闭包的使用
JavaScript没有私有变量,但是闭包可以实现一个类似于其他语言的私有变量
function Obj(num1) {
let num = num1 // 闭包模拟私有变量
this.getNum = function () {
return num
}
this.addNum = function () {
num++
}
}
let a = new Obj(2)
a.addNum()
上面的例子,其中可以通过getNum方法可以获取私有变量num的值,但是不能直接访问num这个私有变量。并且不能修改,有效的阻止了对变量不可控的修改
在外部是不能直接访问num的
回调函数中使用闭包
下面代码实现一个动画
let box1 = document.getElementById('box1')
let box2 = document.getElementById('box2')
function animation(ele) {
let ticket = 0
let timer = 0
timer = setInterval(function () {
if (ticket < 100) {
ticket++
ele.style.width = `${ticket}px`
} else {
clearInterval(timer)
}
}, 100)
}
animation(box1)
animation(box2)
假设,这个动画没有使用闭包来完成,则里面使用的变量ele(DOM元素的引用)、ticket(计数器)、timer(计时器)都得是全局变量,这样一个元素使用动画的时候没有问题,但是如果是多个的话,就会出问题,变量会发生冲突。
所以使用闭包,就会使得每个动画都能够有自己的作用域链中的私有变量,不会互相影响
使用闭包的理由
一次性同时做许多事情,例如事件绑定、动画甚至是服务端请求都会变得非常困难
这里这个动画例子让我想起来在刚学习js的时候写了一个轮播图,当时是写了一个全局的方法,也需要使用计数器和定时器,但是页面会有多个地方需要使用轮播图,所以写了公共的方法。然后在执行的时候发现页面轮播图效果没有按照预期效果执行,计数器的累加错误导致不同地方的轮播图没有按照顺序显示,后来就改成了构造函数来实现,这样计数器就没有再被互相影响,当时只知道这么写管用,但是不知道原因,现在理解了。
当时出现问题的原因就是因为执行元素没有属于自己的私有变量,导致被互相影响
通过执行上下文来跟踪代码
前面讲过JavaScript有两种代码类型:一种是全局函数、一种是函数代码
既然有两种类型的代码,name就有两种执行上下文:全局执行上下文和函数执行上下文
二者差别是:
全局执行上下文只有一个,当JavaScript程序开始执行时就有奖创建了全局上下文;而函数执行上下文是在每次调用函数时,就会创建一个新的
执行上下文也称为调用栈