I'm having a really rough time wrapping my head around prototypes in JavaScript.
Previously I had trouble calling something like this:
o = new MyClass();
setTimeout(o.method, 500);
and I was told I could fix it by using:
setTimeout(function() { o.method(); }, 500);
And this works. I'm now having a different problem, and I thought I could solve it the same way, by just dropping in an anonymous function. My new problem is this:
MyClass.prototype.open = function() {
$.ajax({
/*...*/
success: this.some_callback,
});
}
MyClass.prototype.some_callback(data) {
console.log("received data! " + data);
this.open();
}
I'm finding that within the body of MyClass.prototype.some_callback the this keyword doesn't refer to the instance of MyClass which the method was called on, but rather what appears to be the jQuery ajax request (it's an object that contains an xhr object and all the parameters of my ajax call, among other things).
I have tried doing this:
$.ajax({
/* ... */
success: function() { this.some_callback(); },
});
but I get the error:
Uncaught TypeError: Object #<an Object> has no method 'handle_response'
I'm not sure how to properly do this. I'm new to JavaScript and the concept of prototypes-that-sometimes-sort-of-behave-like-classes-but-usually-don't is really confusing me.
So what is the right way to do this? Am I trying to force JavaScript into a paradigm which it doesn't belong?
Am I trying to force JavaScript into a paradigm which it doesn't belong?
When you're talking about Classes yes.
So what is the right way to do this?
First off, you should learn how what kind of values the this keyword can contain.
Simple function call
myFunc(); - this will refer to the global object (aka window) [1]
Function call as a property of an object (aka method)
obj.method(); - this will refer to obj
Function call along wit the new operator
new MyFunc(); - this will refer to the new instance being created
Now let's see how it applies to your case:
MyClass.prototype.open = function() {
$.ajax({ // <-- an object literal starts here
//...
success: this.some_callback, // <- this will refer to that object
}); // <- object ends here
}
If you want to call some_callback method of the current instance you should save the reference to that instance (to a simple variable).
MyClass.prototype.open = function() {
var self = this; // <- save reference to the current instance of MyClass
$.ajax({
//...
success: function () {
self.some_callback(); // <- use the saved reference
} // to access instance.some_callback
});
}
[1] please note that in the new version (ES 5 Str.) Case 1 will cause this to be the value undefined
[2] There is yet another case where you use call or apply to invoke a function with a given this
Building on #gblazex's response, I use the following variation for methods that serve as both the origin and target of callbacks:
className.prototype.methodName = function(_callback, ...) {
var self = (this.hasOwnProperty('instance_name'))?this.instance_name:this;
if (_callback === true) {
// code to be executed on callback
} else {
// code to set up callback
}
};
on the initial call, "this" refers to the object instance. On the callback, "this" refers to your root document, requiring you to refer to the instance property (instance_name) of the root document.
Related
I want to parse the function I get and execute them. I am trying to de-serialize a function's reference I get from my application's state.
There are two types params I get to parse.
1. Plain function.
2. Function's reference from another class.
For No.1 I got a solution. I use
function foo() {
alert('native function');
return 'Hello, serialised world!';
}
// Serializing
var serializedFunc = foo.toString();
// Deserializing
var actualFunction = new Function('return ' + serializedFunc)();
But for No.2 If I follow the above solution, I get reference error.
var actualFunction = new Function('return ' + AppControllerHelper.testFunction)();
I get reference error.
I know the cause of this error. This error occurs because, the deserialized form of the function looks something like this (given below). It can execute the plain function, but when executing a function's reference. It is unable to find the AppControllerHelper inside the scope and so it throws the error.
// Case No 1. - Executes the body of the anonymous function when called with no err.
(function anonymous(){
return function() { function foo() { alert('native function'); return 'Hello, serialised world!';} }
})
// case no. 2 - When trying to execute it looks for AppControllerHelper and since not found it throws
reference err.
(function anonymous(){
return function() { AppcontrollerHelper.testFunction }
});
Could some one suggest me good solution for this.
Thanks in Advance.
You probably don't need/want to do that in JS.
Take a look at the this keyword and how it works in JS and the methods call(), apply() and bind().
MDN this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
methods: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
This is an example of manually serializing and deserializing a function:
https://gist.github.com/briancavalier/4a820b32e0d2abca89f7
However, I don't think it's a good option for multiple reasons.
As #GBra has mentioned, we don't have to serialize and deserialize the functions.
I used the concept of bind() in javascript to overcome my challenge. with the bind() function I can store the function in a variable and use it later.
let sampleObject = {
foo: function() { console.log("this is a test function") }
}
// passing 'sampleObject' as param will put the 'this' to sampleObject.
let bindFunction = sampleObject.foo.bind(sampleObject);
bindFunction();
I am working with dojo in the ESRI Web App Builder and have come across a situation where I need to run an AJAX call and still access a variable from the base class. Below is my code with comments in it to explain exactly where it is successful and exactly where it is failing:
define(['dojo/_base/declare', 'jimu/BaseWidget', 'dojo/request', "esri/layers/WMSLayer", "esri/config", "dojo/domReady!"], function (declare, BaseWidget, request, WMSLayer, esriConfig) {
return declare([BaseWidget], {
baseClass: 'jimu-widget-mywidget',
// This function is called by a button press (Normally the WMSLayer variable would be set by user input)
addWms: function () {
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // this.map is inherited from BaseWidget as far as I can see. This adds a wms to my map without error
request("request.html").then(function(data){
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // This is now in another context....I get the error HERE.
// At this point map is not defined because this anonymous function is running
// in a different context. ( at least I think that's what is happening )
}, function(err){
// Hopefully there are no typos in my example XD
});
}
});
});
My question is --> How do I access the "map" variable from withing the callback function of "request"?
I want to be able to run this.map.addLayers from my call to the GetCapabilities of a WMS service. The request would normally call it and I get all the way to the end of my code until I can't access the "map" variable anymore as I know it.
Dojo type answer is preferred but plain old javaScript is also fine. Please avoid JQuery answers.
Resources are:
ESRI JavaScript library
Dojo
ESRI Web App Builder
The problem you're running into is the classic problem of execution context being lost when an asynchronous callback is invoked (and thus this no longer means what you want). There are generally two ways to resolve this.
One way is to create a variable in the outer scope that references what this is, so that the inner function can access it:
var self = this;
request('request.html').then(function (data) {
// ...
self.map.addLayer(wmsLayer);
});
The other way is to use context binding, either via Function#bind (ES5) or dojo/_base/lang.hitch:
// Function#bind:
request('request.html').then(function (data) {
// ...
this.map.addLayer(wmsLayer);
}.bind(this));
// lang.hitch:
request('request.html').then(lang.hitch(this, function (data) {
// ...
this.map.addLayer(wmsLayer);
}));
It's also common with the binding approach to break out the asynchronous handler function to another internal instance method:
_addRequestedLayer: function () {
// ...
this.map.addLayer(wmsLayer);
},
addWms: function () {
// ...
// ES5:
request('request.html').then(this._addRequestedLayer.bind(this));
// ...or lang.hitch, with late binding:
request('request.html').then(lang.hitch(this, '_addRequestedLayer'));
There is also a tutorial on lang.hitch.
In your request callback, this does not refer to your class anymore. A simple way to have access to the map in the callback is assigning the reference of the map in a variable on addWms function scope and then using it in your callback:
addWms: function() {
var map = this.map;
// Your code here
request('request.html').then(function (data) {
// Your code here
map.addLayer(wmsLayer); // Note that you're using map instead of this.map
}
}
You can also use Dojo hitch function, where you can pass a function and the context that it should be applied. I suggest that you take a look at this link http://dojotoolkit.org/reference-guide/1.10/dojo/_base/lang.html#hitch.
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'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.
I have the following code that creates an object in JavaScript. It uses prototype to define functions and constructors.
function objectClass(){
this.variables = new Array();
}
objectClass.prototype =
{
contructor: objectClass,
setInfo: function(){
$.ajax({
url: "info.json",
success: function(){
//for each json element returned...
this.variables.push(json[i]);
}
});
}
getInfo: function(){
return this.variables;
},
}
This is a similar example of what I am trying to do. I need to be able to return the array of variables when I call obj.getInfo(). It always throws an error. I believe it is because the "this" is referring to the scope of the ajax success function.
Any ideas on how to get it to reference the objects variable?
That's correct, the this value is not automatically passed and thus not set to the instance. To force this, you can use the context property that $.ajax accepts:
$.ajax({
context: this, // `this` is the instance here
This sets the this value inside the success callback to the one you specified.