Javascript: Having trouble with .map() on IE - javascript

This code runs great on Chrome, FFX, etc. It takes the content of a textarea and separates all lines in different array items (new lines are represented by empty array items). When testing it on IE, it throws an error. Code:
This is tregex's value and the call:
var tregex = /\n|([^\r\n.!?]+([.!?]+|$))/gim;
var source = $('#text').val().match(tregex).map($.trim);
The code throws this error message because of .map() (IE only)
User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1;
Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR
3.5.30729) Timestamp: Fri, 13 Jul 2012 21:52:48 UTC
Message: Object doesn't support this property or method Line: 128
Char: 6 Code: 0 URI: http://mydomain.com/src/common.js
Why? Any way I can support it on IE7+? (this was tested on IE8).

IE is a terrible browser, and as such doesn't have a built-in map function (at least not in IE7-8). Since you're trying to call map on the results of a Regular Expression match (as opposed to calling it on a jQuery results object), the only map you can use is the built-in one (that IE doesn't have).
There are many libraries that simulate map for you however, including jQuery, Underscore, and Mochikit.
Here's an example of how you could use jQuery's to do what you're trying to do:
$.map($('#text').val().match(tregex), $.trim);

I believe your code may be working in FF and Chrome by accident. Did you mean to use jQuery.map? You are using Array.map which isn't supported in IE prior to 9.
Consider the following as a replacement.
var source = $.map($('#text').val().match(tregex), $.trim);
This uses jQuery's map implementation to loop through and do the trim.

I'm fairly sure Array.map() was only added in IE9. You should loop through the array manually instead.

You can add the support just by including the shim for it:
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.com/#x15.4.4.19
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 ({}.toString.call(callback) != "[object 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, Writable: 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;
};
}
You can also include the es5shim which adds .map and all the other missing array methods in IE7-8 plus other goods like Function#bind

Related

How are array methods aware of the values they are called on?

Can someone explain to me how are array methods aware of the value that we called them on,
As part of the prototype inheritance shouldn't it exist on the Array.prototype
if we say
let animals = ['dog','cat']
animals.map( x => console.log(x))
//dog
//cat
I just don't understand how is map aware of that we passed ['dog','cat'].
I usually see that if you call a function then you need to call it like map(animals).
thank you in advance
or looking at the map polyfill where is the line that we assign it to the arguments of the array.
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');
}
var O = Object(this);
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');
}
if (arguments.length > 1) {
T = arguments[1];
}
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while (k < len) {
var kValue, mappedValue;
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
// For best browser support, use the following:
A[k] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
Array.prototype.mymap = function(callback) {
var myArray = this; // here is the magic
var newArray = [];
for (var i = 0; i < myArray.length; i++) {
newArray.push(callback(myArray[i]));
}
return newArray;
};
console.log([0, 1, 2, 3].mymap(a => a * 2));
Well if we looked at the definition of Map according to W3Schools:
The map() method creates a new array with the results of calling a
function for every array element.
So the function map checks every element and does something with that.
In this example you say: "Console.log every element in this array"
So it's logic that the function map is aware what is in the array.
map() is a method of the Array object. In JavaScript, we say that the animals object uses "prototypical inheritance" of the Array object. It's kind of like saying animals inherits the properties and method of the Array base class (if you come from a world of OOP).
Since map is a function that exists for all Array objects, animals is no exception.
By definition map() takes a function, and returns an Array. It then iterates through each element of the array it was called from (in this case the animals array), and applies the passed function to each element.
Usually you would use this with a return statement in order to generate a new array. For instance, you could write something like:
var upperCaseAnimals = animals.map(x => return(x.toUpperCase()))
This would create a new array, upperCaseAnimals which contains all elements of the animals array with toUpperCase() applied to them.
But you can also use it to "do something" with each element of the array. This is what's being done in the sample above. Instead of giving a return value for each element, you're just console.log()ing each element.

alternative for foreach and use regular for loop

This doesnt really work in IE
[].forEach.call(document.querySelectorAll('.some-class-selector'), function(arg) {
callSomeFucntion(arg)
});
because forEach doesn't work on IE. if I want to do this using for loop, how would I do this?
Need to add this for run forEach in IE
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7" />
What version of IE are you using? The Microsoft docs page for forEach lists the version that don't support it - mainly IE 6,7 and 8 or quirks mode (https://learn.microsoft.com/en-us/scripting/javascript/reference/foreach-method-array-javascript)
You can also use a polyfill if your browser won't support it MDN forEach:
forEach() was added to the ECMA-262 standard in the 5th edition; as
such it may not be present in other implementations of the standard.
You can work around this by inserting the following code at the
beginning of your scripts, allowing use of forEach() in
implementations that don't natively support it. This algorithm is
exactly the one specified in ECMA-262, 5th edition, assuming Object
and TypeError have their original values and that callback.call()
evaluates to the original value of Function.prototype.call().
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback/*, thisArg*/) {
var T, 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 (arguments.length > 1) {
T = arguments[1];
}
// 6. Let k be 0.
k = 0;
// 7. Repeat while k < len.
while (k < len) {
var kValue;
// 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. Call the Call internal method of callback with T as
// the this value and argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined.
};
}

is `.map` not for looping?

I've answered a question here before about How to get number of response of JSON? and I suggested for them to use the map function instead of using a for loop but someone commented that .map is not for looping and to use forEach instead.
Are there any downsides to using map over a for loop?
I also researched this and found a site stating that map > forEach .
Map is used to transform each element in an array into another representation, and returns the results in a new sequence. However, since the function is invoked for each item, it is possible that you could make arbitrary calls and return nothing, thus making it act like forEach, although strictly speaking they are not the same.
Proper use of map (transforming an array of values to another representation):
var source = ["hello", "world"];
var result = source.map(function(value) {
return value.toUpperCase();
});
console.log(result); // should emit ["HELLO, "WORLD"]
Accidentally using .map to iterate (a semantic error):
var source = ["hello", "world"];
// emits:
// "hello"
// "world"
source.map(function(value) {
console.log(value);
});
The second example is technically valid, it'll compile and it'll run, but that is not the intended use of map.
"Who cares, if it does what I want?" might be your next question. First of all, map ignores items at an index that have an assigned value. Also, because map has an expectation to return a result, it is doing extra things, thus allocating more memory and processing time (although very minute), to a simple process. More importantly, it may confuse yourself or other developers maintaining your code.
The map() method creates a new array with the results of calling a
provided function on every element in this array.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
callback is invoked with three arguments: the value of the element, the index of the element, and the Array object being traversed.
If a thisArg parameter is provided to map, it will be passed to callback when invoked, for use as its this value. Otherwise, the value undefined will be passed for use as its this value. The this value ultimately observable by callback is determined according to the usual rules for determining the this seen by a function.
map does not mutate the array on which it is called (although callback, if invoked, may do so).
The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback. If existing elements of the array are changed, or deleted, their value as passed to callback will be the value at the time map visits them; elements that are deleted are not visited.
Ref from MDN:
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
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 (arguments.length > 1) {
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,
// Writable: true,
// Enumerable: true,
// Configurable: true },
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, k, {
// 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;
};
}
The best way to think about map is to think of it as a "functional" for loop with some superpowers.
When you call .map on an array, two things happen.
You give it a function, and that function gets called every time it iterates. It passes the item into your function at the current index of the loop. Whatever value you return in this function "updates" that given item in a new array.
The .map function returns you an array of all the values that got returned by the function you give to map.
Let's look at an example.
var collection = [1, 2, 3, 4];
var collectionTimesTwo = collection.map(function (item) {
return item * 2;
});
console.log(collection) // 1, 2, 3, 4
console.log(collectionPlusOne) // 2, 4, 6, 8
On the first line we define our original collection, 1 through 4.
On the next couple line we do our map. This is going to loop over every item in the collection, and pass each item to the function. The function returns the item multiplied by 2. This ends up generating a new array, collectionTimesTwo -- the result of multiplying each item in the array by two.
Let's look at one more example, say we have a collection of words and we want to capitalize each one with map
var words = ['hello', 'world', 'foo', 'bar'];
var capitalizedWords = words.map(function (word) {
return word.toUpperCase();
})
console.log(words) // 'hello', 'world', 'foo', 'bar'
console.log(capitalizedWords) // 'HELLO', 'WORLD', 'FOO', 'BAR'
See where we're going?
This lets us work more functionally, rather than like the following
var words = ['hello', 'world', 'foo', 'bar'];
var capitalizedWords = [];
for (var i = 0; i < words.length; i++) {
capitalizedWords[i] = words[i].toUpperCase();
}
There are a few things that can be said objectively, disregarding the subjective parts.
What is clear and concise use? If I use map() anyone reading the code assumes I'm doing what it says: mapping the values somehow. Being it a lookup table, calculation or whatever. I take the values and return (the same amount of) values transformed.
When I do forEach() it is understood I will use all the values as input to do something but I'm not doing any transformations and not returning anything.
Chaining is just a side effect, not a reason to use one over the other. How often do your loops return something you can or want to reuse in a loop, unless you're mapping?
Performance. Yes, it might be micro-optimization, but why use a function that causes an array to be gathered and returned if you're not going to use it?
The blog post you linked to is quite messy. It talks about for using more memory and then recommends map() because it's cool, even though it uses more memory and is worse in performance.
Also as an anecdote the test linked to there runs for faster than forEach on my one browser. So objective performance cannot be stated.
Even if opinions shouldn't count on SO, I believe this to be the general opinion: use methods and functions that were made for that use. Meaning for or forEach() for looping and map() for mapping.
The phrase "map isn't for looping" was probably a little bit inaccurate, since of course map replaces for-loops.
What the commenter was saying was that you should use forEach when you just want to loop, and use map when you want to collect results by applying an operating to each of the array elements. Here is a simple example:
> a = [10, 20, 30, 40, 50]
[ 10, 20, 30, 40, 50 ]
> a.map(x => x * 2)
[ 20, 40, 60, 80, 100 ]
> count = 0;
0
> a.forEach(x => count++)
undefined
> count
5
Here map retains the result of applying a function to each element. You map when you care about each of the individual results of the operation. In contrast, in your case of counting the number of elements in an array, we don't need to produce a new array. We care only about a single result!
So if you just want to loop, use forEach. When you need to collect all of your results, use map.

JavaScript performance: Call vs Apply

Is there a performance benefit in switching from func.apply(obj, params) to func.call(obj) when params is an empty array or null?
I mean, is calling func.call(obj) any faster than calling func.apply(obj, null)?
I'm mostly interested in performance under NodeJS 4.x.
This is for an algorithm that has to make a lot of such calls.
On this page there is a comparison. https://jsperf.com/call-apply-segu
Call was faster on my machine.
Basically, they will do the same steps:
Function.prototype.apply (thisArg, argArray)
If IsCallable(func) is false, then throw a TypeError exception.
If argArray is null or undefined, then
Return the result of calling the [[Call]] internal method of func, providing thisArg as the this value and an empty list of arguments.
Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ]
)
If IsCallable(func) is false, then throw a TypeError exception.
Let argList be an empty List.
If this method was called with more than one argument then in left to right order starting with arg1 append each argument as the last
element of argList
Return the result of calling the [[Call]] internal method of func, providing thisArg as the this value and argList as the list of arguments.
So the difference, if any, should be implementation dependent, and negligible.
Ha, interesting: it looks like apply is slower than call. 8-)
~/tmp ω cat test.js
function work(a, b, c) {
// do some work
}
var a = [1, 2, 3];
for (var j = 0; j < 4; j++) {
console.time('apply-ing');
for (var i = 0; i < 1000000; i++) {
work.apply(this, a);
}
console.timeEnd('apply-ing');
console.time('call-ing');
for (var i = 0; i < 1000000; i++) {
work.call(this, 1, 2, 3);
}
console.timeEnd('call-ing');
}
~/tmp ω node test.js
apply-ing: 42ms
call-ing: 5ms
apply-ing: 40ms
call-ing: 5ms
apply-ing: 42ms
call-ing: 5ms
apply-ing: 39ms
call-ing: 6ms
~/tmp ω node --version
v4.1.2
~/tmp ω

Angular equivalent of jQuery $.map?

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.

Categories

Resources