VueBloghyhero6

挖新坑,数组reduce实现(学习笔记)

2021-05-10 / 2021-05-10 / 343次浏览

写在前面,由于在B站录制游戏视频玩,导致最近晚上睡得晚,由于不可描述的问题,现在爆困。衰老,生病和死亡,人类本身这台机器还是太不耐操了。唯有克己,将有限的时间,用到有价值的地方。不多闲扯了,进入今天的笔记。

关于使用语法,我们来看菜鸟教程的吧,目前还用不上

array.reduce(function(total,currentValue,currntIndex,arr),initialValue)

函数,必需。用于执行每个数组元素的函数。
函数参数:
total  必需。初始值,或者计算结束后的返回值。
currentValue 必需。当前元素
currentIndex 可选。当前元素的索引。
arr 可选。当前元素所属的数组对象

initialValue。 可选,传递给函数的初始值

举例使用:数组的元素之和

<button onclick="myFunction()">点我</button>
 
<p>数组元素之和: <span id="demo"></span></p>
 
<script>
var numbers = [15.5, 2.3, 1.1, 4.7];
 
function getSum(total, num) {
    return total + Math.round(num);
}
function myFunction(item) {
    document.getElementById("demo").innerHTML = numbers.reduce(getSum, 0);
}
</script>

确实实现了数组的顺序往下叠加

const f1 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('p1 running')
        resolve(1)
    }, 1000)
})

const f2 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('p2 running')
        resolve(2)
    }, 1000)
})

const array = [f1, f2]

const runPromiseInSequence = (array, value) => array.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(value)
)

runPromiseInSequence(array, 'init')

执行结果如下图:

建议小伙伴们去试一下上面的代码,在浏览器中,reduce 确实对promise 进行了一波顺序操控。

reduce 实现 pipe (简单来讲这个是一个管道函数)
reduce 的另外一个典型应用可以参考函数方法 pipe 的实现:pipe(f, g, h) 是一个 curry 化函数,它返回一个新的函数,这个新的函数将会完成(...args)=> h(g(f(...args)))) 的调用。即 pipe 方法返回的函数会接收一个参数,这个参数传递给 pipe 方法第一个参数,以供其调用.

const pipe = (...functions) => input => functions.reduce(
    (acc, fn) => fn(acc),
    input
)

实际来说知乎对这种做法褒贬不一,如果过分了函数式,会导致代码及其难懂,后面同事看不懂甚至可能会直接导致重构,

https://zhuanlan.zhihu.com/p/52207982

业务中使用相对要少的原因是,第一可能用 for 循环等直接以重复代码的方式写出,好处是后续人接收代码确实容易看懂,坏处就是可能会有重复的代码,且感觉代码量是巨大的。过度的函数包装写法,让人完全看不懂你在写什么,不是不好,需要了解并在恰当的时候使用。 按照我目前的也还没有使用过 reduce

既然了解了使用方法那么我们能否实现一个呢。
实现一个 reduce
来自于 MDN 的 polyfill:

if(!Array.prototype.reduce) {
   Object.defineProperty(Array.prototype, 'reduce', {
       value: function(callback /*, initialValue*/) {
           if (this === null) {
              throw new TypeError('Array.prototype.reduce' + 'called on null or undefined')
           }
           if (typeof callback !== 'function') {
               throw new TypeError(callback + 'is not a function')
           }
           var o = Object(this)
           
           var len = o.length >>> 0
           
           var k = 0
           
           var value
           
           if (arguments.length >= 2) {
              value = arguments[1]
           } else {
               while (k < len && !(k in o)) {
                k++
               }
               if ( k >= len ) {
                   throw new TypeError('Reduce of empty array' + 'with no initial value')
               }
               value = o[k++]
           }
           while (k < len) {
               if (k in o) {
                   value = callback(value,o[k], k, o)
               }
               k++
           }
           return value
       }
   })
}

有点懵逼,于是乎我们去MDN 直接看一波
Object
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object

Object.defineProperty()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Array.prototype.reduce()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

MDN 三条API 自取

稍微用浏览器测试了一下来自于MDN代码,毕竟大佬把注释全都删除
了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>reduce进行测试</title>
</head>
<body>
<button onclick="myFunction()">点我</button>

<p>数组元素之和: <span id="demo"></span></p>
<script>
    // Polyfill
    // Production steps of ECMA-262, Edition 5, 15.4.4.21
    // Reference: http://es5.github.io/#x15.4.4.21
    // https://tc39.github.io/ecma262/#sec-array.prototype.reduce
    if (Array.prototype.reduce) {
        Object.defineProperty(Array.prototype, 'reduce', {
            value: function(callback /*, initialValue*/) {
                if (this === null) {
                    throw new TypeError( 'Array.prototype.reduce ' +
                            'called on null or undefined' );
                }
                if (typeof callback !== 'function') {
                    throw new TypeError( callback +
                            ' is not a function');
                }

                // 1. Let O be ? ToObject(this value).
                var o = Object(this);
                console.log(Object(this))

                // 2. Let len be ? ToLength(? Get(O, "length")).
                var len = o.length >>> 0;  // 进行了 无符号位移

                console.log('o.length >>> 0 -->', o.length >>> 0)

                // Steps 3, 4, 5, 6, 7
                var k = 0;
                var value;

                if (arguments.length >= 2) {
                    value = arguments[1];
                } else {
                    while (k < len && !(k in o)) {
                        k++;
                    }

                    // 3. If len is 0 and initialValue is not present,
                    //    throw a TypeError exception.
                    if (k >= len) {
                        throw new TypeError( 'Reduce of empty array ' +
                                'with no initial value' );
                    }
                    value = o[k++];
                }

                // 8. Repeat, while k < len
                while (k < len) {
                    // a. Let Pk be ! ToString(k).
                    // b. Let kPresent be ? HasProperty(O, Pk).
                    // c. If kPresent is true, then
                    //    i.  Let kValue be ? Get(O, Pk).
                    //    ii. Let accumulator be ? Call(
                    //          callbackfn, undefined,
                    //          « accumulator, kValue, k, O »).
                    if (k in o) {
                        value = callback(value, o[k], k, o);
                    }

                    // d. Increase k by 1.
                    k++;
                }

                // 9. Return accumulator.
                return value;
            }
        });
    }
    var numbers = [15.5, 2.3, 1.1, 4.7];

    function getSum(total, num) {
        return total + Math.round(num);
    }
    function myFunction(item) {
        document.getElementById("demo").innerHTML = numbers.reduce(getSum, 0);
    }
</script>
</body>
</html>

reduce 可以干的事情

数组求和

let arr = [1, 2, 3];
let res = arr.reduce((acc, v) => acc + v);
console.log(res); // 6

数组去重

let arr = [1, 1, 1, 2, 3, 3, 4, 3, 2, 4];
let res = arr.reduce((acc, v) => {
  if (acc.indexOf(v) < 0) acc.push(v);
  return acc;
}, []);
console.log(res); // [1, 2, 3, 4]

统计数组中每一项出现的次数:

let arr = ['Jerry', 'Tom', 'Jerry', 'Cat', 'Mouse', 'Mouse'];
let res = arr.reduce((acc, v) => {
  if (acc[v] === void 0) acc[v] = 1;
  else acc[v]++;
  return acc;
}, {});
console.log(res); // {Jerry: 2, Tom: 1, Cat: 1, Mouse: 2}

将二维数组展开成一维数组:

let arr = [[1, 2, 3], 3, 4, [3, 5]];
let res = arr.reduce((acc, v) => {
  if (v instanceof Array) {
    return [...acc, ...v];
  } else {
    return [...acc, v];
  }
});
console.log(res); // [1, 2, 3, 3, 4, 3, 5]

扩展了一波回来之后,
回归我们的文章代码 使用了 value 作为初始值,并通过 while 循环,依次累加计算出 value 结果并输出。相比于MDN大佬的实现方式是:

Array.prototype.reduce = Array.prototype.reduce || function(func, initialValue) {
    var arr = this
    var base = typeof initialValue === 'undefined' ? arr[0] : initialValue
    var startPoint = typeof initialValue === 'undefined' ? 1 : 0
    arr.slice(startPoint)
        .forEach(function(val, index) {
            base = func(base, val, index + startPoint, arr)
        })
    return base
}

使用 forEach 替代了 while 实现结果的累加,他们本质趋近相同。 当然 forEach 的方法是 ES5 新增的。因此,用ES5的一个API(forEach),去实现另外一个 ES5 的API并没有什么意义,大佬这个位置还是让大家理解为主,实际上这个位置来讲,我看代码看资料,也看了许久,也许对大佬来讲真的不算什么。但是对此觉得吃力的朋友,我还是还是建议多次反复研究这个api。

通过 Koa only 模块源码认识 reduce

看到这个位置如果基本还能看懂的话,那么基本对 reduce 有点认识了。最后再看一个 Koa 源码的 only 模块,以求加深印象。

var o = {
    a: 'a',
    b: 'b',
    c: 'c'
}
only(o, ['a','b'])   // {a: 'a',  b: 'b'}

可以看出这个模块基本上来说就是一个筛选模块。
该方法返回一个经过指定筛选属性的新对象。
only 模块实现:

var only = function(obj, keys){
    obj = obj || {}
    if ('string' == typeof keys) keys = keys.split(/ +/)
    return keys.reduce(function(ret, key){
        if (null == obj[key]) return ret
        ret[key] = obj[key]
        return ret
    },{})
}

数组reduce就是说到这吧,实话说我惊艳于它对promse 的精准控制,我所知道的API,只有async await 可以做到精准控制这一点。 且不说它的筛选,求和,数组去重,统计出现次数,数组展开,这个api 出来数组数据看起来貌似及其强悍,适合于复杂业务逻辑。例如对个接口并行回调,串联场景。

路漫漫兮修远兮。
吾将上下而求索。