I'm confused as to what the problem is with context in a JS constructor. Within the ctor I have a function declared. Before the call to that function this is set to the context of the ctor. Inside the function the value of this is set to window. I don't understand why. In the HTML the ctor is called with 'new'.
function MyCtor() {
var myFunc = function() {
debugger; // #2
// code for myFunc
}
debugger; // #1
myFunc();
debugger; // #3
}
At debugger #1, this is set to MyCtor. At #2 this is window. And at #3 it is back to MyCtor.
I'm sure I'm missing something basic here, but I've read a lot on scope and context; obviously not enough.
The this object is one of the most annoyingly hard-to-understand concepts in Javascript. And that's quite a contest to win... First off, you have to understand that it will be specific to each function you call - the context in which you call myFunc won't set it how you want it. Here's one way you can do it:
function MyCtor() {
this.myFunc = function() {
debugger; // #2
// code for myFunc
}
debugger; // #1
this.myFunc();
debugger; // #3
}
Generally, there are only a few situations in which you can rely upon a function's this to be a particular value. All of them to my knowledge:
objectToBeThis.aFunction = function() { ... } // declare this function as
// an object property at any time -
objectToBeThis.aFunction();
Or, not used as often is:
aFunction.call(objectToBeThis, extraArgument1, extraArgument2);
When a named, but not "owned" function (ie, var functionName = function(), or function functionName()) is called, then it will have window as its this argument. This part I'm less sure of as a certainty, but I just wouldn't use this inside such a method.
As in the case of your code, there's also "new MyCtor" - in which a new object is created to be returned, and that object is set to this inside of the constructor method.
Related
Hello I am having an issue with self invoking a function that belongs to an object's prototype. My goal is to create a method for a custom object that invokes a function it has upon creating an instance.
function Message(text) {
this.text = text;
}
Message.prototype.send = function() {
alert(this.text);
}();
var testMessage = new Message("Hello Goodbye");
The issue here is I receive undefined when I create an instance of the Message object. It may have to do with the way the keyword, "this" is defined as, although I am not completely sure. However if instead of adding the method to the prototype and just put it in the function, it works fine like the one below.
function Message(text) {
this.text = text;
this.send = function() {
alert(text);
}();
}
var testMessage = new Message("Hello Goodbye");
The send method runs exactly when you invoke it with () - examine your control flow.
In your first case, you define a function on Message's prototype attribute, then invoke it. It alerts undefined, because Message.prototype (this in that context) does not contain a text attribute.
In your second case you add text to this before invoking send, thus making it defined when the alert runs.
I am new to Javascript and am still getting my head round the various ways of creating objects i.e constructor+new, prototypal, functional & parts.
I have created what I think is an object factory using the module pattern and want to know what the correct method of calling an internal method would be. Is it via this or function name.
Here is my module:
function chart() {
function my() {
// generate chart here, using `width` and `height`
}
my.sayHi = function(){
console.log('hi');
my.sayBye();
};
my.sayBye = function(){
console.log('Bye');
};
return my;
}
var test = chart();
test.sayHi();
You can see that the first function calls the second using my.sayBye() or is it better to use this.sayBye(). Both produce the same result and run without error.
The module pattern allows you to dispense with the 'this' variable if you want to. I would probably rewrite the above code to look like this and then the question becomes moot.
function chart() {
var hiCount = 0;
function sayHi(){
console.log('hi');
hiCount++;
sayBye();
};
function sayBye(){
console.log('Bye');
};
return {
sayHi : sayHi,
sayBye: sayBye
};
}
var test = chart();
test.sayHi();
In the above code all is defined within the function chart. As JavaScript's scope is at the function level every time the chart function is called a new set of functions will be defined. And a new set of variables can also be defined that are private to the function as they are defined in the function and are not accessible from outside. I added hiCount as an example of how you could do this. The Module pattern allows privacy in JavaScript. It eats more memory than the prototype pattern though as each time a function is declared it is not shared between other instances of the same class. That is the price you have to pay in Javascript to have class variables that are private. I willingly pay it. Removing 'this' from my code makes it easier to understand and less likely that I will fall into problems of misplaced scope.
Using "this" is better approach because you would be able to bind the function directly to the parent function object.And you dont need to return anything from the function.
where as in your case you are explicitly returning another function
Here is the use of "this" approach
function chart() {
this.sayHi = function(){
console.log('hi');
}
}
var test = new chart();
test.sayHi();
Using this approach you would be able to call anything in the prototype of function "chart"
Eg
chart.prototype.hello = function(){
console.log('hello')
}
So you would be able to call the hello function from the same object(test)
While this issue occurred to me specifically with KnockoutJS, my question is more like a general javascript question.
It is good to understand however that ko.observable() and ko.observableArray() return a method so when assigning a value to them, you need to call the target as method instead of simply assigning a value to them. The code that I'm working with should also support plain objects and arrays, which I why I need to resolve to a method to call to assign a value to the target.
Think of these 2 examples:
Non-working one (this context changed in called method):
// Assigning value to the target object
var target;
// target can be of any of thr following types
target = ko.observableArray(); // knockout observable array (function)
// target = ko.observable(); // knockout observable (function)
// target = {}; // generic object
// target = []; // generic array
//#region resolve method to call
var method;
if (isObservable(target)) {
// if it is a knockout observable array, we need to call the target's push method
// if it is a konckout observable, we need to call the target as a method
method = target.push || target;
} else {
// if target is a generic array, we need to use the array's push prototype
// if target is a generic object, we need to wrap a function to assign the value
method = target.push || function(item){ target = item; };
}
//#endregion
// call resolved method
method(entity);
Working one (this context is fine):
if (isObservable(target)) {
if (target.push) {
target.push(entity);
} else {
target(entity);
};
} else {
if (target.push) {
target.push(entity);
} else {
target = entity;
};
}
Now, to the actual question:
In the first approach, later in the execution chain when using a knockout observable knockout refers to this context within itself, trying to access the observable itself (namely this.t() in case someone is wondering). In this particular case due to the way of callin, this has changed to window object instead of pointing to the original observable.
In the latter case, knockout's this context is just normal.
Can any of you javascript gurus tell me how on earth my way of calling can change the 'this' context of the function being called?
Ok, I know someone wants a fiddle so here goes :)
Method 1 (Uncaught TypeError: Object [object global] has no method 'peek')
Method 2 (Works fine)
P.S. I'm not trying to fix the code, I'm trying to understand why my code changes the this context.
UPDATE:
Thanks for the quick answers! I must say I hate it when I don't know why (and especially how) something is happening. From your answers I fiddled up this quick fiddle to repro the situation and I think I got it now :)
// So having an object like Foo
function Foo() {
this.dirThis = function () {
console.dir(this);
};
};
// Instantiating a new Foo
var foo = new Foo();
// Foo.dirThis() has it's original context
foo.dirThis(); // First log in console (Foo)
// The calling code is in Window context
console.dir(this); // Second log in console (Window)
// Passing a reference to the target function from another context
// changes the target function's context
var anotherFoo = foo.dirThis;
// So, when being called through anotherFoo,
// Window object gets logged
// instead of Foo's original context
anotherFoo(); // 3rd log
// So, to elaborate, if I create a type AnotherFoo
function AnotherFoo(dirThis){
this.dirThis = dirThis;
}
// And and instantiate it
var newFoo = new AnotherFoo(foo.dirThis);
newFoo.dirThis(); // Should dir AnotherFoo (4th in log)
If you're after a way to choose the 'this' that will get used at the time of call,
you should use bind, that's exactly done for that.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
So if SomeObject has a push method, then storing it like this won't work :
var thePushMethod = someObject.push;
since you loose the context of the function when writing this.
Now if you do :
var thePushMethod = someObject.push.bind(someObject);
the context is now stored inside thePushMethod, that you just call with
thePushMethod();
Notice that you can bind also the arguments, so for instance you might write :
var pushOneLater = someObject.push.bind(someObject, 1 );
// then, later :
pushOneLater(); // will push one into someObject
Consider this example,
function Person () {
this.fname = "Welcome";
this.myFunc = function() {
return this.fname;
}
};
var a = new Person();
console.log(a.myFunc());
var b = a.myFunc;
console.log(b());
Output
Welcome
undefined
When you make a call to a.myFunc(), the current object (this) is set as a. So, the first example works fine.
But in the second case, var b = a.myFunc; you are getting only the reference to the function and when you are calling it, you are not invoking on any specific object, so the window object is assigned. Thats why it prints undefined.
To fix this problem, you can explicitly pass the this argument with call function, like this
console.log(b.call(a));
So, for your case, you might have to do this
method.call(target, entity);
Check the fixed fiddle
I have a function localised to the main function and i want to use this to call it but it doesn't seem to work.
My code has:
function option(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity
transition_opacity(div_id,opacity,0,function(){this.load});
function load(){
console.log('test'); //does not happen
}
}
Have i misunderstood the use of this or is the scope lost when i use function(){} to call load?
From your code it is not obvious, what object this could refer to. It depends on how option is called. However, if you define the load function inside of the option function anyway, it is best to just reference it directly. You will have to move the declaration of test above the transition_opacity call though:
function option(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity;
function load() {
console.log('test');
}
transition_opacity(div_id,opacity,0,load);
}
As you can see, I just reference load directly. You could make another function which calls the load function inside (i.e. function() { load(); } – note the parentheses which calls the function) but that would give you no benefit but would just add another unneeded function to the stack. So just refer to the actual function itself.
For more information on the this keyword, check out this question. Spoiler: It’s more complicated than you would expect.
The scope of this is lost in this instance, probably pointing to the document. You can capture this to a variable in the outer scope to make this work as intended.
var context = this;
transition_opacity(div_id,opacity,0,function(){context.load();})
The above will not work however. This is because load does not exist on the context of this. You would need to define the load function as such:
context.load = function(){
console.log('test');
}
Both.
First, your load function is not a member/property of any this, the way you have it coded. Your load function is simply a nested function that exists within your option function, as has been sort of implicitly noted in other responses.
In your option function, if you want 'load' to become a member of 'this', you'd need to say so, like this:
function option(){
this.load = function(){}; // now load is actually a property of whatever this is
}
Second, you and the other poster are correct that 'this' is no longer the same 'this' by the time your anonymous function is called.
Whenever you call a function, a brand new 'this' is created and exists within the scope of that function. If you just call a function like this:
transition_opacity(args);
.. then within transition_opacity, 'this' just refers to the window object, or maybe window.document. For 'this' to refer to anything other than window or window.document, you need to (in effect) do one of the following:
myObject.transition_opacity(args);
transition_opacity.call(myObject, arg1, arg2, ..);
transition_opacity.apply(myObject, argArray);
or
var myObject = new transition_opacity(args);
In each of those cases, within transition_opacity, 'this' refers to myObject (or, well, in the last case, it refers to a new object that is being created and assigned to myObject).
Here is a way to do what it looks like you're trying to do:
var MyNamespace = {
option: function(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity;
var _this = this;
transition_opacity(div_id,opacity,0,function(){
// Careful! Inside here, 'this' is just window or window.document,
// unless transition_opacity sets it to something using call or apply,
// in which case that 'this' is probably not the 'this' you want.
// So carefully refer to the saved instance of 'this':
_this.load();
});
},
load: function(){
console.log('test'); // now it should happen
}
}
.
.
MyNamespace.option(room, slot); // inside option, 'this' is MyNamespace.
Here's another way to do it:
function MyClass(){};
MyClass.prototype = {
// all the same stuff that is in MyNamespace above..
};
.
.
var myObject = new MyClass();
myObject.option(room, slot);
Clear as mud?
Just use
transition_opacity(div_id,opacity,0,load);
You have defined a 'load' within another function as an 'Function Declaration', so now it is only accessible within 'option' function and in other functions defined in this one by name 'load'. You can't access it by using 'this.load' no matter what 'this' is. If you want to access 'load' function as 'this.load' you can try this example to understand how 'this' keywoard works
// Function Declaration
function f1(callback){
callback();
};
// Function Declaration
function f2(){
// Function Expression
this.load = function(){
console.log("test");
};
f1(this.load);
};
var obj = new f2(); // test, this == obj, so obj.load() now exists
obj.load(); //test, this == obj
f2(); //test, this == window, so window.load() now exists
load(); //test, window is the global scope
If I have a JavaScript constructor function, and I set a destroy method on its prototype. Is it possible to delete (or at least unset) the instance from the destroy method? Here's an example of what I'm trying to do.
Klass.prototype = {
init: function() {
// do stuff
},
destroy: function() {
// delete the instance
}
};
k = new Klass
k.destroy()
console.log(k) // I want this to be undefined
I understand that I can't simply do this = undefined from with the destroy method, but I thought I could get around that by using a timeout like so:
destroy: function() {
var self = this;
setTimeout( function() {
self = undefined
}, 0)
}
I thought the timeout function would have access to the instance via self from the closure (and it does), but that doesn't seem to work. If I console.log(self) from inside that function it shows up as undefined, but k in the global scope is still an instance of Klass.
Does anyone know how to make this work?
k is a reference that points out to an instance of Klass. when you call destroy as a method of Klass the this inside the function gets bound to the object you called a destroy method on. It now is another reference to that instance of Klass. The self that you close on in that little closure is yet another reference to that instance. When you set it to undefined you clear that reference, not the instance behind it. You can't really destroy that instance per se. You can forget about it (set all the references to undefined and you won't find it again) but that is as far as you can go.
That said, tell us what you want to accomplish with this and we'll be glad to help you find a solution.
Though deleting its own object instance is possible, it is very tacky. You might want to check out this article.
What you are trying to do is impossible. Even if you overwrite this it will not affect any variable holding a reference to that instance. And calling a function on it will still have the correct this.
The only thing you could do is setting a variable in your destroy function that will make any other function throw an exception when called. But that would be a bad idea since it would slow things down (ok, that's negligible) and you can just put in the docs of your class that it is not supposed to be used anymore after destroy() has been called.
This works, but it requires that you know what the name of variable to which the new Klass() is instantiated:
function Klass() {};
Klass.prototype = {
init: function() {
//
},
destroy: function() {
// Delete the variable that references the instance of the constructor.
delete window.k;
}
};
k = new Klass();
k.destroy();
console.log(k);
If the k variable is named anything else it doesn't work:
// Won't work.
u = new Klass();
u.destroy();
It also assumes that the local variable k is really in the global scope. If it were inside a function it would not work either. For instance:
// Won't work.
var fn = function() {
k = new Klass();
k.destroy();
};
Finally it assumes a browser environment for the window object.