函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行。这使得富有表现力的高阶函数抽象如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. Android 中R文件丢失问题解决方案

    Project → clean 项目上右键→android Tools→ fix project 检查xml文件中有无命名错误,特别是@+id写成@id的[特别是这条,注意看控制台打印的xml错误]

  2. 浅析Java异常

    1.什么是异常 结构不佳的代码不能运行,这是Java的基本理念. 发现错误的理想时机是在编译期.然而,编译器并不能发现所有的错误,余下的问题就需要在程序运行时解决.这就需要错误能通过某种方式,把适当的 ...

  3. SVN快速入门(TSVN)

    作者: 北京群英汇信息技术有限公司 网址: http://www.ossxp.com/ 版本: 0.1-35 日期: 2011-07-05 10:51:59 版权信息: 目录 1   安装Tortoi ...

  4. 多个TableView的练习

    效果图: 左边图片的代码: // // SecViewController.m // UI__多个TableView练习 // // Created by dllo on 16/3/17. // Co ...

  5. Javascript写入txt和读取txt文件示例

    1. 写入 FileSystemObject可以将文件翻译成文件流. 第一步: 例: 复制代码 代码如下: Var fso=new ActiveXObject(Scripting.FileSystem ...

  6. 每天一个linux命令(30):cal 命令

    cal命令可以用来显示公历(阳历)日历.公历是现在国际通用的历法,又称格列历,通称阳历.“阳历”又名“太阳历”,系以地球绕行太阳一周为一年,为西方各国所通用,故又名“西历”. 1.命令格式: cal  ...

  7. html 中添加背景音乐

    Embed (一).基本语法: embed src=url 说明:embed可以用来插入各种多媒体,格式可以是 Midi.Wav.AIFF.AU.MP3等等, Netscape及新版的IE 都支持.u ...

  8. card-test

    <!DOCTYPE html><html> <head> <title>test</title> <style type=" ...

  9. JWPlayer中字幕文件的配置

    最近应项目要求研究JWPlayer,视研究进度可能会将解决的问题或者一些配置方法写在这里. jwplayer支持vtt和srt格式的字幕文件,在视频中可以选择加载多个字幕文件(常用于多语言字幕),并且 ...

  10. BZOJ 2435 道路修建 NOI2011 树形DP

    一看到这道题觉得很水,打了递归树形DP后RE了一组,后来发现必须非递归(BFS) 递归版本84分: #include<cstdio> #include<cstring> #in ...