一、概念
1、闭包是指有权访问另一个函数作用域中的变量的函数。
2、作用域链:
- 函数创建时,会创建一个预先包含全局变量对象的作用域链,保存在内部的[[scope]]属性;
- 函数调用时,创建一个执行环境,复制[[scope]]属性中的作用域链构建这个执行环境的作用域链,函数的局部变量对象被创建并推入作用域链前端;
- 函数内部局部环境的变量对象只在函数执行过程中存在,执行完毕后就会被销毁;
- 作用域链本质上是一个指向变量对象的指针列表,只引用但不包含变量对象。
二、闭包
存在闭包时,被返回的闭包函数被赋值给了一个全局变量,闭包函数的作用域链一直引用着它外层的函数的活动对象,这个函数执行完就不会被销毁,除非解除对闭包函数的引用,除了全局作用域外的作用域才会被销毁。
function compare (attr) {
return function (obj1, obj2) {//闭包函数
var val1 = obj1[attr]
var val2 = obj2[attr]
return val1-val2
}
}
let compare1 = compare('name')
compare函数赋值给compare1,compare1引用compare内部返回的匿名函数,此匿名函数作用域链包含compare的变量对象
const result = compare1('diana', 'js')
调用compare1,可以访问compare作用域链
compare1 = null
解除对匿名函数的引用,销毁其作用域链
三、闭包与变量
1、一个典型的闭包函数问题
function foo () {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function () {//闭包函数
return i
}
}
return result
}
var foo1 = foo()
foo()函数执行,返回result给foo1
var res1 = foo1[2]
通过闭包调用foo()函数变量,此时foo的循环已执行完毕,变量i=10
console.log(res1())//10
闭包只能取得包含函数中任何变量的最终值
分析:
1)首先var foo1 = foo(),函数foo()依次执行函数内的代码,包括for循环,将一个个匿名函数function () { return i }整体作为元素添加到数组result内,但是这个函数并没有被调用,最终for循环依次执行直到i = 10。匿名函数引用的i就是外部变量for循环中的i,所以之后数组内的函数的i都是10。
2)调用函数foo后,变量foo1即数组result。数组里面元素依次是10个function () { return i }。var res1 = foo1[2],开始执行数组第三项function () { return i },此时的i = 10,所以,返回值就是10,包括其他数组项也都是10。
2、由于闭包不立即执行,调用时才执行,所以利用这个特性将其改为立即执行,则可以在函数循环中取得同一变量不同值
function foo () {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = (function (num) {//用参数num绑定i当前值
return function () {//闭包函数
return num//函数执行i次,返回的num=i
}
})(i)//立即执行函数,i值从0到10依次传递
}
return result
}
var foo2 = foo()
此时foo执行,其内部函数也在循环中立即执行,每次不同的值添加到数组result中,因此返回的result数组赋值的foo2中值也是0-10
var res2 = foo2[3]
console.log(res2())//3
3、利用闭包函数实现函数复用
function foo (x) {
return function (y) {//闭包函数
return x - y
}
}
const foo1 = foo(5)
const foo2 = foo(10)
返回函数赋值给foo1、foo2,此处传参5、10为x
console.log(foo1(2))//3
console.log(foo2(2))//8
执行闭包函数,传参2为y
4、利用闭包实现计数器
function counter () {
var i = 0
return function () {
return i+=1
}
}
var counts = counter()
console.log(counts())//1
console.log(counts())//2
console.log(counts())//3
闭包引用一直不销毁,则其引用的变量 i 也一直存在
console.log(counter()())//1
console.log(counter()())//1
console.log(counter()())//1
但每次初始化counter函数,则内部的变量i和闭包函数也都重新创建
四、this
this指向的是运行函数所处的执行环境
var name = 'window';
function foo () {
var name = 'foo'
return function () {
return this.name
}
}
var f1 = foo()
console.log(f1())//window
匿名函数内this指向全局
var name = 'window'
var obj = {
name: 'obj',
getName: function () {
var that = this
return function () {
return that.name
}
}
}
console.log(obj.getName()())//obj
把函数作用域中的this保存在一个变量,使用闭包函数访问这个变量
五、内存泄漏
如果闭包函数的作用域链中包括了html元素,则此元素无法被销毁,需要手动置为null