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

时间:2022-09-06 15:32:41

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

闭包回顾

看下面这个图
[Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

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. &lbrack;Effective JavaScript 笔记&rsqb;第28条:不要信赖函数对象的toString方法

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

  2. &lbrack;Effective JavaScript 笔记&rsqb; 第4条:原始类型优于封闭对象

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

  3. &lbrack;Effective JavaScript 笔记&rsqb; 第5条:避免对混合类型使用&equals;&equals;运算符

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

  4. &lbrack;Effective JavaScript 笔记&rsqb; 第11条:熟练掌握闭包

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

  5. &lbrack;Effective JavaScript 笔记&rsqb;第35条:使用闭包存储私有数据

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

  6. &lbrack;Effective JavaScript 笔记&rsqb;第68条:使用promise模式清洁异步逻辑

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

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

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

  8. &lbrack;Effective JavaScript 笔记&rsqb;第50条:迭代方法优于循环

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

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

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

随机推荐

  1. 记一次裸迁 MySQL 经历

    记一次裸迁MySQL经历 前言:博主企业有一台企业阿里云机器,因为安装了云锁,造成服务器动不动就给我所死服务器.(就是那种 chattr +i /bin/bash ,分分钟日死狗 )趁着周末,Boos ...

  2. minigui交叉编译整理

    简介 MiniGUI 是一款面向嵌入式系统的高级窗口系统(Windowing System)和图形用户界面(Graphical User Interface,GUI)支持系统,由魏永明先生于 1998 ...

  3. delphi 10&period;1 berlin最新的开发框架:咏南中间件&plus;咏南开发框架,购买后提供全部的源码

    咏南中间件+咏南开发框架支持最新的delphi 10.1(berlin),老用户提供免费升级. 购买提供:中间件源码 附带福利(赠送): CS开发框架源码BS开发框架源码移动APP源码中间件集群源码二 ...

  4. Lamp源码包安装实录

    Lamp源码包安装实录 附件中是安装步骤,下载站点里包含视频(http://down.51cto.com/data/460776) 本文出自 "李晨光原创技术博客" 博客,请务必保 ...

  5. Go Slices&colon; usage and internals

    Introduction Go's slice type provides a convenient and efficient means of working with sequences of ...

  6. POJ 1026 Cipher(更换)

                                                                   Cipher Time Limit: 1000MS   Memory Li ...

  7. LeetCode&lpar;31&rpar;-Factorial Trailing Zeroes

    题目: Given an integer n, return the number of trailing zeroes in n!. Note: Your solution should be in ...

  8. IEnumerable&lt&semi;T&gt&semi;和IQueryable&lt&semi;T&gt&semi;区别

    LINQ查询方法一共提供了两种扩展方法,在System.Linq命名空间下,有两个静态类:Enumerable类,它针对继承了IEnumerable<T>接口的集合进行扩展:Queryab ...

  9. TCP接收缓存大小的手动调整

    给出了几个可调节的参数,它们可以帮助您提高 Linux TCP/IP 栈的性能. 表 1. TCP/IP 栈性能使用的可调节内核参数 可调节的参数 默认值 选项说明 /proc/sys/net/cor ...

  10. AVL平衡二叉树实现,图解分析,C&plus;&plus;描述,完整可执行代码

    body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...