I think I am having a problem with closure/scoping. When I observer the progress of MyObject i always get the value final value of i.
Example
var a = new MyObject();
a.progress(function(msg){console.log(msg)}); // always prints 1000/1000
Observable Object
function MyObject()
{
var this.dfd = $.Deferred();
return this.dfd.promise();
}
MyObject.prototype.aProcess = function()
{
var self = this;
for (var i = 0; i < 1000; i++)
{
(function(i)
{
self.notify("Updating " + (i+1) + "/" + 1000);
// Bunch of Processes
})(i);
}
}
MyObject.prototype.notify = function(message)
{
console.log(message) // works fine
this.dfd.notify(message);
}
Demo
You are doing .process before returning the deferred, so by the time you attach progress listener, the notifications have already run.
Try this:
http://jsfiddle.net/Xe47R/2/
function MyObject() {
this.dfd = $.Deferred();
//Don't explicitly return an object, otherwise the class is useless.
};
MyObject.prototype.process = function() {
//The closure was useless here
for (var i = 0; i < 1000; i++) {
this.notify("Updating " + (i + 1) + "/" + 1000);
}
};
MyObject.prototype.notify = function(message) {
//Remove console.log from here to avoid confusion
this.dfd.notify(message);
}
var a = new MyObject();
a.dfd.promise().progress(function(msg) {
console.log(msg)
}); // always prints 1000/1000
a.process();
Related
Here, I have this two code :
var mod = function() {
var a = function() {
this.fucname = 'hello';
};
a.prototype.build = function() {
return 'before '+this.fucname;
};
return new a();
};
for( var i=0; i<10000; i++ ){
var newfuc = mod();
};
and
var a = function() {
this.fucname = 'hello';
};
a.prototype.build = function() {
return 'before '+this.fucname;
};
for( var i=0; i<10000; i++ ){
var newfuc = new a();
};
After I check both in chrome dev, the second code take a JS HEAP 3.0MB,
the first code take a JS HEAP 10MB.
Is that mean, the build function has been created 10000 time in the first code? and how can I refine it without remove the cover mod?
I have to pass something into the function...
If you want to hide the constructor but also only evaluate it once, you can make use of an IIFE to create a new scope:
var mod = (function() {
var a = function() {
this.fucname = 'hello';
};
a.prototype.build = function() {
return 'before ' + this.fucname;
};
return function() {
return new a();
};
})();
for (var i = 0; i < 10000; i++) {
var newfuc = mod();
}
I'm making something to visualize photographs.
The goal is to select the picture you want in the "list" to make it appear on the main HTML element. But to help you find where you are in the list there's a class putting borders on the element you selected.
The issue :
The function executing with the event this.block.onclick = function () begins well, the .selected is removed from the initial selected element, but when comes this.block.classList.add('selected'); I get this error:
media_visu.js:26 Uncaught TypeError: Cannot read property 'classList' of undefined
I tried to put the function outside, tried className, setAttribute, but nothing changed: my this.block seems to be undefined.
mediavisu.js :
var mediaVisu = function () {
'use strict';
window.console.log('mediaVisu loaded');
var i,
visu = document.querySelector("#img"),
Album = [];
function Photo(nb, folder) {
this.block = document.querySelector("#list_img_" + nb);
this.place = 'url(../src/' + folder + '/' + nb + '.jpg)';
this.block.onclick = function () {
for (i = 0; i < Album.length; i += 1) {
window.console.log(Album[i].block);
if (Album[i].block.classList.contains('selected')) {
Album[i].block.classList.remove('selected');
}
}
visu.style.background = this.place;
window.console.log(visu.style.background);
window.console.log(this.place);
this.block.classList.add('selected');
};
Album[Album.length] = this;
}
var test_a = new Photo(1, "test"),
test_b = new Photo(2, "test"),
test_c = new Photo(3, "test"),
test_d = new Photo(4, "test"),
test_e = new Photo(5, "test");
window.console.log(Album);
for (i = 0; i < Album.length; i += 1) {
window.console.log(Album[i]);
}
};
in the onclick function, this will be the element that was clicked
so you can simply use
this.classList.add('selected');
you may need to rethink using this.place as this wont be the this you think it is .. a common solution is as follows
function Photo(nb, folder) {
this.block = document.querySelector("#list_img_" + nb);
this.place = 'url(../src/' + folder + '/' + nb + '.jpg)';
var self = this;
this.block.onclick = function () {
for (i = 0; i < Album.length; i += 1) {
window.console.log(Album[i].block);
if (Album[i].block.classList.contains('selected')) {
Album[i].block.classList.remove('selected');
}
}
visu.style.background = self.place;
window.console.log(visu.style.background);
window.console.log(self.place);
this.classList.add('selected');
};
Album[Album.length] = this;
}
alternatively, using bind
function Photo(nb, folder) {
this.block = document.querySelector("#list_img_" + nb);
this.place = 'url(../src/' + folder + '/' + nb + '.jpg)';
this.block.onclick = function () {
for (i = 0; i < Album.length; i += 1) {
window.console.log(Album[i].block);
if (Album[i].block.classList.contains('selected')) {
Album[i].block.classList.remove('selected');
}
}
visu.style.background = this.place;
window.console.log(visu.style.background);
window.console.log(this.place);
this.block.classList.add('selected');
}.bind(this);
Album[Album.length] = this;
}
note: now you go back to this.block.classList.add('selected') as this is now the this you were expecting before
You can access to it with 'this' (as mentionned in the previous answer) or with the event target :
this.block.onclick = function (e) {
for (i = 0; i < Album.length; i += 1) {
window.console.log(Album[i].block);
if (Album[i].block.classList.contains('selected')) {
Album[i].block.classList.remove('selected');
}
}
visu.style.background = this.place;
window.console.log(visu.style.background);
window.console.log(this.place);
e.target.classList.add('selected');
};
I've just asked this question (multiple errors while momoizing function inside another function) and I've got a nice answer... but! Just to understand a little more about JavaScript, I'd like to know if the momoized function can be written in this style:
function main () {
function memoized_f(){
//memoizing code
}
}
EDIT: Please notice I'm not asking what is the difference in the code above, I'm asking if it is possible to memoize the second one!
So, how to rewrite this?
function main() {
var create_node = (function() {
var memo;
console.log("memo: " + memo);
console.log("create_node")
function f() {
var value;
if (memo) {
value = memo.cloneNode();
console.log("clone node");
console.log(value);
} else {
var value = document.createElement("div");
value.innerHTML = "hello";
console.log("new node");
console.log("value: " + value);
memo = value;
}
return value;
}
return f;
})();
var collection = [];
for (var i = 0; i < 10; i++) {
collection.push(create_node());
};
// Display results
for (var i = 0; i < 10; i++) {
console.log(i + ". " + collection[i]);
}
}
main();
Since functions in javascript are an object, you can just use that function to memoize the value. I think it would make more sense in fib example, but here is your original post.
function main() {
// memoizing function
function create_node() {
var value;
// read from memo on function object
if (create_node.memo) {
value = create_node.memo.cloneNode();
value.innerHTML = 'cloned';
console.log("clone node");
console.log(value);
} else {
var value = document.createElement("div");
value.innerHTML = "hello";
console.log("new node");
console.log("value: " + value);
// retain memo on the function object
create_node.memo = value;
}
return value;
}
var collection = [];
for (var i = 0; i < 10; i++) {
collection.push(create_node());
};
// Display results
for (var i = 0; i < 10; i++) {
console.log(i + ". " + collection[i]);
document.getElementById('container').appendChild(collection[i]);
}
}
main();
<div id="container"></div>
Your actual memoized function is f. The (function(){ ... })() IIFE wrapping merely provides a an additional closure-layer to hide the variable memo so that it is visible only to f.
To repeat that: the (function(){...})() expression is not your memoized function. It is wrapping that restricts visibility of an inner variable and ultimately returns your memoized function f, which is defined inside of it. If you were okay with exposing memo to other code in main and not restrict its visibility to the memoized function only, you could eliminate the IIFE wrapping entirely and simply rename f to create_node:
function main() {
var memo;
function create_node() {
var value;
if (memo) { value = memo.cloneNode(); }
else {
var value = document.createElement("div");
value.innerHTML = "hello";
memo = value;
}
return value;
}
// use `create_node` as originally done
// NOTE that other code can manipulate `memo` now, though!
}
main();
If you like, you can supply the closure wrapping via a function declaration instead of IIFE:
function createMemoizedFunc() {
var memo;
function f() {
var value;
if (memo) { value = memo.cloneNode(); }
else {
var value = document.createElement("div");
value.innerHTML = "hello";
memo = value;
}
return value;
}
return f;
}
var create_node = createMemoizedFunc();
I'm facing for the first time OOP in JavaScript and all the troubles that comes with it...
I have this function/Object/class/whatever which has a method mainLoop() that should display some falling text - just like in the movie The Matrix. When I call it though I get undefined variables errors and using the debugger I see that inside mainLoop() this is pointing to Window instead of the object that called the method.
Here's the code:
function Matrix(config) {
return {
//[...lots of other vars...],
drops: [],
lines: [],
//final string to put in the container
str: "",
mainLoop: function(){
var tmp = "";
//randomly create a "character drop"
//(not if there's already a drop)
for(var i = 0; i < this.cols; i++){
if(this.drops[i] == 0 && Math.random() < this.freq){
this.drops[i] = irandom(this.rows) + 1;//new drop
tmp += randomChar();//output drop
}
else tmp += lines[0].charAt(i);
}
this.lines[0] = tmp; // <-------------- ERROR
//update already created drops
tmp = "";
for(var j = 0; j < this.cols; j++){
if(this.drops[j] > 0){
tmp += this.randomChar();
this.drops[j]--;
}
else tmp += " ";
}
this.lines[this.rowIndex] = tmp;
this.rowIndex = (this.rowIndex+1) % this.rows;
//render the entire text
this.str = "";
for(var l in this.lines)
this.str += l + "<br/>";
$(container).html = this.str;
},
start: function(){
for(var i = 0; i < this.cols; i++)
this.drops[i] = 0;
timer = setInterval(this.mainLoop ,this.delay);
},
stop: function(){
clearInterval(this.timer);
},
randomChar: function(){
return this.chars.charAt(irandom(this.chars.length));
},
irandom: function(x){
return Math.floor(Math.random()*x);
}
}
};
And then I call this function like this:
var config = {
container: "#container",
rows: 20,
cols: 20,
delay: 2000
};
var m = Matrix(config);
m.start();
The browser console says:
TypeError: this.lines is undefined
(code comment shows the exact point of the error). Furthermore, the debugger says that, at that point, this points to Window, not to m as I would expect... what's wrong with my reasoning? Thanks in advance for any help.
Alter your start function:
start: function(){
var self = this;
for(var i = 0; i < this.cols; i++)
this.drops[i] = 0;
timer = setInterval(function() {
self.mainLoop();
}, this.delay);
}
this was poiting at window because the scope has changed.
Since JavaScript is prototype-based, maybe (if you haven't already) try doing it following this model:
function Matrix(config) {
this.property = config.firstmember;
this.property2 = config.secondmember;
return function() { console.log('hello world') };
}
Matrix.prototype = {
someMethod: function() {
//do something
},
start: function() {
//console.log('hello world');
},
stop: function() {
//do something
}
}
var config = {
firstMember: 'foo',
secondMember: 'bar'
}
var m = new Matrix(config);
//console output: "hello world"
/*var m = {
property: 'foo',
property2: 'bar',
____proto___: Matrix: {
someMethod: function() {
//do something
},
start: function() {
//console.log('hello world');
},
stop: function() {
//do something
}
}
}*/
Also, see the answer to this question regarding setInterval.
setInterval callback functions are members of the Window object; therefore, 'this' refers to the window. You will need to pass in a parameter of the current object to the callback that is inside setInterval. See the link above for more details.
If you need a reference to the calling object, I'd suggest passing it down as a parameter to the function.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
JavaScript closures and variable scope
Assign click handlers in for loop
I have this script:
var MyClass = {
MyArray: new Array(0, 1, 2, 3, 4),
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
var cen = document.getElementById("cen_" + i); // It is an img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc1(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}
},
MyFunc2: function(id) {
alert(id);
}
}
My problem is that, at this line :cen.onclick = function() { MyClass.MyFunc1(i); }; the argument sent to MyFunc2 is always -1. The MyFunc1 function should create four images, each one with an onclick event. When you click on each image, the MyFunc2 function should show the corresponding i value. It looks like the i value is not "saved" for each event and image element created, but only its "pointer".
Thanks!
You should be familiar with the concept of JavaScript closures to understand why this happens. If you are, then you should remember that every instance of the
function() { MyClass.MyFunc1(i); };
function closure contains i's value of -1 (since it is the final value of this variable after the entire loop finishes executing.) To avoid this, you might either use bind:
cen.onclick = (function(i) { MyClass.MyFunc1(i); }).bind(null, i);
or use an explicitly created closure with the proper i value.
It's a normal case and misunderstand of closures, see this thread and you may get some clue, the simply way to fix this problem is to wrap your for loop body with an Immediate Invoked Function Expression
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}(i));
}
}
You are capturing a variable that changes inside the loop, so you always get the last value of i.
You can easily fix that by creating a closure:
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
})(i);
}
},