zoukankan      html  css  js  c++  java
  • [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

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

    闭包回顾

    看下面这个图
    1465186516975

    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方法来产生新函数
  • 相关阅读:
    IE6常见CSS解释BUG及hack
    超链接标签a
    图片标签img
    如何让一个元素始终在窗口水平垂直居中
    display属性及属性值
    设置省略号
    如何让一个图片垂直居中
    post与get的区别
    绝对路径与相对路径
    数组的操作方法
  • 原文地址:https://www.cnblogs.com/wengxuesong/p/5563369.html
Copyright © 2011-2022 走看看