简体   繁体   中英

Javascript: What is the benefit of using function context vs passing as parameter

Other than tricking existing functions that already implement this as something, why would you want to write a javascript function so that you need to alter its context (via .call or .apply ) rather than explicitly passing the "context" as another parameter? Is there a performance benefit?

Example:

function tryIncrement(inc, context) {
    context = context || this; // just so we can reuse same fn for the example

    if( typeof context.val!= typeof 1|| typeof inc != typeof 1 ) return false;
    context.val += inc;
    return true;
}

var a = {name: 'A', val: 5}, b = {name: 'B', val: 20};

// reassign internal context
for(var i = 0, n = [1,3,"not a num",5]; i < n.length; i++) {
    if( tryIncrement.call(a, n[i]) ) console.log('incremented', i, n[i], a);
    else console.log('failed to increment', i, n[i], a);
}

// provide explicit context;
// could just as easily declared function so context was first param
// so it looked the same as previous implementation
for(var i = 0, n = [1,3,"not a num",5]; i < n.length; i++) {
    if( tryIncrement(n[i], b) ) console.log('incremented', i, n[i], b);
    else console.log('failed to increment', i, n[i], b);
}

As far as I can tell the use of this isn't really any different than another parameter, it just has a more complicated way of being modified.

I think the easiest way to answer your question is to imagine if the creator of the base Javascript language had followed your conventions.

A world without this

A world without this is a scary noisy place with lots of excessive duplication:

var arr = [1,2,3,4];
arr.reverse(arr); //4321

More opportunities for misleading or verbose syntax

var str = "stringtobesplit";
"abiglongstringnotbeingsplit".split(str,":");
String.prototype.split(str,":");

And its not at all rid of apply at least:

Math.max.apply(arr);  //didn't add the initial `this` since it doesn't exist

Effectively there would be a choice between creating only global functions, or creating functions on prototypes or objects that made assumptions about the types of the arguments it was receiving but didn't enforce those assumptions. For instance imagine the toString method in our fantasy world.

You could either create a global toString method which would take in an object of every type ever, and try to make them all work, or you could have a function on the prototypes of each type as it works currently, with no enforcement that it would be called on that type. Someone could call

Array.prototype.toString(str)

And we would need to handle it gracefully (for what its worth doing this with apply seems to revert to the Object.prototype.toString and returns [Object String] ). So we would need to identify the correct prototype method to call in those cases, which means my guess is that the convention would be to call

str.toString(str) 

or something along those lines.

So whats the point?

this is built to handle the common case for javascript methods on the prototype chain. It gives us a shorthand to allow an object to act on itself without duplicating the call to it or having to know exactly what its prototype is. Without it, we would either have to have no functions on objects, or would have to explicitly call the function on itself every time, introducing extra syntax and potential errors.

call and apply are the exception cases, and apply at least would have uses even if this went away. Its never a good idea to write your apis to the exception cases. If you're creating object oriented code, you should use this as an easy way to refer to the object that is the context for the call. If you write this well, then call and apply should be used rarely and in special situations.

TL;DR - this was designed as part of Javascript for a reason, use it when you're creating methods on objects for more clear and understandable syntax.

There are many cases where you may wish to use this instead of passing an extra parameter. Consider the following function for example:

Function.prototype.async = function () {
    setTimeout.bind(null, this, 0).apply(null, arguments);
};

This function allows us to defer a function call as follows:

alert.async("This will display later.");
alert("This will display first.");

You can see the demo here: http://jsfiddle.net/AjwQu/

Instead of binding the function to this we could have passed it as a parameter instead:

function async(funct) {
    setTimeout.bind(null, funct, 0).apply(null, [].slice.call(arguments, 1));
}

We would use it like this now:

async(alert, "This will display later.");
alert("This will display first.");

The result is the same: http://jsfiddle.net/63dBF/

However to get the arguments we have to use [].slice.call(arguments, 1) instead. In the first example we could simply use arguments as the function was not a part of the argument list.

Everything has it's advantages and disadvantages. You just need to know what to use when. Hope this helps a bit.

Bonus: It's really easy to convert a function that uses this into a function that accepts an extra parameter and vice versa. First let's define a few utility functions:

var functProto = Function.prototype;

var bind = functProto.bind;

var bindable = bind.bind(bind);
var callable = bindable(functProto.call);
var appliable = bindable(functProto.apply);

The bindable function allows you to create a bindable version of an existing function which when called returns a new function bound to the given arguments.

The callable function allows you to create a callable version of an existing function which when called calls the existing function with the given arguments and this pointer.

The appliable function allows you to create an appliable version of an existing function which when called applies the given arguments and this pointer to the existing function.

Then given the function in the first example we can create the function in the second example as follows:

var async = callable(functProto.async);

See the demo here: http://jsfiddle.net/3dSBS/

Similarly we can convert the function in the second example into the function in the first example as follows:

Function.prototype.async = function () {
    return async.apply(null, [this].concat([].slice.call(arguments)));
};

See the demo here: http://jsfiddle.net/rJQyS/

As you can see it's much easier to write a function using this and then construct the function accepting the context as a parameter from it than the other way around.

When you do object oriented programming your functions WILL depend on the context and it does not make sense do provide it as a parameter, as this would deafeat the purpose of object oriented programming.

It also makes sense to provide an implicit context for callbacks. You do not have to remember the correct order of the parameters if you only need the context. You would not have to use parameters at all in that case. So instead of

function mayCallback(param1, param2, context)

you could just write

function myCallback()

and use this , if you do not need param1 and param2.

To address my main purpose -- is there a performance benefit using this over a function parameter? -- the answer seems to be no :

http://jsperf.com/function-context-vs-parameter

Although there seems to be a slight benefit (may not be significant, however) around using parameter values instead of instance ( this ) variables within objects.

(Please test for yourself and comment if it's different)

Regarding the purpose being addressed by the other answers: there are some neat use cases as pointed out by @Aadit , maintainability is debatably a personal preference, but like @ben336 said if you're working with Objects (and thus OOP) then this can be more useful.

The ECMAScript 5th-edition native function bind may be an interesting bridge between the two worlds, or at least a time-sucking tangent to explore.

The instance vs parameter values test referenced above may also be a good example of my point -- if you're building a static library of functionality, you can "hijack" obj.callback2 by scoping to a different this , or just call obj.callback directly on your alternate context.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM