Sorry if this has been answered already, but I could not find an appropriate answer on here.
I've started writing my javascript code in a modular style lately and I have a question regarding how module variable scope works.
The following code gives me a conflicting answer.
I have a module named Base that declares two strings and an array. It also has a function called fetchData that uses the jQuery getJSON shortcut to set these variables with server data. Unfortunately when I ask for Base's string1 or string2, I get undefined. I understand that this is probably due to the fact that I have it set their values two functions deep (inside the AJAX callback and inside fetchData) and the scope limits it from seeing Base.string1 and Base.string2.
However, when I look at Base.array1 from outside the module, it's set to the appropriate data I pulled from the server, even though it's set from the same scope as the strings.
Here's the code:
namespace.Base = (function(){
var string1, string2, array1 = [];
function fetchData(){
$.getJSON('backendScript.php', function(data){
string1 = data.string1;
string2 = data.string2;
arrayCount = data.arr.length;
for(var i = 0; i<arrayCount; i++){
array1[i] = data.arr[i];
}
})
}
return{
fetchData: fetchData,
string1: string1,
string2: string2,
array1: array1
}
})();
If I change
string1 = data.string1;
to
namespace.Base.string1 = data.string1;
it works like I want.
So my question is, why is array1 set correctly when it's set from the same scope as the strings?
Also, what is the remedy for setting module-level variables from within the module's functions without having to give a global path (e.g. namespace.Base.string1)?
The problem is that you actually have two different references, the variable string1 within the closure of the anonymous function that you invoke to create namespace.Base, and namespace.Base.string1, which is on the object returned from that anonymous function. Your assignment of the variable string1 to the object property string1 is a one-time set, not a live reference. Further modification of the variable string1 will not affect the object property. Here's what you want:
namespace.Base = (function() {
var my = {
string1: null,
string2: null,
array1: [],
fetchData: function () {
$.getJSON('backendScript.php', function(data){
my.string1 = data.string1;
my.string2 = data.string2;
var arrayCount = data.arr.length;
for (var i = 0; i < arrayCount; i++){
my.array1[i] = data.arr[i];
}
});
}
};
return my;
})();
Now the local, but public, members of namespace.Base are in the object my. You can create private variables using var within the anonymous function, or create more public properties by adding them to my.
I would be a good idea to get familiar with closures and how they work:
How do JavaScript closures work?
Your "scope" issue is not actually a scope issue. The issue is that arrays are pointers to their data, strings are not.
namespace.Base is set to the results (returned value) of the anonymous function. -- It is set to be an object containing a function ref (fetchData), two empty strings and an array.
If you later call the fetchData function, then it will change the contents of array1.
But it will also create two new strings (from data.string1 and data.string2). The old values of string1 and string2 (which are namespace.Base.string1 and namespace.Base.string2) are not changed. So they are left as empty strings (not what you want).
Example of this. Try it in Firebug--
s1 = "Hi";
s2 = s1; // s2 => "Hi"
s1 = "Bye"
alert(s2); // *** s2 is still "Hi", it was not changed!
// But arrays are different:
a1 = ["Hi"];
a2 = a1;
a1[0] = "Bye";
alert(a2[0]); // a2[0] is now "Bye"
Added: Asynch Timing error
Also, note that your code is wrong as written since you're not giving the caller any way to know when the Ajax call has completed:
namespace.Base.fetchData(); // starts the Ajax call via getJSON method
var a = namespace.Base.array1; // ERROR!! The value of namespace.Base.array1 is
// indeterminate since you don't know if the
// the Ajax request has completed yet or not!
You appear to be trying to convert the asynchronous Ajax call (which invokes a callback function once the answer has been received from the remote server) into a synchronous call which will not return until the results have been received.
This is a really bad idea. (If you want to know more, ask another question in SO.)
Related
I have a code that is supposed to read a google sheet and push these into an array but i keep encountering an error that proData.push is not a function What could I be missing in this code?
function getData() {
var values = SpreadsheetApp.getActive().getSheetByName("Projects").getRange("A1:O").getValues();
values.shift();
var proData = [];
values.forEach(function(value) {
var proData = {};
proData.project_state = value[0];
proData.project_name= value[4];
proData.project_code= value[5];
proData.end_date= value[2];
proData.push(proData);
})
Logger.log(JSON.stringify(proData));
return proData;
}
I will appreciate help in looking at this.
This line tells the whole story:
proData.push(proData);
You're trying to push an object onto itself? Clearly this is an indication that something is wrong. So let's look at where you define proData:
var proData = {};
That at least explains the error. proData is an object, not an array. And an object indeed doesn't have a function called push. You may have thought it was an array, because you defined an identically named array here:
var proData = [];
But for the line where you call .push, how is the system to know which variable you intend for what purpose? In a higher scope you have an array named proData, but in the current scope of this operation you obscured that with an object named proData. And in doing so made the array inaccessible within the scope of the function passed to .forEach.
To avoid confusing both the JavaScript engine and yourself in this matter, simply use different variable names. Re-naming the variable in the smaller scope has a lower impact, so that's a good candidate. (Though it's not always the best choice. If the variable in the higher scope is semantically not clear about what it contains then it should be re-named.)
Something like this:
values.forEach(function(value) {
var pd = {};
pd.project_state = value[0];
pd.project_name = value[4];
pd.project_code = value[5];
pd.end_date = value[2];
proData.push(pd);
});
In reviewing the following article: https://html-online.com/articles/get-url-parameters-javascript/
They show the following example:
var number = getUrlVars()["x"];
Can someone please explain the syntax where the parameter is outside of the function?
If it was written as the following would you have asked the question?
var myVariables = getUrlVars()
var number = myVariables["x"];
The function call getUrlVars is returning an object {x: 123} and the developer is reading a property of the object right away. Instead of writing it in two steps, they wrote it as one.
Doing it that way is great if you are only reading one property from the object, but if you want to read more than one, it would be better to write it out by storing getUrlVars() into a variable and than using that to read the properties. That way you are not executing getUrlVars more than once.
var num = getUrlVars()["x"]; This expression is used when the called function is returning a object and you want to get only desired value out of the returned object. Here ["x"] is not a parameter it used as object property accessor.
function getUrlVars(){
return{
'x': 'This is x',
'y': 'This is y'
}
}
var num = getUrlVars()["x"];
console.log(num)
var number = getUrlVars('["x"]'); In this expression you're passing [x] as parameter to a function.
very basic JavaScript programmer here!
I was busy on some code with variables that look like this:
blocktype1;
blocktype2;
blocktype3;
blocktype4;
... //everything between blocktype4 and blocktype70, the three dots are not actual code!
blocktype70;
Now I was using eval() in a function where a value was given to one of the blocktype variables. The blocktype depended on the variable "number".
This is what I had for that part:
eval("blocktype" + number) = 3
What I want is, say "number" is 27, then I want the variable blocktype27 to get a value of 3.
When I check the console it says:
ReferenceError: Invalid left-hand side in assignment
Could anyone possibly help me?
I would prefer just vanilla JavaScript and still the use of eval.
Thank you for your time!
The 'correct' solution would probably be to use an Array which is ideal for sequences and are accessible by index.
var number = 1;
var val = 3;
var blocktype = []; // so clean
blocktype[number] = val;
However, properties can be accessed as with the bracket notation as well. This assumes the variables are in global scope and are thus properties of the global (window) object.
var blocktype1; // .. etc
window["blocktype" + number] = val;
The problem with the eval is that is effectively the same as doing f() = 3 which does not make sense: only variables/properties can be assigned to1.
However eval is a built-in function and the results of a function cannot be assigned to, per the error message. It could be written as
var blocktype1; // .. etc (see dandavis' comment)
eval("blocktype" + number + " = " + val);
// What is actually eval'd is:
// eval("blocktype1 = 3")
which quickly exposes a flaw with eval. If val was the string "Hello world!" with would result in eval("blocktype1 = Hello world!") which is clearly invalid.
1 For the gritty: the left-hand side of an assignment has to be a Reference Specification Type, which is a more wordy way of describining the above behavior. (It is not possible for a JavaScript function to return a RST, although it could technically be done for vendor host objects.)
Feel free not to accept this, since it's specifically not using eval(), but:
You can allocate an array of size 71 like so:
var blocktype = new Array(71);
(your number values apparently start at 1, so we'll have to ignore the first element, blocktype[0], and leave room for blocktype[70], the 71st)
You can now assign elements like this:
blocktype[number] = 3;
and use them like so:
alert( blocktype[number] );
I've seen something similar to this code in the Google API JavaScript, I mean the r=Array part. Here is an example of what they have done:
var r = Array;
var t = new r('sdsd' , 'sdsd');
alert(t[0]);
Few questions about this:
Is it legal to write like this and won't cause any problems?
I can do something similar with other keywords like ´For´ loop or with the ´this´ keyword?
Can I have article about this JavaScript official keyword shortcuts etc..?
Thank you in advance.
That works because Array is an object. You can do that with any object. For example, the Date object:
var d = Date;
console.log((new d()).getTime()); //Prints time
You cannot do that for keywords such as for or while because they are language constructs that will be recognised by the interpreter.
You can do it with this:
document.getElementById("b").onclick = function() {
var x = this; //this holds a reference to the DOM element that was clicked
x.value = "Clicked!";
}
In fact, that can be very useful sometimes (to keep a reference to this so you can access it from an anonymous inner function for example). This also works because, to put it simply, this will be a reference to an object.
Yes
for - no. this - yes.
You can store references to any JavaScript object in a variable. String, Array, Object, etc. are JavaScript objects that are built-in to the language. for, if, while, etc. are are JavaScript statements, and cannot be stored or referenced any other way.
You can do it the other way around as well (and really mess yourself up in the process):
Array = 0;
var myArray = new Array("a", "b", "c"); // throws error
This is easily undone like this:
Array = [].constructor;
Edit: Being able to assign the value of this to a variable is essential when nesting functions that will execute in a different scope:
function Widget() {
var that = this;
this.IsThis = function() {
return isThis();
};
function isThis() {
return that == this;
}
}
new Widget().IsThis(); // false!
Maybe not the best example, but illustrates losing scope.
You cannot reassign the value of this:
function doSomething() {
this = 0; // throws error
}
I have a function that takes a string object name and I need the function to create an new instance of a object that has the same name as the value of the string
For example,
function Foo(){}
function create(name){
return new name();
}
create('Foo'); //should be equivalent to new Foo();
While I know this would be possible via eval, it would be good to try and avoid using it. I am also interested if anyone has an alternative ideas to the problem (below)
I have a database and a set of (using classical OO methodology) classes, roughly one for each table that define common operations on that table. (Very similar to Zend_Db for those who use PHP). As everything is asynchronous doing tasks based on the result of the last one can lead to very indented code
var table1 = new Table1Db();
table1.doFoo({
success:function(){
var table2 = new Table2Db();
table2.doBar({
notFound:function(){
doStuff();
}
});
}
});
The obvious solution is to create helper methods that abstracts the asynchronous nature of the code.
Db.using(db) //the database object
.require('Table1', 'doFoo', 'success') //table name, function, excpected callback
.require('Table2', 'doBar', 'notFound')
.then(doStuff);
Which simplifies things. However the problem is that I need to be able to create the table classes, the names of which can be inferred from the first augment passed to require which leads me to the problem above...
Why not simply pass the constructor function into the require method? That way you sidestep the whole issue of converting from name to function. Your example would then look like:
Db.using(db) //the database object
.require(Table1Db, 'doFoo', 'success') //table constructor, function name, expected callback
.require(Table2Db, 'doBar', 'notFound')
.then(doStuff);
However, if you really want to use a string...
Why are you deadset on avoiding using eval? It is a tool in the language and every tool has its purpose (just as every tool can be misused). If you're concerned about allowing arbitrary execution, a simple regular expression test should render your usage safe.
If you're dead-set on avoiding eval and if all of your constructor functions are created in the default global scope (i.e. the window object), this would work:
function create(name) {
return new window[name]();
}
If you want to get fancy and support namespace objects (i.e. create('MyCompany.MyLibrary.MyObject'), you could do something like this:
function create(name) {
var current,
parts,
constructorName;
parts = name.split('.');
constructorName = parts[parts.length - 1];
current = window;
for (var i = 0; i < parts.length - 1; i++) {
current = current[parts[i]];
}
return new current[constructorName]();
}
You were at the gate of completeness. While Annabelle's solution let's you to do what's you've just wanted in the way you wanted (passing strings), let me offer you an alternative. (passing function references)
function Foo(){}
function create(name){
return new name();
}
create(Foo); // IS equivalent to new Foo();
And voila, it works :) I told you. You were at the doorsteps of the solution.
What happened is that you've try to do this
new 'Foo'()
Which doesn't makes much sense, does it? But now you pass the function by reference so the line return new name(); will be transformed into return new Foo(); just how you would expect.
And now the doors are opened to abstract the asynchronousness of your application. Have fun!
Appendix: Functions are first-class objects, which means that they can be stored by reference, passed as an argument by reference or returned by another function as values.