Read lots of posts lately, trying to demystify variable scopes in Javascript/SAPUI5. The way that I have understood things we have mainly 2 kinds of them - local variables, declared inside functions and visible only within and global variables declared outside functions and therefore "globally" accessible. Tried that with a really small example and, after what debugging revealed, I feel quite puzzled here. Tiny sample below:
onTestButtonPress: function() {
var url = "/DEV/sap/opu/odata/SAP/ZCONTRACTS_SRV/Agreement_ExportSet";
var oTest1 = [];
var promise = $.Deferred();
$.ajax({
url: url,
type: "GET",
dataType: "json",
success: function(xhrData) {
var oTest2 = xhrData;
promise.resolve();
}
});
var readyToGo = function() {
jQuery.sap.log.error("check");
};
jQuery.when(promise).done().then( jQuery.proxy(readyToGo, this) );
}
On $.ajax... and jQuery.when... statements, oTest1 var has a value of [] while within the functions contained in the main function (ajax success function & readyToGo), same variable is producing a reference error.
According to the "theory" and unless I miss something, since oTest1 was declared at the beginning of the main function, it should be visible within the two nested functions as well. What do I miss here or, to put it in a different way, where (or how maybe?) should I declare my variable in the function in order to be visible?
Thanks in advance,
Greg
UPDATE: After a (fair) suggestion, modified the code and attached code for the variable in doubt. New code is this:
onTestButtonPress: function() {
var url = "/DEV/sap/opu/odata/SAP/ZCONTRACTS_SRV/Agreement_ExportSet";
var oTest1 = [];
var promise = $.Deferred();
$.ajax({
url: url,
type: "GET",
dataType: "json",
success: function(xhrData) {
oTest1 = xhrData;
promise.resolve();
}
});
var readyToGo = function() {
jQuery.sap.log.error(oTest1);
};
jQuery.when(promise).done().then( jQuery.proxy(readyToGo, this) );
}
This code is running just fine and variable is OK. Still running the old code, debugger produces this:
This is the reason that I didn't use the var in the sample, is this normal?
You get an error in the code in the screenshot because oTest1 is not in scope.
Scope is determined when the function is created, and since the oTest1 variable is not used in the function then it isn't in scope.
You attempt to add it to the scope after the function has been created by reading the variable with the debugger. It is too late.
Add console.log(oTest1) before debugger and it will be in scope.
Related
After my previous question, I come up to the following working code that is intended to refresh the DOM periodically by replacing the <div id="test_css_id">...</div> itself. The behavior of both AJAX requests present in the below code is to reload the same code itself.
<div id="test_css_id">
<a id="link_css_id" href="test_url.html">LINK</a>
<script type="text/javascript">
var refreshTimer;
$('#link_css_id').click(function(event) {
event.preventDefault();
$.ajax({
url: $(this).attr('href'),
type: 'PUT',
success: function(data) {
clearInterval(refreshTimer);
$('#test_css_id').replaceWith(data); // Replaces all code including JavaScript with the response data (note: the response data is exactly the same as the code shown here).
}
});
});
$(document).ready(function() {
function refreshFunction(){
$.ajax({
url: 'test_url.html',
type: 'GET',
success: function(data) {
clearInterval(refreshTimer);
$('#test_css_id').replaceWith(data); // Replaces all code including JavaScript with the response data (note: the response data is exactly the same as the code shown here).
}
});
}
refreshTimer = setInterval(refreshFunction, 1000);
});
</script>
</div>
However, as said by the author of the accepted answer, "there are other ways you can do it [..] one way is to wrap all of that code into a module". I am not expert in JavaScript but I would like to understand and learn it a little more.
How can I wrap all of that code into a module in order to avoid using global variables?
Your current code looks like this:
var refreshTimer; //a global variable
$(...).click(...);
To make refreshTimer not global, you need to put it inside a function:
function main(){
var refresherTimer; //this variable is now local to "main"
$(...).click(...);
}
main();
However, doing it this way won't solve the problem completely. While we did get rid of the global variables, we added a new one - the "main" function itself.
The final trick is to turn the "main" function into an anonymous function and invoke it directly. This is the famous "module pattern":
(function(){
var refreshTimer; //local variable to that anonymous function
$(...).click(...);
}()); //and here we call the function to run the code inside it.
The extra parenthesis around everything are important. If you do just function(){}() instead of (function(){}()) then you will get a syntax error.
Here's a nice description of the module pattern in JavaScript.
I'm trying to change the value of a variable in a closure:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
}
});
This does not work because myVariable is not visible to the closure. How do I change this code so that the value of myVariable changes?
Contrary to your belief, your code works. But seeing what you're trying to do and reading between the lines I'm guessing you're trying to do this:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
}
});
alert(myVariable); // at this point myVariable is undefined!!
If so, you need to learn about asynchronous functions.
Basically, the $.ajax() function returns before actually submitting the ajax request. It will do the ajax request later when the browser is not busy executing javascript. Which means that the assignment will not have happened yet when you try to alert the value of myVariable.
Read my response here for more detail: JS global variable not being set on first iteration
The only good solution is to change the way you think about coding. (There is an arguably bad solution that involves turning the ajax call to synchronous but lets not get in to that, you can google it if you want or read the manual). Instead of doing this:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
}
});
/*
* Do lots of stuff with the returned value
* of the myVariable variable
*
*/
you now need to write it like this:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
/*
* Do lots of stuff with the returned value
* of the myVariable variable
*
*/
}
});
Basically moving any and all code that you would have written after the ajax call into the success callback. This takes getting used to (judging from how many variants of this question exist on the internet). But once you're used to it it becomes second nature.
There is a name for this style of programming. It is variously known as: "event driven programming" or "continuation-passing style" or "evented programming". You can google the various terms if you want to know more.
If that code is in the global scope, myVariable is visible to the inner function. If you're worried about it being shadowed by a local variable, explicitly access it as a property of the global:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
window.myVariable = data;
}
});
Below is just some sample Javascript that I posted that shows 2 different ways that javascript functions are being defined and called.
Is there a name for these different methods?
Which method is preferred?
The first code block looks really simple, pretty much the same as a procedural PHP function is defined and called.
The second I realize is set up more like a class/namespace it just get's a little confusing for me as I have not studied javascript too much yet. Am I correct in my thinking that all these functions could be coded in either method as the first or second code blocks and still work?
Sorry if my question is not clear enough, I will revise if needed, thanks for the help/info
initCommentsHelp();
function initCommentsHelp() {
$('#view-comments-help-a').live('click', function() {
$('#comments-help').slideToggle("normal");
return false;
});
}
VS doing this
Screenshot.Like.Shot.toggle();
Screenshot.Comment.toggle();
Screenshot.Flag.flag();
Screenshot.Flag.unflag();
var Screenshot = {
Like: {
Shot: {
toggle: function() {
if ($('.fav a.fav-toggle.processing').length == 0) {
$.ajax({
type: 'POST',
url: url,
data: data,
beforeSend: function() {
$('.fav-toggle').addClass('processing');
$link.text('Wait...');
},
success: function(responseHtml) {
$('#like-section').replaceWith(responseHtml);
}
});
}
return false;
}
},
Comment: {
toggle: function() {
var link = $(this);
var data = link.hasClass('liked-by-current-user') ? {_method: 'delete'} : null;
$.ajax({
type: 'POST',
url: this.href,
data: data,
success: function(responseHtml) {
link.closest('.comment').replaceWith(responseHtml);
}
});
return false;
}
}
},
Flag: {
// Flag a screenshot as inappropriate or Appropriate
flag: function(){
var link = $(this);
var screenshotId = link.modelId();
if(!confirm("Are you sure you want to flag this shot?"))
return false;
$.ajax({
type: 'POST',
url: this.href,
data: {
screenshot_id: screenshotId
},
success: function(responseHtml) {
$('#flag-section').html(responseHtml);
}
});
return false;
},
unflag: function() {
var link = $(this);
var screenshotId = link.modelId();
$.ajax({
type: 'POST',
url: this.href,
data: {
_method: 'delete',
screenshot_id: screenshotId
},
success: function(responseHtml) {
$('#flag-section').html(responseHtml);
}
});
return false;
}
},
};
The first way is generally preferred for writing standalone functions. You can write them as
function testFunction() {
// your code here...
}
or
var testFunction = function() {
// your code here...
}
The second example you have posted is used for namespacing your objects. You can read more about namespacing in this article : Namespacing in JavaScript
A function that's an object property (called via an object reference, e.g. obj.func()) is what's called a "method". A function not associated with an object is called a "free function". Methods have special access privileges not afforded to free functions. Exactly what those privileges are depends on the language, but all OO languages include a special variable (you can consider it a hidden parameter) available within the function body to access the object the method is bound to. In JS, the name of this parameter is this.
this exists in free functions, where it refers to the global object. You can think of free functions and global variables as being properties of a global, default object. For browsers, the global object is window. Free functions, then, are similar to global variables, which are generally bad. Free functions don't as often cause problems as global variables, but they still can, and for the same reasons. As a result, some developers use objects as namespaces to prevent name collisions.
For example, one module might create a sign function that returns whether a number is positive, negative or 0. Another module might have a sign function that digitally signs a message. These modules are created by different companies, each unaware of the other. Imagine a developer wants to use both modules. If both were defined as free functions, whichever were defined second would replace the first, wreaking havoc in the other module. To prevent this, each function can be defined as properties of separate objects.
The actual difference between free functions and methods is in how they are accessed. Methods are accessed as a property of an object, while free functions are accessed directly by name or as a variable. Note that the same function can be treated as a method or free function, depending on how you access it.
var obj = {
type: 'method',
meth: function (){
return this.type;
}
};
var func = obj.meth,
name = 'free';
// the following two lines call the same function, though `this` will be bound differently for each.
obj.meth(); // returns 'method'
func(); // returns 'free'
You can even take a free function and call it as a method in a number of ways:
function func(a, b) {
return this.foo+a+b;
}
var obj = {foo: 'bar'};
// call func as a method using the `call` method
func.call(obj, 'baz', 'bam');
// call func as a method using the `apply` method
func.apply(obj, ['baz', 'bam']);
// call func as a method in the usual way
obj.meth = func;
obj.meth(1, 2); // 'foo12'
If you look more closely at your second sample, you'll note that most of the methods use the this variable. These must remain methods; making them free functions will likely cause bugs.
One is defining the functions in an object and the other is just defining a function by itself. Functions are first class objects in JavaScript so they don't need an object to be defined.
I've been working with getters and setters to avoid the prospect of using global variables. However, I've run into a problem. The below code, which works fine with integer variables, throws an exception when I try to run an AJAX call instead. Can someone explain to me why this is happening?
function Object_XML() {
me = this;
me.xml = null;
}
Object_XML.prototype = {
getXML: function() {
return me.xml
},
setXML: function(data) {
me.xml = data;
},
loadXML: function() {
$.ajax({
type: "GET",
url: "questions.xml",
dataType: "xml",
success: function(xml) {
me.setXML(xml);
} //close success
});//close AJAX
}//close setXML
};
$(document).ready(function() {
var data = new Object_XML();
alert("This is an " + data.getXML());
data.setXML();
alert("This is an " + data.getXML());
});
Thanks, Elliot Bonneville
You just negated your use of private variables with getters and setters by using me = this; You just made me a global variable by not using var. (any variable not defined using var gets attached to the global namespace)
In your case since you're working within the same object scope you can just use this and avoid the me as personally, i think it's confusing. But if you want to stick to that paradigm, use var me = this;
Your example is really unclear, where does the error happen? You're calling data.setXml() with no parameters, so me.xml will bet set to undefined. That is to be expected if you pass nothing into the method.
Also keep in mind that due to the async nature of your call, if you were to do something like:
data.loadXml();
console.log("data.getXML();", data.getXML()); // would be undefined
data.getXML() at that moment would still be undefined as it's likely your asynchronous call hasn't returned yet, thus not setting the xml attribute of your object.
I'm trying to use a closure (I think that's what it is..), I'd just like to execute a function with a local variable at some point in the future, like this:
function boo() {
var message = 'hello!';
var grok = function() { alert(message); }
foo(grok);
}
function foo(myClosure) {
$.ajax({
timeout: 8000,
success: function(json) {
myClosure();
}
}
}
I could get around this by using global variables and such, but would rather use something like the above because it at least seems a bit cleaner. How (if possible) do you do this?
Thanks
----------- Update --------------------
Sorry I wasn't clear - I was wondering if this is the correct syntax for the closure, I tried it out and it seems ok. Thank you.
Your existing code looks perfectly fine except for that missing paren at the end. ;)
If you're looking to understand the concept of closures more deeply, think of it this way: whenever something in a closured language is defined, it maintains a reference to the local scope in which it was defined.
In the case of your code, the parameter to $.ajax() is a newly-created object ("{ timeout: 8000, etc. }"), which contains a newly-created function (the anonymous "success" function), which contains a reference to a local variable ("myClosure") in the same scope. When the "success" function finally runs, it will use that reference to the local scope to get at "myClosure", even if "foo()" ran a long time ago. The downside to this is that you can end up with a lot of unfreeable data tied up in closures -- the data won't be freed until all references to it have been removed.
In retrospect, I may have confused you more than helped you. Sorry if that's the case. :\
Unless you actually want to make an AJAX call, setTimeout might be more along the lines of what you are looking for:
function foo(myClosure) {
setTimeout(myClosure, 8000); // execute the supplied function after 8 seconds
}
If your question was more along the lines of "Am I creating a closure correctly?", then yes, your function boo is doing the right thing.
Is it what you want?
var boo = (function() {
var message = 'hello!';
return function() {
foo(function() {
alert(message);
});
};
})();
function foo(myClosure) {
$.ajax({
timeout: 8000,
success: function(json) {
myClosure();
}
}
}
or just
function boo() {
$.ajax({
timeout: 8000,
success: function(json) {
alert('hello!');
// do sth with json
// ...
}
}); // <- missed a paren
}
The example is too simple to know what you want btw.