前言

Underscore 是一个JavaScript实用库,提供了类似Prototype.js (或 Ruby)的一些功能,但是没有扩展任何JavaScript内置对象。

它弥补了部分jQuery没有实现的功能,同时又是Backbone.js必不可少的部分。

Underscore提供了80多个函数,包括常用的: mapselectinvoke — 当然还有更多专业的辅助函数,如:函数绑定, JavaScript模板功能, 强类型相等测试, 等等.

在新的浏览器中, 有许多函数如果浏览器本身直接支持,将会采用原生的,如 forEachmapreducefilter,everysome 和 indexOf

我们使用Underscore一般配合backbone,用得最多的还是里面的模板引擎,我们今天就来一窥underscore的神秘面纱

PS:老规矩,我们还是看着API 一个个来看源码,如果确实有用的就写demo,我估计只会多模板引擎一块深入研究

本文参考了愚人码头翻译的中文api

template(templateString, [data], [settings])

模板引擎是underscore在我们项目中用得最多的东西,夸张点来说,除了模板引擎,其它东西我们都想将之移除

因为zepto本身提供了很多不错的方法,而移除其它无用方法后可以节约13-5k的流量,是一个不错的选择

模板引擎是实现数据与行为分离的一大利器,其实大概两年前我就干过这个事情了

两年前的模板引擎

当时的想法很傻很天真,做了最简单的对象替换,还恬不知耻的说自己用了模板预加载的模式,现在看来确实不错。。。。适合我坑爹的个性

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="../zepto/zepto.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
var data = [];
var i;
for (i = 0; i < 10; i++) {
var temp = {};
temp.name = "name_" + i.toString();
temp.age = "age_" + i.toString();
temp.home = "home_" + i.toString();
temp.test = "test_" + i.toString();
data.push(temp);
}
var template = "<div>{name}</div><div>{age}</div><div>{home}</div><div>{test}</div><hr/>"
var wl2 = $("#wl2"); //现在做法
function update() {
var now = new Date();
var beginTime = now.getTime(); var templateObj = [];
var reg = /\{[A-Za-z]*\}/;
var para = reg.exec(template);
var tempHtml = template;
while (para && para.length > 0) {
var len = para.index;
var temp = {};
temp.html = tempHtml.substr(0, len);
temp.field = para[0].substr(1, para[0].length - 2); ;
templateObj.push(temp);
tempHtml = tempHtml.substr(len + para[0].length);
para = reg.exec(tempHtml);
}
var end = {};
end.html = tempHtml;
templateObj.push(end); var html = "";
$.each(data, function (index, dataItem) {
var tempHtm = "";
$.each(templateObj, function (i, item) {
if (item.field) {
tempHtm = tempHtm + item.html + dataItem[item.field];
} else {
tempHtm = tempHtm + item.html;
}
});
html += tempHtm;
});
wl2.append(html);
}
update();
}); </script>
</head>
<body>
<div id="wl2">
</div>
</body>
</html>

当时想法比较简单,而且核心代码也比较少,提供一个模板加一个二维数据即可

① 第一步首先是解析模板,将模板变为字符串数组

 var templateObj = [];
var reg = /\{[A-Za-z]*\}/;
var para = reg.exec(template);
var tempHtml = template;
while (para && para.length > 0) {
var len = para.index;
var temp = {};
temp.html = tempHtml.substr(0, len);
temp.field = para[0].substr(1, para[0].length - 2); ;
templateObj.push(temp);
tempHtml = tempHtml.substr(len + para[0].length);
para = reg.exec(tempHtml);
}
var end = {};
end.html = tempHtml;
templateObj.push(end);
<div>{name}</div><div>{age}</div><div>{home}</div><div>{test}</div><hr/>

上面一段模板解析结束后就变成了这个样子了:

可以看到已经将需要替换的标识给取了出来,接下来就进入第二步

② 现在就只需要将模板中的标识变为data的数据即可

 var html = "";
$.each(data, function (index, dataItem) {
var tempHtm = "";
$.each(templateObj, function (i, item) {
if (item.field) {
tempHtm = tempHtm + item.html + dataItem[item.field];
} else {
tempHtm = tempHtm + item.html;
}
});
html += tempHtm;
});
wl2.append(html);

这个代码本身不难,但是还是有很多缺陷的,比如说模板中的js就无法解析,当时为了解决这个问题还用到了eval自己注入自己......

现在回过头来,项目中的underscore模板用得很不错,所以今天我们首要来看看他的模板方法

初试模板

 var compiled = _.template("hello: <%= name %>");
compiled({name: 'moe'});
=> "hello: moe" var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>";
_.template(list, {people: ['moe', 'curly', 'larry']});
=> "<li>moe</li><li>curly</li><li>larry</li>" var template = _.template("<b><%- value %></b>");
template({value: '<script>'});
=> "<b>&lt;script&gt;</b>"

这就是underscore的模板相关的语法,只要提供模板,然后template一下,再给他个data就行了

最帅的就是其中可以使用js了,只不过js需要放到<%%>里面,于是我们来一点点看他的源码

事实上underscore的机制就是执行<%%>中的逻辑即可,其它都是配套的,或者其它都是字符串,由我们选择是否打印与否

源码分析

 _.templateSettings = {
evaluate: /<%([\s\S]+?)%>/g,
interpolate: /<%=([\s\S]+?)%>/g,
escape: /<%-([\s\S]+?)%>/g
};
var noMatch = /(.)^/; var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
}; var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; _.template = function (text, data, settings) {
var render;
settings = _.defaults({}, settings, _.templateSettings); var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g'); var index = 0;
var source = "__p+='";
text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function (match) { return '\\' + escapes[match]; }); if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n"; if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n"; try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
} if (data) return render(data, _);
var template = function (data) {
return render.call(this, data, _);
}; template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; return template;
};

不得不说这段代码很经典啊,两年前我绝对不会想到如此重要的模板功能竟会只有这点代码......事实上看源码之前也不知道

① 参数说明

template本身是一个方法,可以提供三个参数,第一个为模板文本,第二个为对应模板数据,第三个为基本配置信息,一般不予传递

如果我们传递了data的话,会直接返回解析后的html字符串,没有传递的话就会返回一个编译过的模板

这里用到了defaults方法,我们来看看他是干什么的:

_.defaults(object, *defaults)

defaults对象填充objectundefined属性。并且返回这个object。一旦这个属性被填充,再使用defaults方法将不会有任何效果。

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

② 正则分析

这里用到的几个正则都是匹配全部字符,并且不会保存分组信息,首先看一个简单的

_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};

以上三个最后会被形成一个正则字符串组:

 var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
//=>/<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g

这个正则会由左向右的顺序匹配,如果成功了就不找后面的了,然后下面开始解析模板

③ 模板解析阶段

我们先不管他的逻辑,我们用这个正则来匹配下我们的代码看看有些什么东西

matcher.exec(text)
["<%-name %>", "name ", undefined, undefined]

以上为他输出的结果,第一个是匹配到的对象,第一个参数为匹配到的字符串,第二个为括号中的字符串,现在我们回到程序

程序中用了replace方法,该方法可使用正则表达式,第二个参数为一个函数,这里就需要各位对replace方法比较熟悉了:

function函数具有几个参数:

第一个参数为每次匹配的全文本($&)。
中间参数为子表达式匹配字符串,个数不限,也就是括号中的东西
倒数第二个参数为匹配文本字符串的匹配下标位置。
最后一个参数表示字符串本身。

比如我这里第一次的匹配就应该是这个样子:

["<%-name %>", "name ", undefined, undefined, 5, 
"<div><%-name %>{name}</div><div><%=age %>{age}</div><div><%=home %>{home}</div><div>{test}</div>,
<%if(name == "name_0") %>,我是叶小钗,<%else %>,我是素还真"]

我们来看看他的replace:

 text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; }); if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});

他正则有三个括号,所以他参数也相对而言有三个对应参数了,只不过后面的为空罢了,但是随着匹配类型不同,为空的顺序会不同

PS:各位注意这里的位置参数offset,后面有大用的,这里与我原来的做法类似

好了,我们这里来看他是如何一步步解析的,其实这里有个原则就是,如果能有js解析的一定还是用js简单,比如我们原来字符串转换json使用eval

④ 解析细节

按照我们给出的模板,第一次应该被解析到<%-name%>,这个东西插入的html会被转义比如

//<script>=>&lt;script&gt;

他这里又分了几步走,第一步是将匹配字符串之前的字符串给保存起来,这里对应的“<div>”,

text.slice(index, offset)//text.slice(0, 5)=><div>

要注意的是,他这里做了字符串转义处理

 source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
//比如字符串中具有\n会被换成\\n,里面的引号也会做处理
 if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
/*
"__p+='<div>'+
((__t=(name ))==null?'':_.escape(__t))+
'"
*

第一步处理后,我们的字符串变成了这样,然后我们的index当然会后移:

index = offset + match.length;

其中<%-str%>与<%=str%>都比较简单,特殊的情况发生在第三个地方

 if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}

各位看到了,在模板中出现js表达式时候(非-、=情况),时候后面多了一个__p+=,这个东西学问就大了,因为这里字符串会重新多出一行

"__p+='<div>'+
((__t=(name ))==null?'':_.escape(__t))+
'{name}</div><div>'+
((__t=(age ))==null?'':__t)+
'{age}</div><div>'+
((__t=(home ))==null?'':__t)+
'{home}</div><div>{test}</div>,';
if(name == "name_0")
__p+='"

这个就比较经典了,如果使用js 的eval解析的话,前面会作为一个语句,后面会作为一个新的语句,整个模板解析结束便是这个东西了:

"__p+='<div>'+
((__t=(name ))==null?'':_.escape(__t))+
'{name}</div><div>'+
((__t=(age ))==null?'':__t)+
'{age}</div><div>'+
((__t=(home ))==null?'':__t)+
'{home}</div><div>{test}</div>,';
if(name == "name_0")
__p+=',我是叶小钗,';
else
__p+=',我是素还真';
"

而对javascript function方法比较熟悉的朋友会知道,javascript其实本身就是使用Function构造函数搞出来的,比如说:

 function test() {
alert('叶小钗');
}
var test1 = new Function('alert(\'叶小钗\')');
test();
test1();

test1与test2实际上是等价的,而上面模板解析出来的字符串,不难联想会被我们封装为一个函数

PS:最简单的解析果然是javascript的自己的解析!

render = new Function(settings.variable || 'obj', '_', source);

这里underscore毫不犹豫的将解析结束的模板封装为了一个函数,render最后形成的函数体如下:

 function anonymous(obj, _ /**/) {
var __t, __p = '', __j = Array.prototype.join, print = function () { __p += __j.call(arguments, ''); };
with (obj || {}) {
__p += '<div>' +
((__t = (name)) == null ? '' : _.escape(__t)) +
'{name}</div><div>' +
((__t = (age)) == null ? '' : __t) +
'{age}</div><div>' +
((__t = (home)) == null ? '' : __t) +
'{home}</div><div>{test}</div>,';
if (name == "name_0")
__p += ',我是叶小钗,';
else
__p += ',我是素还真';
}
return __p;
}

不得不说,这个template代码写的真好......,这里还有一点需要注意的就是这里with语句的作用于问题,没有with的话可能就需要做其它处理了,也许是call

函数封装结束只等待调用即可:

var template = function(data) {
return render.call(this, data, _);
};

至此,我们队template的分析结束,我也为自己两年前的问题画下圆满的句号......

Collections

each(list, iterator, [context])

underscore也对each做了实现,如果引用了zepto/jquery包的话可以更加精简:

 var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
};
_.each([1, 2, 3], alert);
=> alerts each number in turn...
_.each({one: 1, two: 2, three: 3}, alert);
=> alerts each number value in turn...

由于ECMAScript 5中的array提供了新的ForEach方法,这里如果支持最新的方法,使用即可

第一个参数为对象集合,第二个为回调函数,第三个为回调函数执行作用域,此方法与zepto类似又多了一个参数,这里不细究

map(list, iterator, [context])

码头哥的解释是,通过变换函数(iterator替代器、回调函数),将list集合值映射到新数组

 _.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results.push(iterator.call(context, value, index, list));
});
return results;
};
_.map([1, 2, 3], function(num){ return num * 3; });
=> [3, 6, 9]
_.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]

ECMAScript5 array同样实现了相关功能,其实就是用于处理数组的,根据处理结果返回新的数组,这个不予深究,想做精简也可以换下

reduce(list, iterator, memo, [context])

 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo;
};
var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
=> 6

该函数将list中的集合归结为单独一个值,每个值会被回调函数处理

这个方法有点迷糊,我们稍微来理一理(这里忽略系统采用原生ECMAScript 5 的做法):

① each方法中的回调第一个参数为当前值,第二个为index(这点好像与zepto不同),第三个为数组对象本身

② 如果传入参数过少(可能不含有回调函数或者传入失误,这里做容错处理,不予关注)

③ 调用回调函数返回处理的数值,由此逻辑结束

这里逻辑乱主要是传入的参数过多,而且参数有所重复,所以我开始有点迷糊

find(list, iterator, [context])

遍历数组,返回第一个回调检测为真的值

 _.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> 2

比较简单,不予理睬,感觉何map有点类似的感觉

filter(list, iterator, [context])

遍历list,返回回调函数返回true的值

 _.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results.push(value);
});
return results;
};
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]

简单来说就是过滤数组

where(list, properties)

遍历list,返回一个数组,这个数组包含对象所列出属性所有的值

 _.where = function(obj, attrs, first) {
if (_.isEmpty(attrs)) return first ? void 0 : [];
return _[first ? 'find' : 'filter'](obj, function(value) {
for (var key in attrs) {
if (attrs[key] !== value[key]) return false;
}
return true;
});
};
_.where(listOfPlays, {author: "Shakespeare", year: 1611});
=> [{title: "Cymbeline", author: "Shakespeare", year: 1611},
{title: "The Tempest", author: "Shakespeare", year: 1611}]

这个函数会调用find或者filter做筛选,本身意义不大

reject(list, iterator, [context])

返回list中没有通过iterator真值检测的元素集合,与filter相反。

 _.reject = function(obj, iterator, context) {
return _.filter(obj, function(value, index, list) {
return !iterator.call(context, value, index, list);
}, context);
};
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [1, 3, 5]

every(list, [iterator], [context])

 _.every = _.all = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
_.every([true, 1, null, 'yes'], _.identity);
=> false

some(list, [iterator], [context])

 var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
_.some([null, 0, 'yes', false]);
=> true

如果list中有任何一个元素通过 iterator 的真值检测就返回true。一旦找到了符合条件的元素, 就直接中断对list的遍历. 如果存在原生的some方法,就使用原生的some

contains(list, value)

 _.contains = _.include = function(obj, target) {
if (obj == null) return false;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
return any(obj, function(value) {
return value === target;
});
};
_.contains([1, 2, 3], 3);
=> true

如果list包含指定的value则返回true(愚人码头注:使用===检测)。如果list 是数组,内部使用indexOf判断。

PS:搞了这么久,这个东西比较靠谱,较实用

invoke(list, methodName, [*arguments])

list的每个元素上执行methodName方法。 任何传递给invoke的额外参数,invoke都会在调用methodName方法的时候传递给它。

 _.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
return (isFunc ? method : value[method]).apply(value, args);
});
};
_.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
=> [[1, 5, 7], [1, 2, 3]]

pluck(list, propertyName)

pluck也许是map最常使用的用例模型的版本,即萃取对象数组中某属性值,返回一个数组。

 _.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
_.pluck(stooges, 'name');
=> ["moe", "larry", "curly"]

max(list, [iterator], [context])

返回list中的最大值。如果传递iterator参数,iterator将作为list排序的依据。

 _.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
return Math.max.apply(Math, obj);
}
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity, value: -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed > result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
_.max(stooges, function(stooge){ return stooge.age; });
=> {name: 'curly', age: 60};

该方法源码内部原理就是求数组最大值......

min(list, [iterator], [context])

返回list中的最小值。如果传递iterator参数,iterator将作为list排序的依据。

 _.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
return Math.min.apply(Math, obj);
}
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity, value: Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
var numbers = [10, 5, 100, 2, 1000];
_.min(numbers);
=> 2

sortBy(list, iterator, [context])

返回一个排序后的list拷贝副本。如果有iterator参数,iterator将作为list排序的依据。迭代器也可以是字符串的属性的名称进行排序的(比如 length)。

 _.sortBy = function(obj, value, context) {
var iterator = lookupIterator(value);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
};
_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
=> [5, 4, 6, 3, 1, 2]

groupBy(list, iterator, [context])

把一个集合分组为多个集合,通过 iterator 返回的结果进行分组. 如果 iterator 是一个字符串而不是函数, 那么将使用 iterator 作为各元素的属性名来对比进行分组.

 var group = function(behavior) {
return function(obj, value, context) {
var result = {};
var iterator = value == null ? _.identity : lookupIterator(value);
each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj);
behavior(result, key, value);
});
return result;
};
};
_.groupBy = group(function(result, key, value) {
(_.has(result, key) ? result[key] : (result[key] = [])).push(value);
});
_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
=> {1: [1.3], 2: [2.1, 2.4]} _.groupBy(['one', 'two', 'three'], 'length');
=> {3: ["one", "two"], 5: ["three"]}

indexBy(list, iterator, [context])

给定一个list,和 一个用来返回一个在列表中的每个元素键 的iterator 函数(或属性名), 返回一个每一项索引的对象。和groupBy非常像,但是当你知道你的键是唯一的时候可以使用indexBy 。

 _.indexBy = group(function(result, key, value) {
result[key] = value;
});
var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
_.indexBy(stooges, 'age');
=> {
"40": {name: 'moe', age: 40},
"50": {name: 'larry', age: 50},
"60": {name: 'curly', age: 60}
}

countBy(list, iterator, [context])

排序一个列表组成一个组,并且返回各组中的对象的数量的计数。类似groupBy,但是不是返回列表的值,而是返回在该组中值的数目。

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

shuffle(list)

返回一个随机乱序的 list 副本, 使用 Fisher-Yates shuffle 来进行随机乱序.

 _.shuffle = function(obj) {
var rand;
var index = 0;
var shuffled = [];
each(obj, function(value) {
rand = _.random(index++);
shuffled[index - 1] = shuffled[rand];
shuffled[rand] = value;
});
return shuffled;
};
_.shuffle([1, 2, 3, 4, 5, 6]);
=> [4, 1, 6, 3, 5, 2]

sample(list, [n])

从 list中产生一个随机样本。传递一个数字表示从list中返回n个随机元素。否则将返回一个单一的随机项。

 _.sample = function(obj, n, guard) {
if (arguments.length < 2 || guard) {
return obj[_.random(obj.length - 1)];
}
return _.shuffle(obj).slice(0, Math.max(0, n));
};
_.sample([1, 2, 3, 4, 5, 6]);
=> 4 _.sample([1, 2, 3, 4, 5, 6], 3);
=> [1, 6, 2]

toArray(list)

list(任何可以迭代的对象)转换成一个数组,在转换 arguments 对象时非常有用。

 _.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
if (obj.length === +obj.length) return _.map(obj, _.identity);
return _.values(obj);
};
(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
=> [2, 3, 4]

PS:该方法比较实用

size(list)

返回list的长度。

 _.size = function(obj) {
if (obj == null) return 0;
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
};
_.size({one: 1, two: 2, three: 3});
=> 3

该方法无用

阶段总结

collection与array我这里都不管他了,感觉意义不大,有需要的朋友自己去看API和源码吧

http://www.css88.com/doc/underscore/#first

下面就挑一点我感兴趣的来说吧

Functions

functions前面提到了几个函数如bind或者bindAll与zepto proxy类似,又有少许不同,我们这里不予关注,我们来看看其他的

memoize(function, [hashFunction])

Memoizes方法可以缓存某函数的计算结果。对于耗时较长的计算是很有帮助的。如果传递了 hashFunction 参数,就用 hashFunction 的返回值作为key存储函数的计算结果。 hashFunction 默认使用function的第一个参数作为key

 _.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
var fibonacci = _.memoize(function(n) {
return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
});

这个函数有点意思,他在函数外层保留了一个闭包,然后,每次都会用到,这个知识点,我们这里来详细说说

......

throttle(function, wait, [options])

创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数。 对于想控制一些触发频率较高的事件有帮助。(愚人码头注:详见:javascript函数的throttle和debounce

默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。如果你想禁用第一次首先执行的话,传递{leading: false},还有如果你想禁用最后一次执行的话,传递{trailing: false}

 _.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
options || (options = {});
var later = function() {
previous = options.leading === false ? 0 : new Date;
timeout = null;
result = func.apply(context, args);
};
return function() {
var now = new Date;
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);

结语

来不起了,有点晚了,待续......

【初探Underscore】再说模版引擎的更多相关文章

  1. Underscore模版引擎的使用-template方法

    之前项目里有遇到在DOM中增加大量的html结构的时候,傻乎乎的在js中写一堆模版,然后用replace一个一个做替换.当时就是难看了点,不觉得啥,现在了解了模版引擎之后回头来看真的比较捉急了,以后是 ...

  2. 构建自己的PHP框架--构建模版引擎(1)

    前段时间太忙,导致好久都没有更新博客了,今天抽出点时间来写一篇. 其实这个系列的博客很久没有更新了,之前想好好规划一下,再继续写,然后就放下了,今天再捡起来继续更新. 今天我们来说一下,如何构建自己的 ...

  3. Smarty模版引擎的原理

    Smarty是一个使用php写出来的模版引擎,用来将原本与html代码混杂在一起PHP代码逻辑分离,实现前后端分离. Smarty模板优点: 1. 速度:采用Smarty编写的程序可以获得最大速度的提 ...

  4. js模版引擎handlebars.js实用教程——为什么选择Handlebars.js

    返回目录 据小菜了解,对于java开发,涉及到页面展示时,比较主流的有两种解决方案: 1. struts2+vo+el表达式. 这种方式,重点不在于struts2,而是vo和el表达式,其基本思想是: ...

  5. 简单JavaScript模版引擎优化

    在上篇博客最简单的JavaScript模板引擎 说了一下一个最简单的JavaScript模版引擎的原理与实现,作出了一个简陋的版本,今天优化一下,使之能够胜任日常拼接html工作,先把上次写的模版函数 ...

  6. Symfony2模版引擎使用说明手册

    一.基本使用 {{ demo }}输出一个demo变量; {% func %}通常是包含一个twig函数例如 for; 举个for循环的例子: {% for i in 0..10 %} <em& ...

  7. Asp.net NVelocity 模版引擎

    NVelocity.dll是Java中常用的一个模版,下面是常用的模版引擎 1,返回string类型的html代码 /// <summary> /// 获取html模版 /// </ ...

  8. PHP模版引擎 – Twig

    在网站开发过程中模版引擎是必不可少的,PHP中用的最多的当属Smarty了.目前公司系统也是用的Smarty,如果要新增一个页面只需把网站的头.尾和左侧公共部分通过Smarty的include方式引入 ...

  9. Nodejs学习笔记(五)--- Express安装入门与模版引擎ejs

    目录 前言 Express简介和安装 运行第一个基于express框架的Web 模版引擎 ejs express项目结构 express项目分析 app.set(name,value) app.use ...

  10. T4教程1 T4模版引擎之基础入门

    T4模版引擎之基础入门   额,T4好陌生的名字,和NuGet一样很悲催,不为世人所熟知,却又在背后默默无闻的奉献着,直到现在我们项目组的人除了我之外,其它人还是对其豪无兴趣,基本上是连看一眼都懒得看 ...

随机推荐

  1. 【原创】C#玩高频数字彩快3的一点体会

    购彩风险非常高,本人纯属很久以前对数字高频彩的一点研究.目前已经远离数字彩,重点研究足球篮球比赛资料库和赛果预测. 这是一篇在草稿箱保存了1年多的文章,一直没发现,顺便修改修改分享给大家.以后会有更多 ...

  2. maven -- 学习笔记(三)之搭建nexus私服

    下载和安装nexus (1)官网链接http://www.sonatype.org/nexus/archived/ (直接点击下载链接,发现下载不了,FQ+迅雷就可以下载) (2)解压到指定文件夹,然 ...

  3. 用实例讲解Spark Sreaming--转

    原文地址:http://www.infoq.com/cn/articles/spark-sreaming-practice 本篇文章用Spark Streaming +Hbase为列,Spark St ...

  4. RPC原理详解

    RPC 功能目标 RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性. 为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分 ...

  5. Microsoft Azure News(1) 新的数据中心Japan East, Japan West and Brazil South

    <Windows Azure Platform 系列文章目录> 微软官方博客今天表示,微软宣布将在日本东部(埼玉县)和日本西部(大阪)提供Windows Azure服务.就在最近,微软为W ...

  6. 使用log4j配置不同文件输出不同内容

    敲代码中很不注意写日志,虽然明白很重要.今天碰到记录日志,需要根据内容分别输出到不同的文件. 参考几篇文章: 感觉最详细:http://blog.csdn.net/azheng270/article/ ...

  7. Kylin的cube模型

    1. 数据仓库的相关概念 OLAP 大部分数据库系统的主要任务是执行联机事务处理和查询处理,这种处理被称为OLTP(Online Transaction Processing, OLTP),面向的是顾 ...

  8. swift3.0的改变

    Swift在这2年的时间内,发展势头迅猛,在它开源后,更是如井喷一样,除了 iOS.mac 平台,还支持了 Linux. 而今年下半年, Swift 3.0 也会随之发布.https://github ...

  9. Python语言特性之1:函数参数传递

    问题:在Python文档中好像没有明确指出一个函数参数传递的是值传递还是引用传递.如下面的代码中"原始值"是不放生变化的: class PassByReference: def _ ...

  10. rtf格式的一些说明,转载的

    RTF是Rich TextFormat的缩写,意即多文本格式.这是一种类似DOC格式(Word文档)的文件,有很好的兼容性,使用Windows"附件"中的"写字板&quo ...