你的位置:首页 > Java教程

[Java教程][Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码


函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行。这使得富有表现力的高阶函数抽象如map和forEach成为可能。它也是js异步I/O方法的核心。与此同时,也可以将代码表示为字符串的形式传递给eval函数以达到同样的功能。
程序员面临一个选择:应该将代码表示为函数还是字符串?
毫无疑问,应该将代码表示为函数。字符串表示代码不够灵活的一个重要原因是:它们不是闭包。

闭包回顾

看下面这个图
1465186516975

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方法来产生新函数