I know that Javascript Arrays are actually objects, and because they are objects, they can have properties. Here's an example:
var a = [1, 2, 3];
a.currentIndex = 2;
a.next = function() { ... };
a.prev = function() { ... };
a.length // returns 3
To me this seems like it could come in very handy. I see numerous reasons why you might want to store state or utility functions on the actual array itself and not on some other variable. It even seems better than having the array as a property of an object with the other stuff stored on that object.
Here's my question:
Does anyone know of any issues with storing properties on a Javascript array? Does it work in all browsers? Is there any evidence that this will change with future versions of Javascript? Is there any general wisdom about whether or not it's a good practice?
(p.s. For the record, I don't need to iterate over the array with a for...in loop. I understand that such a loop would include the properties as well)
Since you already ruled out the for in issue, my answer here is a clear "no" - there is no issue to worry about. All Array.prototype methods will only apply on the "indexed" keys (0...n).
The best example here is the well know jQuery library, it also uses Array-Like objects to store DOM nodes on but it also has lots of methods which are attached to that object (jQuery uses the prototype there tho). However, other librarys like Zepto, just put those methods directly on the "array" object itself.
So again, no there is no other caveat and you're save doing it.
Just throwing out one more thing -- None of the "copying" array prototype functions will copy any of your extra properties, if that's important, i.e. .splice, .slice, concat will give you new "clean" arrays without currentIndex, etc
Yes it works in all browsers. And it is valid javascript (arrays are objects). One could think of a number of reasons why you want to use the Array constructor instead of an object, but if this is your preferred coding style, go with it.
Related
I was working on an AJAX-enabled asp.net application.
I've just added some methods to Array.prototype like
Array.prototype.doSomething = function(){
...
}
This solution worked for me, being possible reuse code in a 'pretty' way.
But when I've tested it working with the entire page, I had problems..
We had some custom ajax extenders, and they started to behave as the unexpected: some controls displayed 'undefined' around its content or value.
What could be the cause for that? Am I missing something about modifing the prototype of standart objects?
Note: I'm pretty sure that the error begins when I modify the prototype for Array. It should be only compatible with IE.
While the potential for clashing with other bits o' code the override a function on a prototype is still a risk, if you want to do this with modern versions of JavaScript, you can use the Object.defineProperty method, e.g.
// functional sort
Object.defineProperty(Array.prototype, 'sortf', {
value: function(compare) { return [].concat(this).sort(compare); }
});
Modifying the built-in object prototypes is a bad idea in general, because it always has the potential to clash with code from other vendors or libraries that loads on the same page.
In the case of the Array object prototype, it is an especially bad idea, because it has the potential to interfere with any piece of code that iterates over the members of any array, for instance with for .. in.
To illustrate using an example (borrowed from here):
Array.prototype.foo = 1;
// somewhere deep in other javascript code...
var a = [1,2,3,4,5];
for (x in a){
// Now foo is a part of EVERY array and
// will show up here as a value of 'x'
}
Unfortunately, the existence of questionable code that does this has made it necessary to also avoid using plain for..in for array iteration, at least if you want maximum portability, just to guard against cases where some other nuisance code has modified the Array prototype. So you really need to do both: you should avoid plain for..in in case some n00b has modified the Array prototype, and you should avoid modifying the Array prototype so you don't mess up any code that uses plain for..in to iterate over arrays.
It would be better for you to create your own type of object constructor complete with doSomething function, rather than extending the built-in Array.
What about Object.defineProperty?
There now exists Object.defineProperty as a general way of extending object prototypes without the new properties being enumerable, though this still doesn't justify extending the built-in types, because even besides for..in there is still the potential for conflicts with other scripts. Consider someone using two Javascript frameworks that both try to extend the Array in a similar way and pick the same method name. Or, consider someone forking your code and then putting both the original and forked versions on the same page. Will the custom enhancements to the Array object still work?
This is the reality with Javascript, and why you should avoid modifying the prototypes of built-in types, even with Object.defineProperty. Define your own types with your own constructors.
There is a caution! Maybe you did that: fiddle demo
Let us say an array and a method foo which return first element:
var myArray = ["apple","ball","cat"];
foo(myArray) // <- 'apple'
function foo(array){
return array[0]
}
The above is okay because the functions are uplifted to the top during interpretation time.
But, this DOES NOT work: (Because the prototype is not defined)
myArray.foo() // <- 'undefined function foo'
Array.prototype.foo = function(){
return this[0]
}
For this to work, simply define prototypes at the top:
Array.prototype.foo = function(){
return this[0]
}
myArray.foo() // <- 'apple'
And YES! You can override prototypes!!! It is ALLOWED. You can even define your own own add method for Arrays.
You augmented generic types so to speak. You've probably overwritten some other lib's functionality and that's why it stopped working.
Suppose that some lib you're using extends Array with function Array.remove(). After the lib has loaded, you also add remove() to Array's prototype but with your own functionality. When lib will call your function it will probably work in a different way as expected and break it's execution... That's what's happening here.
Using Recursion
function forEachWithBreak(someArray, fn){
let breakFlag = false
function breakFn(){
breakFlag = true
}
function loop(indexIntoSomeArray){
if(!breakFlag && indexIntoSomeArray<someArray.length){
fn(someArray[indexIntoSomeArray],breakFn)
loop(indexIntoSomeArray+1)
}
}
loop(0)
}
Test 1 ... break is not called
forEachWithBreak(["a","b","c","d","e","f","g"], function(element, breakFn){
console.log(element)
})
Produces
a
b
c
d
e
f
g
Test 2 ... break is called after element c
forEachWithBreak(["a","b","c","d","e","f","g"], function(element, breakFn){
console.log(element)
if(element =="c"){breakFn()}
})
Produces
a
b
c
There are 2 problems (as mentioned above)
It's enumerable (i.e. will be seen in for .. in)
Potential clashes (js, yourself, third party, etc.)
To solve these 2 problems we will:
Use Object.defineProperty
Give a unique id for our methods
const arrayMethods = {
doSomething: "uuid() - a real function"
}
Object.defineProperty(Array.prototype, arrayMethods.doSomething, {
value() {
// Your code, log as an example
this.forEach(v => console.log(v))
}
})
const arr = [1, 2, 3]
arr[arrayMethods.doSomething]() // 1, 2, 3
The syntax is a bit weird but it's nice if you want to chain methods (just don't forget to return this):
arr
.map(x=>x+1)
[arrayMethods.log]()
.map(x=>x+1)
[arrayMethods.log]()
In general messing with the core javascript objects is a bad idea. You never know what any third party libraries might be expecting and changing the core objects in javascript changes them for everything.
If you use Prototype it's especially bad because prototype messes with the global scope as well and it's hard to tell if you are going to collide or not. Actually modifying core parts of any language is usually a bad idea even in javascript.
(lisp might be the small exception there)
I get that since javascript allows numeric keys for objects, the existence of array-like objects is therefore technically possible, but why did they ever become common?
Maybe the thought was that these array-like objects don't just have numeric keys, e.g. arguments has the callee property, so they can't be proper arrays to accommodate those properties.
But in javascript, it's perfectly valid to treat an array as an object and use non-numeric keys:
var myArguments = [];
myArguments[0] = 0;
myArguments['callee'] = function(){console.log('callee')};
If the object is array-like and would benefit from having access to the functions it would otherwise inherit from the array prototype, what would be advantage of making it an array-like object instead?
Edit: in case it wasn't clear in my question, an array like object would be something like the arguments object that has sequential numeric properties starting from zero, and has a length property that is one less than the highest numeric key. It also doesn't inherit from the array prototype, and doesn't provide access to array methods.
There is no big advantage, as you can access objects and arrays in very similar ways, array[number] for arrays, and object.propery, and object["property"] for objects. Arrays are better for storing data, not functions, in my opinion, and objects are better for storing sets of functions, in my opinion. For example, arrays could store your answers in a memory game, but an object would store the command for the game, like start or end.
I think the biggest advantage of using Array-Like object, is an object itself. An object has less overhead than an Array.
Can read: Array vs. Object efficiency in JavaScript
The other advantage of using objects, Language does not have to create/allocate contiguous memory as in Array. The object is more dynamic in nature. The array has to create a memory block in advance. and update on overload.
Read more: How are the JavaScript Arrays internally resizing?
Another factor, I think lookup in a map or object is faster.
Edit: As #Bergi stated, I forgot to add length property to the object. Also, I realized that my answer was not relevant. So I updated it after a short research.
In Typescript an arraylike object is defined like below:
interface ArrayLike<T> {
length: number;
[n: number]: T;
}
There are two main examples of array-like objects: arguments and HTMLCollection. The reason for using an array-like object is to use an array as only a data container without any behavior. As you could realize, if you try to use, say forEach, on arguments object you will get a type error since it's not defined on it. If you strictly believe that the array you are creating is only to be used as a data container without any methods whatsoever, then go with array-like objects. However, important to notice that they are getting scarcer day by day especially after ES6.
Actually Array in JS is an Object :)
As long as You need array, it is bad idea to create array-like objects, because JS engines optimize speed of arrays.
Yet, its possible.
This example is from http://eloquentjavascript.net/code/#5.1.
My question is the first bullet-pointed detail; others may be helpful details, but are additional; also see the first short program to see my question in context.
- Why is arrays.reduce() used instead of reduce(arrays()). I know that their's works with the arrays.reduce, but why?
This is an answer to a comment that is useful, but additional to the original question.
My question is with this first program. Since it uses arrays.reduce,
reduce would be a method of arrays, I am not sure why reduce is a
method of arrays. The reason might be in the design decisions of
JavaScript? Thanks, #cookie monster, for that comment!
This is the program with the context of my question-
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));
// → [1, 2, 3, 4, 5, 6]
These next details are additional, but may(or may not) be of use:
I know that the [] at the end is used because it is the start parameter in the function reduce so that the other arrays are added to that empty array. I know the .concat is putting together the two arrays like + with strings, but for arrays. Here is what the reduce function, even though it is standard, looks like:
function reduce(array, combine, start){
var current = start;
for(var i = 0; i < array.length; i++)
current = combine(current, array[i]);
return current;
}
One of their other examples showed my way with a single array, if that helps. It looked like:
console.log(reduce([1, 2, 3, 4], function(a, b){
return a + b;
}, 0));
// 10
Thanks! :)
In object oriented design, a guiding principle is that you create or declare objects and those objects have a series of methods on them that operate on that particular type of object. As Javascript is an object oriented language, the built-in functions follow many of the object oriented principles.
.reduce() is a function that operates only on Arrays. It is of no use without an Array to operate on. Thus, in an object oriented design paradigm, it makes complete sense to place the .reduce() function as a method on an Array object.
This object-oriented approach offers the following advantages vs. a global reduce() function:
It is consistent with the object oriented principles used elsewhere in the language.
It is convenient to invoke the .reduce() function on a particular array by using array.reduce() and it is obvious from the syntax which array it is operating on.
All array operations are grouped as methods on the Array object making the API definition more self documenting.
If you attempt to do obj.reduce() (invoke it on a non-array), you will immediately get an runtime error about an undefined .reduce() method.
No additional space is taken in the global scope (e.g. no additional global symbol is defined - lessening the chance for accidental overwrite or conflict with other code).
If you want to know why anyone ever used a global reduce() instead, you would need to understand a little bit about the history of Javascript evolution. Before ES5 (or for users running browsers who hadn't yet implemented ES5 like IE8), Javascript did not have a built-in .reduce() method on the Array object. Yet, some developers who were familiar with this type of useful iteration capability from other languages wanted to to use it in their own Javascript or in their own Javascript framework.
When you want to add some functionality to an existing built-in object like Array in Javascript, you generally have two choices. You can add a method to the prototype (in object-oriented fashion) or you can create a global function that takes the Array as its first argument.
For the Array object in particular, there are some potential issues with adding iterable methods to the Array prototype. This is because if any code does this type of iteration of an array (a bad way to iterate arrays, but nevertheless done by many):
for (var prop in array)
they will end up iterating not only the elements of the array, but also any iterable properties of the array. If a new method is assigned to the prototype with this type of syntax:
Array.prototype.myMethod = function() {}
Then, this new method will end up getting iterated with the for (var prop in array) syntax and it will often cause problems.
So, rather than adding a new method to the prototype, a safer alternative was to just use a global function and avoid this issue.
Starting in ES 5.1, there is a way using Object.defineProperty() to add non-iterable methods to an object or prototype so for newer browsers, it is now possible to add methods to a prototype that are not subject to the problem mentioned above. But, if you wanted to support older browsers (like IE8) and use reduce() type of functionality, you're still stuck with these ancient limitations.
So ... even though a global reduce() is less object oriented and is generally not as desirable in Javascript, some older code went that route for legitimate safety/interoperability reasons. Fortunately, we are putting that road behind us as old version of IE drop off in usage (thank god for Microsoft dropping XP support to finally accelerate the demise of old versions of IE). And newer browsers already contain array.reduce() built in.
JavaScript was/is influenced by the language Scheme, a dialect of Lisp. In Scheme higher order functions are a key component/feature of the language. In fact the reduce function is pretty much equivalent to the fold function in Scheme. In the case of the reduce function in JavaScript, the creators of the language noticed that programmers often need to traverse arrays in a certain fashion, and gave programmers a higher order function where they can pass in a function to specify how they want to manipulate the data. Having higher order functions allows programmers to abstract redundant code therefore creating shorter, cleaner, more readable code.
You can use the reduce function in JavaScript to do many things other than flatten lists. Look here an example.
I want to create an array in javascript and remember two ways of doing it so I just want to know what the fundamental differences are and if there is a performance difference in these two "styles"
var array_1 = new Array("fee","fie","foo","fum");
var array_2 = ['a','b','c'];
for (let i=0; i<array_1.length; i++){
console.log(array_1[i])
}
for (let i=0; i<array_2.length; i++){
console.log(array_2[i])
}
They do the same thing. Advantages to the [] notation are:
It's shorter.
If someone does something silly like redefine the Array symbol, it still works.
There's no ambiguity when you only define a single entry, whereas when you write new Array(3), if you're used to seeing entries listed in the constructor, you could easily misread that to mean [3], when in fact it creates a new array with a length of 3 and no entries.
It may be a tiny little bit faster (depending on JavaScript implementation), because when you say new Array, the interpreter has to go look up the Array symbol, which means traversing all entries in the scope chain until it gets to the global object and finds it, whereas with [] it doesn't need to do that. The odds of that having any tangible real-world impact in normal use cases are low. Still, though...
So there are several good reasons to use [].
Advantages to new Array:
You can set the initial length of the array, e.g., var a = new Array(3);
I haven't had any reason to do that in several years (not since learning that arrays aren't really arrays and there's no point trying to pre-allocate them). And if you really want to, you can always do this:
var a = [];
a.length = 3;
There's no difference in your usage.
The only real usage difference is passing an integer parameter to new Array() which will set an initial array length (which you can't do with the [] array-literal notation). But they create identical objects either way in your use case.
This benchmark on JSPerf shows the array literal form to be generally faster than the constructor on some browsers (and not slower on any).
This behavior is, of course, totally implementation dependent, so you'll need to run your own test on your own target platforms.
I believe the performance benefits are negligible.
See http://jsperf.com/new-array-vs-literal-array/4
I think both ways are the same in terms of performance since they both create an "Array object" eventually. So once you start accessing the array the mechanism will be the same. I not too sure about how different the mechanisms to construct the arrays be (in terms of performance) though it shouldn't be any noticeable gains using one way to the other.
I've been following Backbone Collection's convention of having arrays of data objects and using _.find/findWhere etc to loop through the array, even when I wasn't using Backbone. However it seems like it would be more efficient to instead store them as an associative array with the id as keys if I know that they will be unique. Are there any pitfalls to this that I'm not seeing?
So basically:
var map = {};
map["someId"] = someObject;
map["someOtherId"] = someOtherObject;
// ...later to get the object:
var o = map["someId"];
If so, then the answer to "are there any pitfalls...I'm not seeing" is "no": Looking up properties on objects is a very common operation, which JavaScript engines do very quickly.
In fact, since normal JavaScript arrays aren't really arrays at all, it's markedly more efficient to look things up this way rather than storing them in arrays and using forEach or similar to find them. Every time you get an entry from an array (e.g., a[0] or whatever), that's a property access operation, just like looking up a property in an object. (In fact, that's exactly what it is, barring the JavaScript engine knowing it can optimize the operation.) Getting an element from one of the new typed arrays is faster because they really are arrays (although searching through them will still be non-trivial), but getting an element from a standard array is a property lookup on an object, so you might as well just do one lookup (on your map, using your key) instead.
(Side note: In JavaScript, the term "associative array" isn't usually used. It's just an object. Sometimes you also hear "map".)