-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JavaScript专题之学underscore在数组中查找指定元素 #37
Comments
厉害了 大神 |
@FrontToEnd []~( ̄▽ ̄)~* |
方法都好妙 |
🤒 之前看过loadsh的源码解读,感觉实现的方式都是差不多的~ |
@HowardTangHw 确实如此,毕竟两者“本是同根生”~ |
好了,看了你的专题,我要开始读源码了 |
这里i的初始笔误了么 |
@GopherJ 这是一个倒序循环,所以 i 是从 length 开始,i 的值依此递减 |
@mqyqingfeng 是我糊涂了么233?还是没懂,有 arr[arr.length] 这个项么? |
@GopherJ 哈哈,尴尬了😂 感谢指出~ |
@mqyqingfeng 哈哈我就说肯定笔误了。感谢分享啊!写的非常不错,有些地方我估计还得来来回回看几次,期待新的文章! |
楼主能不能解释下为什么参数中经常会写context,而实际并未传入呀,基本上都是undefined |
@dowinweb 像 ES5 的 map、reduce、forEach、find 等等函数,第二个参数都是 context,这是需要开发者写的时候传入的,这其实还算是个蛮常见的场景,举个例子: function Greet() {
this.prefix = 'Hello'
this.friends = ['dowinweb', 'kevin']
}
Greet.prototype.sayHello = function(){
var self = this;
var res = this.friends.map(function(item){
return self.prefix + ' ' + item
})
return res
}
var greet = new Greet();
console.log(greet.sayHello()) // [ "Hello dowinweb", "Hello kevin" ] 一般 map 里的函数,如果要使用外部 this 的值,需要通过 其实我们可以写的更加优雅,就是利用 context 这个参数: function Greet() {
this.prefix = 'Hello'
this.friends = ['dowinweb', 'kevin']
}
Greet.prototype.sayHello = function(){
var res = this.friends.map(function(item){
return this.prefix + ' ' + item
}, this)
return res
}
var greet = new Greet();
console.log(greet.sayHello()) // // [ "Hello dowinweb", "Hello kevin" ] 这个算是一个小技巧吧~ |
@mqyqingfeng 嗯嗯,学习了,谢谢 |
倒序查找下标函数里,“item == 1”的返回值不应该是3吗? |
@Qinghuanhehuan 你这个逻辑想错了呀。给你一个数组[1,2,3,4],让你找出最后一个1出现的index,你说这个index应该是多少?明显应该是0嘛。。 |
@Qinghuanhehuan 虽然是从后面找,可是下标还是从前面开始的,无论从前面开始找,还是后面开始找,最终返回的是元素在数组下的下标,而不是第几位 |
@Qinghuanhehuan 你只是倒序查找这个数组,并不是把数组倒序,然后正向查找 |
@delayk @HowardTangHw 嗯嗯,懂了,谢谢! |
@delayk @HowardTangHw 感谢回答哈~ @Qinghuanhehuan ,其实你可以这样想哈,我们倒序查找也是为了找出该元素的下标,然后通过 arr[下标值] 来获取元素,如果 [1, 2, 3, 4] 查找 item === 1,返回值是 3 的话,我们通过下标获取该元素,arr[3] 不就取成了 4 吗? |
传入一个函数,返回另一个函数,这属于函数柯里化吗? |
@iiicon 所以我也感觉写的so nb,脑洞太大 |
@iiicon 函数柯里化是指将一个 n 元函数转化为 n 个一元函数,我觉得这个例子叫做高阶函数更好些,高阶函数就是传入一个函数,返回一个函数。 说起来这篇文章并没有讲到这样的例子呀~ 关于平时写的时候抽象不到这个层次的问题,实际上,这种方法很多时候是"被迫"要去做的,你比如说函数防抖: document.getElementById('test').onmouseover = fn 每当 mouseover 的时候,就会去执行 fn 函数,如果我们要做防抖,我们可以直接在 fn 函数内部去做,但这样就会跟 fn 的代码连在一起,如果我们要给多个函数都做防抖,岂不是每次都要写一遍,所以我们才会单独抽离写一个 debounce 函数: var debounceFn = debounce(fn);
document.getElementById('test').onmouseover = debounceFn 你看赋给 onmouserover 的值必须要是一个函数,所以你写 debounce 函数的时候,不就是要传入一个函数,返回另一个函数吗? 关于你写的这个 cb 函数,我最一开始以为是 underscore 中的 cb 函数,后来觉得这个有点像 bind 函数的模拟实现: fucntion bind(fn, context){
return function(){
return fn.apply(context,arguments);
}
} bind 函数其实也是同样的道理,我们只是想将一个函数的 this 指向一个固定的 context,如果我们直接使用 fn.call(context) ,就执行了 fn 函数,其实我们现在还并不想执行这个函数,所以为了不让他立刻执行,就可以返回一个函数,里面”包裹“着执行 fn 函数的代码。 嗯,说了这么多,还是要多看多思考…… 最后, 记得早点睡觉哈~ 😀 |
@mqyqingfeng 我其实是拷贝的你的 sortedIndex 第二版的 iteratee 函数,我感觉和 bind 关系不大,如果不抽象一般就 iteratee(array[mid], fn, context), 我一般就这样 还有就是在南方这边工作就是要中午睡一大觉,晚上不熬个夜感觉贼浪费,不过你说的很对,得爱惜自己的身体 |
@iiicon 哈哈,我竟然没有发现……😂 这个 cb 函数是我当时简写 underscore 的 cb 函数得来的……现在看来,写法上有点莫名其妙,给你造成了困扰,我很抱歉,完整的 cb 函数还要参考 underscore 系列之内部函数 cb 和 optimizeCb 如果我现在写的话,应该是写成: function cb(func, context) {
if (context === void 0) return func;
return function() {
return func.apply(context, arguments);
};
} 我还是修改一下吧~ |
@mqyqingfeng 在具有 |
@Tan90Qian 感谢指出~ 确实有这个问题~ 此外,关于 |
最近我在手动实现数组的常用方法,同事推荐看你的这篇文章,受益匪浅。 |
|
|
字符串的indexOf 有很大的学问,楼主可以看看。 |
可能是我技术的问题,我感觉稍微有点乱。typeof idx == "number这个步骤只对后面的for循环有用吧,我觉得可以把它跟for循环写在一起。然后我有个问题sortedIndex && idx && length其中的 idx是什么意思,而且还是else if判断的,那么传入idx时,sortedIndex是无效的
|
@yinju123 https://github.com/jashkenas/underscore/blob/58df1085cdb05cb0888719c5fe5493948604ab69/test/arrays.js#L351-L353 underscore 的测试案例里面可以看出来,idx 是在非数字的情况下,才会触发 sortedIndex 的执行。 |
感觉把 const createIndexOfFinder = dir => {
return (arr, item, fromIndex) => {
let end = arr.length;
let i = 0;
if (typeof fromIndex === 'number') {
if (dir > 0) { // 正向查找
......
} else { // 逆向查找
end = fromIndex >= 0 ? Math.min(fromIndex, end) : fromIndex + end;
}
}
for (i = dir > 0 ? i : end; i >= 0 && i <= end; i += dir) {
......
}
return -1;
}
} |
针对于lastIndexOf,关于NaN的查找哪里有点bug 可以增加一条判断 不知道说的对不对 |
前言
在开发中,我们经常会遇到在数组中查找指定元素的需求,可能大家觉得这个需求过于简单,然而如何优雅的去实现一个 findIndex 和 findLastIndex、indexOf 和 lastIndexOf 方法却是很少人去思考的。本文就带着大家一起参考着 underscore 去实现这些方法。
在实现前,先看看 ES6 的 findIndex 方法,让大家了解 findIndex 的使用方法。
findIndex
ES6 对数组新增了 findIndex 方法,它会返回数组中满足提供的函数的第一个元素的索引,否则返回 -1。
举个例子:
findIndex 会找出第一个大于 15 的元素的下标,所以最后返回 3。
是不是很简单,其实,我们自己去实现一个 findIndex 也很简单。
实现findIndex
思路自然很明了,遍历一遍,返回符合要求的值的下标即可。
findLastIndex
findIndex 是正序查找,但正如 indexOf 还有一个对应的 lastIndexOf 方法,我们也想写一个倒序查找的 findLastIndex 函数。实现自然也很简单,只要修改下循环即可。
createIndexFinder
然而问题在于,findIndex 和 findLastIndex 其实有很多重复的部分,如何精简冗余的内容呢?这便是我们要学习的地方,日后面试问到此类问题,也是加分的选项。
underscore 的思路就是利用传参的不同,返回不同的函数。这个自然是简单,但是如何根据参数的不同,在同一个循环中,实现正序和倒序遍历呢?
让我们直接模仿 underscore 的实现:
sortedIndex
findIndex 和 findLastIndex 的需求算是结束了,但是又来了一个新需求:在一个排好序的数组中找到 value 对应的位置,保证插入数组后,依然保持有序的状态。
假设该函数命名为 sortedIndex,效果为:
也就是说如果,注意是如果,25 按照此下标插入数组后,数组变成 [10, 20, 25, 30],数组依然是有序的状态。
那么这个又该如何实现呢?
既然是有序的数组,那我们就不需要遍历,大可以使用二分查找法,确定值的位置。让我们尝试着去写一版:
现在的方法虽然能用,但通用性不够,比如我们希望能处理这样的情况:
所以我们还需要再加上一个参数 iteratee 函数对数组的每一个元素进行处理,一般这个时候,还会涉及到 this 指向的问题,所以我们再传一个 context 来让我们可以指定 this,那么这样一个函数又该如何写呢?
indexOf
sortedIndex 也完成了,现在我们尝试着去写一个 indexOf 和 lastIndexOf 函数,学习 findIndex 和 FindLastIndex 的方式,我们写一版:
fromIndex
但是即使是数组的 indexOf 方法也可以多传递一个参数 fromIndex,从 MDN 中看到 fromIndex 的讲究可有点多:
再看看 lastIndexOf 的 fromIndex:
按照这么多的规则,我们尝试着去写第二版:
优化
到此为止,已经很接近原生的 indexOf 函数了,但是 underscore 在此基础上还做了两点优化。
第一个优化是支持查找 NaN。
因为 NaN 不全等于 NaN,所以原生的 indexOf 并不能找出 NaN 的下标。
那么我们该如何实现这个功能呢?
就是从数组中找到符合条件的值的下标嘛,不就是我们最一开始写的 findIndex 吗?
我们来写一下:
第二个优化是支持对有序的数组进行更快的二分查找。
如果 indexOf 第三个参数不传开始搜索的下标值,而是一个布尔值 true,就认为数组是一个排好序的数组,这时候,就会采用更快的二分法进行查找,这个时候,可以利用我们写的 sortedIndex 函数。
在这里直接给最终的源码:
值得注意的是:在 underscore 的实现中,只有 indexOf 是支持有序数组使用二分查找,lastIndexOf 并不支持。
The text was updated successfully, but these errors were encountered: