素材牛VIP会员
Javascript中的array.reduce的问题及一个奇怪的抛出异常
 54***66  分类:Node.js  人气:747  回帖:1  发布于6年前 收藏

今天看到javascript的函数式编程的一篇文章,讲的是用reduce这个函数统计一个字符串中各字母出现的次数,代码如下:`var res = str.split('')

         .reduce((pre, cur) => (pre[cur]++ || (pre[cur] = 1), pre), {})`

然后我不知道上式中箭头函数的右边部分中的pre是什么意思,箭头函数右边不就是要返回的表达式吗,为什么上面上面的式子里面还多了个pre?然后我试着改写了一下:

var res = str.split('')
            .reduce(function(pre,cur){
             console.log(pre) ;
             console.log(cur) ;
             if( pre[cur] === 1)
             {
              pre[cur]++;
             } else
               pre[cur] = 1;
            }, {});

结果报错为:

if( pre[cur] === 1)
       ^
TypeError: Cannot read property 'd' of undefined

为什么会报这样的错?
谁能回答下这两个问题吗??感激不尽。百度谷歌了好久都找不到答案。。

 标签:node.jsjavascript

讨论这个帖子(1)垃圾回帖将一律封号处理……

Lv6 码匠
on***de JAVA开发工程师 6年前#1

先改写一下,让你看得清楚些。

const str = 'ddd'
const initialVal = {}

let res = str.split('').reduce((pre, cur) => (pre[cur]++ || (pre[cur] = 1), pre), initialVal)

语法上的confusion

可以繁写为:

(pre, cur) => {
  return pre[cur]++ || (pre[cur] = 1), pre
}

形如 return 1, 3, 4 总是返回最后一个合法的值。

这里用这种偏门写法主要是为了能在一行中写完,使其既执行语句,还能返回值pre,供给下一次遍历消费。

算是片面追求短小。

加了注释的ES5写法

var str = 'ddd';
// cache 是 reduce 函数进行累加操作时候的初始值。
// 在这里,它主要用来缓存字符串中出现的字符和它们的出现次数。
// 遍历结束后,它的结构可能是 { d: 3 } 。
var cache = {};

var res = str.split('').reduce(function (pre, cur) {
  // pre 是一个 plain Object, 初始值为 cache ,可能形如 { d: 1 }。
  // 而 cur 是当前遍历到的字符,可能为 d。
  var cachedTimes = pre[cur]; // 所以这一步是在向buffer(缓存)中询问,是否已经缓存过 d,并且得到它的次数。

  if (cachedTimes) { 
    pre[cur] += 1; // 如果有值,说明已经缓存过,加上这次访问,其次数应该 + 1
  } else {
    pre[cur] = 1; // 如果为undefined,说明没有缓存过,在缓存中注册一下,次数为1
  }
  return pre; // 把修改后的 pre 传给下一次遍历消费。
}, cache);

在js中,使用cache表来缓存遍历次数是一个常见的优化办法。因为js中访问属性是很快的。

你改写的函数的错误之处

额,其实讲到这里你应该很明白了。然而你的reduce的回调函数体中并没有返回累计结果,传给下一次遍历。所以第二次遍历时,preundefined
你的str中第二个字符估计是dundefined.d当然报错啦。


PS 这种写法会更优雅吗?

回复追问

Q1

使用array.reduce的话必须使用返回值对吧?

是的。必须给一个返回值,供下次遍历消费。

Q2

return pre[cur]++ || (pre[cur] = 1), pre 应该是return (pre[cur]++ || (pre[cur] = 1), pre)吧?

可加可不加,你的源码中加了是因为 arrow function 直接返回一个对象时需要用括号包裹。

为了严谨,我们用babel转译一下。

(pre, cur) => {
  return pre[cur]++ || (pre[cur] = 1), pre
}

babel的转译码

"use strict";

(function (pre, cur) {
  return pre[cur]++ || (pre[cur] = 1), pre;
});

babel的结果是不加的。

试试执行下面的同类代码

function test() {
  return console.log(123), 345;
}
var val = test(); // 先打印123, val 为 345

Q3

pre[cur]++ || (pre[cur] = 1),这个式子难道不是说两个表达式只要有一个为真就返回那个真的值吗?我以为这个返回就是return的意思,所以当时就一直不懂这个式子怎么会有两个返回值(||运算其中一个结果和pre,这就是我当时的想法。)

你分成两步看。

return pre[cur]++ || (pre[cur] = 1), pre
// 相当于
var a = pre[cur]++ || (pre[cur] = 1)
var b = pre
return a, b

根据return a, b 总是输出 b 的原则,输出b(即pre)。

说实话这种偷懒的语法我也是第一次看到,额,反正业务代码里我是不会这么写的,可阅读性太差了。毕竟是example,当然很fancy了。

 文明上网,理性发言!   😉 阿里云幸运券,戳我领取