Variable scope in functions - javascript

I have a SCOPE problem. When I declare "var text" outside the function all works. But inside the function it works only in the first part. Here is what I mean:
This is a buffer function. Executing buffer("anything") saves "anything". Executing buffer() - without the properties will return all properties.
buffer("Al")
buffer("ex")
buffer() <= should return Alex
But the SCOPE of "text" is wrong and it does not return the saved properties.
function makeBuffer() {
var text = "";
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
var buffer = makeBuffer;
buffer("One", "Two");
document.write(buffer());

That is normal behaviour.
A variable defined in a given scope goes away when the scope goes away. Each call the to the function creates a new scope.
Declaring the variable outside the function is the standard way to share a value between invocations of it.

What you want is a factory:
function makeBuffer() {
var text = "";
return function buffer() {
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
}
var buffer = makeBuffer();
buffer("One", "Two");
document.write(buffer());

You could do this using an object. This will make your code much more organized.
var Buffer = function() {
this.text = "";
}
Buffer.prototype.append = function() {
for (var i = 0; i < arguments.length; i++) {
this.text += arguments[i];
}
}
Buffer.prototype.get = function() {
return this.text;
}
var buffer = new Buffer();
buffer.append("One", "Two");
document.write(buffer.get());
Using ES6 the syntax gets even sweeter:
class Buffer {
constructor() {
this.text = "";
}
append() {
this.text = this.text.concat(...arguments);
}
get() {
return this.text;
}
}
var buffer = new Buffer();
buffer.append("One", "Two");
document.write(buffer.get());

As Quentin properly pointed out in his answer, that is a normal behavior.
An alternative option to keep the value on your function without declaring the variable outside is scope is to add it as a property to the function itself.
As in JavaScript a function is a first class object, you can put such data directly into function object (like in any other objects).
An example below, please note how to get property text from your function (buffer.text).
function makeBuffer() {
makeBuffer.text = "";
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
makeBuffer.text += arguments[i];
console.log(`New text - (${makeBuffer.text})`);
}
} else {
console.log(`text - (${makeBuffer.text})`);
return makeBuffer.text;
}
}
var buffer = makeBuffer;
buffer("Al", "ex");
console.log(`buffer.text - (${buffer.text})`);
Alternatively consider using a closure in order to keep the value of text between function calls.
Closures are functions that refer to independent (free) variables
(variables that are used locally, but defined in an enclosing scope).
In other words, these functions 'remember' the environment in which
they were created. More info here.
let makeBuffer = function() {
// closure
let text = "";
return function() {
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
};
var buffer = makeBuffer();
buffer("Al", "ex");

Related

JavaScript remove an IIFE event listener

I'm trying to remove click events from a list of id's after adding them with an IIFE like this
function setupPlayer(player){
var squareState = {};
for (i = 0; i < allSquares.length; i++) {
if(allSquares[i].innerHTML === "") {
// set up a click event for each square
document.getElementById(allSquares[i].getAttribute('id')).addEventListener('click', (clickSquare)(i));
}
}
}
The clickSquare function returns
function clickSquare(i){
var num = i;
return function() {
document.getElementById(allSquares[num].getAttribute('id')).innerHTML=player;
}
}
Then I try to remove them with
function removeClickEvents(){
for (let i = 0; i < allSquares.length; i++) {
document.getElementById(allSquares[i].getAttribute('id')).removeEventListener('click', clickSquare);
}
}
I've tried naming the returned anonymous function and using removeEventListener on that to no avail.
To remove event listener from a DOM element you need to pass the same function you used while adding event listener, as the parameter.
In javascript when you create an object it creates a new instance of that object class, so it won't be equal to another object even if it is created with same parameters
Example:
{} != {} // returns true
[] != [] // returns true
Same goes with function, whenever you write function (){} it creates a new instance of Function class.
Example:
function a() {
return function b() {}
}
a() != a() // returns true
Solution:
So for you to be able to remove the event listeners, you will have to store the functions you have passed to addEventListener
var listeners = [];
function setupPlayer(player) {
var squareState = {};
for (i = 0; i < allSquares.length; i++) {
if(allSquares[i].innerHTML === "") {
listeners[i] = clickSquare(i);
document.getElementById(allSquares[i].getAttribute('id')).addEventListener('click', listeners[i]);
}
}
}
function clickSquare(i) {
var num = i;
return function() {
document.getElementById(allSquares[num].getAttribute('id')).innerHTML=player;
}
}
function removeClickEvents() {
for (let i = 0; i < allSquares.length; i++) {
if(listeners[i]) {
document.getElementById(allSquares[i].getAttribute('id')).removeEventListener('click', listeners[i]);
}
}
}
From your code where you are using
document.getElementById(allSquares[i].getAttribute('id'))
I am assuming that allSquares[i] is a DOM element already, your code can be more simplified
var listeners = [];
function setupPlayer(player) {
var squareState = {};
for (i = 0; i < allSquares.length; i++) {
if(allSquares[i].innerHTML === "") {
listeners[i] = clickSquare(i);
allSquares[i].addEventListener('click', listeners[i]);
}
}
}
function clickSquare(i) {
var num = i;
return function() {
allSquares[num].innerHTML=player;
}
}
function removeClickEvents() {
for (let i = 0; i < allSquares.length; i++) {
if(listeners[i]) {
allSquares[i].removeEventListener('click', listeners[i]);
}
}
}
The function is being called immediately at (clickSquare)(i). At code at Question allSquares appears to be the element itself, clickSquare function can be referenced directly and event.target can be used within event handler to reference the current element in allSquares collection
let player = 123;
setInterval(() => player = Math.random(), 1000);
onload = () => {
let allSquares = document.querySelectorAll("div[id|=square]");
let button = document.querySelector("button");
button.onclick = removeClickEvents;
function setupPlayer(player) {
var squareState = {};
for (let i = 0; i < allSquares.length; i++) {
if (allSquares[i].innerHTML === "click") {
// set up a click event for each square
allSquares[i].addEventListener('click', clickSquare);
}
}
}
function clickSquare(event) {
console.log(event.target);
event.target.innerHTML = player;
}
function removeClickEvents() {
for (let i = 0; i < allSquares.length; i++) {
allSquares[i].removeEventListener('click', clickSquare);
}
}
setupPlayer(player);
}
<div id="square-0">click</div>
<div id="square-1">click</div>
<div id="square-2">click</div>
<button>remove events</button>

Value being undefined? [duplicate]

I'm appending onclick events to elements that I'm creating dynamically. I'm using the code below, this is the important part only.
Test.prototype.Show= function (contents) {
for (i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
}
}
First it says that it's undefined. Then I changed and added:
var content = content[i];
menulink.onclick = function () { return that.ClickContent.apply(that, [content]); };
What is happening now is that it always append the last element to all onclick events( aka elements). What I'm doing wrong here?
It's a classical problem. When the callback is called, the loop is finished so the value of i is content.length.
Use this for example :
Test.prototype.Show= function (contents) {
for (var i = 0; i < contents.length; i++) { // no need to have <= and -1
(function(i){ // creates a new variable i
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
})(i);
}
}
This immediately called function creates a scope for a new variable i, whose value is thus protected.
Better still, separate the code making the handler into a function, both for clarity and to avoid creating and throwing away builder functions unnecessarily:
Test.prototype.Show = function (contents) {
for (var i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = makeHandler(i);
}
function makeHandler(index) {
return function () {
return that.ClickContent.apply(that, [contents[index]]);
};
}
};
A way to avoid this problem altogether, if you don't need compatibility with IE8, is to introduce a scope with forEach, instead of using a for loop:
Test.prototype.Show = function (contents) {
contents.forEach(function(content) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function() {
return that.ClickContent.call(that, content);
};
});
}

Rolling my own Require

Forward
It has come to my attention that this problem was a "Function Scoping - Not Block Scoping" answer, the difference being for ... in opposed to for ... i<n ... i++ The solution could be wrapping the for(var p in ...) { with a function to give them their own scope (much like was done with Array.prototype.forEach). Thank you for your help.
Problem
I'm rolling my own tiny Require.js solution as opposed to the actual Require.js library (no I haven't taken a look at their source).
My callback function never seems to be executed, but I can't figure out why. It's probably a simple logical error, but you know how it goes when you stare at your own code for too long. (Everything looks logically sound)
Usage
Used as follows:
require(["LibraryA", "LibraryB", "LibraryC"], function() {
//code which requires libraries a-c here
});
Code
var require = (function() {
var scriptsLoaded = [];
return function(paths, callback) {
var pathsDoneLoading = {};
for(var i = 0; i < paths.length; i++) {
pathsDoneLoading[paths[i]] = false;
}
for(var p in pathsDoneLoading) {
for(var i = 0; i < scriptsLoaded.length; i++) {
if(p === scriptsLoaded[i]) {
pathsDoneLoading[p] = true;
}
}
}
for(var p in pathsDoneLoading) {
if(pathsDoneLoading[p] === false) {
var script = document.createElement("script");
script.src = p + ".js";
script.onload = function() {
scriptsLoaded.push(p);
pathsDoneLoading[p] = true;
for(var p in pathsDoneLoading) {
if(pathsDoneLoading[p] !== true) {
return;
}
}
callback();
};
document.documentElement.appendChild(script);
}
}
}
})();
Plunker
https://plnkr.co/edit/OFWTUpmV3sIJAjhGhcLO
It's the old for loop variable catching problem.
During your loop where it's iterating through what paths are loading (for (var p in pathsDoneLoading) {) the p is being bound to the function inside. The problem is that p changes and persists after the loop has complete so every single function is adding the last key in the pathsDoneLoading object to the scriptsLoaded array (and setting the matching value in pathsDoneLoading to true).
An example of the problem:
var obj = { a: 1, b: 2, c: 3 };
var funcs = [];
for (var key in obj) {
funcs.push(function() {
return key; // Remember, `key` is bound to the function, not the block
});
}
funcs.forEach(function(f) {
document.querySelector('pre').innerText += f() + '\n';
});
<pre></pre>
To fix this, you can wrap it in an IIFE to create a new binding per p value.
for (var p in pathsDoneLoading) {
(function(p) {
if (pathsDoneLoading[p] === false) { // or `if (!pathsDoneLoading[p])`
...
})(p);
}
That way every time you generate an onload handler, they have a unique binding to the intended path.
Our previous example fixed with this solution:
var obj = { a: 1, b: 2, c: 3 };
var funcs = [];
for (var key in obj) {
(function(key) {
funcs.push(function() {
return key; // Remember, `key` is bound to the function, not the block
});
})(key);
}
funcs.forEach(function(f) {
document.querySelector('pre').innerText += f() + '\n';
});
<pre></pre>
Simply put, you'll need capture the for loop iterator because it's a moving target and by the time onload fires, it will have changed.
var require = (function() {
var scriptsLoaded = [];
return function(paths, callback) {
var pathsDoneLoading = {};
for(var i = 0; i < paths.length; i++) {
pathsDoneLoading[paths[i]] = false;
}
for(var p in pathsDoneLoading) {
for(var i = 0; i < scriptsLoaded.length; i++) {
if(p === scriptsLoaded[i]) {
pathsDoneLoading[p] = true;
}
}
}
for(var p in pathsDoneLoading) {
if(pathsDoneLoading[p] === false) {
var script = document.createElement("script");
script.src = p + ".js";
// Wrapping in a closure
script.onload = (function(Vp) {
scriptsLoaded.push(Vp);
pathsDoneLoading[Vp] = true;
for(var prop in pathsDoneLoading) {
if(pathsDoneLoading[prop] !== true) {
return;
}
}
callback();
// Execute the closure function and send in your
// var(s) as argument(s) to capture it's current state
}(p));
document.documentElement.appendChild(script);
}
}
}
})();

Get object caller name by function call JavaScript

I'm writing a piece of code to easily save error logs in an object for debugging.
What I'm trying to achieve is to get the Object name from the function it was called from like so:
var MainObject = {
test : function() {
return MainObject.test.caller;
// When called from MainObject.testcaller,
// it should return MainObject.testcaller.
},
testcaller : function() {
return MainObject.test(); // Should return MainObject.testcaller, Returns own function code.
},
anothercaller : function() {
return MainObject.test(); // Should return MainObject.anothercaller, Returns own function code.
}
}
However when I run this code it returns the function code from MainObject.testcaller.
JSFiddle example
Is there any way this is possible?
Update
After looking at Rhumborl's answer, I discovered that assigning the value through another function would lead it to point back at the function name without the object itself.
Code:
(function (name, func) {
MainObject[name] = func;
})('invalid', function() {
return MainObject.test("blah");
});
// This now points at invalid() rather than MainObject.invalid()
Updated fiddle
There is a non–standard caller property of functions that returns the caller function, however that is a pointer to a function object and doesn't tell you the object it was called as a method of, or the object's name. You can get a reference to the function through arguments.callee.
There is also the obsolete arguments.caller, but don't use that. It also provides a reference to the calling function (where supported).
Once you have a reference to the calling function (if there is one), you then have the issue of resolving its name. Given that Functions are Objects, and objects can be referenced by multiple properties and variables, the concept of a function having a particular name is alluvial.
However, if you know that the function is a property of some object, you can iterate over the object's own enumerable properties to find out which one it is.
But that seems to be a rather odd thing to do. What are you actually trying to do? You may be trying to solve a problem that can be worked around in a much more robust and simpler way.
Edit
You can do what you want in a very limited way using the method described above for the case in the OP, however it is not robust or a general solution:
var mainObject = {
test : function() {
var obj = this;
var caller = arguments.callee.caller;
var global = (function(){return this}());
var fnName, objName;
for (var p in global) {
if (global[p] === obj) {
objName = p;
}
}
for (var f in obj) {
if (obj[f] === caller) {
fnName = f;
}
}
return objName + '.' + fnName;
},
testcaller : function() {
return mainObject.test();
},
anothercaller : function() {
return mainObject.test();
}
}
console.log(mainObject.testcaller()); // mainObject.testcaller
console.log(mainObject.anothercaller()); // mainObject.anothercaller
but it's brittle:
var a = mainObject.anothercaller;
console.log(a()); // mainObject.anothercaller
var b = {
foo : mainObject.anothercaller
}
console.log(b.foo()); // mainObject.anothercaller
Oops.
You can use this trick at http://www.eriwen.com/javascript/js-stack-trace/ which throws an error, then parses the stack trace.
I have updated it for the latest versions of Firefox, Chrome and IE. Unfortunately it doesn't work well on my IE9 (and I haven't tested it on Opera).
function getStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist += 0; //doesn't exist- that's the point
} catch (e) {
if (e.stack) { //Firefox/Chrome/IE11
var lines = e.stack.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
var line = lines[i].trim();
if (line.match(/^at [A-Za-z0-9\.\-_\$]+\s*\(/)) {
// Chrome/IE: " at Object.MainObject.testcaller (url:line:char)"
var entry = line.substring(3, line.indexOf('(') - 1);
// Chrome appends "Object." to the front of the object functions, so strip it off
if (entry.indexOf("Object.") == 0) {
entry = entry.substr(7, entry.length);
}
callstack.push(entry);
} else if (line.match(/^[A-Za-z0-9\.\-_\$]+\s*#/)) {
// Firefox: "MainObject.testcaller#url:line:char"
callstack.push(line.substring(0, lines[i].indexOf('#')));
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
} else if (window.opera && e.message) { //Opera
var lines = e.message.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i + 1]) {
entry += lines[i + 1];
i++;
}
callstack.push(entry);
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE9 and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
return callstack;
}
var MainObject = {
test: function (x) {
// first entry is the current function (test), second entry is the caller
var stackTrace = getStackTrace();
var caller = stackTrace[1];
return caller + "()";
},
testcaller: function () {
return MainObject.test(1, null);
}
}
function SomeFunction() {
return MainObject.test("blah");
}
document.body.innerHTML += '<b style="color: red">' + MainObject.testcaller() + '</b>';
document.body.innerHTML += '<div>Calling SomeFunction() returns: <b style="color: red">' + SomeFunction() + '</b></div>';
MainObject.test() should return: <b style="color: blue">MainObject.testcaller()</b>
<hr />
MainObject.test() returns:
Updated fiddle here

Mutable variable is accessible from closure. How can I fix this?

I am using Typeahead by twitter. I am running into this warning from Intellij. This is causing the "window.location.href" for each link to be the last item in my list of items.
How can I fix my code?
Below is my code:
AutoSuggest.prototype.config = function () {
var me = this;
var comp, options;
var gotoUrl = "/{0}/{1}";
var imgurl = '<img src="/icon/{0}.gif"/>';
var target;
for (var i = 0; i < me.targets.length; i++) {
target = me.targets[i];
if ($("#" + target.inputId).length != 0) {
options = {
source: function (query, process) { // where to get the data
process(me.results);
},
// set max results to display
items: 10,
matcher: function (item) { // how to make sure the result select is correct/matching
// we check the query against the ticker then the company name
comp = me.map[item];
var symbol = comp.s.toLowerCase();
return (this.query.trim().toLowerCase() == symbol.substring(0, 1) ||
comp.c.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1);
},
highlighter: function (item) { // how to show the data
comp = me.map[item];
if (typeof comp === 'undefined') {
return "<span>No Match Found.</span>";
}
if (comp.t == 0) {
imgurl = comp.v;
} else if (comp.t == -1) {
imgurl = me.format(imgurl, "empty");
} else {
imgurl = me.format(imgurl, comp.t);
}
return "\n<span id='compVenue'>" + imgurl + "</span>" +
"\n<span id='compSymbol'><b>" + comp.s + "</b></span>" +
"\n<span id='compName'>" + comp.c + "</span>";
},
sorter: function (items) { // sort our results
if (items.length == 0) {
items.push(Object());
}
return items;
},
// the problem starts here when i start using target inside the functions
updater: function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
};
$("#" + target.inputId).typeahead(options);
// lastly, set up the functions for the buttons
$("#" + target.buttonId).click(function () {
window.location.href = me.format(gotoUrl, $("#" + target.inputId).val(), target.destination);
});
}
}
};
With #cdhowie's help, some more code:
i will update the updater and also the href for the click()
updater: (function (inner_target) { // what to do when item is selected
return function (item) {
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}}(target))};
I liked the paragraph Closures Inside Loops from Javascript Garden
It explains three ways of doing it.
The wrong way of using a closure inside a loop
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Solution 1 with anonymous wrapper
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Solution 2 - returning a function from a closure
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
Solution 3, my favorite, where I think I finally understood bind - yaay! bind FTW!
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}
I highly recommend Javascript garden - it showed me this and many more Javascript quirks (and made me like JS even more).
p.s. if your brain didn't melt you haven't had enough Javascript that day.
You need to nest two functions here, creating a new closure that captures the value of the variable (instead of the variable itself) at the moment the closure is created. You can do this using arguments to an immediately-invoked outer function. Replace this expression:
function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
With this:
(function (inner_target) {
return function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}
}(target))
Note that we pass target into the outer function, which becomes the argument inner_target, effectively capturing the value of target at the moment the outer function is called. The outer function returns an inner function, which uses inner_target instead of target, and inner_target will not change.
(Note that you can rename inner_target to target and you will be okay -- the closest target will be used, which would be the function parameter. However, having two variables with the same name in such a tight scope could be very confusing and so I have named them differently in my example so that you can see what's going on.)
In ecmascript 6 we have new opportunities.
The let statement declares a block scope local variable, optionally initializing it to a value.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Since the only scoping that JavaScript has is function scope, you can simply move the closure to an external function, outside of the scope you're in.
Just to clarify on #BogdanRuzhitskiy answer (as I couldn't figure out how to add the code in a comment), the idea with using let is to create a local variable inside the for block:
for(var i = 0; i < 10; i++) {
let captureI = i;
setTimeout(function() {
console.log(captureI);
}, 1000);
}
This will work in pretty much any modern browser except IE11.

Categories