18

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.

5
  • Oops - forgot 1 thing. In the first String inheritance example (the "real" inheritance example), I had also tried a few things to the effect of "this.constructor.apply(this, arguments);" to try and call String's constructor in myString's constructor, to no avail. Commented Jul 24, 2011 at 1:40
  • If you search for "javascript inheritance" there are a LOT of posts here on SO that discuss many of the issues you ask about. Commented Jul 24, 2011 at 1:40
  • 1
    @encoder: For arrays, your example seems to work, but there are several problems, for example, the length property won't update automatically when you set an array index, and it won't affect the rest of properties of your object when you assign a value to it (e.g. x.length = 0; //empty array). It's more complicated than it might seem. Check this article. Commented Jul 24, 2011 at 2:08
  • Not sure if an edit triggers email alerts, so I'm commenting to say I edited my original post. Commented Jul 24, 2011 at 18:12
  • I know this is an old question, but it occurs to me that the practical way of dealing with native objects is to "extend" the native object itself by adding methods and properties to it (via the prototype such as adding a method to String.prototype) rather than subclassing it. This is done all the time now as a polyfill to make new method compatibility with old implementations and can also be done to create your own methods (with namespace collision caveats). Commented Feb 8, 2014 at 6:49

5 Answers 5

14

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.

Sign up to request clarification or add additional context in comments.

1 Comment

About Object.keys(String.prototype), it won't find anything, all properties of built-in constructors are non-enumerable, you need to use Object.getOwnPropertyNames.
5

This line is not valid:

this = n;

this is not a valid lvalue. Meaning, you cannot assign to the value referenced by this. Ever. It's just not valid javascript. Your example will work if you do:

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

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

myString.prototype = new String; // or String.prototype;

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

Regarding your workaround, you should realise that all you're doing is making a String object, augmenting a function property, and then calling it. There is no inheritance taking place.

For example, if you execute x instanceof myString in my example above it evaluates to true, but in your example it isn't, because the function myString isn't a type, it's just a regular function.

2 Comments

You need to override the valueOf and toString methods from String.prototype on your myString.prototype object, because those two methods are "non-generic", they work only on "real" strings, and they are used internally by type conversion when for example, you use any of the String.prototype methods (e.g. x.charAt(0);), and in other cases (e.g. x + 'bar'), if you don't override them, all those methods and operations will be unusable, they will all throw a TypeError.
@CMS, that's a very good point. Not sure there's much to do in these toy examples, since nothing would make much intuitive sense, maybe just stubs.
1

You can't assign the this in a constructor

this=n is an error

Your myarray is just an alias for the native Array- any changes you make to myarray.prototype are changes to Array.prototype.

Comments

1

I would look into creating a new object, using the native object as a backing field and manually recreating the functions for the native objects. For some very rough, untested sample code...

var myString = {
    _value = ''
    ,substring:_value.substring
    ,indexOf:_value.indexOf
}

Now, I'm sure this wont work as intended. But I think with some tweaking, it could resemble on object inherited from String.

Comments

0

The ECMAScript6 standard allows to inherit directly from the constructor functions of a native object.

class ExtendedNumber extends Number
{
    constructor(value)
    {
        super(value);
    }

    add(argument)
    {
        return this + argument;
    }
}

var number = new ExtendedNumber(2);
console.log(number instanceof Number); // true
console.log(number + 1); // 4
console.log(number.add(3)); // 5
console.log(Number(1)); // 1

In ECMAScript5 it is possible to inherit like this:

function ExtendedNumber(value)
{
    if(!(this instanceof arguments.callee)) { return Number(value); }

    var self = new Number(value);

    self.add = function(argument)
    {
        return self + argument;
    };

    return self;
}

However, the generated objects are not primitive:

console.log(number); // ExtendedNumber {[[PrimitiveValue]]: 2}

But you can extend a primitiv type by extending his actual prototype that is used:

var str = "some text";
var proto = Object.getPrototypeOf(str);
proto.replaceAll = function(substring, newstring)
{
    return this.replace(new RegExp(substring, 'g'), newstring);
}

console.log(str.replaceAll('e', 'E')); // somE tExt

A cast to a class-oriented notation is a bit tricky:

function ExtendedString(value)
{
    if(this instanceof arguments.callee) { throw new Error("Calling " + arguments.callee.name + " as a constructor function is not allowed."); }
    if(this.constructor != String) { value = value == undefined || value == null || value.constructor != String ? "" : value; arguments.callee.bind(Object.getPrototypeOf(value))(); return value; }

    this.replaceAll = function(substring, newstring)
    {
        return this.replace(new RegExp(substring, 'g'), newstring);
    }
}

console.log(!!ExtendedString("str").replaceAll); // true

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.