1、this 对象: 在闭包中使用 this 会让代码变复杂。如果内部函数没有使用箭头函数定义,则 this 对象会在运行时绑定到执行函数的上下文。如果在全局函数中调用,则 this 在非严格模式下等于 window,在严格模式下等于 undefined。如果作为某个对象的方法调用,则 this 等于这个对象。匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。不过,由于闭包的写法所致,这个事实有时候没有那么容易看出来。来看下面的例子:
1 2 3 4 5 6 7 8 9 10
window.identity = "The Window"; let object = { identity: "My Object", getIdentityFunc() { returnfunction () { returnthis.identity; }; }, }; console.log(object.getIdentityFunc()()); // 'The Window'
前面介绍过,每个函数在被调用时都会自动创建两个特殊变量:this 和 arguments。内部函数永远不可能直接访问外部函数的这两个变量。但是,如果把 this 保存到闭包可以访问的另一个变量中,则是行得通的。比如:
2、内存泄露: 由于 IE 在 IE9 之前对 JScript 对象和 COM 对象使用了不同的垃圾回收机制(第 4 章讨论过),所以闭包在这些旧版本 IE 中可能会导致问题。在这些版本的 IE 中,把 HTML 元素保存在某个闭包的作用域中,就相当于宣布该元素不能被销毁。来看下面的例子:
1 2 3 4
functionassignHandler() { let element = document.getElementById("someElement"); element.onclick = () =>console.log(element.id); }
以上代码创建了一个闭包,即 element 元素的事件处理程序。而这个处理程序又创建了一个循环引用。匿名函数引用着 assignHandler()的活动对象,阻止了对 element 的引用计数归零。只要这个匿名函数存在,element 的引用计数就至少等于 1。也就是说,内存不会被回收。其实只要这个例子稍加修改,就可以避免这种情况,比如:
1 2 3 4 5 6
functionassignHandler() { let element = document.getElementById("someElement"); let id = element.id; element.onclick = () =>console.log(id); element = null; }
在这个修改后的版本中,闭包改为引用一个保存着 element.id 的变量 id,从而消除了循环引用。不过,光有这一步还不足以解决内存问题。因为闭包还是会引用包含函数的活动对象,而其中包含 element。即使闭包没有直接引用 element,包含函数的活动对象上还是保存着对它的引用。因此,必须再把 element 设置为 null。这样就解除了对这个 COM 对象的引用,其引用计数也会减少,从而确保其内存可以在适当的时候被回收。 3、立即调用函数表达式(IIFE,Immediately Invoked Function Expression):
// IIFE (function () { for (var i = 0; i < count; i++) { console.log(i); } })(); console.log(i); // 抛出错误
// 内嵌块级作用域 { let i; for (i = 0; i < count; i++) { console.log(i); } } console.log(i); // 抛出错误
// 循环的块级作用域 for (let i = 0; i < count; i++) { console.log(i); } console.log(i); // 抛出错误
let divs = document.querySelectorAll("div"); // 达不到目的! for (var i = 0; i < divs.length; ++i) { divs[i].addEventListener("click", function () { console.log(i); }); }