I have a class that I use to load external resources via an XMLHttpRequest (this is for WebGL) so I'm loading models, shaders etc. My plan was to put a loading display up whilst it did all these requests and then when it's finally complete I want it to run a callback function from the original function that created it. However, I'm getting strange results when I try to run that call back (such as it has no access of any of the objects within the class that did the loading).
I can get around this problem by passing "this" into the loading class and then doing
self = this;
promise(self.callback());
but I'd much rather specify the function that I want it to callback to after its done the loading. Does anyone know if this can be done? My code looks like this:
Main Class
this.loadingClass = new LoadingClass(this.LoadingComplete, resources);
Main.prototype.LoadingComplete = function()
{
// Returns undefined if i specify the callback function instead of just "this"
console.log(this.loadingClass.anyOfTheMembersOfThisClass);
}
Loading Class
LoadingClass = function(callback, resources) {
..
Promise.all(resources).then(function(loadedResources)
{
..
callback();
});
}
When you pass the function object as
(this.LoadingComplete, resources)
the object to which it was bound, will not be passed. So, only the function object LoadingComplete is passed to LoadingClass and when it is invoked as
callback()
the this value will be undefined (in strict mode).
To fix this,
you need to bind the this object, like this
new LoadingClass(this.LoadingComplete.bind(this), resources)
if your environment supports ES2015's Arrow functions
new LoadingClass(() => this.LoadingComplete(), resources);
In both these cases, when the LoadingComplete is invoked from LoadingClass, the this will be retained.
You are detouching callback (read about "this") function from the root object, so of course it loses context. Specify bindingContext explicitly with Function.prototype.bind method:
this.loadingClass = new LoadingClass(this.LoadingComplete.bind(this), resources);
Related
I am study the udacity's course and encounter a problem.
https://www.udacity.com/course/viewer#!/c-cs255/l-49464373/e-73862317/m-73162952
function xhrGet(reqUri,callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", reqUri, true);
xhr.onload = callback;
xhr.send();
}
var TILEDMapClass = Class.extend({
// Boolean flag we set once our map atlas
// has finished loading.
fullyLoaded: false,
//-----------------------------------------
// Load the json file at the url 'map' into
// memory. This is similar to the requests
// we've done in the past using
// XMLHttpRequests.
load: function (map) {
// Perform an XMLHttpRequest to grab the
// JSON file at url 'map'. We've provided
// the xhrGet function from the optional
// unit for you to use if you want.
//
// Once the XMLHttpRequest loads, set the
// 'fullyLoaded' flag to true.
//
// YOUR CODE HERE
xhrGet(map, function(){
this.fullyLoaded = true;
});
}
});
// We define a single global instance of our
// map for the rest of our game code to access.
var gMap = new TILEDMapClass();
the link says that it use gMap.load.apply(gMap, [jsonURL]);
http://forums.udacity.com/questions/100058023/scope-of-this#cs255
but I think that inspite the fact using the called mothod.(The load will belong to gMap)
But because
xhr.onload = function(){
this.fullyLoaded = true;
}
is a method belong to the xhr object,
and the this is inside an an anonymous function
the this should reference the xhr not gMap.
Why the this reference gMap?
this is funny within closures. You have to remember that the this keyword will usually refer to the owner of the method. Usually the caller (window for global functions) but when a method is called as a property of an object, this will refer to the object itself.
See this: "this refers to the parent object inside function code if the function is called as a property of the parent." Understanding this
The rules directly from Understanding this:
By default, this refers to the global object.
When a function is called as a property on a parent object, this
refers to the parent object inside that function.
When a function is called with the new operator, this refers to the
newly created object inside that function.
When a function is called using call or apply, this refers to the
first argument passed to call or apply. If the first argument is null
or not an object, this refers to the global object.
this doesn't necessarily mean the function or object it's being called on, if you're used to using jQuery and are confused by this, the jQuery methods actually set this on all of its functions for convenience by calling one of these two functions which set this to the caller:
call(object, arg0, arg1...);
apply(object, args[]);
So basically, unless the function is setting this by calling one of the above functions, it will be set to some outer function/object or window.
"this" in a javascript function has nothing to do with the object to which the function belongs, but what object it is executed against
Contrast with Java, where those are the same because a method is truly part of an object and cannot exist without one (not considering statics).
For example:
var blah = {
test: function () {
console.log('test');
}
};
var f = blah.test;
var bleh = {
test: blah.test
}
If I then make each of these three function calls, what is "this" pointing to in each call?
blah.test(); // this points to blah
f(); // this is null (or undefined, not sure which)
bleh.test(); // this is bleh
I can also use Function.call to call a function object in the context of any object: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
f.call(window); // this is window
Understanding "this" is difficult when working with callbacks because the callback function is usually invoked by some other library (like jquery for instance) and their API may or may not make a guarantee to what "this" refers to. What you can do as a work-around:
someAsyncFunction(function () {
bleh.test();
});
That will ensure the function you care about is called with a predictable "this" reference.
I want to achieve something similar to this:
A function someFunction has a method .lockThis(<object>) and when first called:
var foreverThis = {};
someFunction.lockThis(foreverThis);
// ... or
// someFunction = someFunction.lockThis(foreverThis);
It will bind this function to the <object> so that the next (or any other) time this function is executed via as-function or as-method call, or via apply/call - it will have initially given <object> as this.
I can, of course, define Function.prototype.lockThis which will call the function via .apply()/call() and specify given object as a context, but then it will still be possible to substitute context of someFunction if someone calls it via apply() as well.
Is there a way to lock function context in JavaScript in a way that it will always have provided object as this without changing the code of the function itself?
That's called bind():
someFunction = someFunction.bind(foreverThis);
What is the recommended way to pass cached jQuery references, e.g. $domContainer in var $domContainer = $('#container'); to functions as a callback if the functions are defined before and outside of $(document).ready()?
Example:
<script src="/plugins.js"></script>
In this external file of re-usable functions
function rowAction ( event ) { // how do I get context here?
// how can I access $domTable and $domFilters
// I can access $(event.target) and traverse the dom
// back to $domTable, but $domTable is not defined
// in the external file, although a reference is cached
// in the local scope of $(document).ready();
// likewise, $domTable could be accessed through event.delegateTarget
// however, how can I pass $domFilters, and other vars?
}
In the main script
<script src="/scripts.js"></script>
The standard document ready
$(document).ready(function(){
// Cached References
var $domFilters = $('#form .filter'), // more than 1 reference returned
$domTable = $('#results'); // 1 reference returned
$domTable.on('click','.menu',rowAction);// reference function in plugins.js
// how do I pass $domFilters to rowAction to avoid dom lookups?
// I could pass $domFilters to a plugin like $(...).plugin({f:$domFilters});
// if it had optional arguments, but if it's not a plugin, what's the
// equivalent way to do it?
});
Would the way to approach this be to use an inline function to wrap the callback function name?
Any pointers to a standard practice would be welcome too.
You can follow a modular approach by defining a NameSpace. Then you won't have to use ready.
//These four will be open
var objects, handlers, bindings,
NameSpace = {
//This is where you cache references
objects: {
domcontainer: $('.domcontainer')
},
//Do the events to handlers bindings
bindings: function(){
domcontainer.on('click', handlers.clickCallback)
}
//The actual handlers
handlers: {
clickCallback: function(e){
//Do something
}
},
//Initial things
init: function(){
objects = this.objects;
handlers = this.handlers;
//This will start the handler binding.
this.bindings();
}
};
$(function () {
NameSpace.init();
});
If you are adding objects on the fly, then inside objects you can add references as functions that return the actual object reference. Whenever you need to refer an object, it will be available already hence avoiding DOM look up.
If you are looking to access $domTable you can use the event.delegateTarget property of the event object without having to traverse the dom. You will have to wrap it in a jQuery object though.
Edit
A standard way of passing the context and extra properties to an external function would be to use call() or apply() jQuery has it's own wrapper for that behaviour also called proxy()
In your example with $domTable the context is already passed through as the target of the selector so everything you need would be available to you.
In your $domFilters example there is no event object to pass as the context since event are mapped to actual events in the dom and all you have there is a collection so you couldn't use that function since it relies on the event object.
If I was calling another function though from a collection whilst passing the context I would use something like this.
$domFilters = $('#form .filter');
$domFilters.each(function(){
// Call the external function passing the jQuery wrapped item
// as the context.
externalFunction.call($(this));
});
// Your external function
function externalFunction(){
// 'this' would now refer to the context passed in call.
// i.e your $(.filter) item.
}
Your utility function has to be designed to be able to handle whatever is passed to it as context plus any additional arguments though.
I want to run some JS code when an image's CSS property "display" has been changed by any other JS script/functions. Is there any method to monitor that change and setup a callback function?
$(this).bind.('propertychange', function(){})
cannot do this, and setInterval is also a bad idea.
What else could be done?
This is what you are looking for:
document.documentElement.addEventListener('DOMAttrModified', function(e){
if (e.attrName === 'style') {
console.log('prevValue: ' + e.prevValue, 'newValue: ' + e.newValue);
}
}, false);
This is inside the legacy JavaScript files that you do not want to modify:
// this is your original, unmodified function
function originalFunction(sel) {
alert(sel);
$(sel).css("display","none");
}
This is in your code:
// here is a sample callback function you pass into the extended function below
function myCallback(s) {
alert("The image with src = '" + $(s).attr("src") + "' has been modified!");
}
// here is how you can extend the function to do what you want
// without needing to modify the actual code above
originalFunction = (function(legacyFn, callback) {
// 1 arg function to be returned and reassigned to originalFunction
return function(sel) {
// call "original" originalFunction, with alert and image hide.
legacyFn(sel);
if(callback) callback(sel); // invoke your callback
}
})(originalFunction, myCallback);
The variable originalFunction is assigned a function that takes one argument. The function that takes one argument is returned by an anonymous, self-executing function that takes 2 arguments, the reference to the originalFunction before it is modified, and the reference to the callback function. These two function references become "locked" inside the closure so that when the originalFunction is then assigned a new value by the self-executing function, the legacyFn parameter still contains a reference to the originalFunction prior to it being modified.
In summary, at a higher level, originalFunction and myCallback are passed in as parameters to the self-executing anonymous function and are passed into the variables legacyFn and callback, and a new function is then assigned to originalFunction.
Now, when you call originalFunction('.someClassOnAnImage'), the legacyFn will fire, which will alert the selector and set the display property to none. Afterwards, the callback function, if it exists, will fire, and you'll then see:
The image with src = '.someClassOnAnImage' has been modified!
While this isn't as nice as a hypothetical or platform-specific addEventListener, it does allow you to modify the behavior of the functions in the legacy code without having to physically crack open those files and modify them. This simply extends the functions to perform additional behaviors but without needing to modify the original functions or even the original files for that matter.
You could neatly include all of your extensions in a separate JavaScript file (or whatever JavaScript file you're working in) and if you ever want to go back to the original behavior, you simply remove your extended functions.
The Answer: See this other post >> is there an alternative to DOMAttrModified that will work in webkit
The Rant:
The DOM Mutation events hold the key to your problem. However, in the new wave of browser wars, Wekit and Gecko can't agree on stuff. While Gecko has DOMAttrModified, webkit has something called mutation observer (which breaks the pattern of event handlers being attached to events but hey who cares for consistency when we want to lock users/coders in right? ;)
P.S: Just adding this here for future seekers of the same wisdom.
Building upon Jeff's suggestion, I would recommend writing a single function that modifies the image style property and then using that function as the bottleneck that all other functions must go through to modify that image style property.
function showImage(selector, callback) {
$(selector).css("display","block");
if(callback)
callback();
}
function hideImage(selector, callback) {
$(selector).css("display","none");
if(callback)
callback();
}
Something like the above two functions can be invoked from anywhere in your JavaScript when you must change the image CSS property. The functions also take a function as a parameter, which would be executed afterwards assuming the function was passed in as the 2nd parameter.
You could further simplify this into a single function, but I'll leave that to you as I don't know exactly what your goals are in doing this.
this thing almost works:
function myClass(url) {
this.source = url;
this.rq = null;
this.someOtherProperty = "hello";
// open connection to the ajax server
this.start = function() {
if (window.XMLHttpRequest) {
this.rq = new XMLHttpRequest();
if (this.rq.overrideMimeType)
this.rq.overrideMimeType("text/xml");
} else
this.rq = new ActiveXObject("Microsoft.XMLHTTP");
try {
this.rq.onreadystatechange = connectionEvent;
this.rq.open("GET", this.source, true);
this.rq.send(null);
this.state = 1;
} catch (err) {
// some error handler here
}
}
function connectionEvent() {
alert("i'm here");
alert("this doesnt work: " + this.someOtherProperty);
}
} // myClass
so it's nothing more than having the XMLHttpRequest object as a member of my class, instead of globally defined, and invoking it in the traditional way. however, inside my connectionEvent callback function, the meaning of "this" is lost, even though the function itself is scoped inside myClass. i also made sure that the object that i instantiate from myClass is kept alive long enough (declared global in the script).
in all the examples of using javascript classes that i saw, "this" was still available inside the inner functions. for me, it is not, even if i take my function outside and make it a myClass.prototype.connectionEvent. what am i doing wrong? thank you.
The reason it's not working is that in Javascript, this is defined entirely by how a function is called, not where it's defined. This is different than some other languages.
To have this mean what you expect, you'd have to ensure that explicitly by "binding" it:
this.start = function() {
var self = this; // Set up something that survives into the closure
/* ...lots of stuff omitted... */
this.rq.onreadystatechange = function() {
// Call `connectionEvent`, setting `self` as `this` within the call
connnectionEvent.call(self);
};
There's more information about this management in this blog post, but basically: When a function is called without any particular effort made to set this, this within the function will always be the global object (window, on browsers). There are two ways to set this when making a call:
Using Function#call (or Function#apply) as I did above, passing in the object reference to use as this as the first parameter. That calls the function and sets this to whatever you passed in. The difference between #call and #apply is how you supply further arguments to pass into the function. With #call you supply them as further arguments to the #call call (e.g. func.call(thisArg, arg0, arg1, arg2)), whereas with #apply you supply them as an array in the second argument (func.apply(thisArg, [arg0, arg1, arg2])).
Using dotted notation: If you have an object that has a property with a function assigned to it (like your start property), calling it by using the object instance, a dot, and the property name (this.start() or foo.start(), etc.) will call the function and set this to the object instance within the call. So the dotted notation does two entirely distinct things: Looks up the property and finds a function as its value, and calls the function such that this is set to the object during the call. Literally it's like: var f = obj.func; f.call(obj).
Slightly off-topic, but: Barring a really good reason to, I wouldn't reinvent this wheel. There are lots of libraries out there to simply XHR calls. jQuery, Prototype, Closure, and nearly all the rest.