回调函数是一种后续传递风格(Continuation Passing Style, CPS)的技术,这种风格的程序编写将函数的业务重点从返回值转移到回调函数中去。而且其相比闭包的好处也不少:
1.如果传入的参数是基础类型(如字符串、数值),回调函数中传入的形参就会是复制值,业务代码使用完毕以后,更容易被回收;
2.通过回调,我们除了可以完成同步的请求外,还可以用在异步编程中,这也就是现在非常流行的一种编写风格;
3.回调函数自身通常也是临时的匿名函数,一旦请求函数执行完毕,回调函数自身的引用就会被解除,自身也得到回收。
3.5 良好的闭包管理
当我们的业务需求(如循环事件绑定、私有属性、含参回调等)一定要使用闭包时,请谨慎对待其中的细节。
循环绑定事件可谓是JavaScript 闭包入门的必修课,我们假设一个场景:有六个按钮,分别对应六种事件,当用户点击按钮时,在指定的地方输出相应的事件。
复制代码 代码如下:
var btns = document.querySelectorAll('.btn'); // 6 elements
var output = document.querySelector('#output');
var events = [1, 2, 3, 4, 5, 6];
// Case 1
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function(evt) {
output.innerText += 'Clicked ' + events[i];
};
}
// Case 2
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = (function(index) {
return function(evt) {
output.innerText += 'Clicked ' + events[index];
};
})(i);
}
// Case 3
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = (function(event) {
return function(evt) {
output.innerText += 'Clicked ' + event;
};
})(events[i]);
}
这里第一个解决方案显然是典型的循环绑定事件错误,这里不细说,详细可以参照我给一个网友的回答;而第二和第三个方案的区别就在于闭包传入的参数。
第二个方案传入的参数是当前循环下标,而后者是直接传入相应的事件对象。事实上,后者更适合在大量数据应用的时候,因为在JavaScript的函数式编程中,函数调用时传入的参数是基本类型对象,那么在函数体内得到的形参会是一个复制值,这样这个值就被当作一个局部变量定义在函数体的作用域内,在完成事件绑定之后就可以对events变量进行手工解除引用,以减轻外层作用域中的内存占用了。而且当某个元素被删除时,相应的事件监听函数、事件对象、闭包函数也随之被销毁回收。
3.6 内存不是缓存
缓存在业务开发中的作用举足轻重,可以减轻时空资源的负担。但需要注意的是,不要轻易将内存当作缓存使用。内存对于任何程序开发来说都是寸土寸金的东西,如果不是很重要的资源,请不要直接放在内存中,或者制定过期机制,自动销毁过期缓存。
4. 检查JavaScript 的内存使用情况
在平时的开发中,我们也可以借助一些工具来对JavaScript 中内存使用情况进行分析和问题排查。
4.1 Blink / Webkit 浏览器
在Blink / Webkit 浏览器中(Chrome, Safari, Opera etc.),我们可以借助其中的Developer Tools 的Profiles 工具来对我们的程序进行内存检查。
4.2 Node.js 中的内存检查
在Node.js 中,我们可以使用node-heapdump 和node-memwatch 模块进行内存检查。
复制代码 代码如下:
var heapdump = require('heapdump');
var fs = require('fs');
var path = require('path');
fs.writeFileSync(path.join(__dirname, 'app.pid'), process.pid);
// ...
复制代码 代码如下: