JavaScript 闭包!
1. 闭包:指的是那些引用了另一个函数作用域变量的函数,通常在嵌套函数中出现闭包。
2. 简单理解一下定义和执行一个函数时发生的事情
【假设在全局作用域中定义函数】:首先,在定义一个函数时,会为这个函数创建一个作用域链,它预装载了全局变量对象,之后将这条作用域链保存到这个函数的内部属性 [[ scope ]] 中,当我们执行这个函数时,会创建函数的执行上下文,并复制函数的 [[ scope ]] 来创建它真正的作用域链,接着将函数的活动对象推入作用域链的前端。也就意味着,函数执行时的作用域链上首先是函数本身的活动对象,第二个是全局变量对象,在函数内部调用变量时,先从活动对象上寻找,若未找到,则从全局变量对象上寻找。函数执行完毕后,函数作用域链和活动对象被销毁【函数执行上下文被销毁】,内存中只剩下全局作用域。
【当在函数内部定义函数时】:首先,还是会为这个内部函数创建一个作用域链,预装载了外部函数的作用域链上的变量对象,将这条作用域链保存到函数内部属性 [[ scope ]] 中,当我们执行到这个内部函数时,会创建函数的执行上下文,并复制 [[ scope ]] 创建它的作用域链,将内部函数本身的活动对象推入作用域链前端。
3. 当在一个函数内部返回另一个函数,并且这个内部函数引用了外部函数作用域的变量【闭包】,那么即使外部函数执行完,虽然它的执行上下文的作用域链被销毁了,但它的活动对象不会被销毁,因为其内部函数还未执行,内部函数作用域链上还有着外部函数的活动对象,直到内部函数执行完被销毁,外部函数的活动对象才会被销毁。【执行上下文包含作用域链和活动对象,在内部函数未执行完前,外部函数只是作用域链被销毁,但执行上下文和执行上下文的活动对象没有被销毁】
4. 内部函数无法直接引用外部函数的 this 和 arguments 特殊变量,因此若需要内部函数引用这两个变量需要手动赋值一下。
window.identity = 'The Window'; let object = { identity: 'My Object', getIdentityFunc() { return function() { return this.identity; }; } }; console.log(object.getIdentityFunc()()); // 'The Window'
改后:
window.identity = 'The Window'; let object = { identity: 'My Object', getIdentityFunc() { let that = this; return function() { return that.identity; }; } }; console.log(object.getIdentityFunc()()); // 'My Object'
5. 内存泄漏问题:闭包很容易引起内存泄漏,当在闭包中访问外部函数获取到的 HTML 元素时,HTML元素永远都无法被销毁,因为内部函数一直引用它,以下为简单示例:
function assignHandler() { let element = document.getElementById('someElement'); element.onclick = () => console.log(element.id); }
易知 element 永远都无法被销毁,修改后:
function assignHandler() { let element = document.getElementById('someElement'); let id = element.id; element.onclick = () => console.log(id); element = null; }
此时 element 没有再被引用,则其内存会在适当的时侯被释放。
6. 立即调用的函数表达式【立即调用的匿名函数】:可以解决 for 循环中 var 声明的循环迭代变量 i 访问值问题。【在 ES6 中直接使用 let 即可解决此问题,不必使用立即调用的匿名函数,而且还要涉及闭包】
var arr = []; for (var i=0;i<5;i++) { arr[i] = function () { console.log(i); } } arr[2](); // 5
var arr1 = []; for (var j=0;j<5;j++) { arr1[j] = (function (j) { return function () { console.log(j) } })(j); } arr1[2](); // 2
7.

浙公网安备 33010602011771号