I have a list of objects I am looping over; each of these objects has a property on it which is a function referring to this. If I create a list of callbacks based on my objects, it seems that I have to "double-wrap" the this-dependent function in order to maintain the correct references. I don't understand why this is - can anyone explain?
function Pokemon(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}
function runFunctions(arrayOfFunctions) {
for (var i = 0, fn; fn = arrayOfFunctions[i]; i++) {
fn();
}
}
var monsters = [
new Pokemon('Charmander'),
new Pokemon('Squirtle'),
new Pokemon('Bulbasaur')
];
var unclosedCalls = [];
var closedCalls = [];
for (var i = 0, monster; monster = monsters[i]; i++) {
var unclosedCall = (function(m) {
return m.sayName
})(monster);
var closedCall = (function(m) {
return function() {
m.sayName();
}
})(monster);
unclosedCalls.push(unclosedCall);
closedCalls.push(closedCall);
}
console.log('--start')
runFunctions(unclosedCalls); // doesn't work
console.log('----')
runFunctions(closedCalls); // works
console.log('--end')
closedCalls is the list of double-wrapped callbacks.
I don't get why m in each creation of unclosedCall is not actually closed over.
Here is a jsbin with my code: http://jsbin.com/qivilusuje/1/edit?js,console,output
The problem with the "unclosed" calls is that the function reference that you return (m.sayName) is immediately disassociated from the variable m from which the function property was retrieved.
A function reference doesn't know anything about which object it was retrieved from so therefore when the function is eventually invoked it has no "context" - this will be set to the global object instead of the object that originally had this function as a property:
var obj = {
func : function() { console.log(this) }
}
obj.func() // outputs "obj" because a.b() calls "b" with "this" === "a"
var ref = obj.func;
ref(); // outputs "window"
To fix it you can have the unclosed call do return m.sayName.bind(m), although having got that far there's no need for the IIFE either, and it would work just as well to say:
var unclosedCall = monster.sayName.bind(monster);
Related
Is there a way to access properties in nested functions like that :
function func(){
this.new_func=function(){
console.log("something");
return 'something';
}
this.someValue=7;
return function(){
return "something_diff";
};
}
var obj=new func();
obj(); //works with returning "something diff"
obj.new_func(); // TypeError: obj.new_func is not a function
obj.someValue; // undefined
I need to delete whole "return function()..." part in order to access "someValue" and "new_func()". Why is it acting like that, and is there a way to somehow access that properties, while still returning another function ??
When you have a constructor that returns an object, that object replaces whatever you assigned to this. So indeed, the members new_func and someValue are lost.
To combine the returned function together with the other members, you can do this:
function func() {
var f = function() {
return "something_diff";
};
f.new_func = function() {
console.log("something");
return 'something';
}
f.someValue = 7;
return f;
}
var obj = new func();
console.log(obj());
obj.new_func();
console.log('someValue:', obj.someValue);
You can do it like this:
var parentFunction = function() {
var nestedFunction = function() {
var value = "nestedValue";
var moreValues = "more values";
return {
value: value,
moreValues: moreValues
}
}
var anotherNestedFunction = function() {
var anotherValue = "nestedValue";
return anotherValue;
}
return {
nested: nestedFunction,
another: anotherNestedFunction
}
}
Then:
var newFunction = new parentFunction();
var nested = newFunction.nested();
console.log("nested value: ", nested.value);
console.log("another nested value: ", newFunction.another);
Here is a working example:
Why is it acting like that, and is there a way to somehow access that properties, while still returning another function ??
Because of the pharenteis:
var obj=new func();
Basically you're firing your function and what is stored the variable obj is what the "func" returns.
In order to access to private properties, you should look at the Closures: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
not sure if this is possible or if I'm doing it wrong. I'm trying to have an object literal reference one of it's own properties that is another object literal. I keep getting an error stating that it is undefined:
var MyObject = {
init: function(){
this.elements.buttons.click(function(){
alert('hit');
});
},
elements: {
buttons: $('button')
}
}
what am i doing wrong?
UPDATE:
main.js is calling init to initialize on demand which probably doofed the scope --
function executeFunctionByName(functionName, context /*, args */) {
var args = [].slice.call(arguments).splice(2);
var namespaces = functionName.split(".");
var func = namespaces.pop();
for(var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
return context[func].apply(this, args);
}
$(document).ready(function(){
// invoke init() for any element that requests it
$('[data-page]').each(function(i, p){
var page = 'APP.MODEL.' + $(p).data('page') + '.init';
executeFunctionByName(page, window)
});
});
If you are invoking that function directly without going through MyObject, this would be undefined. Replace this with MyObject and it should work.
This would not work as this would be defined to window:
var MyObject = {
// init: ...
}
var init = MyObject.init;
init();
See 3. Entering function code: How does the "this" keyword work?
If you wish to continue to use this, you may use Function.prototype.call() or Function.prototype.apply() like so:
var MyObject = {
// init: ...
}
var init = MyObject.init;
init.call(MyObject);
In relation to this question, I'm trying to add a callback to get the data back. So I tried this:
var subgroupIds = [];
var that = this;
this.getSubGroups = function (groupId,callback) {
var anotherObject = this;
this.getGroups("groupId="+groupId, function(groups) {
if ($.isEmptyObject(groups)) {
return;
} else {
$.each(groups, function(index,group) {
subgroupIds.push(group.id);
that.getSubGroups(group.id);
});
anotherObject.callback(group.id);
}
});
}
I thought I have a better understanding of closure after the previous question but I guess I don't...I'm getting the following error:
Uncaught TypeError: Object [object Window] has no method 'callback'
What am I doing wrong here?
Edit
Here's the content of getGroups:
this.getGroups = function(filter,callback,error_callback) {
this.getJSON('/'+apiVersion+'/groups/',function(data){
// run through the filter engine
output = runFilter(data, filter);
callback(output);
},error_callback);
}
It doesn't need to be anotherObject.callback(group.id);, what you need is callback(group.id);
It looks like you're confusing this with arguments object.
arguments holds all parameters that are passed into the function:
var aFunction = function () {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
};
aFunction(1, 2, 3, 4); // 1, 2, 3, 4
While this basically refers to the "owner" of the function (which is, roughly speaking, whatever happens to be before the dot):
var aFunction = function () {
console.log(this);
};
var object1 = { f: aFunction, name: "object1" };
var object2 = { f: aFunction, name: "object2" };
object1.f(); // Object { name="object1", f=function()}
object2.f(); // Object { name="object2", f=function()}
aFunction(); // Window
The callback is a parameter, it is not bound to the context.
I think what you want is to call the callback with anotherObject as the this value, right ?
You can achieve that with :
$.proxy(callback, anotherObject)(group.id);
Or if you only want to execute the callback, and you want to use closure, you need to add :
this.callback = callback; //before
var anotherObject = this;
I am looking at the example here Using apply to chain constructors
I understand it except for this line:
fNewConstr.prototype = fConstructor.prototype;
Why is it necessary and why does it not make it lose the function that was just defined for fNewConstr?
Function.prototype.construct = function (aArgs) {
var fConstructor = this, fNewConstr = function () { fConstructor.apply(this, aArgs); };
// Why doesn't fNewConstr.prototype get completely overwritten?
fNewConstr.prototype = fConstructor.prototype;
return new fNewConstr();
};
function MyConstructor () {
for (var nProp = 0; nProp < arguments.length; nProp++) {
this["property" + nProp] = arguments[nProp];
}
}
var myArray = [4, "Hello world!", false];
var myInstance = MyConstructor.construct(myArray);
alert(myInstance.property1); // alerts "Hello world!"
alert(myInstance instanceof MyConstructor); // alerts "true"
alert(myInstance.constructor); // alerts "MyConstructor"
If you mean, why doesn't fNewConstr (the function) get overwritten when you write
fNewConstr.prototype = ...;
...the answer is because nothing is overwriting it. That code just sets the prototype property of the function.
If your question is: Why doesn't fNewConstr get recreated each time construct is called, the answer is: It is.
This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
I have the following Javascript code, and I'm trying to get a callback to work as shown below. I want to see an alert with "123" in it.
var A = function(arg){
this.storedArg = arg;
this.callback = function(){ alert(this.storedArg); }
}
var B = function() {
this.doCallback = function(callback){ callback(); }
}
var pubCallback = function(){ alert('Public callback') };
var a = new A(123);
var b = new B();
b.doCallback(pubCallback); // works as expected
b.doCallback(a.callback); // want 123, get undefined
I understand what is happening but I'm not sure how to fix it. How can I get a callback function that references my a object? In my case, I can make changes to A but not B.
So what you want is to pass the context to the doCallBack.
E.g.
doCallBack = function (callback, callee) {
callback.apply(callee);
}
So then you would do:
b.doCallBack(a.callback, a);
If you cannot modify the B then you can use closure inside A:
var A = function (arg) {
var self = this;
this.storedArg = arg;
this.callback = function () { alert(self.storedArg); }
}
You can create a variable that holds the wanted scope for this by putting it into variable that
var A = function(arg){
this.storedArg = arg;
var that = this; // Add this!
this.callback = function(){ alert(that.storedArg); }
}
Working demo here: http://jsfiddle.net/vdM5t/
I understand what is happening (during the 2nd callback, "this" is b and not a)
No, JS is no class-based language where something could happen. If function(){ alert(this.storedArg); is just called as callback(); (like in b.doCallback), the this keyword points to the global object (window).
To get around that, you'd have to change A to
var A = function(arg){
var that = this; // store reference to the current A object
this.storedArg = arg;
this.callback = function(){
alert(that.storedArg); // and use that reference now instead of "this"
};
}
If you don't expect the storedArg property to change, you could even make it more simple:
var A = function(arg){
this.storedArg = arg;
this.callback = function(){
alert(arg); // just use the argument of the A function,
// which is still in the variable scope
};
}
You need to pass the context you want the callback to execute in:
var B = function() {
this.doCallback = function(callback, context) {
callback.apply(context);
};
};
b.doCallback(a.callback, a); // 123
http://jsfiddle.net/a9N66/
Because inside A.callback function, this does not refer to A but to window object.
var A = function(arg){
this.storedArg = arg;
this.callback = function(){ alert(this.storedArg); }
-----------------------------------^-----------------
}
You can try this,
var A = function(arg){
this.storedArg = arg;
var that = this;
this.callback = function(){ alert(that.storedArg); }
}
var B = function() {
this.doCallback = function(callback){ callback(); }
}
var pubCallback = function(){ alert('Public callback') };
var a = new A(123);
var b = new B();
b.doCallback(pubCallback); // works as expected
b.doCallback(a.callback); // alerts 123
When you do this:
b.doCallback(a.callback);
that just calls a's callback function without telling it to use a for this; so the global object is used for this.
One solution is to wrap that callback up:
b.doCallback(function() { a.callback(); });
Other solutions include binding the callback to a, using jQuery.proxy() (which is just a fancy way of doing my first solution), or passing in a to doCallback and invoking callback on a using apply.