Why underscore
(以为这部份眼熟的能够直接跳到下一段了…)
近来最先看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 设想中。
浏览一些有名框架类库的源码,就好像和一个个巨匠对话,你会学到许多。为何是 underscore?最主要的原因是 underscore 简短精干(约 1.5k 行),封装了 100 多个有效的要领,耦合度低,异常合适逐一要领浏览,合适楼主如许的 JavaScript 初学者。从中,你不仅能够学到用 void 0 替代 undefined 防止 undefined 被重写等一些小技能 ,也能够学到变量范例推断、函数撙节&函数去抖等经常使用的要领,还能够学到许多浏览器兼容的 hack,更能够学到作者的团体设想思绪以及 API 设想的道理(向后兼容)。
以后楼主会写一系列的文章跟人人分享在源码浏览中进修到的学问。
underscore-1.8.3 源码解读项目地点 https://github.com/hanzichi/underscore-analysis
underscore-1.8.3 源码全文解释 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/underscore-1.8.3-analysis.js
underscore-1.8.3 源码解读系列文章 https://github.com/hanzichi/underscore-analysis/issues
迎接围观~ (假如有兴致,迎接 star & watch~)您的关注是楼主继承写作的动力
数组去重
本日要聊的,也是我之前笔试时碰到过的一个题目,数组去重,不知道如今的笔试题还考不考这个?
数组去重,平常需求是给你一个数组,挪用去重要领,返回数值副本,副本中没有反复元素。平常来讲,两个元素经由过程 ===
比较返回 true 的视为雷同元素,须要去重,所以,1
和 "1"
是差别的元素,1
和 new Number(1)
是差别的元素,{}
和 {}
是差别的元素(援用差别)。(固然假如需求以为 {}
和 {}
算作雷同的元素,那末解法就不一样了)
要领一
无需思索,我们能够获得 O(n^2) 复杂度的解法。定义一个变量数组 res 保留效果,遍历须要去重的数组,假如该元素已存在在 res 中了,则申明是反复的元素,假如没有,则放入 res 中。
function unique(a) {
var res = [];
for (var i = 0, len = a.length; i < len; i++) {
var item = a[i];
for (var j = 0, jLen = res.length; j < jLen; j++) {
if (res[j] === item)
break;
}
if (j === jLen)
res.push(item);
}
return res;
}
var a = [1, 1, '1', '2', 1];
var ans = unique(a);
console.log(ans); // => [1, "1", "2"]
代码异常简朴,那末是不是能更简约些?假如不斟酌浏览器兼容,我们能够用 ES5 供应的 Array.prototype.indexOf 要领来简化代码。
function unique(a) {
var res = [];
for (var i = 0, len = a.length; i < len; i++) {
var item = a[i];
(res.indexOf(item) === -1) && res.push(item);
}
return res;
}
var a = [1, 1, '1', '2', 1];
var ans = unique(a);
console.log(ans); // => [1, "1", "2"]
既然用了 indexOf,那末无妨再加上 filter。
function unique(a) {
var res = a.filter(function(item, index, array) {
return array.indexOf(item) === index;
});
return res;
}
var a = [1, 1, '1', '2', 1];
var ans = unique(a);
console.log(ans); // => [1, "1", "2"]
要领二
法一是将原数组中的元素和效果数组中的元素逐一比较,我们能够换个思绪,将原数组中反复元素的末了一个元素放入效果数组中。
function unique(a) {
var res = [];
for (var i = 0, len = a.length; i < len; i++) {
for (var j = i + 1; j < len; j++) {
// 这一步非常奇妙
// 假如发明雷同元素
// 则 i 自增进入下一个轮回比较
if (a[i] === a[j])
j = ++i;
}
res.push(a[i]);
}
return res;
}
var a = [1, 1, '1', '2', 1];
var ans = unique(a);
console.log(ans); // => ["1", "2", 1]
虽然复杂度照样 O(n^2),然则能够看到效果差别,1 出如今了数组末了面,由于效果数组取的是元素末了一次涌现的位置。
要领三(sort)
假如笔试面试时只答出了上面如许 O(n^2) 的计划,能够还不能使面试官惬意,下面就来讲几种进阶计划。
将数组用 sort 排序后,理论上雷同的元素会被放在相邻的位置,那末比较前后位置的元素就能够了。
function unique(a) {
return a.concat().sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
}
var a = [1, 1, 3, 2, 1, 2, 4];
var ans = unique(a);
console.log(ans); // => [1, 2, 3, 4]
然则题目又来了,1
和 "1"
会被排在一同,差别的 Object 会被排在一同,由于它们 toString() 的效果雷同,所以会涌现如许的毛病:
var a = [1, 1, 3, 2, 1, 2, 4, '1'];
var ans = unique(a);
console.log(ans); // => [1, 2, 3, 4]
固然你完全能够针对数组中能够涌现的差别范例,来写这个比较函数。不过这好像有点贫苦。
要领四 (object)
用 JavaScript 中的 Object 对象来当作哈希表,这也是几年前笔试时的解法,跟 sort 一样,能够去重完全由 Number 基本范例构成的数组。
function unique(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
var a = [1, 1, 3, 2, 1, 2, 4];
var ans = unique(a);
console.log(ans); // => [1, 3, 2, 4]
照样和要领三一样的题目,由于 Object 的 key 值都是 String 范例,所以关于 1
和 "1"
没法离别,我们能够轻微革新下,将范例也存入 key 中。
function unique(a) {
var ret = [];
var hash = {};
for (var i = 0, len = a.length; i < len; i++) {
var item = a[i];
var key = typeof(item) + item;
if (hash[key] !== 1) {
ret.push(item);
hash[key] = 1;
}
}
return ret;
}
var a = [1, 1, 3, 2, '4', 1, 2, 4, '1'];
var ans = unique(a);
console.log(ans); // => [1, 3, 2, "4", 4, "1"]
虽然处理了憎恶的 1
和 "1"
的题目,然则另有别的题目!
var a = [{name: "hanzichi"}, {age: 30}, new String(1), new Number(1)];
var ans = unique(a);
console.log(ans); // => [Object, String]
然则假如数组元素全部是基本范例的 Number 值,键值对法应该是最高效的!
要领五 (ES6)
ES6 布置了 Set 以及 Array.from 要领,太壮大了!假如浏览器支撑,完全能够如许:
function unique(a) {
return Array.from(new Set(a));
}
var a = [{name: "hanzichi"}, {age: 30}, new String(1), new Number(1)];
var ans = unique(a);
console.log(ans); // => [Object, Object, String, Number]
_.unique
末了来看看 underscore 对此的完成体式格局,underscore 将此封装到了 _.unique 要领中,挪用体式格局为 _.unique(array, [isSorted], [iteratee])。个中第一个参数是必需的,是须要去重的数组,第二个参数可选,假如数组有序,则能够传入布尔值 true,第三个参数可选,假如须要对数组迭代的效果去重,则能够传入一个迭代函数。而数组元素去重是基于 ===
运算符的。
实在很简朴,underscore 中的完成体式格局和上面的要领一类似。
我们来看它的中心代码:
for (var i = 0, length = getLength(array); i < length; i++) {
var value = array[i],
// 假如指定了迭代函数
// 则对数组每一个元素举行迭代
computed = iteratee ? iteratee(value, i, array) : value;
// 假如是有序数组,则当前元素只需跟上一个元素对照即可
// 用 seen 变量保留上一个元素
if (isSorted) {
// 假如 i === 0,则直接 push
// 不然比较当前元素是不是和前一个元素相称
if (!i || seen !== computed) result.push(value);
// seen 保留当前元素,供下一次对照
seen = computed;
} else if (iteratee) {
// 假如 seen[] 中没有 computed 这个元素值
if (!_.contains(seen, computed)) {
seen.push(computed);
result.push(value);
}
} else if (!_.contains(result, value)) {
// 假如不必经由迭代函数盘算,也就不必 seen[] 变量了
result.push(value);
}
}
表面的轮回遍历数组元素,关于每一个元素,假如数组有序,则和前一个元素比较,假如雷同,则已涌现过,不到场到效果数组中,不然则到场。而假如有迭代函数,则盘算传入迭代函数后的值,对值去重,挪用 _.contains 要领,而该要领的中心就是挪用 _.indexOf 要领,和我们上面说的要领一殊途同归。
关于 _.unique 要领的细致代码,能够参考 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L519-L547