UNPKG

sugar

Version:

A Javascript utility library for working with native objects.

171 lines (154 loc) 6.09 kB
'use strict'; var forEach = require('../../common/internal/forEach'), spaceSplit = require('../../common/internal/spaceSplit'), classChecks = require('../../common/var/classChecks'), mathAliases = require('../../common/var/mathAliases'), assertArgument = require('../../common/internal/assertArgument'), enhancedMapping = require('./enhancedMapping'), namespaceAliases = require('../../common/var/namespaceAliases'), enhancedMatching = require('./enhancedMatching'), getNormalizedIndex = require('../../common/internal/getNormalizedIndex'), coreUtilityAliases = require('../../common/var/coreUtilityAliases'), methodDefineAliases = require('../../common/var/methodDefineAliases'); var forEachProperty = coreUtilityAliases.forEachProperty, defineInstanceWithArguments = methodDefineAliases.defineInstanceWithArguments, sugarArray = namespaceAliases.sugarArray, min = mathAliases.min, max = mathAliases.max, isBoolean = classChecks.isBoolean; function buildFromIndexMethods() { var methods = { 'forEach': { base: forEachAsNative }, 'map': { wrapper: enhancedMapping }, 'some every': { wrapper: enhancedMatching }, 'findIndex': { wrapper: enhancedMatching, result: indexResult }, 'reduce': { apply: applyReduce }, 'filter find': { wrapper: enhancedMatching }, 'reduceRight': { apply: applyReduce, slice: sliceArrayFromRight, clamp: clampStartIndexFromRight } }; forEachProperty(methods, function(opts, key) { forEach(spaceSplit(key), function(baseName) { var methodName = baseName + 'FromIndex'; var fn = createFromIndexWithOptions(baseName, opts); defineInstanceWithArguments(sugarArray, methodName, fn); }); }); function forEachAsNative(fn) { forEach(this, fn); } // Methods like filter and find have a direct association between the value // returned by the callback and the element of the current iteration. This // means that when looping, array elements must match the actual index for // which they are being called, so the array must be sliced. This is not the // case for methods like forEach and map, which either do not use return // values or use them in a way that simply getting the element at a shifted // index will not affect the final return value. However, these methods will // still fail on sparse arrays, so always slicing them here. For example, if // "forEachFromIndex" were to be called on [1,,2] from index 1, although the // actual index 1 would itself would be skipped, when the array loops back to // index 0, shifting it by adding 1 would result in the element for that // iteration being undefined. For shifting to work, all gaps in the array // between the actual index and the shifted index would have to be accounted // for. This is infeasible and is easily solved by simply slicing the actual // array instead so that gaps align. Note also that in the case of forEach, // we are using the internal function which handles sparse arrays in a way // that does not increment the index, and so is highly optimized compared to // the others here, which are simply going through the native implementation. function sliceArrayFromLeft(arr, startIndex, loop) { var result = arr; if (startIndex) { result = arr.slice(startIndex); if (loop) { result = result.concat(arr.slice(0, startIndex)); } } return result; } // When iterating from the right, indexes are effectively shifted by 1. // For example, iterating from the right from index 2 in an array of 3 // should also include the last element in the array. This matches the // "lastIndexOf" method which also iterates from the right. function sliceArrayFromRight(arr, startIndex, loop) { if (!loop) { startIndex += 1; arr = arr.slice(0, max(0, startIndex)); } return arr; } function clampStartIndex(startIndex, len) { return min(len, max(0, startIndex)); } // As indexes are shifted by 1 when starting from the right, clamping has to // go down to -1 to accommodate the full range of the sliced array. function clampStartIndexFromRight(startIndex, len) { return min(len, max(-1, startIndex)); } function applyReduce(arr, startIndex, fn, context, len, loop) { return function(acc, val, i) { i = getNormalizedIndex(i + startIndex, len, loop); return fn.call(arr, acc, val, i, arr); }; } function applyEach(arr, startIndex, fn, context, len, loop) { return function(el, i) { i = getNormalizedIndex(i + startIndex, len, loop); return fn.call(context, arr[i], i, arr); }; } function indexResult(result, startIndex, len) { if (result !== -1) { result = (result + startIndex) % len; } return result; } function createFromIndexWithOptions(methodName, opts) { var baseFn = opts.base || Array.prototype[methodName], applyCallback = opts.apply || applyEach, sliceArray = opts.slice || sliceArrayFromLeft, clampIndex = opts.clamp || clampStartIndex, getResult = opts.result, wrapper = opts.wrapper; return function(arr, startIndex, args) { var callArgs = [], argIndex = 0, lastArg, result, len, loop, fn; len = arr.length; if (isBoolean(args[0])) { loop = args[argIndex++]; } fn = args[argIndex++]; lastArg = args[argIndex]; if (startIndex < 0) { startIndex += len; } startIndex = clampIndex(startIndex, len); assertArgument(args.length); fn = wrapper ? wrapper(fn, lastArg) : fn; callArgs.push(applyCallback(arr, startIndex, fn, lastArg, len, loop)); if (lastArg) { callArgs.push(lastArg); } result = baseFn.apply(sliceArray(arr, startIndex, loop), callArgs); if (getResult) { result = getResult(result, startIndex, len); } return result; }; } } module.exports = buildFromIndexMethods;