adding custom functions into Array.prototype
Asked Answered
U

9

88

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.

Uncrowned answered 4/6, 2009 at 2:46 Comment(0)
P
98

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); }
});
Ping answered 20/2, 2016 at 1:38 Comment(4)
It is not needed to state enumerable: false as false is the default value of enumerable.Chemurgy
Very useful. There are some basic/common task methods missing in the api.Ordeal
It should be the same as Array.prototype.sortf = function(compare) { return [].concat(this).sort(compare); }Fabrice
@Chemurgy Still better to be explicit about the purpose of the defineProperty usageTeamster
P
79

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.

Pisano answered 4/6, 2009 at 2:53 Comment(10)
I believe the "for (x in y)" construct is for iterating over the members of an object. For indexed-based iteration of an array, I don't think it's suitable. However, your point about interfering with other code on the page is valid - particularly if 3rd-party libraries are using for-in in this way.Austral
Yes, the inverse is true - you should avoid for..in in case some n00b has modified the Array prototype, and you should avoid modifying the Array prototype in case some n00b has used for..in on an array. ;)Pisano
the right answer these days is to use Object.defineProperty(Array.prototype, 'method', ...) which will make the new method non-enumerable.Timberwork
The risk you run when you add methods to built-in prototypes is that a future version of JavaScript will implement a method with the same name, and then your code will break any scripts that depend on the built-in behavior, or your scripts will break because your API may not be compatible with the built-in API.Precedency
Not to resurrect an old post, but can't you use hasOwnProperty in your for in to make sure you're not dealing with a prototypical property?Gerundive
To be blunt, hasOwnProperty is an unfortunate workaround to the possibility that someone has done something evil like modify Array.prototype, and is a good idea when co-existing with third-party code. The existence of this workaround, however, doesn't justify the practice (or make it less evil), and there are other side effects to modifying built-in object prototypes than its effect on for..in.Pisano
Modified answer to point out that even with Object.defineProperty modifying built-in types is still a bad idea because you can still conflict with other code.Pisano
Just don't set any methods to prototype that change the array, then it's all good.Advise
@Pisano Your recommend to "define your own types with your own constructors. How to do that? (So we can add custom functions (as methods) to our arrays.)Myoglobin
@Myoglobin if you want to pass around an array but also have your own methods, you can encapsulate - make your own objects and have a plain array as a member - or you could create objects that behave like arrays but with additional methods. If you're asking how to create an object constructor in Javascript, it usually means any function that you call with new in front, and the function can refer to this inside. examplePisano
P
16

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.

Privy answered 16/4, 2015 at 19:21 Comment(1)
The uplifting to the top is called "hoisting". Cfr. developer.mozilla.org/en-US/docs/Glossary/HoistingGrandaunt
F
2

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.

Faxen answered 4/6, 2009 at 7:1 Comment(0)
M
1

Another option is to create a prototipe:

Array.prototype.coalesce = function(){
    return this.find(a => a !== null);
}

[null, null, 1,2,3].coalesce();
// returns 1
Metabolic answered 16/3, 2023 at 19:27 Comment(0)
A
0

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

Awfully answered 12/5, 2019 at 12:36 Comment(0)
D
0

There are 2 problems (as mentioned above)

  1. It's enumerable (i.e. will be seen in for .. in)
  2. Potential clashes (js, yourself, third party, etc.)

To solve these 2 problems we will:

  1. Use Object.defineProperty
  2. 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]()
Durden answered 3/2, 2022 at 11:21 Comment(0)
S
0

If this issue happen in .at() method, use this prototype:

if (!Array.prototype.at) {
   Object.defineProperty(Array.prototype, 'at', {
      value: function (index) {
          const positiveIndex = index < 0 ? tenter code herehis.length + index : index;
          
          return this[positiveIndex];
      },`enter code here`
      writable: true,
      configurable: true
   });
}
Skirting answered 1/12, 2023 at 10:56 Comment(1)
Informative text in code blockCaudad
C
-2

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)

Clout answered 4/6, 2009 at 3:17 Comment(1)
This doesn't answer the question.Monofilament

© 2022 - 2024 — McMap. All rights reserved.