为何内存快照中onevent属性会被当作闭包处理?
问题
触发largeClosures()和stopAndClear()点击事件后,我用Microsoft Edge开发者工具的Memory面板获取内存快照,发现这两个事件处理器被归类到(closure)条目下,每个占用32字节内存。这些事件是通过HTML的onevent属性(比如onclick)绑定到DOM元素的,按传统理解,闭包应该是像lC、sC、eC这类捕获了外层函数局部变量的函数,为什么这两个事件处理器会被标记为闭包?
示例代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <p>Analyze the impact of closures on memory</p> <p> <button onclick="largeClosures()"> Closures maintain hold on large string </button> </p> <p> <button onclick="smallClosures()"> Closures maintain hold only on small strings </button> </p> <p> <button onclick="evalClosures()">Closures with <code>eval</code></button> </p> <button onclick="stopAndClear()">Stop and clear</button> <script> var closures = []; function createLargeClosure() { var largeStr = new Array(1000000).join("x"); return function lC() { return largeStr; }; } function createSmallClosure() { var smallStr = "x"; var largeStr = new Array(1000000).join("x"); return function sC() { return smallStr; }; } function createEvalClosure() { var smallStr = "x"; var largeStr = new Array(1000000).join("x"); return function eC() { eval(""); return smallStr; }; } function largeClosures() { closures.push(createLargeClosure()); } function smallClosures() { closures.push(createSmallClosure()); } function evalClosures() { closures.push(createEvalClosure()); } function clear() { closures.length = 0; } function stopAndClear() { clear(); } </script> </body> </html>
内存快照截图

原因解释
当你通过HTML的onclick这类onevent属性绑定事件时,浏览器并不会直接把你指定的函数(比如largeClosures)绑定到元素上,而是会自动生成一个包装函数,逻辑类似:
function(event) { largeClosures(); }
这个包装函数需要访问全局作用域中的largeClosures函数,因此它会捕获并持有对全局作用域的引用——这完全符合闭包的定义:一个函数能够访问其词法作用域之外的变量,即使在该作用域之外执行。
开发者工具会把这类捕获了外部作用域的包装函数标记为闭包,和你例子中lC、sC这类捕获外层函数局部变量的典型闭包本质一致,只是它们捕获的作用域层级不同(一个是全局作用域,一个是函数局部作用域)。
另外,哪怕你用JS代码手动绑定类似的匿名函数(比如button.onclick = () => largeClosures()),这个匿名函数同样会被标记为闭包,因为它也需要访问全局作用域的largeClosures。
内容的提问来源于stack exchange,提问作者Lin Du
相关产品推荐
相关产品推荐

