using call and apply for properties? - javascript

Let's say I have a variable like this:
var a = this.property || this.parent.anotherProperty;
It's possible to set the context (by context i mean 'this', maybe the 'scope' is a better word...) for a like when using .call() or .apply() for functions?
EDIT:
I have an helper function that given a value return:
if the value is a function -> value()
if it isn't a function -> value
This is the code:
function unwrapValue(value){
return typeof value === 'function' ? value() : value;
}
unwrapValue is inside a plain object (Utils) and it's called from outside this object:
Utils.unwrapValue(value);
Now, I have a property url in a function (that may be either a function or something else):
this.url = this.baseUrl || this.collection.baseUrl;
I don't know if this.url is a function or something else so I use unwrapValue to get the value of url:
var params = {};
params.url = Utils.unwrapValue(this.url);
And the problem is here, unwrapValue return this.url but setting 'this' to something else (i tought it was the Utils object but for some reason it's the window object) so params.url is window.baseUrl || window.collection.baseUrl which is not what i want.
If value is a function I solved this way:
function unwrapValue(value, context){
if(typeof value === 'function'){
return typeof context === 'undefined' ? value() : value.call(context);
}else{
return value;
}
}
so that if a second parameter context is passed to unwrapValue, value's this will be set to context.
with this question I was searching a way to use context aslo in the case value wasn't a function like:
this.url = this.baseUrl || this.collection.url;
And just to clarify a little more: this.baseUrl and this.collection.url are simple strings
There's a way to solve that?

You possibly want to pass an object literal as the thisArg into that function:
function fn() {
var a = this.a || this.parent.a;
console.log(a);
}
fn.call({
a: false,
parent: {
a: "foobar"
}
});
As you don't pass in further arguments, you could've used apply as well instead of call.

Assuming that a is a function (it's unclear from your question) then you can use Function.bind to set the context for all calls made to it:
var a = (this.a || this.parent.a).bind(this);
See MDN for more information and a shim for older browsers.

Related

How to get context of the object that calls a method? 'This' doesn't work. JS [duplicate]

This question already has answers here:
ES6 arrow function and lexical scope inside a function [duplicate]
(2 answers)
Closed 4 years ago.
How can I get the reference to what's being return to the method call? Like how we have the arguments variable to get all arguments being passed to a function. How can I get what's being returned to so I can chain a function?
let myFunc = function(obj, num){
if (typeof obj === 'number' && typeof num === 'undefined') {
num = obj;
obj = this;
}
console.log(obj.name);
console.log(num);
console.log(Object(this));
return obj;
};
let myObj = {
name: 'Devin',
};
myFunc(myObj, 1);
myObj.myFunc = myFunc;
myObj.myFunc(2).myFunc(3);
edit: It's written out this because it's currently used in many places that I will have to refactor down the road but do not have time to right now. So I'm trying to do a few changes that don't affect current code but will work the way I want moving forward. myFunc(myObj, 1) is current but I have done a minor refactor to inline like so... myObj.myFunc(myObj, 2).myFunc(myObj, 3) ... but I thought I could remove myObj as an argument since it's being returned.
edit 2: Changed arrow es6 function to using function keyword to keep this context and added console.log(Object(this)). But still getting undefined from myObj.name and Object(this) only gives the argument
ANSWER: The problem was that I was using an arrow function and that I had typeof num === 'number' instead of equal to 'undefined'. Thank you.
myFunc.bind(myObj) doesn't serve any good purpose because arrows cannot be bound. In order to use dynamic this it should be regular function, e.g. shorthand syntax:
let myObj = {
name: 'Devin',
myFunc(num) {
console.log(num);
return this;
}
};
myObj.myFunc(2).myFunc(3); // basically a noop

How to pass arguments when extending a jQuery method?

I'm trying to modify .offset() so that it never returns undefined, i.e. for invisible/hidden elements.
The problem that I'm having with that is that I don't know how to properly pass this to the original method, to be honest. Breakpoint-debugging reveals that I keep getting Window when it should be the element in question. Here's what I got:
(function( $ ){
var original = $.fn.offset;
$.fn.offset = function( elem, coordinates, pass ) {
console.log('step 1'); // successful.
console.log(this); // element confirmation.
var o = original(elem, coordinates, pass); // triggers an exception.
console.log('step 2'); // is never reached.
if (o === undefined ) {
console.log('step 3a');
return {};
}
else {
console.log('step 3b');
return o;
}
}
}( jQuery ));
[…]
$('#element').offset(); // method call.
And here's the exception:
Error in event handler for (unknown): TypeError: undefined is not a function
at jQuery.fn.extend.offset (chrome-extension://…/js/jquery-1.11.0.js:10109:10)
at $.fn.offset (chrome-extension://…/js/jquery-modifications.js:35:11)
I've tried different variations – original(arguments), this.original(), original(this, …) – but none worked. In this question an argument called elem is used next to three more arguments – but I'm not sure why. Is it because the API mentions attributeName, value and a callback function? If so then my attempt should work, analogous to the .offset() API. Looking at how jQuery defines these functions didn't help either because .fn.attr doesn't even show up like .fn.offset does.
As you said:
The problem that I'm having with that is that I don't know how to properly pass this to the original method, to be honest.
In order to change function scope (this) there are two Function Prototype methods, called call() and apply()
References to documentation of these two methods:
Function.prototype.call()
Function.prototype.apply()
So, your code should now looks like this:
var original = $.fn.offset;
$.fn.offset = function() {
var o = original.apply(this, arguments);
return typeof o === 'undefined' ? {} : o;
};
and
return typeof o === 'undefined' ? {} : o;
It's just a better (IMO) shorter version of:
if (typeof o === 'undefined') {
return {};
} else {
return o;
}

Is it possible to call function.apply without changing the context?

In some Javascript code (node.js specifically), I need to call a function with an unknown set of arguments without changing the context. For example:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(this, args);
}
The problem in the above is that when I call apply, I'm change the context by passing this as the first argument. I'd like to pass args to the function being called without changing the context of the function being called. I essentially want to do this:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(<otherFn's original context>, args);
}
Edit: Adding more detail regarding my specific question. I am creating a Client class that contains a socket (socket.io) object among other info pertaining to a connection. I am exposing the socket's event listeners via the client object itself.
class Client
constructor: (socket) ->
#socket = socket
#avatar = socket.handshake.avatar
#listeners = {}
addListener: (name, handler) ->
#listeners[name] ||= {}
#listeners[name][handler.clientListenerId] = wrapper = =>
# append client object as the first argument before passing to handler
args = Array.prototype.slice.call(arguments)
args.unshift(this)
handler.apply(this, args) # <---- HANDLER'S CONTEXT IS CHANGING HERE :(
#socket.addListener(name, wrapper)
removeListener: (name, handler) ->
try
obj = #listeners[name]
#socket.removeListener(obj[handler.clientListenerId])
delete obj[handler.clientListenerId]
Note that clientListenerId is a custom unique identifier property that is essentially the same as the answer found here.
If I understand you correctly:
changes context
| n | y |
accepts array n | func() | func.call() |
of arguments y | ???????? | func.apply() |
PHP has a function for this, call_user_func_array. Unfortunately, JavaScript is lacking in this regard. It looks like you simulate this behavior using eval().
Function.prototype.invoke = function(args) {
var i, code = 'this(';
for (i=0; i<args.length; i++) {
if (i) { code += ',' }
code += 'args[' + i + ']';
}
eval(code + ');');
}
Yes, I know. Nobody likes eval(). It's slow and dangerous. However, in this situation you probably don't have to worry about cross-site scripting, at least, as all variables are contained within the function. Really, it's too bad that JavaScript doesn't have a native function for this, but I suppose that it's for situations like this that we have eval.
Proof that it works:
function showArgs() {
for (x in arguments) {console.log(arguments[x]);}
}
showArgs.invoke(['foo',/bar/g]);
showArgs.invoke([window,[1,2,3]]);
Firefox console output:
--
[12:31:05.778] "foo"
[12:31:05.778] [object RegExp]
[12:31:05.778] [object Window]
[12:31:05.778] [object Array]
Simply put, just assign the this to what you want it to be, which is otherFn:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(otherFn, args);
}
'this' is a reference to your function's context. That's really the point.
If you mean to call it in the context of a different object like this:
otherObj.otherFn(args)
then simply substitute that object in for the context:
otherObj.otherFn.apply(otherObj, args);
That should be it.
If you bind the function to an object and you use everywhere the bound function, you can call apply with null, but still get the correct context
var Person = function(name){
this.name = name;
}
Person.prototype.printName = function(){
console.log("Name: " + this.name);
}
var bob = new Person("Bob");
bob.printName.apply(null); //window.name
bob.printName.bind(bob).apply(null); //"Bob"
One way that you can work around the change of context that can happen in JavaScript when functions are called, is to use methods that are part of the object's constructor if you need them to be able to operate in a context where this is not going to mean the parent object, by effectively creating a local private variable to store the original this identifier.
I concede that - like most discussions of scope in JavaScript - this is not entirely clear, so here is an example of how I have done this:
function CounterType()
{
var counter=1;
var self=this; // 'self' will now be visible to all
var incrementCount = function()
{
// it doesn't matter that 'this' has changed because 'self' now points to CounterType()
self.counter++;
};
}
function SecondaryType()
{
var myCounter = new CounterType();
console.log("First Counter : "+myCounter.counter); // 0
myCounter.incrementCount.apply(this);
console.log("Second Counter: "+myCounter.counter); // 1
}
These days you can use rest parameters:
function fn(...args) {
otherFn(...args);
}
The only downside is, if you want to use some specific params in fn, you have to extract it from args:
function fn(...args) {
let importantParam = args[2]; //third param
// ...
otherFn(...args);
}
Here's an example to try (ES next version to keep it short):
// a one-line "sum any number of arguments" function
const sum = (...args) => args.reduce((sum, value) => sum + value);
// a "proxy" function to test:
var pass = (...args) => sum(...args);
console.log(pass(1, 2, 15));
I'm not going to accept this as an answer, as I'm still hoping for something more suitable. But here's the approach I'm using right now based upon the feedback on this question so far.
For any class that will be calling Client.prototype.addListener or Client.prototype.removeListener, I did added the following code to their constructor:
class ExampleClass
constructor: ->
# ...
for name, fn of this
this[name] = fn.bind(this) if typeof(fn) == 'function'
message: (recipient, body) ->
# ...
broadcast: (body) ->
# ...
In the above example, message and broadcast will always be bound to the new ExampleClass prototype object when it's instantiated, allowing the addListener code in my original question to work.
I'm sure some of you are wondering why I didn't just do something like the following:
example = new ExampleClass
client.addListener('message', example.bind(example))
# ...
client.removeListener('message', example.bind(example))
The problem is that every time .bind( ) is called, it's a new object. So that means that the following is true:
example.bind(example) != example.bind(example)
As such, the removeListener would never work successfully, thus my binding the method once when the object is instantiated.
Since you seem to want to be using the bind function as it is defined in Javascript 1.8.5, and be able to retrieve the original this object you pass the bind function, I recommend redefining the Function.prototype.bind function:
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
/** here's the additional code **/
fBound.getContext = function() {
return oThis;
};
/**/
return fBound;
};
Now you can retrieve the original context that you called the bind function with:
function A() {
return this.foo+' '+this.bar;
}
var HelloWorld = A.bind({
foo: 'hello',
bar: 'world',
});
HelloWorld(); // returns "hello world";
HelloWorld.getContext(); // returns {foo:"hello", bar:"world"};
I was just reminded of this question after a long time. Looking back now, I think what I was really trying to accomplish here was something similar to how the React library works with its automatic binding.
Essentially, each function is a wrapped bound function being called:
function SomeClass() {
};
SomeClass.prototype.whoami = function () {
return this;
};
SomeClass.createInstance = function () {
var obj = new SomeClass();
for (var fn in obj) {
if (typeof obj[fn] == 'function') {
var original = obj[fn];
obj[fn] = function () {
return original.apply(obj, arguments);
};
}
}
return obj;
};
var instance = SomeClass.createInstance();
instance.whoami() == instance; // true
instance.whoami.apply(null) == instance; // true
Just push properties directly to the function's object and call it with it's own "context".
function otherFn() {
console.log(this.foo+' '+this.bar); // prints: "hello world" when called from rootFn()
}
otherFn.foo = 'hello';
otherFn.bar = 'world';
function rootFn() {
// by the way, unless you are removing or adding elements to 'arguments',
// just pass the arguments object directly instead of casting it to Array
otherFn.apply(otherFn, arguments);
}

JavaScript - How/Can I set an object reference to null from a function?

I'm wondering if this is even possible. Basically I have a couple objects that I pass to a function, and under certain conditions I want that function to set the object to null.
Ex.
var o = {'val' : 0};
f = function(v)
{
v = null;
};
f(o); // Would like this to set 'o' to null
Unfortunately it seems I can only set the function's argument to null. After calling the function 'o' will still refer to an object.
So, is it even possible to do this? And if so, how?
If you want to change the value of o when f(o) is called, you have two options:
1) You can have f(o) return a new value for o and assign that to o like this:
var o = {'val' : 0};
o = f(o);
// o == null
Inside of f(), you return a new value for o.
function f(v) {
if (whatever) {
return(null);
}
}
2) You put o into another object and pass a reference to that container object into f().
function f(v) {
if (whatever) {
v.o = null;
}
}
var c = {};
c.o = {'val' : 0};
f(c);
// c.o == null;
The javascript language does not have true pointers like in C/C++ that let you pass a pointer to a variable and then reach back through that pointer to change the value of that variable from within the function. Instead, you have to do it one of these other two ways. Objects and arrays are passed by reference so you can reach back into the original object from the function.
JavaScript always passes function arguments "by value". Which means a function can't change what the variable outside the function points to.
However, when you pass an object to a function the "value" that is passed is a reference to the actual object, not a copy of the object. So although you can't set the outside variable to null or to another object you can modify the contents of that object. Which means you can do something like this:
var containerObj = {'o' : {'val' : 0} };
f = function(v) {
v.o = null;
};
f(containerObj.o); // This property would be set to null successfully.
Obviously creating a bunch of container objects just so you can pass them to functions isn't very pretty, but it is one way to do it.
But I'd recommend going with James Montagne's suggestion of having the function return an object or null and assigning the result back to your variable.
Not sure exactly the real use of this, but if you restructured a bit, you could do this:
var o = {'val' : 0};
var f = function(v)
{
if(something){
return null;
}else{
return v;
}
};
o = f(o);
I came here with the same problem. Although the wrapper answer works, it's ugly, and my function is asynchronous, so I can't return a null value.
The reason I wanted o to be null is that later I use if (o) //do something, which might be why OP and others want to set it as null. As such, my solution, although not an answer to the exact question, was
var o = {'val' : 0};
f = function(v) {
v.isNull = true;
}
if (!o.isNull) // do something
The easiest way of doing it would be to create a global variable called NULL and set it to null
var NULL = null
Then you can set an existing variable to NULL without modifying the original object.
var NULL = null;
var original = {};
var ptr = original;
ptr = NULL;
console.log("original is not null = " + (original!==null));
console.log("ptr is null = " + (ptr===null));

Default value for function parameter?

So I've been doing this for as long as I can remember, but I'm curious if this is really what I should be doing. You write a function that takes a parameter, so you anticipate it to have a value, but if it doesn't, you have a good reason to default it, to say zero. What I currently do is write a helper function:
function foo() { return foo(0); };
function foo(bar) { ... };
I just ran across an instance where I did this and I looked at it oddly for a few seconds before understanding my logic behind it. I come from php where it's trivial:
function foo(bar=0) { ... }
Is there a javascript alternative that I'm not aware of?
You can't have overloaded functions in JavaScript. Instead, use object based initialization, or check for the values and assign a default if none supplied.
In your example, the second function foo(bar) will replace the first one.
Here's a function using object initialization.
function foo(config) {
extend(this, config);
}
where extend is a function that merges the config object with the current object. It is similar to the $.extend method in jQuery, or $extend method of MooTools.
Invoke the function and pass it named key value pairs
foo({ bar: 0 });
The other way to initialize is to look at the supplied values, and assign a default if the value is not given
function foo(bar) {
bar = bar || 0;
}
This works as long as bar is not a falsy value. So foo(false) or foo("") will still initialize bar to 0. For such cases, do an explicit check.
function foo(bar) {
bar = (typeof bar == 'undefined' ? 0 : bar);
}
In JavaScript, the argument will be undefined if the user didn't pass it in. You can use the || operator to set the value of the argument if it's undefined:
function foo(bar) {
bar = bar || 0;
...
}
The simplest way I know of is test for a value and then set it to a default value if no value is found. I have not come across a catch all one liner yet, this is the best i have got.
If expecting a string value use this. Default will trigger on these values: [ undefined, null, "" ]
function foo(str) {
str = !!str ? str : 'bar';
...
}
If expecting a number or Boolean value. This allows 0 and false as values. Default will trigger on [ undefined, null, {}, functions ]
Handy for making values arguments that only accept primitive values like number, boolean and string
function foo(val) {
val= !!val == val || val*1 ? val : 10;
...
}
If you're looking to test for objects such as {}, There is documentation on doing this but it isn't so simple.
Hopefully this answers a bit clearer for someone - I ended up using the ol' check for undefined if(typeof functionparameter !== 'undefined') as per:
$.fn.extend({
doThing: function(stringparameter = 'FooBar!', functionparameter){
console.log('Here is your string '+stringparameter);
// Run it if it's been passed through:
if(typeof functionparameter !== 'undefined') functionparameter();
});

Categories

Resources