Studying about closures, I looked at the Developer's Mozilla article about it and saw the code below:
var counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // 0.
counter.increment();
counter.increment();
console.log(counter.value()); // 2.
counter.decrement();
console.log(counter.value()); // 1.
I'm confused about how the function is attributed to the variable counter, because the function is initially envolved by those parentheses, and after all, there are also two unreaseble parentheses together... I just wondered, what's the reason of that syntax? I certainly would do:
var counter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
};
But then I got the error
Uncaught TypeError: counter.value is not a function
When I do console.log(counter.value());
Can someone please explain it to me?
What you're looking at is an Immediately Invoked Function Expression or IIFE. This code is creating a function, then immediately calling that function, then assigning the return value of the function to counter. So counter isn't a function, it's an object with three properties: increment, decrement, and value.
The reason that they used an IIFE was to make what's essentially a private variable. privateCounter is only in scope to other code inside that function, which means only increment, decrement, and value can access it.
If they didn't care about making the variable private, the equivalent code would be:
var publicCounter = 0;
function changeBy(val) {
publicCounter += val;
}
var counter = {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return publicCounter;
}
}
Related
The purpose of the example code below is to be able to restrict a function from printing something via console.log (a loop in this case) if the function is executed twice.
However, the function uses the global variable "condition" and I want to find a way to avoid this. I've been playing around with workarounds, but to no avail. Also, I have checked sources online, but relate to other more complex examples which are beyond my level. This is a simpler example, but haven't been able to crack this.
Grateful for some guidance. Thanks.
let condition = false;
const testFunc = function (value) {
for (let i = 0; i < 10; i++) {
if (!condition) {
console.log(value + i);
}
}
condition = true;
};
testFunc(5);
testFunc(5);
The usual answer is to create yet another scope to hold the state:
function once(fn) {
let called = false
return function(...args) {
if (!called) {
fn(...args)
called = true
}
}
}
const test = once(function() {
console.log('hello')
})
test() // hello
test() // nothing
Thanks for your feedback - I considered the use of using closure by way of returning a function within another.
Would this be a viable option in order to avoid a global variable? Note that "func" needs to be declared globally -
const testFuncEncaps = function (value) {
let trueOrFalse = false;
return function () {
for (let i = 0; i < 10; i++) {
if (!trueOrFalse) {
console.log(value + i);
}
}
trueOrFalse = true;
};
};
let func = testFuncEncaps(5);
func();
func();
I know something very basic I am missing . But can't find what's wrong with it ?
<!DOCTYPE html>
<html>
<body>
<p>A function can access variables defined inside the function:</p>
<button type="button" onclick="alert(makeMyCounter.increment)">Click Me!</button>
<p id="demo"></p>
<script>
var makeMyCounter = function () {
var privateCounter = 0;
return {
increment : function() {
privateCounter += 1;
},
decrement : function() {
privateCounter += -1;
}
}
}();
</script>
</body>
Why is privateCounter returning undefined ? But when debugged via browser , it is being assigned 1 though .
You are using method reference as property, to call method properly use it like that:
makeMyCounter.increment()
next thing You not return in method so it will be undefined. Add return:
return {
increment : function() {
return privateCounter += 1;
},
decrement : function() {
return privateCounter += -1;
}
}
privateCounter isn't a function, so it doesn't return anything.
increment is a function, but you didn't put () after it, so you aren't calling it and it will alert the result of converting the function to a string.
If you were to call it (alert(makeMyCounter.increment());), then it would return undefined because it has no return statement.
When you run your function you just increment its value but there is no return statement.
In javascript if your function has no return statement undefined will be returned by default.
if you need your new value return privateCounter in increment and decrement functions
return {
increment : function() {
privateCounter += 1;
return privateCounter;
},
decrement : function() {
privateCounter += -1;
return privateCounter;
}
}
i have a problem accessing variable inside method in OOP.
this is my code :
var slideshow = {
elSet : $(".slideshow"),
next : function() {
},
swipe : function() {
var clear = this.autorun().loop;
onSwipe(function() {
clearInterval(clear); // not working
});
},
autorun : function() {
var self = this;
var loop = setInterval(function() {
self.next();
}, 5000);
},
initial : function() {
this.swipe();
this.autorun();
}
}
slideshow.initial();
i want to clearInterval from variable loop,
on browser console return error TypeError: this.loop(...) is undefined
what's wrong with my code?
Just assign the interval id returned by setInterval to a variable you can access, or like Barmar's answer return it.
var slideshow = {
elSet: $(".slideshow"),
next: function() {
},
swipe: function() {
var self = this;
onSwipe(function() {
//use the interval id to cancel
clearInterval(self.intervalRef);
});
},
// variable to store the interval id
intervalRef: null,
autorun: function() {
var self = this;
//assign the interval id generated by setInterval to a variable you can access
this.intervalRef = setInterval(function() {
self.next();
}, 5000);
},
initial: function() {
this.swipe();
this.autorun();
}
}
slideshow.initial();
Issues:
var clear = this.autorun().loop; Here this will have scope swipe and not object.
var loop = setInterval(function() {}) Here loop will have scope of autorun and will expire after function execution is over.
You can try something like this:
JSFiddle
function SlideShow() {
// self will hold current object reference for all functions
var self = this;
var interval = null;
function next() {
console.log('next');
}
function swipe() {
onSwipe(function() {
console.log("Clearint Interval")
clearInterval(interval);
});
}
// Private function
function onSwipe(callback) {
console.log("On Swipe");
// Check if value is passed and its a function
if (callback && typeof(callback) === 'function')
callback();
}
function loop() {
interval = setInterval(function() {
next();
}, 5000);
}
function init() {
swipe();
loop();
}
// Public properties
return {
elSet: $(".slideshow"),
next: next,
swipe: swipe,
loop: loop,
initial: init,
interval: interval
}
}
// Create a new instance of SlideShow
var slideshow = new SlideShow();
slideshow.initial();
In the swipe function, you have:
var clear = this.autorun().loop;
This expects this.autorun() to return an object, and tries to access the loop property of that object, which should contain the ID of an interval function. But autorun doesn't return anything, let alone an object with a loop property.
Change autorun to:
autorun : function() {
var self = this;
var loop = setInterval(function() {
self.next();
}, 5000);
return loop;
}
Then you can do:
var clear = this.autorun();
You also shouldn't call this.autorun() in the initial function. It's already called by this.swipe(). Running it again will cause two interval functions to run, and the ID of the second one isn't saved anywhere so that you can clear it.
The following test code, written using Mocha.js fails. I expect the someVal to be increased 3 times and equal 3 in the last test. The issue came up in more complex scenario where I used value set in outer before block to set up another in inner beforeEach block. Simplified case:
describe('increasing 3 times', function() {
before(function() {
this.instanceThis = this;
return this.someVal = 0;
});
beforeEach(function() {
return this.someVal += 1;
});
return describe('going deeper', function() {
before(function() {
return this.someVal += 1;
});
beforeEach(function() {
return this.someVal += 1;
});
return it('has increased someVal to 3', function() {
return this.someVal.should.equal(3);
});
});
});
Explanation
I don't know of any version of Mocha that would run the code you show in your question without error. For your code to work, it would have to be written like this:
require("chai").should();
describe('increasing 3 times', function() {
before(function() {
this.someVal = 0;
});
beforeEach(function() {
this.someVal += 1;
});
describe('going deeper', function() {
var parent_ctx = this.parent.ctx;
before(function() {
parent_ctx.someVal += 1;
// The line above is equivalent to this:
// this.test.parent.ctx.someVal += 1;
});
beforeEach(function() {
parent_ctx.someVal += 1;
});
it('has increased someVal to 3', function() {
parent_ctx.someVal.should.equal(3);
// The above line is equivalent to this:
// this.test.parent.parent.ctx.someVal.should.equal(3);
});
});
});
Inside the function passed to describe and the functions passed to the hooks of a describe block (before, beforeAll, etc.) the value of this is a "context" object which is the same for the describe and all of its own hooks (not the hooks of other describe calls nested in it). So when you assign to this, it assigns to the context. If you want to access this context inside nested calls to describe or in tests you have to walk up the tree of describe and test objects.
Solution
I would do it exactly the same way Second Rikudo suggested: use a variable scoped to your uppermost describe call. For the sake of completeness:
require("chai").should();
describe('increasing 3 times', function() {
var someVal;
before(function() {
someVal = 0;
});
beforeEach(function() {
someVal += 1;
});
describe('going deeper', function() {
before(function() {
someVal += 1;
});
beforeEach(function() {
someVal += 1;
});
it('has increased someVal to 3', function() {
someVal.should.equal(3);
});
});
});
this is not what you think it is. this gets redefined in every function() {} block (unless Mocha calls them in some specific way I'm not familiar with).
What you want is to use scope in your advantage:
describe('increasing 3 times', function() {
var someVal; // Initialization.
before(function() {
someVal = 0; //No need to return from before()
});
beforeEach(function() {
someVal += 1;
});
describe('going deeper', function() {
before(function() {
someVal += 1;
});
beforeEach(function() {
someVal += 1;
});
return it('has increased someVal to 3', function() {
someVal.should.equal(3);
});
});
});
Also, you don't need to return so much. In fact, you almost never have to return in test code.
Can anyone please tell my why this doesn't work?
(function() {
window.Test = {};
})();
Test.Timers = {
c: null,
startTimer: function() { c = 0; setTimeout(this.doWork, 0); },
doWork: function() {
c++;
alert(c);
setTimeout(this.doWork, 0);
}
};
When I call Test.Timers.startTimer(), it only alerts once with a 1.
Thanks
A method doesn't "remember" its owner (its this); you can copy a method from one object to another, and treat it like any other function. It only has the right owner when you actually call it using dot-notation, e.g. this.doWork().
So your problem is that you're passing the function this.doWork to setTimeout, and then it gets called as a function without knowing its owner, and suddenly its this is window instead of your timer object. To fix this, you need to keep track of your this yourself. For example, you might write:
Test.Timers = (function () {
var newTimer = {
c: null,
startTimer: function() {
this.c = 0;
setTimeout(function () { newTimer.doWork(); }, 0);
},
doWork: function() {
this.c++;
alert(this.c);
setTimeout(function () { newTimer.doWork(); }, 0);
}
};
return newTimer;
})();
or:
Test.Timers = (function () {
var startTimer = function() {
newTimer.c = 0;
setTimeout(doWork, 0);
};
var doWork = function() {
newTimer.c++;
alert(newTimer.c);
setTimeout(doWork, 0);
};
var newTimer = {
c: null,
startTimer: startTimer,
doWork: doWork
};
return newTimer;
})();
(Note that I also changed c to this.c or newTimer.c where necessary, since your version refers repeatedly to window.c. Also note that in the second version, if you don't need external code to be able to access c, you can change it to a local variable, making things cleaner.)
As per your comment to ruakh's answer, I prefer the following approach, myself:
Test.Timers = (function () {
var this_ = {
c: null,
startTimer: function() { this_.c = 0; setTimeout(this_.doWork, 0); },
doWork: function() {
this_.c++;
alert(this_.c);
setTimeout(this_.doWork, 0);
}
};
return this_;
})();
That way, the meaning is clear as this_ looks like this, and all you have to do is get used to the closure pattern of making an anonymous function and calling it right away. Also note that I fixed your reference to c to refer to this_.c instead of a global variable c.
Alternatively, you can use .bind() to bind the function's this to a particular thing. This is built-in to Chrome's V8, at least, and perhaps Firefox as well:
Test.Timers = {
c: null,
startTimer: function() { this.c = 0; setTimeout(this.doWork, 0); },
doWork: function() {
this.c++;
alert(this.c);
setTimeout(this.doWork, 0);
}
};
Test.Timers.startTimer = Test.Timers.startTimer.bind(Test.Timers);
Test.Timers.doWork = Test.Timers.doWork.bind(Test.Timers);