I’m getting a JSHint/JSLint error on my code below.
Functions declared within loops referencing an outer scoped variable may lead to confusing semantics. ($, currentVal)
This is directed at the $options.each loop where I’m pushing to currentVal.
Any thoughts on how to solve this?
$(".option input[type=radio]").change(function() {
reload_filterstring(this);
});
$(".option input[type=checkbox]").change(function() {
reload_filterstring(this);
});
function reload_filterstring(that) {
var finalFilterQuery = {};
var currentEl = "";
(filterType = $this.attr("data-filter-type")),
($options = $this.find("ul.options input")),
(query = $this.find('input[name="query"]')),
(finalQuery = $('input[name="finalQuery"]')),
(filterData = []),
(filterQuery = null);
// console.log(filterName);
currentVal = [];
$options.each(function() {
if ($(this).prop("checked") == true) {
currentVal.push($(this).attr("id"));
}
});
finalFilterQuery[filterName] = currentVal.join("|");
}
// console.log(finalFilterQuery);
var str = "";
for (var key in finalFilterQuery) {
if (finalFilterQuery.hasOwnProperty(key)) {
if (str != "") {
str += ",";
}
str += key + "=" + finalFilterQuery[key];
}
}
console.log(str);
}
https://codepen.io/anon/pen/drMRem?editors=1111
I ran with codepen.io JS Analyse and the result was: Don't make functions within a loop. with a link to this JS Hint - don't make functions within a loop. You should move the function out instead of placing it inside a loop, but if you still insist on having a function inside the loop, you can narrow down the scope of currentVal inside the function like below:
$options.each(function($currentVal) {
return function() {
if ($(this).prop("checked") == true) {
$currentVal.push($(this).attr("id"));
};
}(currentVal));
Related
I am trying to call another local function 'MessageScoring' within another function 'NextMessageCalculation' which are both in module.exports, however I am having trouble with getting the scope to change back. The function successfully calls the other local function, however the scope is then changed and variables in the previous scope all return undefined now. How would I fix this or is there a better way to call the local function to avoid this?
module.exports = {
MessageScoring: function(){
var attrs_list = Object.keys(message_attr_obj);
var score = 0;
for (i in attrs_list){
if(user_data[attrs_list[i]]){
score = score + user_data[attrs_list[i]]["beta"];
}
else{
//user has no response with that attr yet so score is not added
}
}
score = score / attrs_list.length;
return score}... //returns an int
NextMessageCalculation: function(){ //has a call to a mongodb and the logic takes place in the callback
MESSAGE_collection.find({'message_set':user_data.message_set,'id':{$nin:[1111]}}).toArray(function(err,doc){
if (doc.length === 0)
{
USERDATA_collection.update({phone:phone_num},{$set:{send_message:false}},function(err,res) {
db.close()
});
}
else{
var highest_score = "";
var next_message = "";
for (i in doc)
{
console.log(doc[i]['id']);
var score = module.exports.MessageScoring(doc[i].attr_list,user_data);
console.log(doc[i]['id']); <---- becomes undefined here after calling scoring function which correctly returns int
if (highest_score === "")
{
console.log(doc[i]);
next_message = doc[i].id;
highest_score = score;
}
if (highest_score < score)
{
next_message = doc[i].id
highest_score = score;
}
console.log(doc[i].id);
}
Use function hoisting, step out from the modules.expors closure, and be sure that MessageScoring doesn't mutate data.
module.exports = {
MessageScoring,
NextMessageCalculation:
}
function MessageScoring() {
}
function NextMessageCalculation(){
const d = [{ id: 'ok' }];
const i = 0;
console.log(d[i]['id']); //returns ok
var score = MessageScoring(doc[i].attr_list,user_data);
console.log(d[i]['id']); //returns ok
}
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
Example i write down
myFunc('asdas')
It's console.log me
'asdas'
Then after i write down
myFunc('as')('ds')('ko')....('other')
function must console.log me
"as ds ko .... other"
I tried do realize this but have many problems with it.
function me (str){
//var temp = str;
return function mes(val) {
val += ' '+ str;
console.log(val);
//return mes;
}
}
How correctly realize this function?
Well, this is a bit funny, but works:
concat = function(x, val) {
val = (val || "") + x;
var p = function(y) { return concat(y, val) };
p.toString = function() { return val };
return p
}
x = concat('a')('b')('c')('d');
document.write(x)
You can generate multiple console logs and chain it like this:
function me(str) {
console.log(str);
return me; // or whatever you called the function
}
me(1)(2)(3);
As far as I'm aware though, there's no way for the function to know when it should be outputting if you're just chaining, though.
The best option I can think of would be this:
function me(str) {
me.str = me.str || ''; // make sure me.str is set
// set me.write if this is the first call to me()
me.write = me.write || function() {
console.log(me.str);
}
me.str += (me.str.length ? ' ' : '') + str; // add a space if needed
return me; // or whatever you called the function
}
me(1)(2)(3).write();
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.
I am trying to pass a variable to a a function that I believe calls another function (I think) but am having problems. The variable I need to use in the second function is productid but several ways thAt I have tried have not worked. either a fix in javascript or Jquery will be great!!!
This is the line that I need the variable for
var error_url = '/ProductDetails.asp?ProductCode' + productid;
this is where the variable originates from...
var productid = form.elements['ProductCode'].value;
and here is the whole js code
function addToCart2(form, button) {
var softAdd = true;
var productid = form.elements['ProductCode'].value;
var qstr;
var bttnName = button.name;
button.disabled = true;
if (form.elements['ReturnTo']) {
form.elements['ReturnTo'].value = "";
}
qstr = serialize(form, bttnName + '.x', '5', bttnName + '.y', '5');
sendAjax('POST','/ProductDetails.asp?ProductCode=' + productid + '&AjaxError=Y', qstr , retrieveProductError2 ,displayServerError,false);
button.disabled = false;
return false;
}
function retrieveProductError2(result, statusCode) {
var ele = document.getElementById('listOfErrorsSpan');
var errorIndex = result.indexOf('<carterror>');
var productIndex = result.indexOf('<ProductIndex>')
if (errorIndex > -1 && productIndex == -1) {
var error_url = '/ProductDetails.asp?ProductCode' + productid;
window.location = error_url;
}
if (errorIndex != -1) {
//ele.innerHTML = result.slice(errorIndex + 11, result.indexOf('</carterror>'));
}
else {
ele.innerHTML = "";
if (productIndex == -1) {
sendAjax('GET','/AjaxCart.asp?GetIndex=True', '', showCart, null, false);
}
else {
productIndex = result.slice(productIndex + 14, result.indexOf('</ProductIndex>'));
sendAjax('GET','/AjaxCart.asp?Index=' + productIndex, '', showCart, null, false);
}
}
}
The easiest way is to just move your variable declaration outside of your method. So change the declaration of product id outside your addToCart2 method. So outside of that method you do this:
var product_id;
Then inside your method remove var from product_id and it will just be an assignment and not declaration.
Where you pass in retrieveProductError2 as your error callback for the sendAjax call, you could instead pass in:
function(result, statusCode) { retreiveProductError2(result, statusCode, productId);}
Then change the definition of your retreiveProductError2 function to accept the additional parameter.