函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行。这使得富有表现力的高阶函数抽象如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方法来产生新函数

[Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码的更多相关文章

  1. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  2. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  3. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  4. [Effective JavaScript 笔记] 第11条:熟练掌握闭包

    理解闭包三个基本的事实 第一个事实:js允许你引用在当前函数以外定义的变量. function makeSandwich(){ var magicIngredient=”peanut butter”; ...

  5. [Effective JavaScript 笔记]第35条:使用闭包存储私有数据

    js的对象系统并没有特别鼓励或强制信息隐藏.所有的属性名都是一个字符串,任意一个程序都可以简单地通过访问属性名来获取相应的对象属性.例如,for...in循环.ES5的Object.keys()和Ob ...

  6. [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

    构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...

  7. [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

    对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...

  8. [Effective JavaScript 笔记]第50条:迭代方法优于循环

    "懒"程序员才是好程序员.复制和粘贴样板代码,一但代码有错误,或代码功能修改,那么程序在修改的时候,程序员需要找到所有相同功能的代码一处处进行修改.这会使人重复发明轮子,而且在别人 ...

  9. [Effective JavaScript 笔记]第62条:在异步序列中使用嵌套或命名的回调函数

    异步程序的操作顺序 61条讲述了异步API如何执行潜在的代价高昂的I/O操作,而不阻塞应用程序继续处理其他输入.理解异步程序的操作顺序刚开始有点混乱.例如,下面的代码会在打印"finishe ...

随机推荐

  1. nodejs初学————安装篇(iis8.5+windows8.1)

    nodejs很久前就想玩玩,不过一直没时间,昨晚花了4个小时来捣鼓到iis上架设成功了,废话不说了. PS:我的系统是windows8.1 x64,所以自带iis8.5的,下载的文件也是x64的. N ...

  2. Servlet响应的中文字符集问题

    在Servlet中利用response向客户端浏览器输出中文时有时会遇到乱码问题,总结如下: response输出流有两种,一是以字节流输出,一是以字符流输出. 一.以字节流输出: 1.默认编码输出木 ...

  3. c# 6.0新特性(一)

    写在前面 接近年底了,基本上没什么活了,就学点新东西,就想着了解下c# 6.0的新特性.在code project上看到了一篇不错的文章,就准备翻译一下,顺便照着学习学习.废话不多说,直奔主题. 原文 ...

  4. ThreadLocal模式的核心元素

    首先来看ThreadLocal模式的实现机理:在JDK的早期版本中,提供了一种解决多线程并发问题的方案:java.lang.ThreadLocal类.ThreadLocal类在维护变量时,世纪使用了当 ...

  5. 每天一个linux命令(18):find 命令概览

    Linux 下find命令在目录结构中搜索文件,并执行指定的操作.Linux下find命令提供了相当多的查找条件,功能很强大.由于find具有强大的功能,所 以它的选项也很多,其中大部分选项都值得我们 ...

  6. 笔记:js的replace函数

    replace函数 js的replace函数与c#的有一个不同,js的只替换第一个字符 例如,var a=',1,2' var b=a.replace(',','') 结果b='1,2'(ps:这是一 ...

  7. Linq 分页不可缺少的两个方法

    //LINQ分页的方法 //1.获取总页数 public int GetPageCount(int pageSize)//pageSize是每页的行数 { //先查出总共有多少行 int rowCou ...

  8. 【BZOJ 1001】狼抓兔子 对偶图+SPFA

    这道题是求图的最小割,也就是用最大流.但因为边太多,最大流算法会T,因此不能用最大流算法. 因为这是个平面图,所以求平面图的最小割可以使用特殊的技巧就是求对偶图然后求对偶图的最短路.把每个面看成一个点 ...

  9. Windows下python的配置

    Windows下python的配置 希望这是最后一次写关于python的配置博客了,已经被python的安装烦的不行了.一开始我希望安装python.手动配置pip并使用pip安装numpy,然而发现 ...

  10. C++处理一个动态规划的问题

    嗯哼,别人问的问题,看的我也头晕,百度了一下动态规划,看了看才想起来该怎么做,今天写了写代码,实现了~ 要求是递归,动态规划,想了想这种方法也是最简单的~ 所谓动态规划:把多阶段过程转化为一系列单阶段 ...