最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Reduce

    正文概述 掘金(孟陬)   2021-03-07   484

    reduce 可谓是 JS 数组方法最灵活的一个,因为可以替代数组的其他方法,比如 map / filter / some / every 等,也是最难理解的一个方法。

    其中一个难点在于判断 accaccumulation 的类型以及如何选择初始值,其实有个小技巧,可以帮助我们找到合适的初始值,我们想要的返回值的类型和 acc 类型需要是一样的,比如求和最终结果是数字,则 acc 应该是数字类型,故其初始化必定是 0

    下面开始巩固对 reduce 的理解和用法。

    map

    根据小技巧,map 最终返回值是数组,故 acc 也应该是一个数组,初始值使用空数组即可。

    /**
     * Use `reduce` to implement the builtin `Array.prototype.map` method.
     * @param {any[]} arr 
     * @param {(val: any, index: number, thisArray: any[]) => any} mapping 
     * @returns {any[]}
     */
    function map(arr, mapping) {
      return arr.reduce((acc, item, index) => [...acc, mapping(item, index, arr)], []);
    }
    

    测试

    map([null, false, 1, 0, '', () => {}, NaN], val => !!val);
    
    // [false, false, true, false, false, true, false]
    

    filter

    根据小技巧,filter 最终返回值也是数组,故 acc 也应该是一个数组,使用空数组即可。

    /**
     * Use `reduce` to implement the builtin `Array.prototype.filter` method.
     * @param {any[]} arr 
     * @param {(val: any, index: number, thisArray: any[]) => boolean} predicate 
     * @returns {any[]}
     */
    function filter(arr, predicate) {
      return arr.reduce((acc, item, index) => predicate(item, index, arr) ? [...acc, item] : acc, []);
    }
    

    测试

    filter([null, false, 1, 0, '', () => {}, NaN], val => !!val);
    
    // [1, () => {}]
    

    some

    some 当目标数组为空返回 false,故初始值为 false

    function some(arr, predicate) {
      return arr.reduce((acc, val, idx) => acc || predicate(val, idx, arr), false)
    }
    

    测试:

    some([null, false, 1, 0, '', () => {}, NaN], val => !!val);
    // true
    
    some([null, false, 0, '', NaN], val => !!val);
    // false
    

    附带提醒,二者对结果没影响但有性能区别,acc 放到前面因为是短路算法,可避免无谓的计算,故性能更高。

    acc || predicate(val, idx, arr)
    

    predicate(val, idx, arr) || acc
    

    every

    every 目标数组为空则返回 true,故初始值为 true

    function every(arr, predicate) {
      return arr.reduce((acc, val, idx) => acc && predicate(val, idx, arr), true)
    }
    

    findIndex

    findIndex 目标数组为空返回 -1,故初始值 -1。

    function findIndex(arr, predicate) {
      const NOT_FOUND_INDEX = -1;
    
      return arr.reduce((acc, val, idx) => {
        if (acc === NOT_FOUND_INDEX) {
          return predicate(val, idx, arr) ? idx : NOT_FOUND_INDEX;
        }
        
        return acc;
      }, NOT_FOUND_INDEX)
    }
    

    测试

    findIndex([5, 12, 8, 130, 44], (element) => element > 8) // 3
    

    pipe

    一、实现以下函数

    /**
     * Return a function to make the input value processed by the provided functions in sequence from left the right.
     * @param {(funcs: any[]) => any} funcs 
     * @returns {(arg: any) => any}
     */
    function pipe(...funcs) {}
    

    使得

    pipe(val => val * 2, Math.sqrt, val => val + 10)(2) // 12
    

    利用该函数可以实现一些比较复杂的处理过程

    // 挑选出 val 是正数的项对其 val 乘以 0.1 系数,然后将所有项的 val 相加,最终得到 3
    const process = pipe(
      arr => arr.filter(({ val }) => val > 0), 
      arr => arr.map(item => ({ ...item, val: item.val * 0.1 })), 
      arr => arr.reduce((acc, { val }) => acc + val, 0)
    );
    
    process([{ val: -10 }, { val: 20 }, { val: -0.1 }, { val: 10 }]) // 3
    

    二、实现以下函数,既能实现上述 pipe 的功能,而且返回函数接纳参数个数可不定

    /**
     * Return a function to make the input values processed by the provided functions in sequence from left the right.
     * @param {(funcs: any[]) => any} funcs 
     * @returns {(args: any[]) => any}
     */
    function pipe(...funcs) {}
    

    使得以下单测通过

    pipe(sum, Math.sqrt, val => val + 10)(0.1, 0.2, 0.7, 3) // 12
    

    其中 sum 已实现

    /**
     * Sum up the numbers.
     * @param args number[]
     * @returns {number} the total sum.
     */
    function sum(...args) {
      return args.reduce((a, b) => a + b);
    }
    

    参考答案

    一、返回函数接受一个参数

    省略过滤掉非函数的 func 步骤

    /**
     * Return a function to make the input value processed by the provided functions in sequence from left the right.
     * @param {(arg: any) => any} funcs
     * @returns {(arg: any) => any}
     */
    function pipe(...funcs) {
      return (arg) => {
        return funcs.reduce(
          (acc, func) => func(acc),
          arg
        )
      }
    }
    

    二、返回函数接受不定参数

    同样省略了过滤掉非函数的 func 步骤

    /**
     * Return a function to make the input value processed by the provided functions in sequence from left the right.
     * @param {Array<(...args: any) => any>} funcs
     * @returns {(...args: any[]) => any}
     */
    function pipe(...funcs) {
    	// const realFuncs = funcs.filter(isFunction);
    
      return (...args) => {
        return funcs.reduce(
          (acc, func, idx) => idx === 0 ? func(...acc) : func(acc),
          args
        )
      }
    }
    

    性能更好的写法,避免无谓的对比,浪费 CPU。

    function pipe(...funcs) {
      return (...args) => {
        // 第一个已经处理,只需处理剩余的
        return funcs.slice(1).reduce(
          (acc, func) => func(acc),
          
          // 首先将特殊情况处理掉当做 `acc`
          funcs[0](...args)
        )
      }
    }
    

    第二种写法的 funcs[0](...args) 这个坑要注意,数组为空就爆炸了,因为空指针了。

    Pluck

    实现 pluck({ a: { b: { c: 'hello pluck' } } }, 'a.b.c') 返回 'hello world'

    函数签名:

    /**
     * pluck the value by key path
     * @param any object
     * @param keyPath string 点分隔的 key 路径
     * @returns {any} 目标值
     */
    function pluck(obj, keyPath) {}
    

    参考答案

    /**
     * Pluck the value by key path.
     * @param any object
     * @param keyPath string 点分隔的 key 路径
     * @returns {any} 目标值
     */
    function pluck(obj, keyPath) {
      if (!obj) {
        return undefined;
      }
    
      return keyPath.split('.').reduce((acc, key) => acc[key], obj);
    }
    

    实现 flattenDeep

    虽然使用 concat 和扩展运算符只能够 flatten 一层,但通过递归可以去做到深度 flatten。

    方法一:扩展运算符

    function flatDeep(arr) {
      return arr.reduce((acc, item) => 
        Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item],
        []
      )
    }
    

    方法二:concat

    function flatDeep(arr) {
      return arr.reduce((acc, item) => 
        acc.concat(Array.isArray(item) ? flatDeep(item) : item),
        []
      )
    }
    

    有趣的性能对比,扩展操作符 7 万次 1098ms,同样的时间 concat 只能执行 2 万次。

    function flatDeep(arr) {
      return arr.reduce((acc, item) => 
        Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item],
        []
      )
    }
    
    var arr = repeat([1, [2], [[3]], [[[4]]]], 20);
    
    console.log(arr);
    console.log(flatDeep(arr));
    
    console.time('concat')
    for (i = 0; i < 7 * 10000; ++i) {
      flatDeep(arr)
    }
    console.timeEnd('concat')
    
    function repeat(arr, times) { let result = []; for (i = 0; i < times; ++i) { result.push(...arr) } return result; }
    

    过滤掉对象中的空值

    实现

    clean({ foo: null, bar: undefined, baz: 'hello' })
    
    // { baz: 'hello' }
    

    答案:

    /**
     * Filter out the `nil` (null or undefined) values.
     * @param {object} obj
     * @returns {any}
     *
     * @example clean({ foo: null, bar: undefined, baz: 'hello' })
     *
     * // => { baz: 'hello' }
     */
    export function clean(obj) {
      if (!obj) {
        return obj;
      }
    
      return Object.keys(obj).reduce((acc, key) => {
        if (!isNil(obj[key])) {
          acc[key] = obj[key];
        }
    
        return acc;
      }, {});
    }
    
    

    enumify

    将常量对象模拟成 TS 的枚举

    实现 enumify 使得

    const Direction = {
      UP: 0,
      DOWN: 1,
      LEFT: 2,
      RIGHT: 3,
    };
    
    const actual = enumify(Direction);
    
    const expected = {
      UP: 0,
      DOWN: 1,
      LEFT: 2,
      RIGHT: 3,
    
      0: 'UP',
      1: 'DOWN',
      2: 'LEFT',
      3: 'RIGHT',
    };
    
    deepStrictEqual(actual, expected);
    

    答案:

    /**
     * Generate enum from object.
     * @see https://www.typescriptlang.org/play?#code/KYOwrgtgBAglDeAoKUBOwAmUC8UCMANMmpgEw5SlEC+UiiAxgPYgDOTANsAHQdMDmAChjd0GAJQBuRi3ZdeA4QG08AXSmIgA
     * @param {object} obj
     * @returns {object}
     */
    export function enumify(obj) {
      if (!isPlainObject(obj)) {
        throw new TypeError('the enumify target must be a plain object');
      }
    
      return Object.keys(obj).reduce((acc, key) => {
        acc[key] = obj[key];
        acc[obj[key]] = key;
    
        return acc;
      }, {});
    }
    
    

    拓展

    1. 请使用 jest 作为测试框架,给本文的所有方法书写单测
    2. 更多习题见 github.com/you-dont-ne…

    起源地下载网 » Reduce

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元