jcen
jcen

Reputation: 1631

Object methods assigned to variables or function arguments fail when invoked

I'm learning javascript right now, seems like beautiful functional language to me, it is wonderful move from PHP, I should have done this earlier. Although, I cannot figure this one out:

var v1 = (/[abc]/).test;
v1('a');

says test method called on incompatible undefined, I'm trying to store the test method of that regex into variable and invoke it later.

but it works with my own functions:

function foo(){
    return 'I\'m foo';
}

var f = foo;
f(); // returns I'm foo

It should work on methods too, since functions are just methods of parent object anyway, right?

Ultimately, the reason I'm trying this is to be able to write something like this:

var a = ['a', 'b', 'c'];
a.every( (/[abc]/).test );

to check each array member against that regex.

Why doesn't this work? Is it limitation in passing built-in functions around? Or am I just doing something wrong?

PS: If you grind your teeth now and muffling something about bad practices, screw good practices, I'm just playing. But I'd like to hear about them too.

Upvotes: 2

Views: 225

Answers (5)

Felix Kling
Felix Kling

Reputation: 816760

it works with my own functions

You are not using this inside the function. Consider this example:

var obj = {
    foo: 42,
    bar: function() {
        alert(this.foo);
    }
};
var f = obj.bar;
f(); // will alert `undefined`, not `42`

It should work on methods too, since functions are just methods of parent object anyway, right?

"Method" is just a colloquial term for a function assigned to a property on object. And functions are standalone values. There is no connection to the object a function is assigned to. How would this even be possible, since a function could be assigned to multiple objects?

Why doesn't this work?

What this refers to inside a function is determined at run time. So if you assign the function to a variable and call it later

var v1 = (/[abc]/).test;
v1('a');

this inside the function will refer to window, not to the regular expression object.

What you can do is use .bind [MDN] to explicitly bind this to a specific value:

var a = ['a', 'b', 'c'];
var pattern = /[abc]/;
a.every(pattern.test.bind(pattern));

Note though that since .bind returns a function, the only advantage over using a function expression is that it is a tad shorter to write.


Is it limitation in passing built-in functions around?

No, the problem exists for every method/function because that's how functions work. The nice thing about built-in functions though is that they often explicitly tell you when this is referring to the wrong type of object (by throwing an error).


Learn more about this.

Upvotes: 3

tom
tom

Reputation: 22979

The test function expects this to be a regular expression. The expression /[abc]/.test gives an unbound function (it does not remember that it belongs to /[abc]/). When you invoke it like you do, this will be undefined and the function will fail.

You can use bind to make the function remember the object it belongs to:

var v1 = /[abc]/.test.bind(/[abc]/);

or

var v1 = RegExp.prototype.test.bind(/[abc]/);

Upvotes: 1

dave
dave

Reputation: 64657

I don't know if this is an acceptable solution, but you can do:

v1 = function(exp) { return (/[abc]/).test(exp); }
v1('a');

Upvotes: 0

Tim
Tim

Reputation: 8616

Your reference to the method has lost its knowledge of what it was a method of. This isn't so much good practice as just the way JS works.

You can do:

var v1 = /[abc]/;
v1.test('a');    

If you must encapsulate the test method, then you could do:

var v1 = function(str){
    return /[abc]/.test(str);
};
v1('a');

Upvotes: 0

jfriend00
jfriend00

Reputation: 707686

If you store just a method, it does not carry with it a reference to your object - it just stores a reference to the .test method, but no particular object. Remember, a method is "just" a property on an object and storing a reference to a method doesn't bind it to that object, it just stores a reference to the method.

To invoke that method on a particular object, you have to call it with that object.

You can make your own function that calls the method on the desired object like this:

var v1 = function(x) {
    return /[abc]/.test(x);
}

Then, when you do this:

v1('a');

It will execute the equivalent of this in your function:

/[abc]/.test('a');

But, it isn't entirely clear why you're doing that as you could also just define the regex and call .test() on it several times:

var myRegex = /[abc]/;
console.log(myRegex.test('a'));
console.log(myRegex.test('b'));
console.log(myRegex.test('z'));

Upvotes: 3

Related Questions