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

    var html = $('<a target="_blank" href="' + adCompContent.clickURL + '">< ...

  2. javascript继承(四)—prototype属性介绍

    js里每一个function都有一个prototype属性,而每一个实例都有constructor属性,并且每一个function的prototype都有一个constructor属性,这个属性会指向 ...

  3. 改善C#程序的建议3:在C#中选择正确的集合进行编码

    要选择正确的集合,我们首先要了解一些数据结构的知识.所谓数据结构,就是相互之间存在一种或多种特定关系的数据元素的集合.结合下图,我们看一下对集合的分类. 集合分类 在上图中,可以看到,集合总体上分为线 ...

  4. tomcat配置和优化

    转载: https://mp.weixin.qq.com/s?__biz=MzA3MzYwNjQ3NA==&mid=2651296654&idx=1&sn=b04fc6cecf ...

  5. 【BZOJ 1507】【NOI 2003】&【Tyvj P2388】Editor 块状链表模板题

    2016-06-18 当时关于块状链表的想法是错误的,之前维护的是一个动态的$\sqrt{n}$,所以常数巨大,今天才知道原因TwT,请不要参照这个程序为模板!!! 模板题水啊水~~~ 第一次写块状链 ...

  6. The Reflection And Amplification Attacks && NTP Reply Flood Attack Based On NTP

    目录 . NTP简介 . NTP协议格式 . NTP Reflect反射漏洞和NTP协议的关系 . 漏洞触发的前提和攻击需要的步骤 . 针对漏洞的攻防思考 1. NTP简介 Network Time ...

  7. ubuntu 12.04安装telnet和ssh服务

    ubuntu安装telnet服务 1. sudo apt-get install xinetd telnetd sudo vi /etc/inetd.conf并加入以下一行,假如没有发现这个文件,自己 ...

  8. php中图片文件的导入,上传与下载

    ---------------------------------------------图片的导入-------------------------------------------------- ...

  9. 转 web项目中的web.xml元素解析

    转 web项目中的web.xml元素解析 发表于1年前(2014-11-26 15:45)   阅读(497) | 评论(0) 16人收藏此文章, 我要收藏 赞0 上海源创会5月15日与你相约[玫瑰里 ...

  10. .NET平台上的Memcached客户端介绍

    早上接到一个任务,需要对Linux服务器的Memcached的update操作进行性能测试,我发现我是一个典型的“手里拿着锤子, 就把所有问题都当成钉子”的人.我第一个念头就是,上Memcached的 ...