Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How best to inherit from native JavaScript object? (Especially String)

I'm a long-time browser but a first time participator. If I'm missing any etiquette details, please just let me know!

Also, I've searched high and low, including this site, but I haven't found a clear and succinct explanation of exactly what I'm looking to do. If I just missed it, please point me in the right direction!

Alright, I want to extend some native JavaScript objects, such as Array and String. However, I do not want to actually extend them, but create new objects that inherit from them, then modify those.

For Array, this works:

var myArray = function (n){
    this.push(n);

    this.a = function (){
        alert(this[0]);
    };
}

myArray.prototype = Array.prototype;

var x = new myArray("foo");
x.a();

However, for String, the same doesn't work:

var myString = function (n){
    this = n;

    this.a = function (){
        alert(this);
    };
}

myString.prototype = String.prototype;

var x = new myString("foo");
x.a();

I've also tried:

myString.prototype = new String();

Now, in trying to research this, I've found that this does work:

var myString = function (n){
    var s = new String(n);

    s.a = function (){
        alert(this);
    };

    return s;
}

var x = myString("foo");
x.a();

However, this almost feels like 'cheating' to me. Like, I should be using the "real" inheritance model, and not this shortcut.

So, my questions:

1) Can you tell me what I'm doing wrong as regards inheriting from String? (Preferably with a working example...)

2) Between the "real" inheritance example and the "shortcut" example, can you name any clear benefits or detriments to one way over the other? Or perhaps just some differences in how one would operate over the other functionally? (Because they look ultimately the same to me...)

Thanks All!

EDIT:

Thank you to everyone who commented/answered. I think @CMS's information is the best because:

1) He answered my String inheritance issue by pointing out that by partially redefining a String in my own string object I could make it work. (e.g. overriding toString and toValue) 2) That creating a new object that inherits from Array has limitations of its own that weren't immediately visible and can't be worked around, even by partially redefining Array.

From the above 2 things, I conclude that JavaScript's claim of inheritablity extends only to objects you create yourself, and that when it comes to native objects the whole model breaks down. (Which is probably why 90% of the examples you find are Pet->Dog or Human->Student, and not String->SuperString). Which could be explained by @chjj's answer that these objects are really meant to be primitive values, even though everything in JS seems to be an object, and should therefore be 100% inheritable.

If that conclusion is totally off, please correct me. And if it's accurate, then I'm sure this isn't news to anyone but myself - but thank you all again for commenting. I suppose I now have a choice to make:

Either go forward with parasitic inheritance (my second example that I now know the name for) and try to reduce its memory-usage impact if possible, or do something like @davin, @Jeff or @chjj suggested and either psudo-redefine or totally redefine these objects for myself (which seems a waste).

@CMS - compile your information into an answer and I'll choose it.

like image 207
encoder Avatar asked Jul 24 '11 01:07

encoder


1 Answers

The painfully simple but flawed way of doing this would be:

var MyString = function() {};

MyString.prototype = new String();

What you're asking for is strange though because normally in JS, you aren't treating them as string objects, you're treating them as "string" types, as primitive values. Also, strings are not mutable at all. You can have any object act as though it were a string by specifying a .toString method:

var obj = {};
obj.toString = function() {
  return this.value;
};
obj.value = 'hello';

console.log(obj + ' world!');

But obviously it wouldn't have any string methods. You can do inheritence a few ways. One of them is the "original" method javascript was supposed to use, and which you and I posted above, or:

var MyString = function() {};
var fn = function() {};
fn.prototype = String.prototype;
MyString.prototype = new fn();

This allows adding to a prototype chain without invoking a constructor.

The ES5 way would be:

MyString.prototype = Object.create(String.prototype, {
  constructor: { value: MyString }
});

The non-standard, but most convenient way is:

MyString.prototype.__proto__ = String.prototype;

So, finally, what you could do is this:

var MyString = function(str) {
  this._value = str;
};

// non-standard, this is just an example
MyString.prototype.__proto__ = String.prototype;

MyString.prototype.toString = function() {
  return this._value;
};

The inherited string methods might work using that method, I'm not sure. I think they might because there's a toString method. It depends on how they're implemented internally by whatever particular JS engine. But they might not. You would have to simply define your own. Once again, what you're asking for is very strange.

You could also try invoking the parent constructor directly:

var MyString = function(str) {
  String.call(this, str);
};

MyString.prototype.__proto__ = String.prototype;

But this is also slightly sketchy.

Whatever you're trying to do with this probably isn't worth it. I'm betting there's a better way of going about whatever you're trying to use this for.

If you want an absolutely reliable way of doing it:

// warning, not backwardly compatible with non-ES5 engines

var MyString = function(str) {
  this._value = str;
};

Object.getOwnPropertyNames(String.prototype).forEach(function(key) {
  var func = String.prototype[key];
  MyString.prototype[key] = function() {
    return func.apply(this._value, arguments);
  };
});

That will curry on this._value to every String method. It will be interesting because your string will be mutable, unlike real javascript strings.

You could do this:

    return this._value = func.apply(this._value, arguments);

Which would add an interesting dynamic. If you want it to return one of your strings instead of a native string:

    return new MyString(func.apply(this._value, arguments));

Or simply:

    this._value = func.apply(this._value, arguments);
    return this;

There's a few ways to tackle it depending on the behavior you want.

Also, your string wont have length or indexes like javascript strings do, a way do solve this would be to put in the constructor:

var MyString = function(str) {
  this._value = str;
  this.length = str.length;
  // very rough to instantiate
  for (var i = 0, l = str.length; i < l; i++) {
    this[i] = str[i];
  }
};

Very hacky. Depending on implementation, you might just be able to invoke the constructor there to add indexes and length. You could also use a getter for the length if you want to use ES5.

Once again though, what you want to do here is not ideal by any means. It will be slow and unnecessary.

like image 181
chjj Avatar answered Sep 29 '22 09:09

chjj