函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行。这使得富有表现力的高阶函数抽象如map和forEach成为可能。它也是js异步I/O方法的核心。与此同时,也可以将代码表示为字符串的形式传递给eval函数以达到同样的功能。
程序员面临一个选择:应该将代码表示为函数还是字符串?
毫无疑问,应该将代码表示为函数。字符串表示代码不够灵活的一个重要原因是:它们不是闭包。
闭包回顾
js的函数值包含了比调用它们时执行所需要的代码还要多的信息。而且js函数值还在内部存储它们可能会引用的定义在其封闭作用域的变量。那些在其所涵盖的作用域内跟踪变量的函数被称为闭包。
详细的信息到之前《[Effective JavaScript 笔记] 第11条:熟练掌握闭包》查看
字符串封装代码
假设有一个简单的多次重复用户提供的动作的函数。
function repeat(n,action){ for(var i=0;i<n;i++){ eval(action); } }
该函数在全局作用域会不作得很好,因为eval函数会将出现的字符串中的所有变量引用作为全局变量来解释。例如,一个测试函数基准执行速度的脚本可能恰好使用全局的start和end变量来存储时间。
var start=[],end=[],timings=[]; repeat(1000,"start.push(Date.now());f();end.push(Date.now())"); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; }
但脚本很脆弱。如果我们简单地将代码移动到一个函数中,那么start和end变量将不再是全局变量。
function benchmark(){ var start=[],end=[],timings=[]; repeat(1000,"start.push(Date.now());f();end.push(Date.now())"); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; } return timings; }
这个时候repeat函数并不能访问benchmark函数的内部变量start,end。还是在全局空间查找start,end变量,如果没有还是较好的情况,可以根据错误提示,完成错误定位。如果这个时候全局中恰好有start,end变量,这个时候就会对全局变量进行修改,产生的行为无法进行预测。
闭包封装代码
还是使用上面的例子,但这一些我们使用闭包来对代码进行处理。
改写repeat函数,参数action是一个函数,而不是字符串
function repeat(n,action){ for(var i=0;i<n;i++){ action(); } }
改写benchmark函数,脚本能安全地引用闭包中的局部变量start,end,该闭包以repeat函数的回调函数传递进来。
function benchmark(){ var start=[],end=[],timings=[]; repeat(1000,function(){ start.push(Date.now()); f(); end.push(Date.now()); }); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; } return timings; }
eval函数的另一个问题是,通常一些高性能的引擎难优化字符串中的代码,因为编译器能不能尽可能早地获得源代码来及时 优化代码。函数表达式在其代码出现的同时就能被编译,这使得它更适合标准化编译。
其它eval相关内容可查看:
《[Effective JavaScript 笔记]第16条:避免使用eval创建局部变量》
《[Effective JavaScript 笔记]第17条:间接调用eval函数优于直接调用》
提示
-
当将字符串传递给eval函数以执行它们的API时,绝不要在字符串中包含局部变量引用
-
接受函数调用的API优于使用eval函数执行字符串的API
附录:代码完整版
把上面的代码整理一下,生成一个可以测试任何函数执行时间的代码
function repeat(n,action){ for(var i=0;i<n;i++){ action(); } } function benchmark(n,fn){ var start=[],end=[],timings=[]; repeat(n,function(){ start.push(Date.now()); fn(); end.push(Date.now()); }); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; } return timings; } //测试代码function concatString(a,b){ return a+b; } benchmark(10000,function(){concatString('1','2');});//常规调用 benchmark(10000,concatString.bind(null,'1','2'));//利于bind方法来产生新函数