UnderscoreJS

Underscore封装了常用的JavaScript对象操作方法,用于提高开发效率。它本身与我们介绍的主题“Backbone”没有半毛钱的关系,因此你可以完全不理会“Backbone”的概念来学习它,或将它单独运用到任何一个页面。(另外,Underscore还可以被使用在Node.js运行环境。)

在学习Underscore之前,你应该先保存它的API地址,因为你将在以后经常访问它:
Underscore.js 1.7.0 中文文档(http://www.css88.com/doc/underscore/)

常用方法介绍:http://www.css88.com/archives/4784

从API中,你已经可以看出,Underscore没有任何复杂的结构和流程,它仅仅提供了一系列常用的函数。如果你将API中的方法从头至尾用一遍,你就会对它非常了解。

尽管如此,但我觉得还是有必要将一些重要的方法拿出来与大家讨论,它们十分重要,却在API中描述地还不够清楚。

1 Underscore对象封装

Underscore并没有在原生的JavaScript对象原型中进行扩展,而是像jQuery一样,将数据封装在一个自定义对象中(下文中称“Underscore对象”)。

你可以通过调用一个Underscore对象的value()方法来获取原生的JavaScript数据,例如:


  
  
  1. // 定义一个JavaScript内置对象
  2. var jsData = {
  3. name : 'data'
  4. }
  5.  
  6. // 通过_()方法将对象创建为一个Underscore对象
  7. // underscoreData对象的原型中包含了Underscore中定义的所有方法,你可以任意使用
  8. var underscoreData = _(jsData);
  9.  
  10. // 通过value方法获取原生数据, 即jsData
  11. underscoreData.value();

2 优先调用JavaScript 1.6内置方法

Underscore中有许多方法在JavaScript1.6中已经被纳入规范,因此在Underscore对象内部,会优先调用宿主环境提供的内置方法(如果宿主环境已经实现了这些方法),以此提高函数的执行效率。

而对于不支持JavaScript 1.6的宿主环境,Underscore会通过自己的方式实现,而对开发者来说,这些完全是透明的。

这里所说的宿主环境,可能是Node.js运行环境,或客户端浏览器。

3 改变命名空间

Underscore默认使用_(下划线)来访问和创建对象,但这个名字可能不符合我们的命名规范,或容易引起命名冲突。

我们可以通过noConflict()方法来改变Underscore的命名,并恢复_(下划线)变量之前的值,例如:


  
  
  1. <script type="text/javascript">
  2. var _ = '自定义变量';
  3. </script>
  4. <script type="text/javascript" src="underscore/underscore-min.js"></script>
  5. <script type="text/javascript">
  6. // Underscore对象
  7. console.dir(_);
  8. // 将Underscore对象重命名为us, 后面都通过us来访问和创建Underscore对象
  9. var us = _.noConflict();
  10. // 输出"自定义变量"
  11. console.dir(_);
  12. </script>

4 链式操作

还记得我们在jQuery中是如何进行链接操作吗?例如:


  
  
  1. $('a')
  2. .css('position', 'relative')
  3. .attr('href', '#')
  4. .show();

Underscore同样支持链式操作,但你需要先调用chain()方法进行声明:


  
  
  1. var arr = [10, 20, 30];
  2. _(arr)
  3. .chain()
  4. .map(function(item){ return item++; })
  5. .first()
  6. .value();

如果调用了chain()方法,Underscore会将所调用的方法封装在一个闭包内,并将返回值封装为一个Underscore对象并返回:


   
   
  1. // 这是Underscore中实现链式操作的关键函数,它将返回值封装为一个新的Underscore对象,并再次调用chain()方法,为方法链中的下一个函数提供支持。
  2. var result = function(obj, chain) {
  3. return chain ? _(obj).chain() : obj;
  4. }

 

5 扩展Underscore

我们可以通过mixin()方法轻松地向Underscore中扩展自定义方法,例如:


  
  
  1. _.mixin({
  2. method1: function(object) {
  3. // todo
  4. },
  5. method2: function(arr) {
  6. // todo
  7. },
  8. method3: function(fn) {
  9. // todo
  10. }
  11. });

这些方法被追加到Underscore的原型对象中,所有创建的Underscore对象都可以使用这些方法,它们享有和其它方法同样的环境。

6 遍历集合

each()和map()方法是最常用用到的两个方法,它们用于迭代一个集合(数组或对象),并依次处理集合中的每一个元素,例如:


  
  
  1. var arr = [1, 2, 3];
  2.  
  3. _(arr).map(function(item, i) {
  4. arr[i] = item + 1;
  5. });
  6.  
  7. var obj = {
  8. first : 1,
  9. second : 2
  10. }
  11.  
  12. _(obj).each(function(value, key) {
  13. return obj[key] = value + 1;
  14. });

map()方法与each()方法的作用、参数相同,但它会将每次迭代函数返回的结果记录到一个新的数组并返回。

7 函数节流

函数节流是指控制一个函数的执行频率或间隔(就像控制水流的闸门一样),Underscore提供了debounce()和throttle()两个方法用于函数节流。

为了更清楚地描述这两个方法,假设我们需要实现两个需求:

  需求1:当用户在文本框输入搜索条件时,自动查询匹配的关键字并提示给用户(就像在Tmall输入搜索关键字时那样)

首先分析第1个需求,我们可以绑定文本框的keypress事件,当输入框内容发生变化时,查询匹配关键字并展示。假设我想查询“windows phone”,它包含13个字符,而我输入完成只花了1秒钟(好像有点快,就意思意思吧),那么在这1秒内,调用了13次查询方法。这是一件非常恐怖的事情,如果Tmall也这样实现,我担心它会不会在光棍节到来之前就挂掉了(当然,它并没有这么脆弱,但这绝对不是最好的方案)

更好的方法是,我们希望用户已经输入完成,或者正在等待提示(也许他懒得再输入后面的内容)的时候,再查询匹配关键字。

最后我们发现,在我们期望的这两种情况下,用户会暂时停止输入,于是我们决定在用户暂停输入200毫秒后再进行查询(如果用户在不断地输入内容,那么我们认为他可能很明确自己想要的关键字,所以等一等再提示他)

这时,利用Underscore中的debounce()函数,我们可以轻松实现这个需求:


  
  
  1. <input type="text" id="search" name="search" />
  2. <script type="text/javascript">
  3. var query = _(function() {
  4. // 在这里进行查询操作
  5. }).debounce(200);
  6.  
  7. $('#search').bind('keypress', query);
  8. </script>

你能看到,我们的代码非常简洁,节流控制在debounce()方法中已经被实现,我们只告诉它当query函数在200毫秒内没有被调用过的话,就执行我们的查询操作,然后再将query函数绑定到输入框的keypress事件。

query函数是怎么来的?我们在调用debounce()方法时,会传递一个执行查询操作的函数和一个时间(毫秒数),debounce()方法会根据我们传递的时间对函数进行节流控制,并返回一个新的函数(即query函数),我们可以放心大胆地调用query函数,而debounce()方法会按要求帮我们做好控制。

  需求2:当用户拖动浏览器滚动条时,调用服务器接口检查是否有新的内容

再来分析第2个需求,我们可以将查询方法绑定到window.onscroll事件,但这显然不是一个好的做法,因为用户拖动一次滚动条可能会触发几十次甚至上百次onscroll事件。

我们是否可以使用上面的debounce()方法来进行节流控制?当用户拖动滚动条完毕后,再查询新的内容?但这与需求不符,用户希望在拖动的过程中也能看到新内容的变化。

因此我们决定这样做:用户在拖动时,每两次查询的间隔不少于500毫秒,如果用户拖动了1秒钟,这可能会触发200次onscroll事件,但我们最多只进行2次查询。

利用Underscore中的throttle()方法,我们也可以轻松实现这个需求:


  
  
  1. <script type="text/javascript">
  2. var query = _(function() {
  3. // 在这里进行查询操作
  4. }).throttle(500);
  5.  
  6. $(window).bind('scroll', query);
  7. </script>

代码仍然十分简洁,因为在throttle()方法内部,已经为我们实现的所有控制。

你可能已经发现,debounce()和throttle()两个方法非常相似(包括调用方式和返回值),作用却又有不同。

它们都是用于函数节流,控制函数不被频繁地调用,节省客户端及服务器资源。

  • debounce()方法关注函数执行的间隔,即函数两次的调用时间不能小于指定时间。
  • throttle()方法更关注函数的执行频率,即在指定频率内函数只会被调用一次。

 

5.8 模板解析

Underscore提供了一个轻量级的模板解析函数,它可以帮助我们有效地组织页面结构和逻辑。

我将通过一个例子来介绍它:


  
  
  1. <!-- 用于显示渲染后的标签 -->
  2. <ul id="element"></ul>
  3.  
  4. <!-- 定义模板,将模板内容放到一个script标签中 -->
  5. <script type="text/template" id="tpl">
  6. <% for(var i = 0; i < list.length; i++) { %>
  7. <% var item = list[i] %>
  8. <li>
  9. <span><%=item.firstName%> <%=item.lastName%></span>
  10. <span><%-item.city%></span>
  11. </li>
  12. <% } %>
  13. </script>
  14. <script type="text/javascript" src="underscore/underscore-min.js"></script>
  15. <script type="text/javascript">
  16. // 获取渲染元素和模板内容
  17. var element = $('#element'),
  18. tpl = $('#tpl').html();
  19.  
  20. // 创建数据, 这些数据可能是你从服务器获取的
  21. var data = {
  22. list: [
  23. {firstName: '<a href="#">Zhang</a>', lastName: 'San', city: 'Shanghai'},
  24. {firstName: 'Li', lastName: 'Si', city: '<a href="#">Beijing</a>'},
  25. {firstName: 'Wang', lastName: 'Wu', city: 'Guangzhou'},
  26. {firstName: 'Zhao', lastName: 'Liu', city: 'Shenzhen'}
  27. ]
  28. }
  29.  
  30. // 解析模板, 返回解析后的内容
  31. var html = _.template(tpl, data);
  32. // 将解析后的内容填充到渲染元素
  33. element.html(html);
  34. </script>

在本例中,我们将模板内容放到一个<script>标签中,你可能已经注意到标签的type是text/template而不是text/javascript,因为它无法作为JavaScript脚本直接运行。

我也建议你将模板内容放在<script>中,因为如果你将它们写在一个<div>或其它标签中,它们可能会被添加到DOM树中进行解析(即使你隐藏了这个标签也无法避免)。

 

_.template模板函数只能解析3种模板标签(这比Smarty、JSTL要简单得多):

<%  %>:用于包含JavaScript代码,这些代码将在渲染数据时被执行。

<%= %>:用于输出数据,可以是一个变量、某个对象的属性、或函数调用(将输出函数的返回值)。

<%- %>:用于输出数据,同时会将数据中包含的HTML字符转换为实体形式(例如它会将双引号转换为&quot;形式),用于避免XSS攻击。

当我们希望将数据中的HTML作为文本显示出来时,常常会使用<%- %>标签。

Underscore还允许你修改这3种标签的形式,如果我们想使用{% %}、{%= %}、{%- %}作为标签,可以通过修改templateSettings来实现,就像这样:


  
  
  1. _.templateSettings = {
  2. evaluate : /\{%([\s\S]+?)\%\}/g,
  3. interpolate : /\{%=([\s\S]+?)\%\}/g,
  4. escape : /\{%-([\s\S]+?)%\}/g
  5. }

在本例中,我们将模板内容和需要填充的数据传递给template方法,它会按以下顺序进行处理:

  • 将模板内容解析为可执行的JavaScript(解析模板标签)
  • 通过with语句将解析后的JavaScript作用域修改为我们传递的数据对象,这使我们能够直接在模板中通过变量形式访问数据对象的属性
  • 执行解析后的JavaScript(将数据填充到模板)
  • 返回执行后的结果

我们经常会遇到一种情况:多次调用template方法将数据渲染到同一个模板。

假设我们有一个分页列表,列表中的每一条数据都通过模板渲染,当用户进入下一页,我们会获取下一页的数据并重新渲染,实际上每次渲染的模板都是同一个,但刚才描述的template所有处理过程总会被执行。

其实Underscore的template方法提供了一种更高效的调用方式,我们将上面代码中的最后两句修改为:


  
  
  1. // 解析模板, 返回解析后的内容
  2. var render = _.template(tpl);
  3. var html = render(data);
  4. // 将解析后的内容填充到渲染元素
  5. element.html(html);

你会发现细微的差别:我们在调用template方法时只传递了模板内容,而没有传递数据,此时template方法会解析模板内容,生成解析后的可执行JavaScript代码,并返回一个函数,而函数体就是解析后的JavaScript,因此当我们调用该函数渲染数据时,就省去了模板解析的动作。

你应该将返回的函数存储起来(就像我将它存储在render变量中一样),再通过调用该函数来渲染数据,特别是在同一个模板可能会被多次渲染的情况下,这样做能提高执行效率(具体提升多少,应该根据你的模板长度和复杂度而定,但无论如何,这都是一个良好的习惯)。

前言

从其他语言转向Javascript时,通常都会遇到一些困惑性问题。比如,Java中的HashMap在Javascript中如何实现?Javascript面向对象式编程如何实现继承?如何实现通用的iterator对集合对象做遍历?如何对Array实现快速排序?….

如果你真的可以自己实现这些功能,那么你的Javascript基础很扎实的!我很佩服你!但对于大部分人来说,这些基础功能应该是由底层API支持的,就像JDK一样。Underscore为我们提供了这样的一个实用工具包,而且它真的很实用!

只有你动手做了,你才能有收获。

目录

  1. Underscore介绍
  2. Underscore安装
  3. 集合部分:数组或对象
  4. 数组部分
  5. 函数部分
  6. 对象部分
  7. 实用功能
  8. 链式语法
  9. 字符串处理Underscore.String

1. Underscore介绍

Underscore 是一个JavaScript实用库,提供了类似Prototype.js的一些功能,但是没有继承任何JavaScript内置对象。它弥补了部分jQuery没有实现的功能,同时又是Backbone.js必不可少的部分。

Underscore提供了80多个函数,包括常用的: map, select, invoke — 当然还有更多专业的辅助函数,如:函数绑定, JavaScript模板功能, 强类型相等测试, 等等. 在新的浏览器中, 有许多函数如果浏览器本身直接支持,将会采用原生的,如 forEach, map, reduce, filter, every, some 和 indexOf.

Underscore介绍,摘自 http://learning.github.io/underscore/

2. Underscore安装

Underscore.js是一个Javascript功能类库,不依赖于环境,可以加载到HTML中在浏览器运行,也可以直接在nodejs的环境中使用。

为了方便介绍函数功能,我这里选择在nodejs的环境中运行。

我的系统环境

win7 64bit, node v0.10.5, npm 1.2.19

安装Underscore


~ D:\workspace\javascript>mkdir nodejs-underscore
~ D:\workspace\javascript>cd nodejs-underscore

~ D:\workspace\javascript\nodejs-underscore>npm install underscore
npm http GET https://registry.npmjs.org/underscore
npm http 304 https://registry.npmjs.org/underscore
npm http GET https://registry.npmjs.org/underscore/-/underscore-1.5.1.tgz
npm http 200 https://registry.npmjs.org/underscore/-/underscore-1.5.1.tgz
underscore@1.5.1 node_modules\underscore

underscore有80多的函数,下面我只介绍一些,我比较感兴趣的,实用的。

3. 集合部分: 数组或对象

新建一个collection.js文件,测试underscore对集合的支持。

~ vi collection.js

//加载underscore库
var _ = require("underscore")._;

each: 对集合循环操作

_.each([1, 2, 3], function (ele, idx) {
    console.log(idx + ":" + ele);
});
=> 0:1
1:2
2:3

map: 对集合以map方式遍历,产生一个新数组

console.log(
    _.map([1, 2, 3], function(num){
        return num * 3;
    })
);
=> [3, 6, 9]

reduce: 集合元素合并集的到memo

console.log(
    _.reduce([1, 2, 3], function (memo, num) {
        return memo + num;
    }, 0)
);
=> 6

filter: 过滤集合中符合条件的元素。注:find:只返回第一个

console.log(
    _.filter([1, 2, 3, 4, 5, 6], function(num){
        return num % 2 == 0;
    })
);
=> [ 2, 4, 6 ]

reject: 过滤集合中不符合条件的元素

console.log(
    _.reject([1, 2, 3, 4, 5, 6], function(num){
        return num % 2 == 0;
    })
);
=> [ 1, 3, 5 ]

where: 遍历list, 返回新的对象数组

var list = [
    {title:"AAA",year: 1982},
    {title:"BBB",year: 1900},
    {title:"CCC",year: 1982}
];
console.log(
    _.where(list,{year: 1982})
);
=> [ { title: 'AAA', year: 1982 }, { title: 'CCC', year: 1982 } ]

contains:判断元素是否在list中

console.log(
    _.contains([1, 2, 3], 3)
);

invoke:通过函数名调用函数运行

console.log(
    _.invoke([[5, 1, 7]], 'sort')
);
=> [ [ 1, 5, 7 ] ]

pluck: 提取一个集合里指定的属性值

var users = [
    {name: 'moe', age: 40},
    {name: 'larry', age: 50}
];
console.log(
    _.pluck(users, 'name')
);
=> [ 'moe', 'larry' ]

max,min,sortBy: 取list中的最大,最小元素,自定义比较器

console.log(
    _.max(users, function (stooge) {
        return stooge.age;
    })
);
=> { name: 'larry', age: 50 }

var numbers = [10, 5, 100, 2, 1000];
console.log(
    _.min(numbers)
);
=> 2

console.log(
    _.sortBy([3, 4, 2, 1 , 6], function (num) {
        return Math.max(num);
    })
);
=> [ 1, 2, 3, 4, 6 ]

groupBy: 把一个集合分组成多个集合

console.log(
    _.groupBy(['one', 'two', 'three'], 'length')
);
=> { '3': [ 'one', 'two' ], '5': [ 'three' ] }

countBy: 把一个数据分组后计数

onsole.log(
    _.countBy([1, 2, 3, 4, 5], function (num) {
        return num % 2 == 0 ? 'even' : 'odd';
    })
);
=> { odd: 3, even: 2 }

shuffle: 随机打乱一个数据

console.log(
    _.shuffle([1, 2, 3, 4, 5, 6])
);
=> [ 1, 5, 2, 3, 6, 4 ]

toArray: 将list转换成一个数组

console.log(
    (function () {
        return _.toArray(arguments).slice(1);
    })(1, 2, 3, 4)
);
=> [ 2, 3, 4 ]

size: 得到list中元素个数

console.log(
    _.size({one: 1, two: 2, three: 3})
);

4. 数组部分

新建一个array.js


~ vi array.js

var _ = require("underscore")._;

first,last,initial,rest: 数组的元素操作。


var nums = [5, 4, 3, 2, 1];
console.log(_.first(nums));
console.log(_.last(nums));
console.log(_.initial(nums,1));
console.log(_.rest(nums,1));
=> 5
1
[ 5, 4, 3, 2 ]
[ 4, 3, 2, 1 ]

indexOf,lastIndexOf,sortedIndex:取索引位置


console.log(_.indexOf([4, 2, 3, 4, 2], 4));
console.log(_.lastIndexOf([4, 2, 3, 4, 2], 4));
console.log(_.sortedIndex([10, 20, 30, 40, 50], 35));
=> 0
3
3

range: 创建一个范围整数数组


console.log(_.range(1,10));
console.log(_.range(0, -10, -1));
=> [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
[ 0, -1, -2, -3, -4, -5, -6, -7, -8, -9 ]

compact:数组去除空值


console.log(
    _.compact([0, 1, false, 2, '', 3])
);
=> [ 1, 2, 3 ]

flatten:将一个嵌套多层的数组(嵌套可以是任何层数)转换为只有一层的数组


console.log(
    _.flatten([1, [2], [3, [[4]]]])
);
=> [ 1, 2, 3, 4 ]

without: 去掉元素


console.log(
    _.without([1, 2, 1, 0, 3, 1, 4], 0,1 )
);
=> [ 2, 3, 4 ]

union,intersection,difference,uniq: 并集,交集,差集,取唯一


console.log(_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]));
console.log(_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]));
console.log(_.difference([1, 2, 3, 4, 5], [5, 2, 10]));
console.log(_.uniq([1, 2, 1, 3, 1, 2]));
=> [ 1, 2, 3, 101, 10 ]
[ 1, 2 ]
[ 1, 3, 4 ]
[ 1, 2, 3 ]

zip: 合并多个数组中的元素,是group的反向操作


console.log(
    _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false])
);
=> [ [ 'moe', 30, true ],
  [ 'larry', 40, false ],
  [ 'curly', 50, false ] ]

object: 把数组转换成对象


console.log(
    _.object(['moe', 'larry', 'curly'], [30, 40, 50])
);
=> { moe: 30, larry: 40, curly: 50 }

5. 函数部分

新建一个function.js


~ vi function.js
var _ = require("underscore")._;

bind: 绑定函数到对象上, 无论何时函数被调用, 函数里的this都指向对象.


var func = function(greeting){ return greeting + ': ' + this.name };
func = _.bind(func, {name : 'moe'}, 'hi');
console.log(func());
=> hi: moe

bindAll: 绑定方法名到对象上, 当这些方法被执行时将在对象的上下文执行. 绑定函数用作事件处理时非常方便, 否则函数调用时 this 关键字根本没什么用.


var buttonView = {
    label   : 'underscore',
    onClick : function(){ console.log('clicked: ' + this.label); },
    onHover : function(){ console.log('hovering: ' + this.label); }
};
var func  = _.bindAll(buttonView, 'onClick', 'onHover');
func.onClick();
=> clicked: underscore

partial:在不改变this的情况下,通过参数填充数据


var add = function(a, b) { return a + b; };
add5 = _.partial(add, 5);
console.log(add5(10));
=> 15

memoize: 通过缓存计算结果使函数具有记忆功能。


var fibonacci = _.memoize(function(n) {
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(10));
=> 55

delay: 在等待xx毫秒之后调用函数,类似于setTimeout


var log = _.bind(console.log, console);
_.delay(log, 1000, 'sleep 1s');
=> sleep 1s

defer: 延迟调用函数, 直到当前调用栈被清空为止, 跟使用 setTimeout 赋予0毫秒的延时很像. 对执行高消耗算法或大型HTML呈现而不阻碍UI更新线程很有用.


_.defer(function(){ console.log('deferred'); });
=> deferred

throttle:返回一个类似于节流阀一样的函数, 当高频率的调用函数, 实际上会每隔 wait 毫秒才会调用一次. 对于高到您感觉不到的高频率执行的函数时非常有用.


var throttled = _.throttle(function(){
    _(5).times(function(n){ console.log(n+":"+new Date()); });
}, 100);
throttled();
=> 0:Wed Aug 28 2013 14:20:48 GMT+0800
1:Wed Aug 28 2013 14:20:48 GMT+0800
2:Wed Aug 28 2013 14:20:48 GMT+0800
3:Wed Aug 28 2013 14:20:48 GMT+0800
4:Wed Aug 28 2013 14:20:48 GMT+0800

debounce: 返回函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的等待xx毫秒之后,可以实现延迟加载。


var lazyLoad = _.debounce(function(){
    console.log("lazy load 3s");
}, 3000);
lazyLoad();
=> lazy load 3s

once: 创建一个只能运行一次的函数. 重复调用此修改过的函数会没有效果, 只会返回第一次执行时返回的结果。单例模式。


var initialize = _.once(function(){console.log('initialize');});
initialize();
initialize();
=> initialize

after: 对循环计数,只有超过计数,才会调用指定的函数


var nums = [1,2,3,4];
var renderNums = _.after(nums.length, function(){
    console.log('render nums');
});
_.each(nums, function(num) {
    console.log('each:'+num);
    renderNums();
});
=> each:1
each:2
each:3
each:4
render nums

wrap: 以函数作为函数传递,可以增加函数调用前后的控制。有点类似于 “模板方法模式”


var hello = function(name) { return "hello: " + name; };
hello = _.wrap(hello, function(func) {
    return "before, " + func("moe") + ", after";
});
console.log(hello());
=> before, hello: moe, after

compose: 组合函数调用关系,把单独的f(),g(),h()组合成f(g(h()))


var greet    = function(name){ return "A: " + name; };
var exclaim  = function(statement){ return "B: "+statement + "!"; };
var welcome = _.compose(exclaim, greet);
console.log(welcome('moe'));
=> B: A: moe!

6. 对象部分

新建一个object.js


~ vi object.js
var _ = require("underscore")._;

keys,values,paris,invert: 取属性名,取属性值,把对象转换成[key,value]数组,对调键值


var obj = {one: 1, two: 2, three: 3}
console.log(_.keys(obj));
console.log(_.values(obj));
console.log(_.pairs(obj));
console.log(_.invert(obj));
=> [ 'one', 'two', 'three' ]
[ 1, 2, 3 ]
[ [ 'one', 1 ], [ 'two', 2 ], [ 'three', 3 ] ]
{ '1': 'one', '2': 'two', '3': 'three' }

functions:返回对象的所有方法名


var fun = {
    fun1:function(){},
    fun2:function(){}
}
console.log(_.functions(fun));
=> [ 'fun1', 'fun2' ]

extend: 复制对象的所有属性到目标对象上,覆盖已有属性


console.log(
    _.extend({name : 'moe'}, {age : 50})
);
=> { name: 'moe', age: 50 }

defaults: 复制对象的所有属性到目标对象上,跳过已有属性


var iceCream = {flavor : "chocolate"};
console.log(
    _.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"})
);
=> { flavor: 'chocolate', sprinkles: 'lots' }

pick,omit: 返回一个对象的副本,保留指定的属性或去掉指定的属性


console.log(
    _.pick({name : 'moe', age: 50, userid : 'moe1'}, 'name', 'age')
);
=> { name: 'moe', age: 50 }
console.log(
    _.omit({name : 'moe', age : 50, userid : 'moe1'}, 'userid')
);
=> { name: 'moe', age: 50 }

clone: 引入方式克隆对象,不进行复制


console.log(
   _.clone({name : 'moe'});
);
=> {name : 'moe'};

tag: 用对象作为参数来调用函数,作为函数链式调用的一环


console.log(
    _.chain([1,2,3,200])
        .filter(function(num) { return num % 2 == 0; })
        .tap(console.log)
        .map(function(num) { return num * num })
        .value()
);
=> [ 2, 200 ]
[ 4, 40000 ]

has: 判断对象是否包含指定的属性名


console.log(_.has({a: 1, b: 2, c: 3}, "b"));

isEqual: 判断两个对象是值相等


var moe   = {name : 'moe', luckyNumbers : [13, 27, 34]};
var clone = {name : 'moe', luckyNumbers : [13, 27, 34]};
console.log(moe == clone);
=> false
console.log(_.isEqual(moe, clone));
=> true

判断对象类型的方法,下面反回值都是true


console.log(_.isEmpty({}));
console.log(_.isArray([1,2,3]));
console.log(_.isObject({}));
console.log((function(){ return _.isArguments(arguments); })(1, 2, 3));
console.log(_.isFunction(console.log));
console.log(_.isString("moe"));
console.log(_.isNumber(8.4 * 5));
console.log(_.isFinite(-101));
console.log(_.isBoolean(true)); 
console.log(_.isDate(new Date()));
console.log(_.isNaN(NaN));
console.log(_.isNull(null));
console.log(_.isUndefined(undefined));
=> true

7. 实用功能

新建一个util.js


~ vi util.js
var _ = require("underscore")._;

noConflict: 把 “_” 变量的控制权预留给它原有的所有者. 返回一个引用给 Underscore 对象.


var underscore = _.noConflict();

identity: 返回与传入参数相等的值. 相当于数学里的: f(x) = x


var moe = {name : 'moe'};
console.log(moe === _.identity(moe));
=> true

times: 设计调用次数


_(3).times(function(n){ console.log(n); });
=> 0
1
2

random: 返回范围内的随机数


console.log(_.random(0, 100));
=> 30

mixin: 封装自己的函数到Underscore对象中,后面Underscore.string就是这种方式的集成


_.mixin({
    capitalize : function(string) {
        return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
    }
});
console.log(_("fabio").capitalize());
=> Fabio

uniqueId:产生一个全局的唯一id,以参数作为前缀


console.log(_.uniqueId('contact_'));
=> contact_1
console.log(_.uniqueId('contact_'));
=> contact_2

escape,unescape:转义HTML字符串,反转到HTML字符串

console.log(_.escape('Curly, Larry & Moe'));
=> Curly, Larry &amp; Moe
console.log(_.unescape('Curly, Larry &amp; Moe'));
=> Curly, Larry & Moe

result: 通过字符串调用对象的函数,或返回属性值

var obj = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
console.log(_.result(obj, 'cheese'));
=> crumpets
console.log(_.result(obj, 'stuff'));
=> nonsense

template: 将 JavaScript 模板编译为可以用于页面呈现的函数, 对于通过JSON数据源生成复杂的HTML并呈现出来的操作非常有用. 模板函数可以通过以下两种方式插入到页面中, 使用<%= … %>, 也可以用<% … %>执行任意的 JavaScript 代码. 如果您希望插入一个值, 并让其进行HTML转义, 当您使用创建一个模板时使用 <%- … %> , 传入一个含有与模板对应属性的对象 data. 如果您要写一个一次性的, 您可以传对象 data 作为第二个参数给模板template 来直接呈现, 这样页面会立即呈现而不是返回一个模板函数. 参数 settings 是一个哈希表包含任何可以覆盖的设置 _.templateSettings.

var compiled = _.template("hello: <%= name %>");
console.log(compiled({name : 'moe'}));
=>hello: moe

var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>";
console.log(_.template(list, {people : ['moe', 'curly', 'larry']}));
=> <li>moe</li> <li>curly</li> <li>larry</li>

var template = _.template("<b><%- value %></b>");
console.log(template({value : '<script>'}));
=> <b>&lt;script&gt;</b>

var compiled = _.template("<% print('Hello ' + epithet); %>");
console.log(compiled({epithet: "stooge"}));
=>Hello stooge

console.log(_.template("Using 'with': <%= data.answer %>", {answer: 'no'}, {variable: 'data'}));
=>Using 'with': no

_.templateSettings = {
interpolate : /\{\{(.+?)\}\}/g
};
var template = _.template("Hello {{ name }}!");
console.log(template({name : "Mustache"}));
=>Hello Mustache!

8. 链式语法

新建一个chaining.js

~ vi chaining.js
var _ = require("underscore")._;

chain: 返回一个封装的对象. 在封装的对象上调用方法会返回封装的对象本身, 直到value() 方法调用为止.

var stooges = [{name : 'curly', age : 25}, {name : 'moe', age : 21}, {name : 'larry', age : 23}];
var youngest = _.chain(stooges)
    .sortBy(function(stooge){ return stooge.age; })
    .map(function(stooge){ return stooge.name + ' is ' + stooge.age; })
    .first()
    .value();
console.log(youngest);
=> moe is 21

对一个对象使用 chain 方法, 会把这个对象封装并 让以后每次方法的调用结束后都返回这个封装的对象, 当您完成了计算, 可以使用 value 函数来取得最终的值. 以下是一个同时使用了 map/flatten/reduce 的链式语法例子, 目的是计算一首歌的歌词里每一个单词出现的次数.

var lyrics = [
    {line : 1, words : "I'm a lumberjack and I'm okay"},
    {line : 2, words : "I sleep all night and I work all day"},
    {line : 3, words : "He's a lumberjack and he's okay"},
    {line : 4, words : "He sleeps all night and he works all day"}
];
console.log(
    _.chain(lyrics)
        .map(function(line) { return line.words.split(' '); })
        .flatten()
        .reduce(function(counts, word) {
            counts[word] = (counts[word] || 0) + 1;
            return counts;
        }, {})
        .value()
);
=> { 'I\'m': 2,
  a: 2,
  lumberjack: 2,
  and: 4,
  okay: 2,
  I: 2,
  sleep: 1,
  all: 4,
  night: 2,
  work: 1,
  day: 2,
  'He\'s': 1,
  'he\'s': 1,
  He: 1,
  sleeps: 1,
  he: 1,
  works: 1 }

value: 提取封装对象的最终值,作为chain()结束标志。


console.log(_([1, 2, 3]).value());

9. 字符串处理Underscore.String

安装underscore.string


~ D:\workspace\javascript\nodejs-underscore>npm install underscore.string
npm http GET https://registry.npmjs.org/underscore.string
npm http 304 https://registry.npmjs.org/underscore.string
underscore.string@2.3.3 node_modules\underscore.string

新建一个string.js,通过mixin()函数,让underscore.string和underscore集成统计实现_.fun()语法。


~ vi string.js

var _  = require('underscore');
_.str = require('underscore.string');
_.mixin(_.str.exports());

字符串的数字格式化


console.log(_.numberFormat(1000, 2));
=> 1,000.00
console.log(_.numberFormat(123456789.123, 5, '.', ','));
=> 123,456,789.12300
console.log(_('2.556').toNumber());
=> 3
console.log(_('2.556').toNumber(2));
=> 2.56
console.log(_.sprintf("%.1f", 1.17));
=> 1.2

字符串基础操作


console.log(_.levenshtein('kitten', 'kittah'));
=> 2
console.log(_.capitalize('epeli'));
=> Epeli
console.log(_.chop('whitespace', 3));
=> [ 'whi', 'tes', 'pac', 'e' ]
console.log(_.clean(" foo    bar   "));
=> foo bar
console.log(_.chars('Hello'));
=> [ 'H', 'e', 'l', 'l', 'o' ]
console.log(_.swapCase('hELLO'));
=> Hello
console.log(_.str.include("foobar", "ob")); //不兼容API,需要用_.str.fun()
=> true
console.log(_.str.reverse("foobar"));//不兼容API,需要用_.str.fun()
=> raboof
console.log(_('Hello world').count('l'));
=> 3
console.log(_('Hello ').insert(6, 'world'));
=> Hello world
console.log(_('').isBlank() && _('\n').isBlank() && _(' ').isBlank());
=> true
console.log(_.join(",", "foo", "bar"));
=> foo,bar
console.log(_.lines("Hello\nWorld"));
=> [ 'Hello', 'World' ]
console.log(_("image.gif").startsWith("image"));
=> true
console.log(_("image.gif").endsWith("gif"));
=> true
console.log(_('a').succ());//指下编码的下一个
=> b

字符串变换


console.log(_.repeat("foo", 3));
=> foofoofoo
console.log(_.repeat("foo", 3, "bar"));
=> foobarfoobarfoo
console.log(_.surround("foo", "ab"));
=> abfooab
console.log(_.quote('foo', "#"));
=> #foo#
console.log(_.unquote('"foo"'));
=> foo
console.log(_.unquote("'foo'", "'"));
=> foo
console.log(_.slugify("Un éléphant à l'orée du bois"));
=> un-elephant-a-loree-du-bois
console.log(['foo20', 'foo5'].sort(_.naturalCmp));
=> [ 'foo5', 'foo20' ]
console.log(_.toBoolean("true"));
=> true
console.log(_.toBoolean("truthy", ["truthy"], ["falsy"]));
=> true
console.log(_.toBoolean("true only at start", [/^true/]));
=> true

字符串替换,截断


console.log(_('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli'));
=> https://edtsech@bitbucket.org/epeli/underscore.strings
console.log(_.trim("  foobar   "));
=> foobar
console.log(_.trim("_-foobar-_", "_-"));
=> foobar
console.log(_('Hello world').truncate(5));
=> Hello...
console.log(_('Hello, world').prune(5));
=> Hello...
console.log(_('Hello, world').prune(5, ' (read a lot more)'));
=> Hello, world
console.log(_.words("   I   love   you   "));
=> [ 'I', 'love', 'you' ]
console.log(_.words("I-love-you", /-/));
=> [ 'I', 'love', 'you' ]
console.log(_('This_is_a_test_string').strRight('_'));
=> is_a_test_string
console.log(_('This_is_a_test_string').strRightBack('_'));
=> string
console.log(_('This_is_a_test_string').strLeft('_'));
=> This
console.log(_('This_is_a_test_string').strLeftBack('_'));
=> This_is_a_test

字符串占位


console.log(_.pad("1", 8));
=>        1
console.log(_.pad("1", 8, '0'));
=> 00000001
console.log(_.pad("1", 8, '0', 'right'));
=> 10000000
console.log(_.pad("1", 8, 'bleepblorp', 'both'));
=> bbbb1bbb
console.log(_.lpad("1", 8, '0'));
=> 00000001
console.log(_.rpad("1", 8, '0'));
=> 10000000
console.log(_.lrpad("1", 8, '0'));
=> 00001000

字符串语义处理

console.log(_.toSentence(['jQuery', 'Mootools', 'Prototype']));
=> jQuery, Mootools and Prototype
console.log(_.toSentence(['jQuery', 'Mootools', 'Prototype'], ', ', ' unt '));
=> jQuery, Mootools unt Prototype
console.log(_.toSentenceSerial(['jQuery', 'Mootools']));
=> jQuery and Mootools
console.log(_.toSentenceSerial(['jQuery', 'Mootools', 'Prototype']));
=> jQuery, Mootools, and Prototype
console.log(_('my name is epeli').titleize());
=> My Name Is Epeli
console.log(_('-moz-transform').camelize());
=> MozTransform
console.log(_('some_class_name').classify());
=> SomeClassName
console.log(_('MozTransform').underscored());
=> moz_transform
console.log(_('MozTransform').dasherize());
=> -moz-transform
console.log(_('  capitalize dash-CamelCase_underscore trim  ').humanize());
=> Capitalize dash camel case underscore trim

HTML相关操作


console.log(_('<div>Blah blah blah</div>').escapeHTML());
=> &lt;div&gt;Blah blah blah&lt;/div&gt;
console.log(_('&lt;div&gt;Blah blah blah&lt;/div&gt;').unescapeHTML());
=><div>Blah blah blah</div>
console.log(_('a <a href="#">link</a>').stripTags());
=>a link
console.log(_('a <a href="#">link</a><script>alert("hello world!")</script>').stripTags());
=>a linkalert("hello world!")

10. 写在最后的话

写在最后的话,本来只是想列举一些函数的,做了3遍真是无从取舍,把API就直接搬过来了。Javascript有如此精巧而强大的类库,顿时让我感到Java的大而臃肿。每种语言的发展历程,确实让我看到了从底到顶的不一样,同时也造就了从顶到底也不一样。不管是代码风格,设计模式,思维模式,架构设计等等,所以掌握多门语言是非常重要的,各取其精华相互借鉴,最后才能让我们树立自己的想法,做出开山立派之大事。

文中实例大部分来自官方:我只做了一些小调整。

underscore 1.5.1: http://underscorejs.org/

underscore中文 1.4.2: http://learning.github.io/underscore

underscore.string: https://github.com/epeli/underscore.string

转载请注明出处:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值