Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind a function to String.prototype so it's always bound to the string

Tags:

javascript

String.prototype.contains = function(str) {
    return this.indexOf(str) !== -1;
};

This snippet does extend String.prototype.
It works well with function calls like 'foobar'.contains('foo');
But it doesn't work well if it's passed as a function, not invoking it:

var str = 'foobar';
['foo', 'bar'].every(str.contains);
TypeError: Object [object global] has no method 'indexOf'

I know you could do:

['foo', 'bar'].every(str.contains.bind(str));

or something like:

['foo', 'bar'].every(function(item) { return str.contains(item); });

But they just make me feel awkward.
In a Constructor function I created, although it doesn't work this way:

function Foo(myStr) {
    this.myStr = myStr;

    this.contains = function(fragment) {
        return this.myStr.indexOf(fragment) !== -1;
    };
}
var someFoo = new Foo('foobar');
['foo', 'bar'].every(someFoo.contains)
TypeError: Cannot call method 'indexOf' of undefined

I could do this to make it work:

function Foo(myStr) {
    var self = this; // Line added
    this.myStr = myStr;

    this.contains = function(fragment) {
        return self.myStr.indexOf(fragment) !== -1; // this changed to self
    };
}

I'm wondering if there's a way to extend String.prototype so that I don't have to bind 'someString'.contains to 'someString' every time I use it as a function without invoking it.

like image 577
octref Avatar asked Mar 16 '26 17:03

octref


2 Answers

Try this:

Object.defineProperty(String.prototype, "contains", {
    get: function () {
        return (function(str) {
            return this.indexOf(str) !== -1;
        }).bind(this);
    }
});

It defines a new property with a getter, meaning every time you "get" the function, for example in ['foo', 'bar'].every(str.contains);, the function defined above would get called and the return value of that function would be returned as str.contains, in this case. The function is the same as yours, only we bind this in the getter which is the string, which is what we want.

If Object.defineProperty isn't defined in your browser (or anyone to use your code), you can use __defineGetter__ instead.

A complete solution would look like this:

addPropertyWithGetter = function(object, property) {

    getter = function () {
        return (function(str) {
            return this.indexOf(str) !== -1;
        }).bind(this);
    }

    if (_.isFunction(Object.defineProperty)) {
        return Object.defineProperty(object, property, {
            get: getter,
        });
    } else {
        object.__defineGetter__(property, getter);
    }
}

And the usage:

addPropertyWithGetter(String.prototype, "contains")

like image 184
Mosho Avatar answered Mar 18 '26 05:03

Mosho


The second parameter for Array.prototype.every() is the thisArg(Value to use as this when executing callback).

So you could do with:

['foo', 'bar'].every(str.contains, str);

For fixing your code:

function Foo(myStr) {
    this.contains = function(fragment) {
        // myStr is accessible here
        return myStr.indexOf(fragment) !== -1;
    };
}
var someFoo = new Foo('foobar');
['foo', 'bar'].every(someFoo.contains);
like image 43
xdazz Avatar answered Mar 18 '26 07:03

xdazz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!