I'm attempting to create a revisable document, with the edits stored as fast-json-patched objects.
The rough outline is :
const jsonpatch = require('fast-json-patch');
/**
* Generate a doc, with all the state changes recorded as
* diffed objects.
* #param {object} docParams The parameters to generate the doc from
* #returns {object} the doc
*/
function generateDoc(docParams) {
const defaults = {
docnumber: 'TS99999',
edits: 1,
};
// create a complete set of properties to work from
const { docnumber, edits } = {
...defaults,
...docParams,
};
// basic docs setup
const doc = {
parameters: { docnumber, edits },
history: [],
state: { docnumber, notes: [] },
};
// update the doc 'edits' times
for (let edit = 0; edit < edits; edit++) {
// clone to preserve the current state
const currentState = JSON.parse(JSON.stringify(doc.state));
// add at least one note, up to every edit
const notesToAdd = Math.ceil(Math.random() * 5);
for (let i = 0; i < notesToAdd; i++) {
doc.state.notes.push('Note: ' + Math.ceil(Math.random() * 500));
}
doc.history.push(jsonpatch.compare(currentState, doc.state));
}
return doc;
}
/**
* Set the current doc state to the state at the spercifier point in time
* #param {object} doc The doc to update
* #param {integer} edit The point in time to use
* #returns {boolean} Was the doc set updated?
*/
function timetravel(doc, editPoint) {
if (
doc.parameters.currentedit === editPoint ||
editPoint > doc.parameters.edits ||
editPoint < 1
) {
return false; // don't travel too far into the future or past!
}
const patchesToApply = doc.history.slice(0, editPoint);
const patches = [].concat.apply([], patchesToApply);
let newDoc = {};
newDoc = jsonpatch.applyPatch(newDoc, patches).newDocument;
doc.state = newDoc;
doc.parameters.currentedit = editPoint;
return true;
}
// Testing....
const doc = generateDoc({
docnumber: 'TS99999',
edits: 5,
});
console.log(doc);
timetravel(doc, 2);
console.log(doc);
Clearly my understanding of what should be happening is wrong, as I get the following error...
/Users/dd/Code/patchtesting/node_modules/fast-json-patch/commonjs/core.js:14
obj[key] = this.value;
just at the jsonpatch.applyPatch line.
I've tried alternative approaches:
// seems to be one popular suggestion...
const patchesToApply = doc.history.slice(0, editPoint);
const patches = [].concat.apply([], patchesToApply);
doc.state = patches.reduce(jsonpatch.applyReducer, {});
doc.parameters.currentedit = editPoint;
or...
// Trying to see the effect of applying a single patch at a time...
patches.forEach(patch => {
console.log(patch);
newDoc = jsonpatch.applyPatch(newDoc, [patch]).newDocument;
console.log(newDoc);
});
The patches that are generated make sense, I just can't seem to apply them :-(
Sorry, it was a basic coding / fencepost-ish error:
...
// basic docs setup
const doc = {
parameters: { docnumber, edits },
history: [],
state: { docnumber, notes: [] },
};
// ADDED: need to record the initial state
doc.history.push(jsonpatch.compare({}, doc.state))
// update the doc 'edits' times
...
;
I have a function that loops over an array of words, and checks if a string passed to the function contains any of the letters in the word. The word with the most matching letters is returned.
My issue is that the const I am copying to the loop scope is getting overriden. Specifically let loopIndex = alphabetIndex; How can I make a variable that I can manipulate in the loop, without it overriding the const?
import _forEach from 'lodash/foreach';
const dictionary = require('./dictionary.json');
/**
* #constructor
*/
var Game = function () {
};
/**
* #desc Return an object of the repeating string characters.
* #param string
* #returns {{}}
*/
Game.prototype.setAlphabet = function (string) {
let alphabet = {};
for (let i = 0; i < string.length; i += 1) {
if (string[i] in alphabet) {
alphabet[string[i]] += 1;
} else {
alphabet[string[i]] = 1;
}
}
return alphabet;
};
/**
* #desc Return the largest word using the string passed to the function.
* #param string
*/
Game.prototype.getWinningWord = function (string) {
let max = 0;
let maxIndex = 0;
// Get the amount of letters used in the string as an object.
const alphabetIndex = this.setAlphabet(string);
// Loop through every word in the dictionary
_forEach(dictionary, function (word, position) {
let sum = 0;
let loopIndex = alphabetIndex;
// For each of the letters, check the index; and if the letter exists in the word, add to the sum.
for (let i = 0; i < word.length; i += 1) {
if (loopIndex[word[i]] > 0) {
sum += 1;
loopIndex[word[i]] -= 1;
}
}
// If the current word has more matching letters than the previous max, set this word to max.
if (sum > max) {
max = sum;
maxIndex = position;
}
if (position > 10) return false;
});
return dictionary[maxIndex];
};
/**
* Init game.
* #type {Game}
*/
var newGame = new Game();
console.log(newGame.getWinningWord('eeosnndsm'));
The distinction here is that while a const variable can never be assigned to again, if the value of that variable is a reference then that reference can change. To avoid this, you need to make a new copy in each iteration of your loop, a simple way to do this is to use Object.assign():
let loopIndex = Object.assign({}, alphabetIndex);
The following code is my attempt at a fairly generic javascript hash code implementation. I'm planning to use this code in conjunction with a hash table implementation (e.g. jshashtable) that utilizes hashCode() if its defined for keys. I have attempted to adhere closely to java's hash code implementations for numbers, strings, and arrays.
Questions:
Are there any issues with this implementation regarding correctness
or performance?
Are there any pre-existing implementations for hash
codes that do the same (or roughly the same) thing?
Aside from
jshashtable, are there other hash table implementations that utilizes
hashCode() and equals() in the same way that I should also consider?
NOTE: I'm aware that the code below could leverage other libs like underscore and jquery but I'd prefer not to have any 3rd party deps for my implementation. This is not say that I'm not interested in hash code libs that themselves may depend on jquery, underscore, etc.
/**
* Computes a hash code for an object based on a given subset of its fields
* #param obj any type
* #param keys an array of strings representing some subset of the keys in obj or undefined
* #returns {Number} a java-like hash code for obj based on the hash codes of a subset of its fields
* specified in keys.
*/
function hashCode(obj, keys) {
if (!isDefined(keys)) return typeHashCode(obj);
var result = 1;
for (var k = 0; k < keys.length; k++) {
var key = keys[k];
if (isDefined(obj[key]))
result = multiplyBy31AndAdd(result, typeHashCode(obj[key]));
}
return result;
}
/**
* #param obj
* #returns {Number}
*/
function typeHashCode(obj) {
var result = 1;
if (isDefined(obj)) {
if (typeof obj === 'string')
result = multiplyBy31AndAdd(result, stringHashCode(obj));
else if (typeof obj === 'number' && isFinite(obj))
result = multiplyBy31AndAdd(result, numberHashCode(obj));
else if (typeof obj === 'object') {
if (nonEmptyObject(obj)) {
if (isDefined(obj[hashCode]))
result = multiplyBy31AndAdd(result, obj.hashCode());
else {
if (Array.isArray(obj))
result = multiplyBy31AndAdd(result, arrayHashCode(obj));
else {
//This is what jshashtable does. If there were an easy and agreed upon way
//of uniquely identifying objects in javascript, a better approach
//may be to use the object's id
result = multiplyBy31AndAdd(result, stringHashCode(obj.toString()));
}
}
}
}
}
return result;
}
/**
* Generates a hash code for a 64 bit floating point number, similar to java's hash
* code implementation. This does not handle NaN and Inf the same way as java.
* More info can be found at [1]
* [1] http://stackoverflow.com/questions/2003493/javascript-float-from-to-bits
* #param num a finite number as defined by isFinite()
* #returns {Number}
*/
function numberHashCode(num) {
var buf = new ArrayBuffer(8);
(new Float64Array(buf))[0] = num;
return (new Uint32Array(buf))[0] ^ (new Uint32Array(buf))[1];
}
/**
* Generates a hash code for a string, similar to java's hash code
* implementation. More info can be found at [1]
* [1] http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
* #returns {Number}
*/
function stringHashCode(str) {
var hash = 0;
if (str.length === 0) return hash;
for (var i = 0; i < str.length; i++) {
var character = str.charCodeAt(i);
hash = multiplyBy31AndAdd(hash, character);
}
return hash;
}
/**
* #param array
* #returns {Number} hash code for the array
*/
function arrayHashCode(array) {
var result = 1;
for (var i = 0; i < array.length; i++) {
result = multiplyBy31AndAdd(result, typeHashCode(obj));
}
return result;
}
/**
* Code taken from:
* http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
* #param currentValue a number
* #param toAdd a number
* #returns {Number} the 32 bit integer representation of 31 * currentValue + toAdd
*/
function multiplyBy31AndAdd(currentValue, toAdd) {
var rv = ((currentValue<<5)-currentValue)+toAdd;
return rv & rv; //Convert to 32 bit integer
}
function isDefined(obj) {
return !(obj === undefined || obj === null);
}
/**
* Taken from http://stackoverflow.com/questions/4994201/is-object-empty
* #param obj an object
* #returns {Boolean} obj is {}
*/
function nonEmptyObject(obj) {
return !(Object.keys(obj).length === 0);
}
I want to check if an object exists, and has a property. Currently I get a "myObject is undefined" error that stops the check.
How can I make the following still work correctly even when myObject may not exist?
if (myObject.myProperty) {
...
} else {
...
}
I am trying to just even check if a object / variable exists but getting an error:
if (foo) { console.log('hello'); } gives the error Uncaught ReferenceError: foo is not defined. Here is a jsfiddle http://jsfiddle.net/cfUss/
You can use the "short circuit" && operator:
if (myObject && myObject.myProperty) {
...
}
If myObject is "falsey" (e.g. undefined) the && operator won't bother trying to evaluate the right-hand expression, thereby avoiding the attempt to reference a property of a non-existent object.
The variable myObject must have course already have been declared, the test above is for whether it has been assigned a defined value.
You can use the optional chaining operator ?. to keep things succinct:
if (myObject?.myProperty) { ... }
which is equal to the a bit more verbose
if (myObject && myObject.myProperty) { ... }
It comes in very handy especially for deeply nested objects.
It's currently (September 2019) a ECMAScript stage 3 proposal but things are looking promising that this feature will become officially available. For the time being, you can already start using it via the respective Babel plugin: https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
Try:
if(myObject && myObject.myProperty){ ... }
This code enters the body of the if block if myObject exists and also has myproperty. If myObject doesn't exist for some reason, the && short-circuits and does not evaluated myObject.myProperty.
I've been suprised not to find this helpful function in basic Javascipt interface.
Below is a helper function I often use in my projects. It checks if the value of the final chain element is reachable without an error "can't get ... of undefined". Use it this way:
getChained(this, "this.path.to.some.object.someFunction().result");
/**
* Deconstructs a chain, checking if every other element is undefined.
* You can use arrays and functions in chain and even pass parameters
* inside them.
*
* The only limitation is no string values with dots are allowed.
*
* #param {Object} chainScope Starting object, the chain scope.
* Guaranteed not to be be undefined.
* Required parameter.
* #param {String} chain A chain from code without first object
* and without first dot.
* Required parameter.
* #param {Array<Array<Object||String||Number||Boolean||undefined>>}
* functionsParameters Parameters for functions in chain.
* Object in the array correspond the order
* of functions in chain. Every object
* contains parameters for its function.
* Object syntax forces us to create parameter
* names even if you only have its value.
* #returns {Object||String||Number||Boolean||undefined} Final chain element value or undefined, if any other chain element returned undefined.
*/
getChained: function (
chainScope,
chain,
functionsParameters) {
var parts;
var part;
var partIndex;
var target = undefined;
var functionIndex = 0;
if (
chainScope === undefined ||
chainScope === null) {
return target;
}
target = chainScope; // The starting scope of a chain.
parts = getParts();
// Relay chain substituting calculated parts with values
// for function calls and arrays:
for (
partIndex = 0;
partIndex < parts.length;
partIndex++) {
if (target === undefined) {
// Chain element is undefined and so is the chain itself:
return undefined;
}
part = parts[partIndex];
if (
part.indexOf("(") >
part.indexOf("\"") &&
part.indexOf("(") >
part.indexOf("\'")) {
// It's a function:
target = getFunctionValue();
functionIndex++;
continue;
}
if (
part.indexOf("[") >
part.indexOf("\"") &&
part.indexOf("]") >
part.indexOf("\'")) {
// It's an array's element:
target = getArrayValue();
continue;
}
if (
typeof part === "string" &&
target !== null &&
target !== undefined) {
// It's an object:
target = target[part];
continue;
}
}
return target;
/**
* Splits string. Separators are dots outside the brackets.
* No splitting for dots inside the brackets.
*/
function getParts() {
var SEPARATOR = ".";
var OPEN_CHARS = [
"(",
"[",
"\"",
"\'"
];
var CLOSE_CHARS = [
")",
"]",
"\"",
"\'"
];
var SUB_SEPARATOR_OPEN = "[";
var SUB_SEPARATOR_CLOSE = "]";
return(
splitBySubSeparator(
splitBySeparator(
chain)));
/**
* Split by chain root separator.
* No splitting between opening and closing characters.
*
* #param {String} chainString Chain to analyse characters.
* #returns {Array<String>} Chain elements splitted.
*/
function splitBySeparator(chainString) {
var parts = [
];
var opened = 0;
var char1;
var chainIndex;
var extract;
var cutFromIndex = 0;
var chainArray;
// String to array and attach the ending dot
// to be able to split using common rule:
chainArray =
(chainString + ".").
split("");
for (
chainIndex = 0;
chainIndex < chainArray.length;
chainIndex++) {
char1 = chainArray[chainIndex];
if (OPEN_CHARS.indexOf(char1) > 0) {
// It's an opening bracket:
opened++;
continue;
}
if (CLOSE_CHARS.indexOf(char1) > 0) {
// It's a closing bracket:
opened--;
continue;
}
if (opened === 0) {
// It's character outside the brackets:
if (char1 === SEPARATOR) {
// It's a dot - everything before it is an element:
extract =
chainArray.slice(
cutFromIndex,
chainIndex). // Cut an element.
join(""); // Array to String.
parts.push(
extract);
cutFromIndex = chainIndex + 1; // Shift to escape a dot.
} else {
// It's an ordinary character:
continue;
}
}
}
return parts;
}
/**
* Splits by root subobject or array elements calls.
* Subcalls are searched inside the splitted chain elements.
* (now separator is "[" instead of ".").
* Can split several consequently called subobjects
* without a need to deconstruct enclosures.
* Second iteration finds array elements and object subcalls
* inside resulting elements (now separator is "[" instead of "."):
*/
function splitBySubSeparator(parts) {
var newParts = [
];
var opened = 0;
var char1;
var partIndex;
var chainIndex;
var chainArray;
for (
partIndex = 0;
partIndex < parts.length;
partIndex++) {
var part = parts[partIndex];
chainArray = part.split("");
for (
chainIndex = 0;
chainIndex < chainArray.length;
chainIndex++) {
char1 = chainArray[chainIndex];
if (
opened === 0 &&
char1 === SUB_SEPARATOR_OPEN) {
// Start of subcall for an array element or object:
part =
part.substr(0, chainIndex) +
SEPARATOR +
part.substr(chainIndex + 1);
opened++;
}
if (
opened > 0 &&
char1 === SUB_SEPARATOR_CLOSE) {
// End of subcall for an array element or object:
part =
part.substr(0, chainIndex) +
"" +
part.substr(chainIndex + 1);
opened--;
}
}
// Split changed element by separators again and
// relay into a cumulative array:
newParts =
newParts.concat(
splitBySeparator(part));
}
return newParts;
}
}
/**
* Creates and returns method call result. Puts required
* parameters into method.
*
* #returns {Object||String||Number||Boolean||undefined} Method execution result.
*/
function getFunctionValue() {
var value;
var name;
name =
part.
split("(")[0];
if (functionsParameters) {
value =
target[name].
apply(
target,
functionsParameters[
functionIndex
]);
} else {
value =
target[name].
apply(
target);
}
return value;
}
/**
* Returns array element.
*
* #returns {Object||String||Number||Boolean||undefined} Value of array element.
*/
function getArrayValue() {
var value;
var arrayName;
var itemName;
arrayName =
part.
split("[")[0];
itemName =
(part.
split("[")[1].
split("]")[0]).
split("\'").
join("").
split("\"").
join("");
if (target[arrayName]) {
value =
target[arrayName][itemName];
}
return value;
}
}
In the midst of doing some debugging, I came upon a situation in which it would be preferable to generalize the toUpperCase method. Here are a few ways that I came up with:
//Local
foo = [];
foo.toUpperCase = String(foo).toUpperCase;
foo.push("a");
foo.toUpperCase();
//Global
foo = [];
window.toUpperCase = function (obj) {return String(obj).toUpperCase();}
foo.push("a");
toUpperCase(foo);
//Prototype
foo = [];
Array.prototype.toUpperCase = String.prototype.toUpperCase;
foo.push("a");
foo.toUpperCase();
//Constructor Prototype
foo = [];
Array.prototype.constructor = String.prototype.toUpperCase;
foo.push("a");
foo.constructor();
//toString override
var foo = [];
foo.push("a");
var bar = String(foo);
foo.toString = function() { return bar.toUpperCase(); }
foo.toString();
Most of the String and Array methods have this disclaimer in the spec:
Therefore, it can be transferred to other kinds of objects for use as a method.
Is there a conventional approach to implementing this type of abstraction?
In case you need toUpperCase function to work only with arrays then you could extend Array class like that:
Array.prototype.toUpperCase = function () {
return String(this).toUpperCase();
};
After that you can write:
var foo = [];
foo.push('a');
foo.toUpperCase();
You don't need to do anything:
String.prototype.toUpperCase.call(anyObject)
If you want to shorten that, you'll need to bind call to toUpperCase:
var toUpperCase = Function.call.bind(String.prototype.toUpperCase);
Static methods attached to a global object is the previously implemented solution (less code, more data).
Generic methods have to be written so that they require this to only have a minimal set of methods. For example, most generic array methods only need this to provide length and indexed access.
There's a drawback when using Array.prototype.slice to convert arguments in an array: it prevents the browser JavaScript engine from performing optimizations
The recommended approach is to use Array.slice generic method – Array generic methods are specially implemented to be used on other types – but it is not a part of the ECMAScript specification
/**
* Implementation of standard Array methods (introduced in ECMAScript 5th
* edition) and shorthand generics (JavaScript 1.8.5)
*
* Copyright (c) 2013 Alex K #plusdude
* http://opensource.org/licenses/MIT
*/
(function (global, infinity, undefined) {
/*jshint bitwise:false, maxlen:95, plusplus:false, validthis:true*/
"use strict";
/**
* Local references to constructors at global scope.
* This may speed up access and slightly reduce file size of minified version.
*/
var Array = global.Array;
var Object = global.Object;
var Math = global.Math;
var Number = global.Number;
/**
* Converts argument to an integral numeric value.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
*/
function toInteger(value) {
var number;
// let number be the result of calling ToNumber on the input argument
number = Number(value);
return (
// if number is NaN, return 0
number !== number ? 0 :
// if number is 0, Infinity, or -Infinity, return number
0 === number || infinity === number || -infinity === number ? number :
// return the result of computing sign(number) * floor(abs(number))
(0 < number || -1) * Math.floor(Math.abs(number))
);
}
/**
* Returns a shallow copy of a portion of an array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.10
*/
function slice(begin, end) {
/*jshint newcap:false*/
var result, elements, length, index, count;
// convert elements to object
elements = Object(this);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// calculate begin index, if is set
if (undefined !== begin) {
// convert to integer
begin = toInteger(begin);
// handle -begin, begin > length
index = 0 > begin ? Math.max(length + begin, 0) : Math.min(begin, length);
} else {
// default value
index = 0;
}
// calculate end index, if is set
if (undefined !== end) {
// convert to integer
end = toInteger(end);
// handle -end, end > length
length = 0 > end ? Math.max(length + end, 0) : Math.min(end, length);
}
// create result array
result = new Array(length - index);
// iterate over elements
for (count = 0; index < length; ++index, ++count) {
// current index exists
if (index in elements) {
// copy current element to result array
result[count] = elements[index];
}
}
return result;
}
/**
* Returns the first index at which a given element
* can be found in the array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.14
*/
function indexOf(target, begin) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// calculate begin index, if is set
if (undefined !== begin) {
// convert to integer
begin = toInteger(begin);
// handle -begin, begin > length
index = 0 > begin ? Math.max(length + begin, 0) : Math.min(begin, length);
} else {
// default value
index = 0;
}
// iterate over elements
for (; index < length; ++index) {
// current index exists, target element is equal to current element
if (index in elements && target === elements[index]) {
// break loop, target element found
return index;
}
}
// target element not found
return -1;
}
/**
* Returns the last index at which a given element
* can be found in the array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.15
*/
function lastIndexOf(target, begin) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// calculate begin index, if is set
if (undefined !== begin) {
// convert to integer
begin = toInteger(begin);
// handle -begin, begin > length - 1
index = 0 > begin ? length - Math.abs(begin) : Math.min(begin, length - 1);
} else {
// default value
index = length - 1;
}
// iterate over elements backwards
for (; -1 < index; --index) {
// current index exists, target element is equal to current element
if (index in elements && target === elements[index]) {
// break loop, target element found
return index;
}
}
// target element not found
return -1;
}
/**
* Executes a provided function once per array element.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18
*/
function forEach(callback, scope) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements) {
// execute callback
callback.call(scope, elements[index], index, elements);
}
}
}
/**
* Tests whether all elements in the array pass the test
* implemented by the provided function.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.16
*/
function every(callback, scope) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements &&
// callback returns false
!callback.call(scope, elements[index], index, elements)) {
// break loop, test failed
return false;
}
}
// test passed, controversy began..
return true;
}
/**
* Tests whether some element in the array passes the test
* implemented by the provided function.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.17
*/
function some(callback, scope) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements &&
// callback returns true
callback.call(scope, elements[index], index, elements)) {
// break loop, test passed
return true;
}
}
// test failed
return false;
}
/**
* Creates a new array with all elements that pass the test
* implemented by the provided function.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.20
*/
function filter(callback, scope) {
/*jshint newcap:false*/
var result = [], elements, length, index, count;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = count = 0; index < length; ++index) {
// current index exists
if (index in elements &&
// callback returns true
callback.call(scope, elements[index], index, elements)) {
// copy current element to result array
result[count++] = elements[index];
}
}
return result;
}
/**
* Creates a new array with the results of calling a provided function
* on every element in this array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.19
*/
function map(callback, scope) {
/*jshint newcap:false*/
var result = [], elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements) {
// copy a return value of callback to result array
result[index] = callback.call(scope, elements[index], index, elements);
}
}
return result;
}
/**
* Apply a function against values of the array (from left-to-right)
* as to reduce it to a single value.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21
*/
function reduce(callback, value) {
/*jshint newcap:false*/
var elements, isset, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// status of the initial value
isset = undefined !== value;
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements) {
// initial value is set
if (isset) {
// replace initial value with a return value of callback
value = callback(value, elements[index], index, elements);
} else {
// current element becomes initial value
value = elements[index];
// status of the initial value
isset = true;
}
}
}
// make sure the initial value exists after iteration
requireValue(isset);
return value;
}
/**
* Apply a function against values of the array (from right-to-left)
* as to reduce it to a single value.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.22
*/
function reduceRight(callback, value) {
/*jshint newcap:false*/
var elements, isset, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// status of the initial value
isset = undefined !== value;
// index of the last element
index = (elements.length >>> 0) - 1;
// iterate over elements backwards
for (; -1 < index; --index) {
// current index exists
if (index in elements) {
// initial value is set
if (isset) {
// replace initial value with a return value of callback
value = callback(value, elements[index], index, elements);
} else {
// current element becomes initial value
value = elements[index];
// status of the initial value
isset = true;
}
}
}
// make sure the initial value exists after iteration
requireValue(isset);
return value;
}
/**
* Returns true if an argument is an array, false if it is not.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.3.2
*/
function isArray(value) {
return "[object Array]" === Object.prototype.toString.call(value);
}
/**
* Tests if an argument is callable and throws an error if it is not.
* #private
*/
function requireFunction(value) {
if ("[object Function]" !== Object.prototype.toString.call(value)) {
throw new Error(value + " is not a function");
}
}
/**
* Throws an error if an argument can be converted to true.
* #private
*/
function requireValue(isset) {
if (!isset) {
throw new Error("reduce of empty array with no initial value");
}
}
/**
* Tests implementation of standard Array method.
* #private
*/
function supportsStandard(key) {
var support = true;
// a method exists
if (Array.prototype[key]) {
try {
// apply dummy arguments
Array.prototype[key].call(undefined, /test/, null);
// passed? implemented wrong
support = false;
} catch (e) {
// do nothing
}
} else {
support = false;
}
return support;
}
/**
* Tests implementation of generic Array method.
* #private
*/
function supportsGeneric(key) {
var support = true;
// a method exists
if (Array[key]) {
try {
// apply dummy arguments
Array[key](undefined, /test/, null);
// passed? implemented wrong
support = false;
} catch (e) {
// do nothing
}
} else {
support = false;
}
return support;
}
/**
* Assigns method to Array constructor.
* #private
*/
function extendArray(key) {
if (!supportsGeneric(key)) {
Array[key] = createGeneric(key);
}
}
/**
* Creates generic method from an instance method.
* #private
*/
function createGeneric(key) {
/** #public */
return function (elements) {
var list;
if (undefined === elements || null === elements) {
throw new Error("Array.prototype." + key + " called on " + elements);
}
list = Array.prototype.slice.call(arguments, 1);
return Array.prototype[key].apply(elements, list);
};
}
/**
* Assign ECMAScript-5 methods to Array constructor,
* and Array prototype.
*/
var ES5 = {
"indexOf": indexOf,
"lastIndexOf": lastIndexOf,
"forEach": forEach,
"every": every,
"some": some,
"filter": filter,
"map": map,
"reduce": reduce,
"reduceRight": reduceRight
};
for (var key in ES5) {
if (ES5.hasOwnProperty(key)) {
if (!supportsStandard(key)) {
Array.prototype[key] = ES5[key];
}
extendArray(key);
}
}
Array.isArray = Array.isArray || isArray;
/**
* Assign ECMAScript-3 methods to Array constructor.
* The toString method is omitted.
*/
[
"concat",
"join",
"slice",
"pop",
"push",
"reverse",
"shift",
"sort",
"splice",
"unshift"
].forEach(extendArray);
/**
* Test the slice method on DOM NodeList.
* Support: IE < 9
*/
/*jshint browser:true*/
if (document) {
try {
Array.slice(document.childNodes);
} catch (e) {
Array.prototype.slice = slice;
}
}
}(this, 1 / 0));
/*globals define*/
// Assumes all supplied String instance methods already present
// (one may use shims for these if not available)
(function() {
'use strict';
var i,
// We could also build the array of methods with the following, but the
// getOwnPropertyNames() method is non-shimable:
// Object.getOwnPropertyNames(String).filter(function(methodName) {
// return typeof String[methodName] === 'function';
// });
methods = [
'quote', 'substring', 'toLowerCase', 'toUpperCase', 'charAt',
'charCodeAt', 'indexOf', 'lastIndexOf', 'startsWith', 'endsWith',
'trim', 'trimLeft', 'trimRight', 'toLocaleLowerCase',
'toLocaleUpperCase', 'localeCompare', 'match', 'search',
'replace', 'split', 'substr', 'concat', 'slice'
],
methodCount = methods.length,
assignStringGeneric = function(methodName) {
var method = String.prototype[methodName];
String[methodName] = function(arg1) {
return method.apply(arg1, Array.prototype.slice.call(arguments, 1));
};
};
for (i = 0; i < methodCount; i++) {
assignStringGeneric(methods[i]);
}
}());
References
MDN: Array Generic Methods
MDN: String Generic Methods
array.generics.js
Mastering the Arcane Art of JavaScript-mancy for C# Developers - Chapter 4: More Useful Function Patterns - Multiple Arguments