分类: js

有关 JavaScript 的技术文章和行业分析文章。

  • 使用 Promise 封装 FileReader

    使用 Promise 封装 FileReader

    Promise 在处理异步的时候是个很好的选择,可以减少嵌套层次,让代码更好读,逻辑更清晰。ES6 将其加入规范,jQuery 3.0 也修改实现向规范靠拢(3.0 发布公告)。一些新增元素比如 .fetch() 原生就 “thenable”,不过大多数以往的 API 还要依赖回调,这个时候,我们只要将它们重新封装,就能避开嵌套陷阱,享受 Promise 带来的愉悦体验。

    Promise 一般用法

    先来看下 Promise 的一般用法。

    // 声明 Promise 对象
    var p = new Promise(function (resolve, reject) {
      // 不管啥时候,该执行then了,就调用 resolve
      setTimeout(function () { 
        resolve(1);
      }, 5000);
    
      // 或者不管啥问题,就调用 reject
      if (somethingWrong) {
        reject('2');
      }      
    });
    
    // 使用 Promise 对象
    p.then(function (num) {
      // 对应上面的 resolve
      console.log(num); // 1
    }, function (num) {
      // 对应上面的 reject
      console.log(num); // 2
    });
    

    Promise 的驱动模型并不复杂:任何操作,假定它只有两个结果,成功或者失败。那么只需要在合适的时间调用合适的程序,进入合适的后续步骤即可。.then() 顾名思义,就是下一步的意思,当前面的 Promise 有了结果——即调用 resolve 或者 reject——之后,就启动对应的处理函数。

    Promise 实例创建后就会开始执行,判定结果需要我们自己来,比如加载成功,或者满足某个条件,等等。通过串联 .then() 则可以完成一系列操作。每次调用 .then() 都会创建一个新的 Promise 实例,它会静静等待前面的实例状态改变后再开始执行。

    封装 FileReader

    接下来开始封装。思路很简单,FileReader 除了提供各种 read 方法,还有几个事件钩子,其中 onerroronload 很明显可以作为判断任务是否完成的依据。加载成功的话,就需要用到文件内容,所以将文件或文件内容传递到下一步也十分必要。

    最后完成的代码如下:

    function reader (file, options) {
      options = options || {};
      return new Promise(function (resolve, reject) {
        let reader = new FileReader();
    
        reader.onload = function () {
          resolve(reader);
        };
        reader.onerror = reject;
    
        if (options.accept && !new RegExp(options.accept).test(file.type)) {
          reject({
            code: 1,
            msg: 'wrong file type'
          });
        }
    
        if (!file.type || /^text\//i.test(file.type)) {
          reader.readAsText(file);
        } else {
          reader.readAsDataURL(file);
        }
      });
    }
    

    为了能真正派上用场,里面还有一些验证文件类型的操作,不过跟本文主旨无关,略过不表。这段代码的核心是创建一个 Promise 对象,等待 FileReader 读取完成后调用 resolve 方法,或者出现问题时调用 reject 方法。

    Github Gist 里也放了一份。

    使用刚才封装好的函数

    接下来就可以在项目中使用了:

    reader(file)
      .then(function (reader) {
        console.log(reader.result);
      })
      .catch(function (error) {
        console.log(error);
      });
    

    .then() 支持两个参数,第一个在 Promise 成功时启动,第二个自然在失败时启动。用 .catch() 可以实现同样地效果。Promise 的好处除了可读性更佳以外,返回的 Promise 对象还可以任意传递,继续进行链式调用,有很大想象空间。

    继续 .then()

    于是我们不妨串联更多操作(本来想写个断点续传的,回头再说吧):

    reader(file)
      .then(function (reader) {
        return new Promise(function (resolve, reject) {
          // 就随便暂停个5秒吧……
          setTimeout(function () {
            resolve(reader.result); 
          }, 5000);
        });
      })
      .then(function (content) {
        console.log(content);
      });
    

    总结

    这其实是我第一次用 Promise,上次翻译 jQuery 发布公告的时候我它也只是一知半解,对它的解读也糊里糊涂。我很喜欢在业余项目中学习使用新技术,最近开发 Chrome 插件的时候就尝试了一把,感觉不错。使用过程比我想象的复杂也比我想象的简单,这套设计很棒,能解决不少实际问题,也给了我很大启发,将来我应该会把很多地方的实现都做这样的修改。


    参考文献

    除去第一段的各个链接,还有一些文章值得一看。

    ECMAScript 6 入门:Promise 对象

  • jQuery 1.12/2.2 未计入文档的修改

    之前可以这样写

        $('[href=#hash]')
    

    现在必须这样写

        $('[href="#hash"]')
    

    其它属性选择器,如 attr^="#value" 等,也一样。

  • jQuery 3.0 beta 发布

    jQuery 3.0 beta 发布

    原创不够,译文来凑。

    跟上篇一样是编译,不准备逐字翻。比如,我会把“we”译成“jQuery官方团队”,或者“他们”。

    正文开始。


    (初译版,待校正)

    歪果仁也要双喜临门,于是 jQuery 官方团队选在 jQuery 面世10周年之际发布 3.0 beta。大家还记得上周发布的1.x和2.x小版本更新吧,他们日后会继续维护这俩分支,一段时间,当然只改bug。因为3.0才是未来嘛!

    需要支持IE6-8的可怜虫请继续使用1.12分支上的最新版。

    没有兼容版了

    only-one

    看过 alpha 发布公告的同学可能还记得,他们起初准备同时发布3.0和“3.0兼容版”,适配老浏览器。但是现在他们想通了。微软今年1月12日宣布放弃IE8910,jQuery 会保守一些,不过至少不打算支持IE8,所以就放弃所谓的兼容版,以后就只有一个版本了。

    尽管大版本号发生变化,jQuery 团队仍然认为升级不会太麻烦。大变化是有,不过影响应该没有很大,而且他们还开发了3.0专用迁移插件,可以帮助我们找到代码中的兼容性问题。所以,请尽早使用新版本,并及时将体验反馈给他们,这样才能让jQuery 越来越好。

    你可以直接通过CDN使用:

    https://code.jquery.com/jquery-3.0.0-beta1.js

    https://code.jquery.com/jquery-3.0.0-beta1.min.js

    或者用NPM安装到本地

        npm install jquery@3.0.0-beta1
    

    重点变化

    接下来就是需要关注的新功能、升级、以及 Bug 修正了。完整列表见于 Github

    .show().hide()

    刚启动3.0的时候,他们尝试将这两个方法修改为“删除行内 display:none 样式”(.show())和 “增加行内 display:none 样式”(.hide())。这样可以极大的简化实现所需的代码,并且显著改善性能(计算量大幅下降了嘛)。但是,这给广大用户带来了不小的麻烦,因为移除 display:none 很多时候并不能让元素显示出来,比如有其它CSS将它置为隐藏。最终 jQuery 团队不得不承认没有办法完成期望中的简化。

    于是他们放弃了这次尝试。不过,即便如此,他们还是想办法改善了隐藏大量元素时的性能。

    .data() 的注意事项

    为了兼容 HTML5 dataset 规范,jQuery 团队升级了 .data() 实现。如今所有的 key 都会从短线连接(a-bc-de)转换成驼峰式(aBcDe),数字不再转换。于是,“foo-bar”转换后和“fooBar”是一样的,但“foo-42”和“foo42”就不一样。当用户直接使用 .data() 取所有数据时,就需要注意这个区别,尤其不要再误用 .data('foo42') 取代 .data('foo-42')

    问题汇报处

    jQuery.Deferred 现在兼容 Promises/A+

    Promise 我用的比较少,看到的文档也不多,不太清楚里面的几个名词怎么翻译,所以我尽量用括号备注。

    jQuery.Deferred 得到升级,兼容 Promises/A+ 和 ES2015 Promises,并且已经通过 Promises/A+ Compliance Test Suite 认证。这意味着 .then() 的使用机制发生了非常显著的变动:

    • .then() 回调函数里抛出的异常,会成为失败(rejection)处理函数的参数。之前,异常会冒泡,中断函数执行,并永久性锁死上下级 Deferred 对象。
    • .then() 返回的 Deferred 对象,如果它的回调函数抛出异常,将会调用失败(rejection)处理函数,并作为参数传进去;如果返回其它不能继续 .then() 的对象,就会调用成功(fulfillment)处理函数,返回值也作为参数传进去。以前,失败处理函数返回任何值都会将其置为失败。
    • 回调函数将固定为异步执行。以前它们在绑定或者解决时会被立即执行。
    • 进度的回调函数不会再把它绑定的 Deferred 对象标记为完成。

    以下代码演示当上级 Deferred 触发 rejected 时,下级调用失败回调函数之后的结果:

        var parent = jQuery.Deferred();
        var child = parent.then( null, function() {
          return "bar";
        });
        var callback = function( state ) {
          return function( value ) {
            console.log( state, value );
            throw new Error( "baz" );
          };
        };
        var grandchildren = [
          child.then( callback( "fulfilled" ), callback( "rejected" ) ),
          child.then( callback( "fulfilled" ), callback( "rejected" ) )
        ];
        parent.reject( "foo" );
        console.log( "parent resolved" );
    

    在 jQuery 3.0 中,会先输出“parent resolved”,然后再执行回调函数;然后下级 Deferred 进入失败状态,执行函数,返回“bar”;“bar”被转化为三级 Deferred 的成功,于是输出“fulfilled bar”;接着,抛出错误“baz”,导致三级函数进入错误处理;最后,输出“rejected baz”。如果是之前的版本,下级 Deferred 会认为上级 Deferred 失败,进入错误处理,输出“rejected bar”;并且在未捕获的错误“baz”被抛出后,整个进程立刻被终止;此时,由于三级函数未处理完,“parent resolved”也不会输出。

    捕获异常不仅对在浏览器里调试有帮助,在失败后的回调函数中处理它们,也使得代码更加直观合理。请谨记,这也意味着使用 Promise 模式的时候,要至少设置一个函数处理失败。不然的话,所有错误都会被忽略掉。

    如果你还想使用以前的代码,可以用 .pipe() 函数替换 .then()。后者虽然已经被标记为不建议使用,但它接口一样,而且会暂时延续之前的逻辑。

    我们还开发了辅助调试调试 Promises/A+ 的工具。如果你觉得有些错误好像没触发出来,可以试用之

    jQuery.when 函数也升级了,现在可以传入任何支持 .then() 的对象,包括原生 Promise 对象。

    https://github.com/jquery/jquery/issues/1722
    https://github.com/jquery/jquery/issues/2102

    Deferreds 对象增加 .catch() 方法

    Promise 对象增加 .catch()方法, 作为 .then(null, fn) 的别名,专门处理失败。

    问题汇报处

    移除 jQuery.ajax 的 Deferred 同名方法的特殊用法

    jqXHR 是 Promise 对象,同时也有一些专有方法,比如 .abort(),用于取消请求。

    现在,越来越多的开发者已经在异步中(如AJAX)使用 Promise 模式,如此一来,jQuery.ajax 返回的对象再包含特殊用法就不合时宜了。

    success, error, complete(这些将被移除)
    done, fail, always(这些应该会保留)

    需要注意的是,那些继续存在的回调函数不会有任何变化,只有 Promise 的方法会受影响。

    问题汇报处

    错误不再被悄无声息地消失

    有时候我们难免会想:“window 的偏移值(window.offset)是多少?”等你回过味儿来你会发现这个问题其实挺蠢的,window 怎么会有偏移呢?

    过去,jQuery 面对这种情况,从来不会抛出错误,而是尽量返回有意义的值,比如刚才那个问题,就返回 { top: 0, left: 0 }。3.0之后,他们尝试不再什么乱七八糟的代码都兼容,而是直接抛出错误,让用户不要忽略这些问题。大家可以试用 beta 版,看看你的代码有没有“参数无效”之类的错误。

    问题汇报处

    .width().height().css('width').css('height')都将返回小数

    之前,jQuery 取宽高的时候会返回四舍五入之后的整数值。有些浏览器可以返回次像素,比如 IE 和 Firefox,有些用户需要这些精细的数据来调整布局。jQuery 官方团队认为这项变化对大多数人没有影响,不过如果你受此困扰,也请告知他们。

    问题汇报处

    移除废弃的事件别名

    jQuery 1.8之后弃用的 .load.unload.error 方法被正式移除。以后都请使用 .on() 注册事件侦听器。

    问题汇报处

    使用 requestAnimationFrame 改善动画效果

    新版 jQuery 在支持 requestAnimationFrame 的平台上会自动使用它来改善性能。除去 IE9 和 低于4.4的Android,都可以藉此让动画效果更平滑,占用更少的CPU时间,降低移动设备的电力损耗。

    其实 jQuery 团队几年前就曾尝试使用这项技术,但是当时遇到很严重的兼容性问题,以致于不得不放弃。如今他们采用新策略,当浏览器 tab 不显示时挂起动画,这样就可以规避大部分问题。不过这样一来,那些必须依赖动画全局实时播放的功能就无法实现了。

    .unwrap( selector )

    3.0之前,.unwrap() 方法不接受任何参数。如今,用户可以通过传入选择器来移除指定的外部容器。

    问题汇报处

    jQuery.fn.domManip 不能再使用了

    1.12 和 2.2 版本把 jQuery.dir,jQuery.siblingjQuery.buildFragmentjQuery.access,和 jQuery.swap 都修改为私有函数。现在,jQuery.fn.domManip 也一样。它们未来都只允许内部使用,不会被载入使用文档。官方团队认为这样做可以避免用户困惑。

    https://github.com/jquery/jquery/pull/2182
    https://github.com/jquery/jquery/issues/2224
    https://github.com/jquery/jquery/issues/2225

    jQuery 自定义选择器提速

    拜 Paul Irish 在 Google 的工作所赐,新版 jQuery 可以躲过一些坑,比如前文提到的 :visible,通过减少冗余代码,获得了17倍的速度提升!

    但用户仍需小心,即便经过优化,:visible:hidden 选择器仍然会消耗大量的系统资源,因为它们需要浏览器检查元素是否显示在页面上。在最坏的情况下,有可能需要重新计算全部 CSS 样式和页面布局!当然也不是说不要用(不然还写它做甚),只是记得测试一下,看有没有因此导致性能问题。

    这项工作他们 1.12/2.2 时就完成了,只不过拿到这里来说。

    问题汇报处


    原文:jQuery 3.0 Beta Released

  • jQuery  2.2 和 1.12 新版本发布

    jQuery 2.2 和 1.12 新版本发布

    本文编译自官方博客,不是照字翻译。

    新年新气象,jQuery 团队于昨日发布了两个新版本:1.12 和 2.2。这两个版本都包含了大量的Bug修正和功能改进。基本上这会是3.0之前最后一次发布。不过由于3.0不做向下兼容,所以届时 jQuery 团队仍然会继续维护这两个版本,当然肯定只做Bug修正。关于3.0的消息将在不久之后公布。

    那么新版本都有些什么变化呢?

    性能提升

    此版本缩短了 Sizzle 的引用路径,这样当原生 querySelectorAllmatchesSelector 无法使用时,可以带来性能提升。在生产环境中效果明显。

    新功能

    小升级很多,这里只拣要紧的说。

    SVG 的类操作

    作为 HTML 库,支持 SVG 元素是理所应当的。新版本里大家就可以使用 .addClass().removeClass().toggleClass().hasClass() 操作 SVG 对象的 class 了。不过需要注意的是,因为 SVG 和 HTML 还是有很多不同,所以如果真的要进行复杂操作,还是选用其它更专业的类库比较好。

    jQuery.post 和 jQuery.get 支持对象参数

    如题:

        jQuery.post({
          url: “/example”
        });
    

    这样一来好处还比较多,比如设置回调函数的 context,或者跨域 post 时可以 withCredential: true

    新运算符支持

    支持 ES6/ES2015 引入的新运算符, jQuery 对象可以用 for-of 遍历了。

        for (element of $elements) {
          console.log(element);
        }
    

    jQuery.htmlPrefilter()

    HTML5不要求标签必须闭合,但是XML要求。这个函数就是用来作转换的。这样我们使用 .html().append().replaceWith() 时就不需要人工转换了。进而,我们也就不需要那么严格的校验输入了。

    jQuery.uniqueSort()

    jQuery.unique() 命名有点问题,没能体现排序,所以这次新增了 jQuery.uniqueSort()。它们俩其实是一回事儿,不过文档中将只记录后者。

    这个函数仍然专注于 DOM 节点的排序和排重,请勿乱用。

    总结

    这个版本理论上没有太大变化,可以平滑升级。不过如果谁还是脸黑遇到什么问题,也请汇报给官方

    具体变化

    就不一一翻译了,想了解的可以看原文:jQuery 2.2 and 1.12 Released

  • npm start, npm stop

    npm start, npm stop

    使用 npm 可以直接调用 package.json 里面 scripts 标签里定义的脚本。比如,Astinus 项目中,index.js 是入口JS,并且需要 ES6 支持,就可以这样:

        // package.json
        {
          "scripts": {
            “start": "node index --harmony"
          }
        }
    

    然后直接运行

        npm start
    

    即可。

    那么怎么终止进程呢?

    首先要在 index.js 里增加一句

        process.title = 'astinus';
    

    声明该应用的标题是“astinus”,然后增加脚本:

        {
          "scripts": {
            "start": "node index --harmony",
            "stop": "killall SIGINT astinus"
          }
        }
    

    之后就可以

        npm stop
    

    参考:

  • bower小技巧一则

    bower小技巧一则

    早先看到过一个用socket服务在线调试PHP的工具,后来怎么也找不到,于是决定自己造轮子。socket.io提供WebSocket服务,nodejs(今天竟然发布了5.0stable,简直丧心病狂) + express.js做服务器端。以前没有正经用过express.js,不过感觉上把Web所需的内容和nodejs所需的内容混在一起应该不是什么最佳实践,还是分开比较好。

    使用express.static('public')可以把public目录映射为web根目录。但是用bower管理依赖的话,默认安装目录在项目的根目录,虽然也可以映射出去,不过实际路径和项目路径有异,Webstorm里的黄色波浪线看起来也很不舒服。

    Google一下果然bower可以解决这个问题。在项目根目录下创建.bowerrc文件,按照JSON的写法,写入:

        {
          "directory": "public/components"
        }
    

    就可以指定别的目录作为安装依赖的位置。然后可以移动原来的依赖文件夹,也可以重新执行bower install

  • Chrome表单验证和keyup导致的灵异问题

    Chrome表单验证和keyup导致的灵异问题

    先说下环境,Mac OS El Capitan + Chrome 46,框架是Backbone + Boostrap。

    我做了一个自动搜索组件,独立测试时一切正常,放到产品中,别的都没问题,只有回车会出问题。代码在这里

    不用说,这个问题很诡异,调试了半天没有头绪,只能通过观察现象去推测:

    1. 没有报错
    2. 除了回车,其它功能正常
    3. 除了回车,keyUpHandler都能被正确触发
    4. 回车后,光标跳到其它元素

    看来看去,第4条最可疑——我按的是回车,它会什么会跳到别的单元格呢?

    这次运气比较好,表单中的接下来的几个元素刚好是日期,用到Bootstrap Datetimepicker这个插件,focus之后会自动填充日期。于是我发现,每次跳到的“其它元素”,都是原先空白的,而且是required的;不会跳到固定的,或者紧挨着的那个文本框。

    于是我便想,会不会是:

    1. 表单submitkeydown之后触发
    2. 触发之后进行表单验证,发现有未填的required元素,于是跳到该元素并提示
    3. Bootstrap Datetimepicker响应focus时间,填入日期,提示消失
    4. 我的Typeahead响应blur事件,隐藏列表
    5. keyup事件触发,但是输入框已没有焦点,就没有触发

    这个推测看起来有点道理,于是我把侦听的事件改成keydown,问题果然解决。

  • 初尝Gulp

    初尝Gulp

    Gulp自出世起,热度就很高。它利用了Node的Stream机制,速度很快;函数式的写法也很便于阅读。这两大优势让它一跃成为最受欢迎的批处理工具,几乎占据了半壁江山(2015前端工具普查)。

    我之前一直用Grunt,为了不落后于时代,前几天在一个业余项目中试用了一下Gulp,以下是我的感受。

    确实好读

    虽然无法得到证实,不过我相信Grunt的设计中有很多都是借鉴Ant得来的——在没有Grunt之前,我曾经用Ant脚本 + Google Closer Compiler + Google Closer Stylesheets 压缩代码——比如,它的配置是JSON格式的,跟XML非常相像。Grunt的问题也在这里:声明任务需要写JSON配置文件,不同的任务需要写在一起,然后通过registerTask('task', ['task1', 'task2:js', 'task3', ....])来组织。这样前后脱节,确实既不好写,也不好读。个把月回来完全看不懂也不算意外……

    而Gulp的设计则更加自由,它充分利用JavaScript的函数式写法,一个任务就处理一批文件,从上往下看,结构非常清晰:

        gulp.task('js', function () {
          gulp.src(['js/'])
            .pipe(concat())
            .pipe(replace('@version@', version))
            .pipe(uglify())
            .pipe(gulp.dest('dist/js/app.js'));
        });
    

    胜者:Gulp

    速度快,然无卵

    Gulp受推崇的另一大原因就是速度快,一般的任务都是毫秒级。

    不过,这对于我来说意义不大。作为一名人民币玩家,哥的惯用IDE是WebStorm和PHPStorm,均支持文件实时编译和自动刷新等常用功能。所以我只需要批处理工具帮我压缩文件和打包即可。

    这个回合:打平。

    Stream VS Native,异步 VS 同步

    前面提到,Gulp利用了Node的Stream机制,它的内部是处理文档流。这方面Grunt比较保守,仍然是把文件读进内存,然后进行字符串操作。二者的机制不同不好对比,不过作为从页面仔成长起来的前端工程师,不得不承认我对的理解并不深刻,对文件读取和二进制操作也不甚了了,所以在理解二者和开发业务插件的时候,我宁可选择Grunt。

    另外,Gulp的任务是异步的,所以在我的使用场景中——先clean目录,然后各种压缩,最后打包成zip——就很难搞(所以我觉得很多人用Gulp只是为了实时编译预处理文件)。后来用了sequence插件才搞定。

    这方面我觉得Grunt虽然保守,但是更接近前端开发者的知识结构,学习成本更低。

    胜者:Grunt

    杂项

    Gulp的团队应该是有代码洁癖的,比如,他们有一个官方插件仓库,这很正常;但是他们竟然还有一个Gulp插件黑名单……简直匪夷所思……

    所以Gulp团队显然不愿意重复发明轮子,比如简单的clean目录,他们就不愿意做,而且也不让别人做,因为已经有很多工具可以实现这个功能了……但是对于初学者比如我来说,就会在插件引擎中找半天,最后还是Google到相关指引才得到正确的做法。

    这方面Grunt团队做的很好。Grunt插件中有一组是官方提供的,以grunt-contrib为命名空间,基本上,使用这套组件就可以完成我所有的日常工作了。

    胜者:Grunt


    总结

    大趋势上,Gulp的确势头迅猛。但是从实际体验中,我觉得Grunt目前更成熟,也能满足工作所需。

    我并不想说Gulp不好,只是现在的我用起来不太顺手。如果哪天我有时间好好搞搞后端,搞搞Node开发,把Stream概念搞清楚,也许我也会转投Gulp。不过目前,还是继续用Grunt吧。

  • 记一个踩过三次的坑……

    记一个踩过三次的坑……

    图文无关。太长时间没写文被降权了么,怎么最近流量跌这么厉害……

    老后台的架构仍然是PHP渲染页面,这里我选择mustache.php作为模板引擎。在某些场景下(比如搜索),我希望在前端使用复用同样的模板。正如前面一篇文章所说,我选择的是Handlebars.js,它号称支持mustache语法。

    然则在使用当中,我发现二者的实现仍然是有区别的。先上模板。

        {{#value}}{{value}}{{/value}}
    

    这段模板很简单,检查value是否存在,如果是的话则将其输出。这时:

    • mustache.php先以value作为逻辑判断依据;接下来,在value里找不到value这个key,便回到上一层,输出value的值
    • 而在handlebars.js眼中,{{value}}的类型也是很重要的,如果是String或者Number,那就直接输出;如果是Object,它就会采用{{#with ..}}{{/with}}操作,从value里面取值,而value里没有value,所以输出空。

    当模板为

        {{#has_value}}{{value}}{{/has_value}}
    

    此时,因为has_value是布尔值,所以Handlebars.js放弃把这个对象当做执行环境,而是继续维持当前的层级,所以就能正确输出value的值。


    这个问题虽然不很复杂,但是调试起来并不方便,而且文档中也没提过,所以三次踩坑每次都耽误不少时间。记录如此,避免再次遭遇。

  • 助力Handlebars

    助力Handlebars

    单页应用中,模板引擎是必备工具。这方面的选择比较丰富,旧轮子不断获得改进,新的轮子也不断被发明出来。我比较喜欢Handlebars,原因如下:

    1. 模板语言简单。继承自mustache,用{{content}}标记出要替换的内容即可。
    2. 包含逻辑判断和循环。在mustache基础上加强了这个部分,使得代码更易读。
    3. 不支持复杂的逻辑,尤其是嵌套JS。我认为这是模板引擎的关键,任何复杂的逻辑放在表现层都会使得系统不稳定。
    4. 支持预编译。可以加快实际运行的速度。
    5. 提供扩展功能,可以方便地增加自定义功能。这也是本文重点。

    简单,是Handlebars的优势;但是原始版有点过分过头,它既缺少Angular模板中的过滤器,难以格式化输出数据;也没有足够的逻辑判断。使得我们必须在使用模板前进行大量预运算,逻辑据处理成模板能辨识的标记,把数据格式化成用户能识别的内容,这与代码复用的原则相违背。所幸Handlebars提供好用的扩展接口,给我们增添功能,适配业务逻辑的机会。

    (更多…)