Extending Native Objects

Adding your own methods to the built in JavaScript objects is considered to be a bad idea - one obvious reason being that the name you give your method may correspond to a new method added to the native object in a future version of the standard.

This is easily avoided for some of the built in objects simply by creating your own object based on the built in one and adding your new methods to that instead.

There are however five of the built in objects where doing this means writing significantly more code to reference the extended version.

For example if you added myMethod to the Object, Array, Number, String and Function built in objects you’d be able to call:

a = {}.myMethod();
b = [].myMethod();
c = 2..myMethod();
d = 'fox'.myMethod();
e = function() {}.myMethod();

What would be useful would be a way to have these call the extended version of the appropriate object within a specific namespace. This would allow you to take advantage of these shortcut references to your own methods without impacting on the way that the built in objects work outside of the namespace you define.

Does anyone know of a way to implement this?

1 Like

There are however five of the built in objects where doing this means
writing significantly more code to reference the extended version.

Nah

a = myMethod({});
b = myMethod([]);
c = myMethod(2);
d = myMethod('fox');
e = myMethod(function() {});

I’m unsure of the type of semantics you’re looking for, but I’m guessing you want a wrapper object, similiar to what deepFreeze or jQuery does to objects to add custom methods.

a = felgall({}).myMethod();
b = felgall([]).myMethod();
c = felgall(2).myMethod();
d = felgall('fox').myMethod();
e = felgall(function() {}).myMethod();
1 Like

That looks like it might work for the first four - not sure about the fifth one though.

Thanks for the suggestion - I’ll investigate.

1 Like

Not sure I am getting very far with this - too many unanswered questions.

I’ll give an example. Let’s say I have created the wrapper (not sure what code it needs but let’s say it exists).

Now I have the following code using the wrapper instead of adding a method to a built in object:

felgall..prototype.memoize = function() {
   var self, cache;
   self = this;
   cache = {};
   return function () {
      var args, entry, i, cArg;  
      args = Array.prototype.slice.call(arguments);  
      entry = '';  
      cArg = null; 
      i = args.length;
      while(i--) {
         cArg = args[i];  
         entry += (cArg === Object(cArg)) ? JSON.stringify(cArg) : cArg;  
       } 
      return (entry in cache) ? cache[entry] : cache[entry] = self.apply(self, args);  
   };  
}

var fib = function (n)   
{  
  if (n===1)   
  {  
    return [0, 1];  
  }   
  else   
  {  
    var s = fib(n - 1);  
    s.push(s[s.length - 1] + s[s.length - 2]);  
    return s;  
  }  
}; 

fib = felgall(fib).memoize();

console.log(fib(42));

First question: How does memoize know that it applies to functions and not to objects, arrays, numbers or strings?

Second question - How does the wrapper know what type to return so that the correct methods will apply?

Third Question - I assume that all of the methods added need to return the same type as they would have if attached to the built in object and also be able to be chained to further felgall methods - how can the value returned be two types at once (for example the value returned from the memoize method needs to be a function AND a felgall (as it may have more felgall methods to be applied before running the function returned.

Unless I have completely overlooked something I don’t think a wrapper will work, at least not for functions, and the other four would each need a different wrapper if only to distinguish which built in object that it needs to inherit from in order to add the method.

Here’s one way, though you can probably get it to work using prototypes too.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script type="text/javascript">
var felgall = (function() {

var StringFellgall = function(string) {
  return Object.assign(string, {
    truncate: function() {
      return string.substr(0,5)
    }
  })
};

var FunctionFellgall = function(fn) {
  return Object.assign(fn, {
    memoize: function() {
      return fn
    }
  })
};

var felgall = function(value) {
  if (typeof(value) == 'string') {
    return StringFellgall(value);
  }
  else if (typeof(value) == 'function') {
    return FunctionFellgall(value);
  }
}

return felgall;

})()

var string = felgall('felgall')
var fn = felgall(function() { return 'felgall' })

console.log(string == 'felgall')
console.log(string.replace('fel', 'gall'))
console.log(string.truncate())
console.log(fn)
console.log(fn())
console.log(fn.memoize())
</script>
</body>
</html>
1 Like

I can see that ending up extremely messy considering that the particular methods that need to be added for a given script would be only a small subset of the rather large collection of methods I currently have (and whic is constantly getting bigger).

I have played around with this a bit and came up with the following approach that looks promising.

I started out looking specifically at arrays (figuring once I get one to work I can then look at the others.

So I came up with code to create a new object based on an array and a wrapper function that converts an array into this new extended array.

var _A = function() {this.push.apply(this, arguments);};
_A.prototype = Object.create(Array.prototype);
_A.prototype.constructor = _A;

var _a = function(a) {
  var x = new _A();
  a.forEach(function(n,p) {x[p] = n;});
  x.length = a.length;
  return x;
};

The new methods can then be added to _A instead of Array and processed against an array by passing the array into the wrapper.

The methods then just need minor rewrites to work with the new extended array object. For example:


_A.prototype.shuffle = function() {
  var r=new _A(),c = this.slice(0);
  while (c.length) r.push(c.splice(Math.random() * c.length, 1)[0]);
  return r;
};
 
_A.prototype.randInt = function(a,b,c) {
"use strict";
if (c === undefined) c = -b;
if (!isFinite(a) || !isFinite(b) || !isFinite(c) || a !== Math.floor(a) || b !== Math.floor(b) || c !== Math.floor(c)) throw new TypeError("All parameters must be integers"); 
return _a(Array.apply(null, Array(a)).map(function() {return Math.floor((Math.random()*Math.abs(b-c+1))+Math.min(b,c));})); 
};

_A.prototype.rotateLeft = function() {
   var t = this.shift();
   this.push(t);
   return this;
};

The only problem I discovered with this approach is that as it copies the array into the extended array the methods only act on the extended array and the original array remains unchanged so the method must return the extended array if the array itself is to be updated.

It does allow s = [0,1,2,3,4,5,6,7,8,9].shuffle();
to be rewritten as s = _a([0,1,2,3,4,5,6,7,8,9]).shuffle();
keeping the new methods away from the Array object.

Not sure if it will work for any of the other four objects though.

I think what I am looking for is somewhere in between the two lots of code.

You’d probably want to have a wrapper for each type, and the entry point “felgall” function would pick the appropriate one.

function felgall(objectToWrap) {
    if (/* objectToWrap is function */) {
        return felgall.makeFunctionWrapper(objectToWrap);
    } else if (/* objectToWrap is array */) {
        return felgall.makeArrayWrapper(objectToWrap);
    } // etc...
}

I presume your wrapper would inherit the native methods, and one problem is that methods such as Array#map will return a native array rather than a felgall wrapped array. You’d have to override that method just to add the behavior of re-wrapping the result.

felgall.arrayPrototype = {
    // ...
    map: function() {
        var mapped = Array.prototype.map.apply(this, arguments);
        return felgall(mapped);
    },
    // ...
};

In the future, when you’re comfortable with the browser support, classes and the species pattern will make this a lot easier.

It was just an example, but it did answer each of your questions. Like I said you probably wanted to create different objects with all of the special methods on the prototype objects rather than extending.

Like my example? :wink:

Ahh, yes. :slight_smile: I hadn’t read the whole thread. And… I just realized the post I replied to wasn’t even the latest in the thread. I goofed this one up pretty good.

A helpful one as I have made some progress. The following will work for Arrays but not yet for Functions or Strings.

var _A = function() {};
_A.prototype = Object.create(Array.prototype);
_A.prototype.constructor = _A;
var _F = function() {};
_F.prototype = Object.create(Function.prototype);
_F.prototype.constructor = _F;
var _S = function() {};
_S.prototype = Object.create(String.prototype);
_S.prototype.constructor = _S;


_A.prototype.shuffle = function() {
  var r=new _A(),c = this.slice(0);
  while (c.length) r.push(c.splice(Math.random() * c.length, 1)[0]);
  return r;
};

_S.prototype.countWords = function() {return this.match(/\S+/g).length;};


var _x = function(obj) {
  var base = {};
  if (typeof(obj) === 'function') {base = new _F(); return Object.assign(base, obj);}
  if (typeof(obj) === 'string') {base = new _S(); return Object.assign(base, obj);}
  if (Object.prototype.toString.call(obj) === '[object Array]' ) {
    base = new _A();
    base = Object.assign(base,obj);
    base.length = obj.length;
    return base;
  }
  return false;
};

// a = _x([1,2,3]).shuffle();
// console.log(a);

console.log(_x('the quick brown fox').countWords());

And even then, you lose the array exotic behavior.

a = _x([1,2,3]).shuffle();
console.log(a, a.length);
a.length = 1;
console.log(a, a.length);
a[42] = 0;
console.log(a, a.length);

You either need to use ES6 classes, or you need to augment real array instances and real function instances, like markbrown showed.

1 Like

Thanks Jeff. I knew there was something obvious about all this that I wasn’t seeing.

_A = {
  shuffle : function() {
     var r=[],c = this.slice(0);
     while (c.length) r.push(c.splice(Math.random() * c.length, 1)[0]);
     return r;
  },
  intervals : function(first,count,gap) {
     if (gap === undefined) gap = 1;
      return Array.apply(null, Array(count)).map(function (z,i) {return first+(i*gap);});
   } 
};
_F = {
  memoize : function() {
   var self, cache;
   self = this;
   cache = {};
   return function () {
      var args, entry, i, cArg;  
      args = Array.prototype.slice.call(arguments);  
      entry = '';  
      cArg = null; 
      i = args.length;
      while(i--) {
         cArg = args[i];  
         entry += (cArg === Object(cArg)) ? JSON.stringify(cArg) : cArg;  
       } 
      return (entry in cache) ? cache[entry] : cache[entry] = self.apply(self, args);  
   };  
 }

};
_S = {
  countWords : function() {return this.match(/\S+/g).length;}
};


var _x = function(obj) {
  if (typeof(obj) === 'function') {return Object.assign(obj, _F);}
  if (typeof(obj) === 'string') {return Object.assign(obj, _S);}
  if (Object.prototype.toString.call(obj) === '[object Array]' ) {
    return Object.assign(obj, _A);
  }
  return obj;
}; 

With that I can just throw whatever methods I want into the appropriate object - gives me the ability to be selective with the methods to be added without having to change the wrapper function (once I add object and number to it).I don’t even have to modify the code in the method as it is still adding to an object of the appropriate type.

I kept thinking I wanted to add the methods to the prototype where that isn’t necessary at all.

Thanks Mark as well. You gave me what I was looking for just about straight away but I was too caught up on looking at the problem the wrong way to see it.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.