每天手写 lodash 方法
base 方法
在手写了几个方法后,发现 lodash 中的方法,除了一少部分可以不借助 base 方法外,其他都需要依赖于它;所以先模仿实现一些 base 文件中提供的方法,我觉得一定有所补益。
baseIteratee
baseIteratee 方法传入一个变量
function baseIteratee(value) {
// Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
// See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
if (typeof value == 'function') {
return value;
}
if (value == null) {
return identity;
}
if (typeof value == 'object') {
return isArray(value)
? baseMatchesProperty(value[0], value[1])
: baseMatches(value);
}
return property(value);
}
1.如果传入的是一个 function ,则会直接返回;
2.如果传入一个 null ,则返回 identity
3.如果传入是数组,返回 baseMatchesProperty(value[0], value[1])
4.如果传入是对象,返回baseMatches(value)
5.如果都不是,返回 property(value)
出了第一个显而易见,其他的都要讲讲:
Array 方法
_.findIndex(array, [predicate=_.identity], [fromIndex=0])
该方法类似_.find,区别是该方法返回第一个通过 predicate 判断为真值的元素的索引值(index),而不是元素本身。
例子
var users = [
{ 'user': 'barney', 'active': false },
{ 'user': 'fred', 'active': false },
{ 'user': 'pebbles', 'active': true }
];
_.findIndex(users, function(o) { return o.user == 'barney'; });
// => 0
// The `_.matches` iteratee shorthand.
_.findIndex(users, { 'user': 'fred', 'active': false });
// => 1
// The `_.matchesProperty` iteratee shorthand.
_.findIndex(users, ['active', false]);
// => 0
// The `_.property` iteratee shorthand.
_.findIndex(users, 'active');
// => 2
手动实现
function myFindIndex(arr = []){
}
官方实现
function findIndex(array, predicate, fromIndex) {
var length = array == null ? 0 : array.length;//数组长度
if (!length) {//如果为空数组,返回-1
return -1;
}
var index = fromIndex == null ? 0 : toInteger(fromIndex);//开始查找的索引
if (index < 0) {//如果index为负值,从末尾开始算,-1就是最后一个元素,依次计算,如果最后大于数组的长度,就是0
index = nativeMax(length + index, 0);
}
//调用baseFindIndex,并且将遍历方法封装,然后将结果作为返回值返回
return baseFindIndex(array, baseIteratee(predicate, 3), index);
}
function baseFindIndex(array, predicate, fromIndex, fromRight) {
var length = array.length,//数组长度
index = fromIndex + (fromRight ? 1 : -1);//数组索引
//循环,对每个元素调用判断方法,如果结果为true,返回对应的index
while ((fromRight ? index-- : ++index < length)) {
if (predicate(array[index], index, array)) {
return index;
}
}
return -1;//返回-1
}
function baseIteratee(value) {
// Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
// See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
if (typeof value == 'function') {
return value;
}
if (value == null) {
return identity;
}
if (typeof value == 'object') {
return isArray(value)
? baseMatchesProperty(value[0], value[1])
: baseMatches(value);
}
return property(value);
}
分析
最合核心的,lodash 使用了 baseIteratee :封装遍历器(让遍历器不仅可以是函数,还可以是属性或者对象)
_.fill(array, value, [start=0], [end=array.length])
使用 value 值来填充(替换) array,从start位置开始, 到end位置结束(但不包含end位置)。
Note: 这个方法会改变 array(注:不是创建新数组)。
例子
var array = [1, 2, 3];
_.fill(array, 'a');
console.log(array);
// => ['a', 'a', 'a']
_.fill(Array(3), 2);
// => [2, 2, 2]
_.fill([4, 6, 8, 10], '*', 1, 3);
// => [4, '*', '*', 10]
手动实现
function myFill(arr,value,start = 0,end = arr.length){
if(!Array.isArray(arr)) return [];
if(!value) return arr;
for(let index = 0;index< arr.length;index++){ // 本想用 for in 的,但是发现稀疏数组,不能呗
if(index >= start && index < end){
arr[index] = value
}
}
return arr
}
官方实现
_.drop(array, [n=1])
创建一个切片数组,去除array前面的n个元素。(n默认值为1。)
例子
_.drop([1, 2, 3]);
// => [2, 3]
_.drop([1, 2, 3], 2);
// => [3]
_.drop([1, 2, 3], 5);
// => []
_.drop([1, 2, 3], 0);
// => [1, 2, 3]
手动实现
function myDrop(arr,n = 1){
if(!Array.isArray(arr))return []
if(n >= arr.length) return []
arr.splice(0,n);
return arr
}
官方实现
function drop(array, n=1) {
const length = array == null ? 0 : array.length
return length
? slice(array, n < 0 ? 0 : toInteger(n), length)
: []
}
比对
实现有误, drop 是一个纯函数,不能如此处理
_.differenceWith
different 系列的,借用基础方法太多,线条过
_.differenceBy(array, [values], [iteratee=_.identity])
作用
这个方法类似_.difference ,除了它接受一个 iteratee (注:迭代器), 调用array 和 values 中的每个元素以产生比较的标准。 结果值是从第一数组中选择。iteratee 会调用一个参数:(value)。(注:首先使用迭代器分别迭代array 和 values中的每个元素,返回的值作为比较值)。
Note: 不像 _.pullAllBy,这个方法会返回一个新数组。
例子
_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
// => [3.1, 1.3]
// The `_.property` iteratee shorthand.
_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
// => [{ 'x': 2 }]
手动实现
实现前先分析一下,这个方法和之前实现过的 _.difference 很像,所以把流程直接借鉴过来;不同之处在于本方法第三个参数。
根据官方的例子来看,如果第三个参数是一个 function,那么就对数组的每一项都运行这个方法iteratee(item)
;如果没传第三个参数或者第三个参数不是 function,则按照 myDifference 返回
function myDifferenceBy(traget,array,itera){
if(!Array.isArray(traget) || !Array.isArray(array) ){
return [];
}
if(typeof itera != 'function'){
return myDifference(traget,array);
}
const resultArr = [];
const flatArr = array.flat(Infinity)
const changedFlatArr = flatArr.map(item => itera(item) )
for(let i of traget){
const changedI = itera(i)
if(!changedFlatArr.includes(changedI)){
resultArr.push(i)
}
}
return resultArr;
}
官方实现
function differenceBy(array, ...values) {
let iteratee = last(values)
if (isArrayLikeObject(iteratee)) {
iteratee = undefined
}
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), iteratee)
: []
}
分析
last
方法返回数组的最后一项目
baseDifference
方法第三个参数传入了 iteratee
;
总的来说, difference 和 differentBy 的核心方法都是 baseDifference;
_.difference(array, [values])
作用
创建一个具有唯一array值的数组,每个值不包含在其他给定的数组中。(注:即创建一个新数组,这个数组中的值,为第一个数字(array 参数)排除了给定数组中的值。)该方法使用 SameValueZero(Array.includes 用的也是 SameValueZero )做相等比较。结果值的顺序是由第一个数组中的顺序确定。
例子
_.difference([3, 2, 1], [4, 2]);
// => [3, 1]
手动实现
function myDifference(traget=[],array = []){
if(!Array.isArray(traget) || !Array.isArray(array) ){
return [];
}
const resultArr = [];
for(let i of traget){
if(!array.flat(Infinity).includes(i)){
resultArr.push(i)
}
}
return resultArr;
}
官方实现
function difference(array, ...values) {
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
: []
}
分析
isArrayLikeObject
用于判断一个变量是否是 ArryLike 或者 ObjectLike;
baseDifference
基本可以理解为作用和本方法(_.difference)没有区别;
baseFlatten
用于扁平化数组
_.concat(array, [values])
作用
创建一个新数组,将array与任何数组 或 值连接在一起。
例子
var array = [1];
var other = _.concat(array, 2, [3], [[4]]);
console.log(other);
// => [1, 2, 3, [4]]
console.log(array);
// => [1]
手动实现
function myConcat(){
const argument = Array.from(arguments)
// 如果不让用 ES6,就换个替换的方案,问题不大
//const argument = Array.prototype.slice.call(arguments);
const arr = argument.shift();
const arg =[]
for(let a of argument){
// 同理,不让用ES6 就自己写一个 isArray 的方法,有很多办法实现
if(Array.isArray(a)){ // 如果是数组就降低一维
arr.push(...a)
}else{ // 如果不是数组就直接 push 进去
arr.push(a)
}
}
return arr
}
官方实现
function concat() {
var length = arguments.length;
if (!length) {
return [];
}
var args = Array(length - 1),
array = arguments[0],
index = length;
while (index--) {
args[index - 1] = arguments[index];
}
return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
}
分析
原理基本是一致的,去遍历传入的参数列表;如果是数组就降低一维,如果不是数组就直接 push 到返回数组中
loadsh 在这个方法中复用了自己之前实现的方法:baseFlatten、copyArray
_.compact(array)
作用
创建一个新数组,包含原数组中所有的非假值元素。例如false, null, 0, "", undefined, 和 NaN 都是被认为是“假值”。
_.compact([0, 1, false, 2, '', 3]);
// => [1, 2, 3]
手动实现
function myCompact(arr= []){
const newArr = [];
for(let item of arr){
!isX(item) && newArr.push(item)
function isX(item){
let isX = false
if(item === false){
isX = true
}else if(item === 0){
isX = true
}else if(item === ''){
isX = true
}else if(item !== item){ // NaN
isX = true
}else if(!item){ // undefined 和 null
isX = true
}
return isX;
}
}
return newArr;
}
官方实现
function compact(array) {
let resIndex = 0
const result = []
if (array == null) {
return result
}
for (const value of array) {
if (value) {
result[resIndex++] = value
}
}
return result
}
分析
- 检查传入值,如果是 null ,则直接返回
- 官方并没有分别去判断某一个值是不是虚值,直接用
if(value)
运算,为真,则必不是虚值 - 没有使用 push ,而是指定下标的形式填充数组
result[resIndex++] = value
(为什么?)
_.chunk(array, [size=1])
将数组(array)拆分成多个 size 长度的区块,并将这些区块组成一个新数组。 如果array 无法被分割成全部等长的区块,那么最后剩余的元素将组成一个区块。
用法
_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]
_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
手动实现:
function myChunk(arr,size = 1){
const result = [];
const arrLength = arr.length;
let index = 0;
let resultNums = 0
while(arrLength !== resultNums ){
let thisArr = arr.splice(index,size)
resultNums += thisArr.length
result.push(thisArr);
}
return result;
}
官方实现
function chunk(array, size = 1) {
size = Math.max(toInteger(size), 0)
const length = array == null ? 0 : array.length
if (!length || size < 1) {
return []
}
let index = 0
let resIndex = 0
const result = new Array(Math.ceil(length / size))
while (index < length) {
result[resIndex++] = slice(array, index, (index += size))
}
return result
}
分析
- 对传入的 size 做了判断,先转为 number,如果小于 0,则直接返回
- 对传入的 array 做了判断,如果不是数组,则直接返回
- 使用 new Array 生成确定长度的数组;