[Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行。这使得富有表现力的高阶函数抽象如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条:使用闭包而不是字符串来封装代码的更多相关文章
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记] 第11条:熟练掌握闭包
理解闭包三个基本的事实 第一个事实:js允许你引用在当前函数以外定义的变量. function makeSandwich(){ var magicIngredient=”peanut butter”; ...
- [Effective JavaScript 笔记]第35条:使用闭包存储私有数据
js的对象系统并没有特别鼓励或强制信息隐藏.所有的属性名都是一个字符串,任意一个程序都可以简单地通过访问属性名来获取相应的对象属性.例如,for...in循环.ES5的Object.keys()和Ob ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合
对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...
- [Effective JavaScript 笔记]第50条:迭代方法优于循环
"懒"程序员才是好程序员.复制和粘贴样板代码,一但代码有错误,或代码功能修改,那么程序在修改的时候,程序员需要找到所有相同功能的代码一处处进行修改.这会使人重复发明轮子,而且在别人 ...
- [Effective JavaScript 笔记]第62条:在异步序列中使用嵌套或命名的回调函数
异步程序的操作顺序 61条讲述了异步API如何执行潜在的代价高昂的I/O操作,而不阻塞应用程序继续处理其他输入.理解异步程序的操作顺序刚开始有点混乱.例如,下面的代码会在打印"finishe ...
随机推荐
- LinuxMint下Docker的安装部署和验证
通过lsb_release命令查看以下我的LinuxMint发行版, 查看以下我的Linux内核版本, Docker要求Linux内核版本必须在要在3.10以上,显然我们的系统是满足的. 1. Doc ...
- SQL Server 收缩日志
一. SQL Server 2008 收缩日志 (1) 使用SQL管理器收缩日志 第一步执行如下命令 ALTER DATABASE platform SET RECOVERY SIMPLE GO 第二 ...
- linux 安装redmine 遇到的问题
1.编译安装ruby-2.3.1时 需要先安装libyaml.libyaml-devel 2. 安装gem install rake ERROR: Loading command: install ( ...
- 新建URL,cookie技术
假如用户禁止了cookie,怎么用session技术 //在这里访问一下session request.getSession(); //这里注意的是 要使用一下session while(iterat ...
- iOS边练边学--iOS中的(ARC下)单粒模式(GCD实现)
一.ARC中实现单粒模式 在.m 保留一个全局的static的实例 static id _名称; 重写allocWithZone:方法,在这里创建唯一的实例 提供一个类方法让外界访问唯一的实例 实现c ...
- 安装Eclipse插件
安装Eclipse插件 从eclipse 3.6开始,eclipse有一个marketplace,这个类似现在手机的app store一样,可以在其中检索相关插件,直接安装,打开help--> ...
- NOIP2013 货车运输 (最大生成树+树上倍增LCA)
死磕一道题,中间发现倍增还是掌握的不熟 ,而且深刻理解:SB错误毁一生,憋了近2个小时才调对,不过还好一遍AC省了更多的事,不然我一定会疯掉的... 3287 货车运输 2013年NOIP全国联赛提高 ...
- PHP Simulation HTTP Request(undone)
目录 . 引言 . file_get_contents版本 . Socket版本 . Curl版本 . Curl版本() . 模拟文件上传 0. 引言 本文总结了通过PHP代码方式模拟各种HTTP请求 ...
- 设置div居中
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- spring事务学习(转账案例)(一)
一.创建数据库并插入数据 create database spring_transaction; use spring_transaction; create table account( id in ...