Lodash系列 – 1. chunk,compact,concat

写在前面

最近想提高一下自己的编程能力,另一方面是为了培养自己规律写作的习惯,所以开了这个专栏,督促自己每天写几个 Lodash 的几个 Api

今天带来三个 Api,分别是 chunk, compact, concat

Api

chunk

先看官网对于 chunk 的介绍

_.chunk(array, [size=1])

Creates an array of elements split into groups the length of size. If array can’t be split evenly, the final chunk will be the remaining elements.

Arguments

  1. array  (Array) : The array to process.
  2. [size=1]  (number) : The length of each chunk

Returns

(Array) : Returns the new array of chunks.

Example

_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]
_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]
 

_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
_.chunk(['a', 'b', 'c', 'd'], 2); // => [['a', 'b'], ['c', 'd']]   _.chunk(['a', 'b', 'c', 'd'], 3); // => [['a', 'b', 'c'], ['d']]

根据提供的例子,参数是一个数组,一个要分割数组的长度,返回一个分割后的数组,如果不能平均分割,最后一个元素被单独放置在一个新的数组里

分析

本质就是切割数组,切割的长度是后面传入的参数 size,可以使用数组原生方法 slice 方法进行切割,同时 slice 规定: 如果被切割的数组长度小于 size,仅仅返回仅剩的数组元素,所以使用 slice 切割是最合适不过的了,最后把切割后的结果放入新的数组

type TChunk = <T>(arr:Array<T> | null | undefined,size?:number) =>T[]
const chunk:TChunk = (arr,size = 1)=>{
if(arr == null) return [];
let len = arr.length;
// 创建一个结果数组,数组的长度是 原数组总长度 / 切割长度 ,并且向上取整
// len = 5, size = 2, res 的长度就应该是 3
let res = new Array(Math.ceil(len / size));
// 切割的开始位置,结束的位置是 index + size
let index = 0;
// 结果数组的索引,切割一次 resIndex 增加一次
let resIndex = 0;
while(index < len){
res[resIndex++] = arr.slice(index,index+=size)
}
return res
}
type TChunk = <T>(arr:Array<T> | null | undefined,size?:number) =>T[]




const chunk:TChunk = (arr,size = 1)=>{



  if(arr == null) return [];

  let len = arr.length;


// 创建一个结果数组,数组的长度是 原数组总长度 / 切割长度 ,并且向上取整
// len = 5, size = 2, res 的长度就应该是 3



  let res = new Array(Math.ceil(len / size));
// 切割的开始位置,结束的位置是 index + size
  let index  = 0;
  
  // 结果数组的索引,切割一次 resIndex 增加一次
  let resIndex = 0;

  while(index < len){
    res[resIndex++] = arr.slice(index,index+=size)
  }

  return res
}
type TChunk = <T>(arr:Array<T> | null | undefined,size?:number) =>T[] const chunk:TChunk = (arr,size = 1)=>{   if(arr == null) return [];   let len = arr.length; // 创建一个结果数组,数组的长度是 原数组总长度 / 切割长度 ,并且向上取整 // len = 5, size = 2, res 的长度就应该是 3   let res = new Array(Math.ceil(len / size)); // 切割的开始位置,结束的位置是 index + size   let index  = 0; // 结果数组的索引,切割一次 resIndex 增加一次   let resIndex = 0;   while(index < len){     res[resIndex++] = arr.slice(index,index+=size)   }   return res }

compact

先看官网对于 compact(紧凑) 的介绍

_.compact(array)

Creates an array with all falsey values removed. The values falsenull0""undefined, and NaN are falsey.

Arguments

  1. array  (Array) : The array to compact.

Returns

(Array) : Returns the new array of filtered values.

Example

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

根据提供的例子,参数是一个数组,返回一个只有 真值 的数组

分析

类似于 es6 中的 Filter + Boolean的效果,可以使用 Array.filter(Boolean) 代替

type Falsey = null | undefined | false | "" | 0;
type TCompact = <T>(arr:Array<T | Falsey>) =>T[];
const compact:TCompact = (array)=>{
let resIndex = 0;
let result:any[] = [];
if (array == null) {
return result
}
for (const value of array) {
// 在 这个地方阻断 falsy 值
if(value){
result[resIndex++] = value
}
}
return result
}
type Falsey = null | undefined | false | "" | 0;
type TCompact = <T>(arr:Array<T | Falsey>) =>T[];


const  compact:TCompact = (array)=>{
  let resIndex = 0;
  let result:any[] = [];



  if (array == null) {
    return result
  }



  for (const value of array) {
  // 在 这个地方阻断 falsy 值
    if(value){
      result[resIndex++] = value
    }
  }
  
  return result
}
type Falsey = null | undefined | false | "" | 0; type TCompact = <T>(arr:Array<T | Falsey>) =>T[]; const  compact:TCompact = (array)=>{   let resIndex = 0;   let result:any[] = [];   if (array == null) {     return result   }   for (const value of array) { // 在 这个地方阻断 falsy 值     if(value){       result[resIndex++] = value     }   }   return result }

concat

先看官网对于 concat 的介绍

_.concat(array, [values])

Creates a new array concatenating(连接) array with any additional arrays and/or values.

Arguments

  1. array  (Array) : The array to concatenate.
  2. [values]  (…)* : The values to concatenate.

Returns

(Array) : Returns the new concatenated array.

Example

var array = [1];
var other = _.concat(array, 2, [3], [[4]]);
console.log(other);
// => [1, 2, 3, [4]]
console.log(array);
// => [1]
var array = [1];
var other = _.concat(array, 2, [3], [[4]]);
 

console.log(other);
// => [1, 2, 3, [4]]
 
console.log(array);
// => [1]
var array = [1]; var other = _.concat(array, 2, [3], [[4]]);   console.log(other); // => [1, 2, 3, [4]]   console.log(array); // => [1]

根据提供的例子,接受两个参数,第一个参数是接收一个数组,后面可以传入 n 多个参数,如果参数中有数组嵌套,还会拍平第一层数组, 返回一个拼接后的数组,原数组不变

分析

类似于 es6 中的 concat + flat的效果,首先要保证原数组不受污染,其次要接收任意多种类型,最后如果类型中有数组嵌套,还要拍平第一层数组

源码中这个方法有很大的借鉴意义

以参数 concat([1],2,[3],[[4]]) 为例

function concat() {
var length = arguments.length;
if (!length) {
return [];
}
var args = Array(length - 1),
array = arguments[0],
index = length; // 4
while (--index) {
args[index - 1] = arguments[index];
}
// 倒序排列
console.log(args,"args") // [2,[3],[[4]]]
return arrayPush(
Array.isArray(array) ?
copyArray(array) : [array],
baseFlatten(args, 1));
}
function concat() {
  var length = arguments.length;


  if (!length) {
    return [];
  }



  var args = Array(length - 1),
      array = arguments[0],
      index = length;  // 4 



  while (--index) {
    args[index - 1] = arguments[index];
  }
  // 倒序排列
  console.log(args,"args") // [2,[3],[[4]]]
  
  return arrayPush(
    Array.isArray(array) ? 
    copyArray(array) : [array],
     baseFlatten(args, 1));
}
function concat() { var length = arguments.length; if (!length) { return []; } var args = Array(length - 1), array = arguments[0], index = length; // 4 while (--index) { args[index - 1] = arguments[index]; } // 倒序排列 console.log(args,"args") // [2,[3],[[4]]] return arrayPush( Array.isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); }

在 最后的 return 中,判断了 array = arguments[0] 是否是一个数组,如果是一个数组,则进行copyArray ,如果不是,则包装成一个数组,同时使用 baseFlatten 对后面的参数进行拍平,最后使用 arrayPush 把拍平后的数组放入第一个参数中

根据代码执行顺序进行依次展开 copyArray->baseFlatten->arrayPush

copyArray
function copyArray(source, array) {
let index = -1
const length = source.length
// 如果有传入 array,使用传入参数,否则就自己构建
array || (array = new Array(length))
// index 从 -1 开始,所以使用 ++index
while (++index < length) {
array[index] = source[index]
}
return array
}
function copyArray(source, array) {
  let index = -1
  const length = source.length
    
   // 如果有传入 array,使用传入参数,否则就自己构建 
  array || (array = new Array(length))
  // index 从 -1 开始,所以使用 ++index
  while (++index < length) {
    array[index] = source[index]
  }

  return array
}
function copyArray(source, array) { let index = -1 const length = source.length // 如果有传入 array,使用传入参数,否则就自己构建 array || (array = new Array(length)) // index 从 -1 开始,所以使用 ++index while (++index < length) { array[index] = source[index] } return array }

比如

// 第二个参数传入数组,保留部分
console.log("? ~ file:", copyArray([1,2,3],[4,5,6,7])); // [1,2,3,7]
// 不传入数组
console.log("? ~ file:", copyArray([1,2,3])); // [1,2,3]
// 第二个参数传入数组,保留部分
console.log("? ~ file:", copyArray([1,2,3],[4,5,6,7])); // [1,2,3,7]
// 不传入数组
console.log("? ~ file:", copyArray([1,2,3])); // [1,2,3]
// 第二个参数传入数组,保留部分 console.log("? ~ file:", copyArray([1,2,3],[4,5,6,7])); // [1,2,3,7] // 不传入数组 console.log("? ~ file:", copyArray([1,2,3])); // [1,2,3]
baseFlatten
const spreadableSymbol = Symbol.isConcatSpreadable;
const toString = Object.prototype.toString
function getTag(value) {
// 如果 value 不传值的话,是undefined
// 如果直接使用 toString.call的话,必须要传入一个参数
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return toString.call(value)
}
// 判断是否是 对象
function isObjectLike(value) {
return typeof value === 'object' && value !== null
}
// 是对象并且是 Arguments
function isArguments(value) {
return isObjectLike(value) && getTag(value) == '[object Arguments]'
}
// 可以展开的数据结构
function isFlattenable(value) {
return Array.isArray(value) || isArguments(value) ||
!!(value && value[spreadableSymbol])
}
function baseFlatten(array, depth, predicate, isStrict, result) {
// predicate 断言 是一个函数
predicate || (predicate = isFlattenable)
// 并没有使用全局变量,使用了闭包
result || (result = [])
if (array == null) {
return result
}
for (const value of array) {
// 说明可以展开
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
// 递归展平数组
baseFlatten(value, depth - 1, predicate, isStrict, result)
} else {
// depth == 1
result.push(...value)
}
} else if (!isStrict) {
// depth < 0 或者 value 不可以展开
// 如果不是严格模式,直接往后追加元素
result[result.length] = value
}
}
return result
}
 const spreadableSymbol = Symbol.isConcatSpreadable;
 
 const toString = Object.prototype.toString



function getTag(value) {
  // 如果 value 不传值的话,是undefined
  // 如果直接使用 toString.call的话,必须要传入一个参数
  if (value == null) {
    return value === undefined ? '[object Undefined]' : '[object Null]'
  }

  return toString.call(value)
}


// 判断是否是 对象
 function isObjectLike(value) {
  return typeof value === 'object' && value !== null
}
 
 // 是对象并且是 Arguments
 function isArguments(value) {
  return isObjectLike(value) && getTag(value) == '[object Arguments]'
}

 
 
// 可以展开的数据结构
function isFlattenable(value) {
  return Array.isArray(value) || isArguments(value) ||
    !!(value && value[spreadableSymbol])
}


function baseFlatten(array, depth, predicate, isStrict, result) {
// predicate 断言 是一个函数
  predicate || (predicate = isFlattenable)
  
// 并没有使用全局变量,使用了闭包
  result || (result = [])


  if (array == null) {
    return result
  }

  for (const value of array) {
  // 说明可以展开
    if (depth > 0 && predicate(value)) {
      if (depth > 1) {
        // Recursively flatten arrays (susceptible to call stack limits).
        // 递归展平数组
        baseFlatten(value, depth - 1, predicate, isStrict, result)
      } else {
      // depth == 1
        result.push(...value)
      }
    } else if (!isStrict) {
    // depth < 0 或者 value 不可以展开
      // 如果不是严格模式,直接往后追加元素
      result[result.length] = value
    }
  }

  return result
}
const spreadableSymbol = Symbol.isConcatSpreadable; const toString = Object.prototype.toString function getTag(value) { // 如果 value 不传值的话,是undefined // 如果直接使用 toString.call的话,必须要传入一个参数 if (value == null) { return value === undefined ? '[object Undefined]' : '[object Null]' } return toString.call(value) } // 判断是否是 对象 function isObjectLike(value) { return typeof value === 'object' && value !== null } // 是对象并且是 Arguments function isArguments(value) { return isObjectLike(value) && getTag(value) == '[object Arguments]' } // 可以展开的数据结构 function isFlattenable(value) { return Array.isArray(value) || isArguments(value) || !!(value && value[spreadableSymbol]) } function baseFlatten(array, depth, predicate, isStrict, result) { // predicate 断言 是一个函数 predicate || (predicate = isFlattenable) // 并没有使用全局变量,使用了闭包 result || (result = []) if (array == null) { return result } for (const value of array) { // 说明可以展开 if (depth > 0 && predicate(value)) { if (depth > 1) { // Recursively flatten arrays (susceptible to call stack limits). // 递归展平数组 baseFlatten(value, depth - 1, predicate, isStrict, result) } else { // depth == 1 result.push(...value) } } else if (!isStrict) { // depth < 0 或者 value 不可以展开 // 如果不是严格模式,直接往后追加元素 result[result.length] = value } } return result }

可能有人对这个const spreadableSymbol = Symbol.isConcatSpreadable; 有疑问
MDN关于 Symbol.isConcatSpreadable 的介绍

内置的 Symbol.isConcatSpreadable 符号用于配置某对象作为 Array.prototype.concat() 方法的参数时是否展开其数组元素。

const alpha = ['a', 'b', 'c'];
const numeric = [1, 2, 3];
let alphaNumeric = alpha.concat(numeric);
console.log(alphaNumeric);
// Expected output: Array ["a", "b", "c", 1, 2, 3]
numeric[Symbol.isConcatSpreadable] = false;
alphaNumeric = alpha.concat(numeric);
console.log(alphaNumeric);
// Expected output: Array ["a", "b", "c", Array [1, 2, 3]]
const alpha = ['a', 'b', 'c'];
const numeric = [1, 2, 3];
let alphaNumeric = alpha.concat(numeric);



console.log(alphaNumeric);
// Expected output: Array ["a", "b", "c", 1, 2, 3]



numeric[Symbol.isConcatSpreadable] = false;
alphaNumeric = alpha.concat(numeric);

console.log(alphaNumeric);
// Expected output: Array ["a", "b", "c", Array [1, 2, 3]]
const alpha = ['a', 'b', 'c']; const numeric = [1, 2, 3]; let alphaNumeric = alpha.concat(numeric); console.log(alphaNumeric); // Expected output: Array ["a", "b", "c", 1, 2, 3] numeric[Symbol.isConcatSpreadable] = false; alphaNumeric = alpha.concat(numeric); console.log(alphaNumeric); // Expected output: Array ["a", "b", "c", Array [1, 2, 3]]

Array-like 对象

对于类数组 (array-like) 对象,默认不展开。期望展开其元素用于连接,需要设置 Symbol.isConcatSpreadable 为 true:

var x = [1, 2, 3];
var fakeArray = {
[Symbol.isConcatSpreadable]: true,
length: 2,
0: "hello",
1: "world"
}
x.concat(fakeArray); // [1, 2, 3, "hello", "world"]
var x = [1, 2, 3];




var fakeArray = {
  [Symbol.isConcatSpreadable]: true,
  length: 2,
  0: "hello",
  1: "world"
}


x.concat(fakeArray); // [1, 2, 3, "hello", "world"]
var x = [1, 2, 3]; var fakeArray = { [Symbol.isConcatSpreadable]: true, length: 2, 0: "hello", 1: "world" } x.concat(fakeArray); // [1, 2, 3, "hello", "world"]

因为 loadash中的 concat 是要和数组的 concat 对齐,当然要抹平差异,如果一个元素的Symbol.isConcatSpreadable为 false,那么就不应该合并,主要是考虑到 「类数组对象」

还有这个 getTag方法

getTag
const toString = Object.prototype.toString
function getTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return toString.call(value)
}
const toString = Object.prototype.toString




function getTag(value) {
    if (value == null) {
        return value === undefined ? '[object Undefined]' : '[object Null]'
    } 
    return toString.call(value) 
}
const toString = Object.prototype.toString function getTag(value) { if (value == null) { return value === undefined ? '[object Undefined]' : '[object Null]' } return toString.call(value) }

为啥要判断 value == null 呢,直接使用 toString.call 好了,toString.call(undefned)也是 [object Undefined],toString.call(null)[object Null],为啥要多此一步呢?

是因为
getTag() 当不传入参数的时候,也应该是 undefined,但是 toString.call 必须要传入参数,所以做了一个判断

arrayPush
function arrayPush(array, values) {
var index = -1,
// 要合并的长度
length = values.length,
// 原数组的长度
offset = array.length;
while (++index < length) {
array[offset + index] = values[index];
}
return array;
}
function arrayPush(array, values) {




      var index = -1,
      // 要合并的长度
      length = values.length,
      // 原数组的长度
      offset = array.length;


  while (++index < length) {
    array[offset + index] = values[index];
  }
  
  return array;
}
function arrayPush(array, values) { var index = -1, // 要合并的长度 length = values.length, // 原数组的长度 offset = array.length; while (++index < length) { array[offset + index] = values[index]; } return array; }

直接在原数组的的基础上不断添加,没有使用push,或者展开运算符,可能性能会好一点?

concat 总结

应该对这段代码有不一样的理解,Lodash 把基础方法拆解的很细,而且大多使用的 es5或者es3 的语法,特别是对于一些细节处理,值得学习

return arrayPush(
Array.isArray(array) ?
copyArray(array) : [array],
baseFlatten(args, 1));
return arrayPush(
    Array.isArray(array) ? 
    copyArray(array) : [array],
     baseFlatten(args, 1));
return arrayPush( Array.isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));

最后

今天是第一天写 lodash,有一些收获,lodash 本身不难,而且代码结构很清晰,一个方法一个文件,对于提高编程能力有帮助,希望自己可以每天坚持写 lodash

打卡:2023/6/20

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYIrBKvZ' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片