Related
Recently had an interview for a programming related gig. The interviewer made me rewrite each first, this I had no problem with and understood completely. Heres my each method:
var each = function(arrayItems,callback){
//if arrayItems is not an array
if(!Array.isArray(arrayItems)){
//write a for in loop to iterate through the object
for(var key in arrayItems){
//store the value, the key and the whole object as the callback parameters
callback(arrayItems[key],key,arrayItems);
}
}
//if arrayItems wasn't an object iterate through the array
for(var i = 0; i < arrayItems.length; i++){
//store the value,key and whole array as the callback's parameters
callback(arrayItems[i],i,arrayItems);
}
};
Second he had my rewrite reduce this no matter how many times I research devdocs and pick apart every piece of code I have trouble understanding here's my method:
var reduce = function(array,callback,initialValue){
// Implementing each into reduce
each(array,function(number){
//This is the line of code that confuses me
initialValue = callback(initialValue,number);
});
};
I'm looking for somebody to elaborate on the second line of code in reduce. How does this work initialValue is a function with initialValue and number as the parameters. Do initialValue and number need to be in a certain order? How does it work that initial value is equal to a function with intialValue and number as the callback? Also how does it know to execute the code if its something like : [number(s)].reduce(function(a,b){return a + b;},intialValue) I know these questions may seem a little vague, the interview is long gone but as a personal challenge I want to understand what is going on better.
"How does this work initialValue is a function with initialValue and number as the parameters."
I assume you mean callback is a function. If so, yes, it will be passed arguments, but more than just two.
The first is what you have as initialValue, though this adds confusion, because after the first time you update it, it's no longer the initial value. Better to use a different variable name.
The second is the current item in the iteration. You have it a name number, but it may not be a number. Again a confusing name.
The third should be the index of the current iteration.
The fourth should be the original collection.
"Do initialValue and number need to be in a certain order?"
Of course. That's the "accumulator" of your final value and the value of the current iteration. The user needs to know which is which.
"How does it work that initial value is equal to a function with intialValue and number as the callback?"
Every iteration, the callback is supposed to return the updated value of the accumulator. That value gets passed as the first argument of the next iteration.
"Also how does it know to execute the code if its something like : [number(s)].reduce(function(a,b){return a + b;},intialValue)"
That's an odd example, but if you had a correct Array, and you passed something like 0, then a is the accumulator, which will be equal to either the initialValue that you passed, or the return value of the previous iteration.
A more reasonable example of the use of .reduce() would be:
var result = [2,4,3,8,6].reduce(function(acc, curr, i, arr) {
console.log(acc, curr);
return acc + curr;
}, 0);
So on the first iteration, acc (short for accumulator) will be 0 (the initial value given), and curr (short for current) will be the number 2, which is the first item in the Array.
The return value is then 0 + 2.
On the second iteration, acc is the value of the last return, which was 0 + 2 or 2, and curr is the second item in the Array, which is 4.
The return value is then 2 + 4.
On the third iteration, acc is the value of the last return, which was 2 + 4 or 6, and curr is the third item in the Array, which is 3.
The return value is then 6 + 3.
...and so on.
So as you can see, it's just taking either the intial value, or the return value of the previous iteration, passing it as the first argument, letting you do whatever manipulation you want, and then taking your return value and passing it as the first argument of the next iteration.
This continues until the loop is complete, at which point .reduce() gives you whatever you returned from the last iteration.
Note that neither of your functions are ECMAScript compliant. The first one operates on plain objects??? And both are missing certain details.
You may check the underscore core functions!
if (typeof (/./) !== 'function') {
_.isFunction = function(obj) {
return typeof obj === 'function';
};
}
_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
_.keys = function(obj) {
if (!_.isObject(obj)) return [];
if (nativeKeys) return nativeKeys(obj);
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys.push(key);
return keys;
};
_.each = function(obj, iterator, context) {
if (obj == null) return obj;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
return obj;
};
_.reduce = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo;
};
I guess it depends on if you want forEach and reduce to be similar (simple but not to spec) or as close as possible/reasonable to the ECMA5 spec. To best understand them, I would suggest reading the specifications.
Array.prototype.forEach ( callbackfn [ , thisArg ] )
callbackfn should be a function that accepts three arguments. forEach calls callbackfn once for each element present in the array, in ascending order. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.
callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.
forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by forEach is set before the first call to callbackfn. Elements which are appended to the array after the call to forEach begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callback will be the value at the time forEach visits them; elements that are deleted after the call to forEach begins and before being visited are not visited.
When the forEach method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If thisArg was supplied, let T be thisArg; else let T be undefined.
Let k be 0.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Call the [[Call]] internal method of callbackfn with T as the this value and argument list containing kValue, k, and O.
Increase k by 1.
Return undefined.
The length property of the forEach method is 1.
NOTE The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the forEach function can be applied successfully to a host object is implementation-dependent.
-
Array.prototype.reduce ( callbackfn [ , initialValue ] )
callbackfn should be a function that takes four arguments. reduce calls the callback, as a function, once for each element present in the array, in ascending order.
callbackfn is called with four arguments: the previousValue (or value from the previous call to callbackfn), the currentValue (value of the current element), the currentIndex, and the object being traversed. The first time that callback is called, the previousValue and currentValue can be one of two values. If an initialValue was provided in the call to reduce, then previousValue will be equal to initialValue and currentValue will be equal to the first value in the array. If no initialValue was provided, then previousValue will be equal to the first value in the array and currentValue will be equal to the second. It is a TypeError if the array contains no elements and initialValue is not provided.
reduce does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by reduce is set before the first call to callbackfn. Elements that are appended to the array after the call to reduce begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them; elements that are deleted after the call to reduce begins and before being visited are not visited.
When the reduce method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If len is 0 and initialValue is not present, throw a TypeError exception.
Let k be 0.
If initialValue is present, then
Set accumulator to initialValue.
Else, initialValue is not present
Let kPresent be false.
Repeat, while kPresent is false and k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let accumulator be the result of calling the [[Get]] internal method of O with argument Pk.
Increase k by 1.
If kPresent is false, throw a TypeError exception.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Let accumulator be the result of calling the [[Call]] internal method of callbackfn with undefined as the this value and argument list containing accumulator, kValue, k, and O.
Increase k by 1.
Return accumulator.
The length property of the reduce method is 1.
NOTE The reduce function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the reduce function can be applied successfully to a host object is implementation-dependent.
Which for me I would write (and these are not 100% to spec, but close). You could of course write them exactly as described in the specs above.
Helper functions
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 throwIfNotAFunction(inputArg) {
if (!isClass(inputArg, 'function')) {
throw TypeError('Argument is not a function');
}
return inputArg;
}
Abstracts described in the speciications
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;
}
forEach
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);
}
}
}
reduce
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;
}
On jsFiddle
Working through Eloquent JavaScript and High Order Functions - section in Functional Programming.
Trying to define a reduce function and use it in a higher order function countWords();, which takes a given array and counts number of times each particular value is present and puts it in an object.
I.e. this works:
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}
I.e. this does not:
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.
Any help on this would be great. Thanks.
Your original reduce is actually broken, despite you saying that it works.
Here's a reduce that actually functions
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}
That said, I'm not entirely sure what you're trying to do with the second half of your post. Are you just trying to write implementations for forEach and reduce so you can understand how they work?
I would write the forEach like this
var forEach = function(arr, callback) {
for (var i=0, len=arr.length; i<len; i++) {
callback(arr[i], i, arr);
}
return arr;
};
And reduce like this
var reduce = function(arr, callback, initialValue) {
var result = initialValue;
forEach(arr, function(elem, idx) {
result = callback(result, elem, idx, arr);
});
return result;
};
Test them out
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
For those curious about reduce callback, I matched the native .reduce callback
I guess it depends on if you want forEach and reduce to be similar (simple) or as close as possible/reasonable to the ECMA5 spec (ignoring browser bugs), I like close as possible/reasonable.
Array.prototype.forEach ( callbackfn [ , thisArg ] )
callbackfn should be a function that accepts three arguments. forEach calls callbackfn once for each element present in the array, in ascending order. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.
callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.
forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by forEach is set before the first call to callbackfn. Elements which are appended to the array after the call to forEach begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callback will be the value at the time forEach visits them; elements that are deleted after the call to forEach begins and before being visited are not visited.
When the forEach method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If thisArg was supplied, let T be thisArg; else let T be undefined.
Let k be 0.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Call the [[Call]] internal method of callbackfn with T as the this value and argument list containing kValue, k, and O.
Increase k by 1.
Return undefined.
The length property of the forEach method is 1.
NOTE The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the forEach function can be applied successfully to a host object is implementation-dependent.
-
Array.prototype.reduce ( callbackfn [ , initialValue ] )
callbackfn should be a function that takes four arguments. reduce calls the callback, as a function, once for each element present in the array, in ascending order.
callbackfn is called with four arguments: the previousValue (or value from the previous call to callbackfn), the currentValue (value of the current element), the currentIndex, and the object being traversed. The first time that callback is called, the previousValue and currentValue can be one of two values. If an initialValue was provided in the call to reduce, then previousValue will be equal to initialValue and currentValue will be equal to the first value in the array. If no initialValue was provided, then previousValue will be equal to the first value in the array and currentValue will be equal to the second. It is a TypeError if the array contains no elements and initialValue is not provided.
reduce does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by reduce is set before the first call to callbackfn. Elements that are appended to the array after the call to reduce begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them; elements that are deleted after the call to reduce begins and before being visited are not visited.
When the reduce method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If len is 0 and initialValue is not present, throw a TypeError exception.
Let k be 0.
If initialValue is present, then
Set accumulator to initialValue.
Else, initialValue is not present
Let kPresent be false.
Repeat, while kPresent is false and k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let accumulator be the result of calling the [[Get]] internal method of O with argument Pk.
Increase k by 1.
If kPresent is false, throw a TypeError exception.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Let accumulator be the result of calling the [[Call]] internal method of callbackfn with undefined as the this value and argument list containing accumulator, kValue, k, and O.
Increase k by 1.
Return accumulator.
The length property of the reduce method is 1.
NOTE The reduce function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the reduce function can be applied successfully to a host object is implementation-dependent.
Which for me I would write (and these are not 100% to spec, but close) and keep in my personal library.
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);
On jsFiddle
Of course this may be a little OTT for what you are doing. :)
The reason is that your ForEach implementation is wrong. you should set i = 0;
function forEach(array, action) {
for(var i = 0; i < array.length; i++) {
action(array[i]);
}
}
There seems to be something wrong. You update an object. ++countMap
function combine(countMap, word) {
countMap[word] = ++countMap || 1;
return countMap;
}
It should be
function combine(countMap, word) {
countMap[word] = ++countMap[word] || 1;
return countMap;
}
I add a jsbin here
I'm transitioning from relying on jQuery to building apps in AngularJS. It's recommended in a number of places to not mix jQuery and Angular code.
One thing I miss though is the jQuery $.map function for arrays. I know this could be re-written using the native Javascript map function, but this is not implemented in all browsers (notably, IE < v9).
So, is there an Angular equivalent, or should I got back to writing for (var x = 0; x < foo; x += 1) {...} so I can stop including jQuery?
UPDATE Sometimes knowing what to search for is all you need. Bergie says 'look for polyfills'. Here's a reference guide (from the Modernizr crew) for a bunch of resources for making modern code work on older browsers: HTML5 Cross Browser Polyfills
Check here: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map
Mozilla has supplied an Array.map polyfill for unsupported browsers
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (thisArg) {
T = thisArg;
}
// 6. Let A be a new array created as if by the expression new Array(len) where Array is
// the standard built-in constructor with that name and len is the value of len.
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while(k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
kValue = O[ k ];
// ii. Let mappedValue be the result of calling the Call internal method of callback
// with T as the this value and argument list containing kValue, k, and O.
mappedValue = callback.call(T, kValue, k, O);
// iii. Call the DefineOwnProperty internal method of A with arguments
// Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true},
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
// For best browser support, use the following:
A[ k ] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
No, there is no equivalent in Angular. And as you discovered, no you don't need to fall back to writing imperative code.
Instead, just use the map function that is built directly into JavaScript. If you need to support IE8, insert the polyfill at the beginning of your scripts.
I have a JavaScript array as below which I need to filter to get the correct child values from the test data below.
var arrChildOptions2 = [
{Parent:'opt1',Value:'opt1',Text:'Parent1 - Child 1'},
{Parent:'opt2',Value:'opt1',Text:'Parent 2 - Child 1'},
{Parent:'opt2',Value:'opt2',Text:'Parent 2 - Child 2'}
];
The values are used to populate a dropdown based on the change event of a parent dropdown as below.
$(function() {
$('#ddl1').change(function() {
$('#ddl2 option:gt(0)').remove();
$('#ddl2').addItems('#ddl2', arrChildOptions2[Parent=opt2]);
});
});
where additems is a function that loops through the array. Problem is I can't get it to filter by parent, I've tried using contains and the above arrChildOptions2[Parent=opt2] but I can't get it to filter, I'd prefer to find a neat solution rather than use a for loop? Any ideas, cheers
You might have more luck using the jQuery.grep() function rather than messing around with loops.
This function "Finds the elements of an array which satisfy a filter function. The original array is not affected".
array.filter() exists in vanilla JavaScript:
function isBigEnough(element) {
return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44]
That documentation page includes a polyfill for older browsers:
if (!Array.prototype.filter)
{
Array.prototype.filter = function(fun /*, thisArg */)
{
"use strict";
if (this === void 0 || this === null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function")
throw new TypeError();
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++)
{
if (i in t)
{
var val = t[i];
// NOTE: Technically this should Object.defineProperty at
// the next index, as push can be affected by
// properties on Object.prototype and Array.prototype.
// But that method's new, and collisions should be
// rare, so use the more-compatible alternative.
if (fun.call(thisArg, val, i, t))
res.push(val);
}
}
return res;
};
}
Yes try jquery grep, like this:
arr = jQuery.grep( JSON_ARRAY, index);
I wrote a simple map implementation for some task. Then, out of curiosity, I wrote two more. I like map1 but the code is kinda hard to read. If somebody is interested, I'd appreciate a simple code review.
Which one is better? Do you know some other way to implement this in javascript?
var map = function(arr, func) {
var newarr = [];
for (var i = 0; i < arr.length; i++) {
newarr[i] = func(arr[i]);
}
return newarr;
};
var map1 = function(arr, func) {
if (arr.length === 0) return [];
return [func(arr[0])].concat(funcmap(arr.slice(1), func));
};
var map2 = function(arr, func) {
var iter = function(result, i) {
if (i === arr.length) return result;
result.push(func(arr[i]));
return iter(result, i+1);
};
return iter([], 0);
};
Thanks!
EDIT
I am thinking about such function in general.
For example, right now I am going to use it to iterate like this:
map(['class1', 'class2', 'class3'], function(cls) {
el.removeClass(cls);
});
or
ids = map(elements, extract_id);
/* elements is a collection of html elements,
extract_id is a func that extracts id from innerHTML */
What about the map implementation used natively on Firefox and SpiderMonkey, I think it's very straight forward:
if (!Array.prototype.map) {
Array.prototype.map = function(fun /*, thisp*/) {
var len = this.length >>> 0; // make sure length is a positive number
if (typeof fun != "function") // make sure the first argument is a function
throw new TypeError();
var res = new Array(len); // initialize the resulting array
var thisp = arguments[1]; // an optional 'context' argument
for (var i = 0; i < len; i++) {
if (i in this)
res[i] = fun.call(thisp, this[i], i, this); // fill the resulting array
}
return res;
};
}
If you don't want to extend the Array.prototype, declare it as a normal function expression.
As a reference, map is implemented as following in jQuery
map: function( elems, callback ) {
var ret = [];
// Go through the array, translating each of the items to their
// new value (or values).
for ( var i = 0, length = elems.length; i < length; i++ ) {
var value = callback( elems[ i ], i );
if ( value != null )
ret[ ret.length ] = value;
}
return ret.concat.apply( [], ret );
}
which seems most similar to your first implementation. I'd say the first one is preferred as it is the simplest to read and understand. But if performance is your concern, profile them.
I think that depends on what you want map to do when func might change the array. I would tend to err on the side of simplicity and sample length once.
You can always specify the output size as in
var map = function(arr, func) {
var n = arr.length & 0x7fffffff; // Make sure n is a non-neg integer
var newarr = new Array(n); // Preallocate array size
var USELESS = {};
for (var i = 0; i < n; ++i) {
newarr[i] = func.call(USELESS, arr[i]);
}
return newarr;
};
I used the func.call() form instead of just func(...) instead since I dislike calling user supplied code without specifying what 'this' is, but YMMV.
This first one is most appropriate. Recursing one level for every array item may make sense in a functional language, but in a procedural language without tail-call optimisation it's insane.
However, there is already a map function on Array: it is defined by ECMA-262 Fifth Edition and, as a built-in function, is going to be the optimal choice. Use that:
alert([1,2,3].map(function(n) { return n+3; })); // 4,5,6
The only problem is that Fifth Edition isn't supported by all current browsers: in particular, the Array extensions are not present in IE. But you can fix that with a little remedial work on the Array prototype:
if (!Array.prototype.map) {
Array.prototype.map= function(fn, that) {
var result= new Array(this.length);
for (var i= 0; i<this.length; i++)
if (i in this)
result[i]= fn.call(that, this[i], i, this);
return result;
};
}
This version, as per the ECMA standard, allows an optional object to be passed in to bind to this in the function call, and skips over any missing values (it's legal in JavaScript to have a list of length 3 where there is no second item).
There's something wrong in second method. 'funcmap' shouldn't be changed to 'map1'?
If so - this method loses, as concat() method is expensive - creates new array from given ones, so has to allocate extra memory and execute in O(array1.length + array2.length).
I like your first implementation best - it's definitely easiest to understand and seems quick in execution to me. No extra declaration (like in third way), extra function calls - just one for loop and array.length assignments.
I'd say the first one wins on simplicity (and immediate understandability); performance will be highly dependent on what the engine at hand optimizes, so you'd have to profile in the engines you want to support.