I'm trying to find out more about closures in Javascript and was going through this: https://developer.mozilla.org/en/JavaScript/Guide/Closures#Practical_closures
According to this article, by using such a function:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
We can then make use of such statements to increase/decrease the font-size of text on a page:
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
While I understand the concept here - i.e. size12, size14 and size16 become closures that allow access to the internal function, I can't help but feel that this is unnecessary. Isn't it easier to just have:
function makeSizer(size) {
document.body.style.fontSize = size + 'px';
}
, and then invoke it with these?
document.getElementById('size-12').onclick = makeSizer(12);
document.getElementById('size-14').onclick = makeSizer(14);
document.getElementById('size-16').onclick = makeSizer(16);
Can anyone tell me if my thinking is right - or maybe I'm just a novice to Javascript and doesn't understand the advantage to using closure in this scenario, in which case I'll be most glad if you can explain the advantage of doing so.
Thanks in advance guys.
No, you can't do that.
It's as if you had written:
document.getElementById('size-12').onclick = (function(size) {
document.body.style.fontSize = size + 'px';
})(12);
The function gets immediately invoked, the style will be applied straight away, and no .onclick handler gets registered because the return value of the function is undefined.
The real point of the example is to show that you can return a function from another function, and that you can then assign that result to an event handler.
If you had left makeSizer() unmodified then you could assign the handlers as proposed without intermediate variables, i.e.:
document.getElementById('size-12').onclick = makeSizer(12);
but that won't work if you change makeSizer() the way you described.
It is also less efficient than storing the "sizer" in a variable if you use the same sizer more than once.
For the example you presented, of course closure is not necessary, but I guess it is just to make it simple to present the concept. There are cases though that closure is the best solution to use: think about how to implement a "private" attribute in javascript or when you need curryng to encapsulate arguments (ie, for a callback function).
I hope the following example helps:
var makeSequencer = function() {
var _count = 0; // not accessible outside this function
var sequencer = function () {
return _count++;
}
return sequencer;
}
var fnext = makeSequencer();
var v0 = fnext(); // v0 = 0;
var v1 = fnext(); // v1 = 1;
var vz = fnext._count // vz = undefined
Yes, those variables (sizeN) are unnecessary. You can directly assign the result of makeSizer() as handlers, which looks far better.
But, the use of these variables is not the concept of closures. The closure in this example is the function makeSizer, which returns a function (even without arguments), which still has access to the size variable.
Though, you need to see the difference between
function makeSizer(size) {
return function resize() {
document.body.style.fontSize = size + 'px';
};
}
and
function resize(size) {
document.body.style.fontSize = size + 'px';
}
Executing makeSizer(5) does not do anything, it returns a function that sets the size to the pre-defined size when invoked. Instead executing resize(5) does set the size directly. You can't use the result of the latter function as an event handler.
Related
I have two functions. The first function calculates variables on domcontentloaded and resize. The second function triggers on both domcontentloaded and scroll. I need 3 variables from the first function inside the second function to calculate some stuff. I am trying to get the return variable array from 1st function upme() to use inside second function doso() - I am getting this error poss isn't defined at htmldocument.doso
JAVASCRIPT
document.addEventListener('DOMContentLoaded', upme);
window.addEventListener('resize', upme);
function upme()
{
var rome = document.getElementById("out-cmnt");
var rect = rome.getBoundingClientRect();
// console.log(rect.top, rect.right, rect.bottom, rect.left);
var poss = rect.top + window.scrollY; var iwwr = window.innerWidth;
var koss = rect.bottom + window.scrollY; var loss = koss - poss;
return [poss, loss];
}
document.addEventListener('DOMContentLoaded', doso);
window.addEventListener('scroll', doso);
function doso()
{
lopp = document.getElementById("Web_1920__1");
hope = lopp.clientHeight; const meme = document.body.scrollHeight;
const keke = hope/meme; const scsc = window.scrollY;
var innr = window.innerHeight;
var saka = upme(); // problem here calling 1st function
var noss = poss - innr + loss; // problem here
if(scsc > noss && window.matchMedia("(min-width: 765px)").matches)
{
// doing something
}
}
doso();
How can I successfully get those variables like poss, loss, koss inside the second function doso() - ? Please help me out. Thanks to everyone involved in this community.
When calling a function, you can only get values you chose to return.
If you want to use them by name you need to declare them with the wanted names. In your example, you only have an array called saka with [0] being poss and [1] being loss.
First, here you can only access poss and loss because that's the only two variables you are returning in the upme function.
You return them as an array, there is an easy way in javascript to retrieve and name variables returned in an array:
var [poss, loss] = upme();
With this snippet of code, you say that the array you are returning from this function is of size 2 and that you want to declare 1 variable for each element, by naming them respectively poss and loss.
If you need more variables, just return more of them in upme then declare and name them when calling the function.
You could also create an object, but this solution is good enough for your problem.
Change this line:
var noss = poss - innr + loss;
to:
var noss = saka.poss - innr + saka.loss;
I'm reading the explanation of closures on Mozilla developer site and am struggling a bit. Please have a look at the following code from Mozilla website. I kind of understand how it works but I'd think that the code below my comments should also work. Why does it not work if one click on 18 and 20?
/* mozilla dev code */
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
/* end of mozilla dev example */
/* my code starts */
/* see - no inner function below */
function makeS(size) {
document.body.style.fontSize = size + 'px'
}
/* Let's see if that works */
var size18 = makeS(18);
document.getElementById('size-18').onclick = size18;
/* What about that? */
document.getElementById('size-20').onclick = makeS(20);
Why
CodePen:
http://codepen.io/wasteland/pen/qqoooW
makeS(18) immediately invokes the function and changes the size. What you assign to the onclick event in that case is actually undefined, since that's what the function returns when invoked, as it has no explicit return.
function makeS(size) {
document.body.style.fontSize = size + 'px'
}
console.log("font size before calling makeS:", document.body.style.fontSize); //it's actually an empty string, hence why it doesn't show up in the output
var size18 = makeS(18);
console.log("font size after calling makeS:", document.body.style.fontSize);
console.log("what is size18?", typeof size18);
By contrast, makeSizer(18) will create a new function that when called, will change the size.
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
console.log("font size before calling makeSizer:", document.body.style.fontSize); //it's actually an empty string, hence why it doesn't show up in the output
var size18Function = makeSizer(18);
console.log("font size after calling makeSizer:", document.body.style.fontSize); //it's still an empty string
console.log("what is size18Function?", typeof size18Function);
//let's call it now
size18Function();
console.log("font size after calling size18Function:", document.body.style.fontSize); //it's now changed
A closure is fact of 'remembering' variables of the scope chain by the function when it is defined. Since function argument is just a local variable during execution, this:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
is an equivalent to:
function makeSizer() {
var size = 1; // or some other value
return function() {
document.body.style.fontSize = size + 'px';
};
}
In the above case, the anonymous function, which is the return value, will 'remember' value of size variable and use it each time it is called.
MakeSizer should be treated as a 'factory' function that provides different values for local variable size.
You cannot acheive this kind of 'remembering' without using a function definition statement inside of 'factory' function.
Onclick of 18 your calling makeS. It does not return anything to size18,siz18 would be undefined here,if you want to make it work through clousre change makeS(),to return a method as similar to makeSizer
check this snippet
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
function makeS(size) {
return function() {
document.body.style.fontSize = size + 'px';
}
}
/* Let's see if that works */
var size18 = makeS(18);
document.getElementById('size-18').onclick = size18;
/* What about that? */
document.getElementById('size-20').onclick = makeS(20);
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
<h1>How closures work</h1>
12
14
16
<div>
18
20
</div>
Hope this helps
A closure refers to the concept of a function retaining access to variables in the lexical scope that it was declared within, even as it is passed, and called in different locations and scopes throughout a program.
Huh?
What does this mean then? Consider the below piece of code
function functionFactory() {
var x = 1;
return function () {
return ++x;
};
}
var first = functionFactory();
var second = functionFactory();
console.log(first()); // outputs 1
console.log(first()); // outputs 2
console.log(first()); // outputs 3
console.log(second()); // outputs 1
Each time functionFactory is called, it declares a private variable x, and then returns an anonymous function that mutates that variable. We can see in the output that each subsequent call of that returned function, first, returns a different value.
When we call second though, it returns 1. Why? Because it was created by a separate invokation of functionFactory, and so it has its own reference to its own closure over x.
Why do I care?
Because this is super-useful. In programming languages (like javascript, but other functional languages too) which treat functions as first-class variables, we can use closures for encapsulation of private variables.
So to your actual question...
The reason why your code is not working, is because onclick expects you to assign a function to it - instead you are assigning the output of your makeS function, which is void. makeSizer is returning a function, that establishes a closure over whatever number value is passed into it as a parameter, and is then assigned to the onclick variable of each element.
Hope this makes things clearer for you.
in my scriptfile.js I layout a simple text re-sizing function. On its own, the function works as expected. However, when I try to add $(window).resize(fitheadliner()); to my scriptfile to fire the the fitheadliner() function on the resize event, it get a "Uncaught ReferenceError: fitheadliner is not defined" error. I have moved the resize function around the scriptfile thinking it may be a scope issue to no avail. Here is the contents of the file:
( function( $ ) {
$.fn.fitheadliner = function() {
this.each(function() {
var
$headline = $(this),
$parent = $headline.parent();
var
textW = $headline.width(),
parentW = $parent.width(),
ratio = parentW / textW;
var
originalSize = parseFloat($headline.css('font-size')),
newSize = originalSize * (0.9 * ratio);
$headline.css("font-size", newSize);
});
};
$(window).resize(fitheadliner());
} )( jQuery );
Here:
$.fn.fitheadliner = …
fitheadliner is defined as a property of $.fn.
But here:
$(window).resize(fitheadliner());
you are attempting to access it as a variable (and call it). Consider:
(function($) {
function fitheadliner() {
...
}
// Assign a reference, don't call the function
$(window).resize(fitheadliner);
}(jQuery));
However, you have a further issue from:
this.each(function() {
The function is called with window as this, and window doesn't have an each method. I don't understand how you aren't seeing other errors (or erroneous behaviour). Perhaps this should be replaced with a selector:
$(<selector>).each(...);
It's not quite a scoping issue. More like a qualification issue.
When this line executes
$(window).resize(fitheadliner());
You are saying, run $(window), then run fitheadliner(), then run the .resize method of the return value of the first call, passing it the return value of the first function call.
It's easy to think that the manner in which you are calling fitheadliner() would tie it to the $ object, but it doesn't. There's no reason it would. Each expression is evaluated independently and then chained appropriately.
Therefore, this expression is looking for a symbol in scope named fitheadliner that must be of type function. There are no symbols in scope with that name. There is, however, a symbol named $.fn.fitheadliner
To get past this error, you need to fully-qualify the reference, like
$(window).resize($.fn.fitheadliner());
But the fact is, I don't think that is totally what you want either. .resize takes a handler function. fitheadliner does not return a function, or return anything. It actually does some work. So I think what you meant to do was to pass a reference to fitheadliner to resize.
That's easy - take the paranthesis out.
$(window).resize($.fn.fitheadliner);
Now, even better, there is probably no reason to attach fitheadliner to the jQuery prototype like that. Try this. It may more closer to what you were trying to do.
( function( $ ) {
function fitheadliner() {
this.each(function() {
var
$parent = $(this).parent(),
$headline = $(this);
var
textW = $(this).width(),
parentW = $parent.width(),
ratio = parentW / textW;
var
originalSize = parseFloat($(this).css('font-size')),
newSize = originalSize * (0.9 * ratio);
$(this).css("font-size", newSize);
});
}
$(window).resize(fitheadliner);
} )( jQuery );
This defines a function in scope called fitheadliner and then passes a reference to it to resize.
Personally, I would take it one step further and inline the function anonymously since it does not need to be reused. But it's a matter of form/preference for me. There's semantically no difference.
( function( $ ) {
$(window).resize(function fitheadliner() {
this.each(function() {
var
$parent = $(this).parent(),
$headline = $(this);
var
textW = $(this).width(),
parentW = $parent.width(),
ratio = parentW / textW;
var
originalSize = parseFloat($(this).css('font-size')),
newSize = originalSize * (0.9 * ratio);
$(this).css("font-size", newSize);
});
});
} )( jQuery );
This question already has an answer here:
passing this.method in setTimeout doesn't work?
(1 answer)
Closed 8 years ago.
In the following snippet I try to access the property offset from within the member function shift(). As it seems, I cannot access it this way, because console.log reports Offset: NaN:
function shiftImg() {
this.offset = 0;
this.shift =
function() {
this.offset++;
console.log("Offset: " + this.offset);
};
}
productImg = new shiftImg;
window.setInterval(productImg.shift, 100);
However, converting the code above from a template paradigm to a closure paradigm works as I'd expect:
function shiftImg() {
var offset = 0;
return {
shift: function() {
offset++;
console.log("Offset: " + offset);
}
}
}
productImg = shiftImg();
window.setInterval(productImg.shift, 100);
In my first example, why I cannot access offset via the operator this?
My Answer:
I'll post here my solution, as I cannot append a standalone answer.
Browsing again into the mess of the horribly-written MDN's documentation, I learned of the bind method:
function shiftImg() {
this.offset = 0;
this.shift =
function() {
this.offset++;
var img = document.getElementById('img');
img.style.paddingLeft = this.offset + 'px';
console.log("Offset: " + this.offset);
};
}
productImg = new shiftImg;
window.setInterval(productImg.shift.bind(productImg), 100);
The nested function doesn't have it's own this context (it'll simply refer to the window), so assign a variable to the this method within shiftImg to which you can refer in the nested function:
function shiftImg() {
var self = this;
this.offset = 0;
this.shift =
function() {
self.offset++;
console.log("Offset: " + self.offset);
};
}
productImg = new shiftImg();
window.setInterval(productImg.shift, 100);
The reason you need to do this is because the call to setInterval which invokes the method, is run in a separate execution context, where this is equal to the window. If you called this.shift() from within shiftImg() you'll see that you it works just fine without the need to add self. See this MDN article for more.
Alternatively you pass an anonymous function to the callback method in setInterval:
window.setInterval(function() {
productImg.shift();
}, 100);
If you use objects and jQuery then you'll find into this problem quite a lot, and jQuery's $.proxy utility method makes doing similar things to above fairly easy.
In the first example, the context of this for the offset is not the same context as shiftImage. The second function closure changes the scope of this.
I wanted to call a function when all required images are loaded. The number of images is known in advance, so I tried attaching a function call to the onload event of each image and count the number of times it was called.
<html>
<head>
<script>
var tractor;
function Tractor()
{
this.init_graphics();
}
Tractor.prototype.init_graphics = function()
{
this.gr_max = 3;
this.load_count = 0;
this.loading_complete(); // #1 test call, works OK
this.img1 = new Image();
this.img1.onload = this.loading_complete; // #2 gets called, but gr_max = undefined, load_count = NaN
this.img1.src = "http://dl.dropbox.com/u/217824/tmp/rearwheel.gif"; //just a test image
}
Tractor.prototype.loading_complete = function()
{
this.load_count += 1;
alert("this.loading_complete, load_count = " + this.load_count + ", gr_max = " + this.gr_max);
if(this.load_count >= this.gr_max) {this.proceed();}
};
function start()
{
tractor = new Tractor();
}
</script>
</head>
<body onload="start();">
</body>
</html>
When it's just called from another function of the object (see #1), it works just as I expected. When, however, it's called from onload event (see #2), the variables become "undefined" or "NaN" or something. What's happening? What am I doing wrong? How do I make it work?
I don't remember ever creating my own objects in Javascript before, so I certainly deeply apologize for this "what's wrong with my code" kind of question. I used this article as a reference, section 1.2, mainly.
Just in case, I put the same code on http://jsfiddle.net/ffJLn/
bind the context to the callback:
this.img1.onload = this.loading_complete.bind(this);
See: http://jsfiddle.net/ffJLn/1/ (same as yours but with this addition)
Here's an explanation of how bind works in detail: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
The basic idea is that it makes this in the bound function equal to whatever you pass as the parameter to bind.
Another option is to create a closure:
var self = this;
this.img1.onload = function() { self.loading_complete() };
Closures are functions that keep references to their context (in fact, all functions in javascript work this way). So here you are creating an anonymous function that keeps a reference to self. So this is another way to maintain context and for loading_complete to have the right this.
See: http://jsfiddle.net/ffJLn/2/ (same as yours but with the second possibility)
When #2 gets called, your this has changed. this now refers to the new Image() rather than the Tractor object.
Try changing...
this.img1.onload = this.loading_complete;
to
var that = this;
this.img1.onload = function() { that.loading_complete(); };
You can now use es6 arrow functions which provide lexical binding:
this.img1.onload = () => { this.loading_complete(); };