Take a look at the following code
//btns is an array passed as a parameter to a function
for(var i = 0, b; b = btns[i]; i++) {
b.handler = function () {
var a = btns[i].some_field; //undefined
//the same for "b.some_field;"
};
}
Why btns[i] is undefined?
PS. the code adds click handler on extjs buttons if that matters.
This happens because by the time the inner function is called (which is after the loop is done) the value of i would be btns.length and therefore the value of btns[i] would be undefined.
You need to close over the value of i like this:
b.handler = function(i) {
return function() {
var a = btns[i].some_field;
}
}(i);
It's important to note that although the variables have the same name, they're different variables; i.e. the inner variable shadows the outer, thereby "fixing" the value.
for(var i = 0, b; b = btns[i]; i++) {
b.handler = function () {
var a = this.btns[i].some_field;
//the same for "b.some_field;"
};
}
give "this." in side the function we have to use "this" to point
Related
I have two JavaScript objects.
Obj1 - static class
Obj2 - instance
After adding item in Obj1 and running the method isAdded() from Obj2 there is a problem.
obj1.func stores a function, that holds the keyword this for Obj2. If I call Obj1.func([args]) this is now for Obj1 instead of Obj2.
Any answer Please?
var Obj1=function(){};
Obj1.func=null;
Obj1.addItem=function(vstup){
// code for add - AJAX async ....
// after adding
Obj1.func(id, nazev);
};
// ----------------------------
var Obj2=function(){
this.variable=null;
this.promenna2=null;
this.isAdded=function(){
this.variable="added";
alert("ok");
};
};
// ---------------------
// in body
window.onload=function(){
var instanceObj2=new Obj2();
obj1.func=instanceObj2.isAdded();
obj1.addItem("test");
}
You are doing obj1.func = instanceObj2.isAdded() which means: setobj1.functo the result ofinstanceObj2.isAdded(), which is: obj1.func = undefined since obj2.isAdded() returns nothing.
if you then execute obj1.isAdded(), which runs Obj1.func, you are essentially executing undefined as a function.
To fix it:
obj1.func = function() { instanceObj2.isAdded(); };
Calling something within another context (aka: running something and setting "this")
To run something with a different this value:
To set the context of a function, you can use either apply or call
function add()
{
var result = this;
for(var i = 0, l = arguments.length; i < l; i++)
result += arguments[i];
return result;
}
var value = 2;
newValue = add.apply(value, [3,4,5]); // = 2 + 3 + 4 + 5;
// newValue = 5
newValue = add.call(value, 3, 4, 5) // same as add.apply, except apply takes an array.
Creating a new function, with a context
In new browsers (ie9+) it's possible to use Function.prototype.bind to create a callback with a set context (this) and set arguments which precede other arguments.
callback = func.bind(object);
callback = function() { func.apply(object, arguments); }
callback = func.bind(object, 1, 2);
callback = function() { func.apply(object, [1, 2]); };
In javascript this refers to currently being used element, So it's reference keep changing
Best method is to store (this) in a variable. and use it where you want that.
Like
var obj1This=this;
var obj2This=this;
And later use them
obj2This.isAdded();
I have the following code:
for(var i = 0; i < nodelist.length; i++) {
var x = functionThatCreatesADivElement();
someElement.appendChild(x.getDiv()); // this works fine
nodelist[i].onclick = function() {
x.someFunction(); // this always refer to the last 'x' object
}
}
function functionThatCreatesADivElement() {
var div = document.createElement("div");
this.someFunction = function() {}
this.getDiv = function() {
return div;
}
return this;
}
the problem is that the execution of nodelist[0].onclick is exactly the same as nodelist[4].onclick (assuming that i = 4 is the last node).
I believe the references of the previously iterated are changing to point to the currently iterated element.
What is the proper way of doing this?
EDIT: Added some more code and changed the name of the function cause it was too confusing
You have two problems. The first problem is that JavaScript variables don't have block scopes.
From MDN:
When you declare a variable outside of any function, it is called a global variable, because it is available to any other code in the current document. When you declare a variable
within a function, it is called a local variable, because it is available only within that
function.
JavaScript does not have block statement scope;
You aren't enclosing a the x variable in a function, so all of your onclick callbacks are using the same x variable, which point to whatever element is last in the loop since that will be the last one to have overwritten x.
Doing this for your loop should work:
nodelist.forEach(function (nodeitem) {
var x = functionThatCreatesADivElement();
someElement.appendChild(x.getDiv());
nodeitem.onclick = function() {
x.someFunction();
}
});
The second problem is that your functionThatCreatesADivElement() constructor function is not being called correctly. Use new functionThatCreatesADivElement() since you are invoking a constructor function.
Solved. I had to use
var x = new functionThatCreatesADivElement();
function functionThatCreatesADivElement() {
var div = document.createElement("div");
this.someFunction = function() {}
this.getDiv = function() {
return div;
}
//return this; //Using new instead of returning this
}
I just write a test html file to learn about object in javascript. The code is as follows
in script tag
<script type="text/javascript">
var obj = new ParentFn();
var obj2 = new AnotherParentFn();
var temp;
function initer()
{
temp = obj.Adding();
obj2.caller();
}
function ParentFn()
{
this.a = 10;
this.b = 20;
}
function AnotherParentFn()
{
this.a = 30;
this.b = 50;
}
AnotherParentFn.prototype.caller = function()
{
var self = this;
temp();
}
ParentFn.prototype.Adding = function()
{
var self = this;
document.getElementById("id_div1").innerHTML = " Method Called and Result of a+b is " + (self.a + self.b);
}
</script>
In body i use
<button onclick="initer()"> Click here to test </button>
<div id="id_div1"></div>
Problem is when AnotherParentFn.prototype.caller is called from initer() function temp variable is still undefined. What is wrong with the code??
My task is to assign the function ParentFn.prototype.Adding in a global variable and call the global variable from AnotherParentFn.prototype.caller function. How to achieve it?
You don't need to save it as a global variable. It's already saved in ParentFn.prototype. All you need to do is invoke it with .call and pass in your desired receiver. You can implement AnotherParentFn.prototype.caller like this:
AnotherParentFn.prototype.caller = function()
{
ParentFn.prototype.Adding.call(this);
}
This way you can get rid of temp completely. You also don't need to assign this to a local var self everywhere.
Parentheses are used to execute a function.
When you assign the value to temp, you are calling the function and assigning the result (undefined) to temp. To store a reference to the function in temp, omit the parentheses.
temp = obj.Adding;
By writing temp = obj.Adding(); it stores the return value. not function pointer in temp. Use this
function initer()
{
temp = obj.Adding;
obj2.caller();
}
First of all, the reference to obj.Adding is not assigned properly; it should be this (without parentheses):
function initer()
{
temp = obj.Adding;
obj2.caller();
}
Then, inside AnotherParentFn.prototype.caller itself, you must pass the current object as this explicitly during the invocation by using .call():
AnotherParentFn.prototype.caller = function()
{
temp.call(this);
}
I have a problem getting the value of 'name' displayed with the following:
for (var name in array) {
var hoverIn = function() {
alert(name);
};
var hoverOut = function() {
};
thing.hover(hoverIn, hoverOut);
}
What I get is an alert window with the last value of name. Clearly I am doing something wrong and I suspect it's a simple fix. Can anyone help?
Thanks.
It's closure problem, name, after that iteration is the last name in array, and callback for hovers isn't executed right away when the iteration happens, thus when the hover function is actually executed, name will always be the last in array.
You need to use IEFE (Immediately executed function expression):
for (var name in array) {
// pass in name to the anonymous function, and immediately
// executes it to retain name when the particular iteration happens
var hoverIn = (function(name) {
return function() {
alert(name);
}
})(name); // if you notice, the pattern is (function(name) {})(name)
// the first () creates an anonymous function, the (name)
// executes it, effectively passing name to the anon fn
var hoverOut = (function(name) {
// same pattern if you need to re-use name inside here, otherwise just
// function() { } should suffice
})(name);
thing.hover(hoverIn, hoverOut);
}
To avoid duplicates of (function() { })() (which honestly is getting tiring to look at), you could also, as #pimvdb pointed out, wrap the whole body in a closure:
for (var name in array) {
(function(name) {
var hoverIn = function() {
alert(name);
}
var hoverOut = function() {
}
thing.hover(hoverIn, hoverOut);
})(name); // closure of for loop body
}
Add a variable inside the loop
var thisName = name;
and then use that in your function
alert(thisName);
You have two ways of dealing with this problem.
The first thing to know is that scope only happens at function level, not within loops in javascript.
If you set a variable within a function from an outside source and don't execute it right away,the variable will be changed over the course of your loop.
You can solve this by closing other the variable:
var names = ["john","paul","george","ringo"];
var store = {};
//this function receives the data as a parameter
//so it will be a safe copy.
function createFunc(name){
//just return a function that will alert the name.
return function(){
alert(name);
}
}
for (var i in names) {
var hoverIn = createFunc(names[i]);
store[names[i]]=hoverIn;
}
store["john"]();
The other way is to create an anonymous function that executes right away
within the loop:
var names = ["john","paul","george","ringo"];
var store = {};
for (var i in names) {
//the function receives the i as a parameter
//and executes, so n is a safe copy of i
(function(n){
var hoverIn = function(){
alert(names[n]);
}
store[names[n]]=hoverIn;
})(i);
}
store["john"]();
Everything is a problem related to closure.
Look at wikipedia for more info.
You should create closure:
for (var name in array) {
var hoverIn = (function() {
return function() {
alert(name);
};
}());
var hoverOut = function() {
};
thing.hover(hoverIn, hoverOut);
}
I'm not sure exactly how to describe what I want. I want to define a function with the parameters being a local VALUE not a reference.
say I have list of objects I want to create
for(i = 0; i < 10; i++){
var div = document.createElement("div");
div.onclick = function(){alert(i);};
document.appendChild(div);
}
Now I believe in this example no matter what div I click on, it would alert "10"; as that is the last value of the variable i;
Is there a way/how do I create a function with the parameters being the value they are at the time I specify the function... if that makes any sense.
You need to create the function inside another function.
For example:
div.onclick = (function(innerI) {
return function() { alert(innerI); }
})(i);
This code creates a function that takes a parameter and returns a function that uses the parameter. Since the parameter to the outer function is passed by value, it solves your problem.
It is usually clearer to make the outer function a separate, named function, like this:
function buildClickHandler(i) {
return function() { alert(i); };
}
for(i = 0; i < 10; i++){
var div = document.createElement("div");
div.onclick = buildClickHandler(i);
document.appendChild(div);
}
You could use an anonymous function:
for(i = 0; i < 10; i++){
(function(i){
var div = document.createElement("div");
div.onclick = function(){alert(i);};
document.appendChild(div);
})(i)
}