sugar
Version:
A Javascript utility library for working with native objects.
171 lines (154 loc) • 6.09 kB
JavaScript
;
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;