无法使此缩减功能起作用

时间:2014-02-10 02:00:15

标签: javascript foreach reduce ecmascript-5 polyfills

使用Eloquent JavaScript and High Order Functions中的Functional Programming部分。

尝试定义reduce函数并在更高阶函数countWords();中使用它,该函数接受给定数组并计算每个特定值存在的次数并将其放入对象中。< / p>

即。这有效:

function combine(countMap, word) {
    countMap[word] = ++countMap[word] || 1; // made the edit

    return countMap;
}

function countWords(wordArray) {
    return wordArray.reduce(combine, {});
}

var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear'];

countWords(inputWords); // {Apple: 2, Banana: 1, Pear: 3}

即。这不是:

function combine(countMap, word) {
    countMap[word] = ++countMap[word] || 1;

    return countMap;
}

function forEach(array, action) {
    for (var i = 0; i < array.length; i++) {
        action(array[i]);
    }
}

function reduce(fn, base, array) {
    forEach(array, function (element) {
        base = fn(base, element);
    });

    return base;
}

function countWords(wordArray) {
    return reduce(combine, {}, wordArray);
}

var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear'];

countWords(inputWords); //    returned this - [object Object] { ... }  - this is no longer an issue after fix keeping it noted for reference to the original issue.

对此的任何帮助都会很棒。感谢。

3 个答案:

答案 0 :(得分:4)

你的原始缩减实际上已被破坏,尽管你说它有效。

这是一个实际运作的reduce

var words = ["foo", "bar", "hello", "world", "foo", "bar"];

var wordIndexer = function(map, word) { 
  map[word] = map[word] || 0;
  map[word]++;
  return map;
};

var count = word.reduce(wordIndexer, {});

console.log(count);

// Object {foo: 2, bar: 2, hello: 1, world: 1}

那就是说,我不完全确定你要在帖子的后半部分做什么。您是否只是尝试编写forEachreduce的实现,以便了解它们的工作原理?


我会像这样写forEach

var forEach = function(arr, callback) {
  for (var i=0, len=arr.length; i<len; i++) {
    callback(arr[i], i, arr);
  }
  return arr;
};

reduce就像这样

var reduce = function(arr, callback, initialValue) {
  var result = initialValue;
  forEach(arr, function(elem, idx) {
    result = callback(result, elem, idx, arr);
  });
  return result;
};

测试它们

var numbers = [10, 20, 30];

forEach(numbers, function(num, idx) {
  console.log(idx, num);
});

// 0, 10
// 1, 20
// 2, 30
//=> [10, 20, 30]

var n = reduce(numbers, function(sum, num, idx, arr) {
  return sum = sum + num;
}, 0);

console.log(n);
//=> 60

对于那些对减少回调感兴趣的人,我匹配了native .reduce callback

答案 1 :(得分:0)

我想这取决于你是希望forEachreduce与ECMA5规范相似(简单)还是尽可能接近/合理(忽略浏览器错误),我希望尽可能接近/合理。

  

Array.prototype.forEach ( callbackfn [ , thisArg ] )

     

callbackfn应该是一个接受三个参数的函数。 forEach按升序为数组中的每个元素调用一次callbackfn。 callbackfn仅针对实际存在的数组元素调用;它不会被称为缺少数组的元素。

     

如果提供了thisArg参数,则每次调用callbackfn时都会将其用作此值。如果未提供,则使用undefined。

     使用三个参数调用

callbackfn:元素的值,元素的索引和被遍历的对象。

     

forEach不直接改变调用它的对象,但是对象可能会被callbackfn的调用变异。

     

forEach处理的元素范围在第一次调用callbackfn之前设置。 callbackfn将不会访问在调用forEach之后附加到数组的元素。如果更改了数组的现有元素,则传递给回调的值将是每次访问它们时的值;

。不会访问在调用forEach开始之后和访问之前删除的元素。

     

当使用一个或两个参数调用forEach方法时,将执行以下步骤:

     
      
  1. 设O是调用ToObject传递此值作为参数的结果。
  2.   
  3. 让lenValue成为使用参数&#34; length&#34;来调用O的[[Get]]内部方法的结果。
  4.   
  5. 设len为ToUint32(lenValue)。
  6.   
  7. 如果IsCallable(callbackfn)为false,则抛出TypeError异常。
  8.   
  9. 如果提供thisArg,则让T为thisArg;否则让T未定义。
  10.   
  11. 设k为0。
  12.   
  13. 重复,而k < LEN
  14.   
  15. 让Pk成为ToString(k)。
  16.   
  17. 设kPresent是用参数Pk调用O的[[HasProperty]]内部方法的结果。
  18.   
  19. 如果kPresent为true,则
  20.   
  21. 让kValue成为用参数Pk调用O的[[Get]]内部方法的结果。
  22.   
  23. 使用T作为此值和参数列表调用callbackfn的[[Call]]内部方法,其中包含kValue,k和O.
  24.   
  25. 将k增加1。
  26.   
  27. 返回undefined。
  28.         

    forEach方法的length属性为1.

         

    注意forEach函数是有意通用的;它不要求它的这个值是一个Array对象。因此,它可以转移到其他类型的对象以用作方法。 forEach函数是否可以成功应用于宿主对象是依赖于实现的。

-

  

Array.prototype.reduce ( callbackfn [ , initialValue ] )

     

callbackfn应该是一个带有四个参数的函数。 reduce作为函数调用回调,对于数组中存在的每个元素,按升序调用一次。

     使用四个参数调用

callbackfn:previousValue(或前一次调用callbackfn的值),currentValue(当前元素的值),currentIndex和被遍历的对象。第一次调用回调时,previousValue和currentValue可以是两个值之一。如果在reduce的调用中提供了initialValue,则previousValue将等于initialValue,currentValue将等于数组中的第一个值。如果没有提供initialValue,那么previousValue将等于数组中的第一个值,currentValue将等于第二个值。如果数组不包含元素且未提供initialValue,则为TypeError。

     

reduce不会直接改变调用它的对象,但是对象可能会被callbackfn的调用所突变。

     

reduce处理的元素范围在第一次调用callbackfn之前设置。 callbackfn将不会访问在调用reduce开始后附加到数组的元素。如果更改了数组的现有元素,则传递给callbackfn的值将是reduce访问它们时的值;

。不会访问在调用reduce开始之后和访问之前删除的元素。

     

使用一个或两个参数调用reduce方法时,将执行以下步骤:

     
      
  1. 设O是调用ToObject传递此值作为参数的结果。
  2.   
  3. 让lenValue成为使用参数&#34; length&#34;来调用O的[[Get]]内部方法的结果。
  4.   
  5. 设len为ToUint32(lenValue)。
  6.   
  7. 如果IsCallable(callbackfn)为false,则抛出TypeError异常。
  8.   
  9. 如果len为0且initialValue不存在,则抛出TypeError异常。
  10.   
  11. 设k为0。
  12.   
  13. 如果存在initialValue,则
  14.   
  15. 将累加器设置为initialValue。
  16.   
  17. 否则,initialValue不存在
  18.   
  19. 让kPresent为假。
  20.   
  21. 重复,而kPresent为假且k < LEN
  22.   
  23. 让Pk成为ToString(k)。
  24.   
  25. 设kPresent是用参数Pk调用O的[[HasProperty]]内部方法的结果。
  26.   
  27. 如果kPresent为true,则
  28.   
  29. 让累加器成为用参数Pk调用O的[[Get]]内部方法的结果。
  30.   
  31. 将k增加1。
  32.   
  33. 如果kPresent为false,则抛出TypeError异常。
  34.   
  35. 重复,而k < LEN
  36.   
  37. 让Pk成为ToString(k)。
  38.   
  39. 设kPresent是用参数Pk调用O的[[HasProperty]]内部方法的结果。
  40.   
  41. 如果kPresent为true,则
  42.   
  43. 让kValue成为用参数Pk调用O的[[Get]]内部方法的结果。
  44.   
  45. 让累加器是使用undefined调用callbackfn的[[Call]]内部方法的结果,因为此值和参数列表包含累加器,kValue,k和O.
  46.   
  47. 将k增加1。
  48.   
  49. 返回累加器。
  50.         

    reduce方法的length属性为1。

         

    注意reduce函数是有意通用的;它不要求它的这个值是一个Array对象。因此,它可以转移到其他类型的对象以用作方法。 reduce函数是否可以成功应用于宿主对象是依赖于实现的。

对我来说,我会写(这些不是100%的规格,但是关闭)并保存在我的个人图书馆。

function firstToCapital(inputString) {
    return inputString.charAt(0).toUpperCase() + inputString.slice(1).toLowerCase();
}

function isClass(inputArg, className) {
    return Object.prototype.toString.call(inputArg) === '[object ' + firstToCapital(className) + ']';
}

function checkObjectCoercible(inputArg) {
    if (typeof inputArg === 'undefined' || inputArg === null) {
        throw new TypeError('Cannot convert argument to object');
    }

    return inputArg;
};

function ToObject(inputArg) {
    checkObjectCoercible(inputArg);
    if (isClass(inputArg, 'boolean')) {
        inputArg = new Boolean(inputArg);
    } else if (isClass(inputArg, 'number')) {
        inputArg = new Number(inputArg);
    } else if (isClass(inputArg, 'string')) {
        inputArg = new String(inputArg);
    }

    return inputArg;
}

function ToUint32(inputArg) {
    return inputArg >>> 0;
}

function throwIfNotAFunction(inputArg) {
    if (!isClass(inputArg, 'function')) {
        throw TypeError('Argument is not a function');
    }

    return inputArg;
}

function forEach(array, fn, thisArg) {
    var object = ToObject(array),
        length,
        index;

    throwIfNotAFunction(fn);
    length = ToUint32(object.length);
    for (index = 0; index < length; index += 1) {
        if (index in object) {
            fn.call(thisArg, object[index], index, object);
        }
    }
}

function reduce(array, fn, initialValue) {
    var object = ToObject(array),
        accumulator,
        length,
        kPresent,
        index;

    throwIfNotAFunction(fn);
    length = ToUint32(object.length);
    if (!length && arguments.length === 2) {
        throw new TypeError('reduce of empty array with no initial value');
    }

    index = 0;
    if (arguments.length > 2) {
        accumulator = initialValue;
    } else {
        kPresent = false;
        while (!kPresent && index < length) {
            kPresent = index in object;
            if (kPresent) {
                accumulator = object[index];
                index += 1;
            }
        }

        if (!kPresent) {
            throw new TypeError('reduce of empty array with no initial value');
        }
    }

    while (index < length) {
        if (index in object) {
            accumulator = fn.call(undefined, accumulator, object[index], index, object);
        }

        index += 1;
    }

    return accumulator;
}

function keys(object) {
    if (!isClass(object, 'object') && !isClass(object, 'function')) {
        throw new TypeError('Argument must be an object or function');
    }

    var props = [],
        prop;

    for (prop in object) {
        if (object.hasOwnProperty(prop)) {
           props.push(prop);
        }
    }

    return props;
}

var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear'];

var counts = reduce(inputWords, function (previous, element) {
    previous[element] = ++previous[element] || 1;

    return previous;
}, {});

forEach(keys(counts), function (key) {
    console.log(key, this[key]);
}, counts);

jsFiddle

当然,对于你正在做的事情,这可能有点OTT。 :)

答案 2 :(得分:-2)

原因是您的ForEach实施错误。你应该设置 i = 0 ;

function forEach(array, action) {
    for(var i = 0; i < array.length; i++) {
        action(array[i]);
    }   
}

似乎有些不对劲。您更新对象。 ++countMap

function combine(countMap, word) {
    countMap[word] = ++countMap || 1;
    return countMap; 
}

应该是

function combine(countMap, word) {
    countMap[word] = ++countMap[word] || 1;
    return countMap; 
}

我添加jsbin here