Just wanted to share a little trick I learned to pass variables into the scope of your JS Array.forEach() method.
I had a situation where I needed to use a forEach loop to build a dataset. But I needed to access variables in the current scope as well (I needed to be able to reference this in the loop).
This is the situation I was in:
var dataset = {
data: [],
backgroundColor:[],
};
items.forEach(function (item) {
dataset.data.push(item.age);
if (item.age < 2) {
dataset.bgColor.push(this.green);
} else if (item.age < 5) {
dataset.bgColor.push(this.yellow);
} else {
dataset.bgColor.push(this.red);
}
}, this);
this.refreshGraph(dataset);
Dataset isn't accessible from within the loop. So how do we access it while iterating?
I haven't seen this solution on stack overflow and it didn't fit any question I could find.
Answer below:
With the abilities of es6
If you'll use an Arrow Function the this will be taken from
items.forEach(item => {
// You can use this as out of the forEach scope
});
From MDN Web Docs:
An arrow function does not have its own this. The this value of the
enclosing lexical scope is used; arrow functions follow the normal
variable lookup rules. So while searching for this which is not
present in current scope, an arrow function ends up finding the this
from its enclosing scope.
Another nice explanation:
https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4
If you have a function out of scope of some data yet need to access it, you can use a curried function that takes that dataset as the first parameter and can still use this normally throughout:
//curried function that uses `dataset` and `this` but it is not
//in the context where the iteration happens
function makeLoopCallback(dataset) {
return function(item) {
dataset.data.push(item.age);
if (item.age < 2) {
dataset.bgColor.push(this.green);
} else if (item.age < 5) {
dataset.bgColor.push(this.yellow);
} else {
dataset.bgColor.push(this.red);
}
}
}
//object to serve as `this` context for a function
var obj = {
green: "Green",
yellow: "Yellow",
red: "Red",
doSomething: function(items) {
var data = {
data: [],
bgColor:[],
};
items.forEach(makeLoopCallback(data), this);
return data;
}
}
//set up some dummy data
var input = [ { age: 1 }, { age: 2 }, { age: 3 }, { age: 4 }, { age: 5 }, { age: 6 } ];
//call the function
console.log(obj.doSomething(input))
An alternative is to use Array#reduce instead of Array#forEach with a function that takes two parameters directly. Since .reduce cannot set the this context, you can just use Function#bind to do it:
//external function that uses `dataset` and `this` but it is not
//in the context where the iteration happens
function external(dataset, item) {
dataset.data.push(item.age);
if (item.age < 2) {
dataset.bgColor.push(this.green);
} else if (item.age < 5) {
dataset.bgColor.push(this.yellow);
} else {
dataset.bgColor.push(this.red);
}
return dataset;
}
//object to serve as `this` context for a function
var obj = {
green: "Green",
yellow: "Yellow",
red: "Red",
doSomething: function(items) {
var data = {
data: [],
bgColor:[],
};
return items.reduce(external.bind(this), data);
}
}
//set up some dummy data
var input = [ { age: 1 }, { age: 2 }, { age: 3 }, { age: 4 }, { age: 5 }, { age: 6 } ];
//call the function
console.log(obj.doSomething(input))
The solution is to pass a JSON object as the this argument.
so before we had:
Array.forEach(function(){}, this)
// "this" is just an object ^^^^ just like anything else in JavaScript
Now we have:
Array.forEach(function(){}, {_self: this, dataset: dataset})
// you can access _self and dataset just as if they were in scope
And now you can make data changes while iterating with an anonymous function :)
Full example:
var dataset = {
data: [],
backgroundColor:[],
};
items.forEach(function (item) {
dataset.data.push(item.age);
if (item.age < 2) {
dataset.bgColor.push(_self.green);
} else if (item.age < 5) {
dataset.bgColor.push(_self.yellow);
} else {
dataset.bgColor.push(_self.red);
}
}, { _self: this , dataset: dataset});
Array.prototype.forEach(callbackFun, ?this)
You can pass dataset as this argument to forEach
var dataset = {
data: [],
backgroundColor:[],
};
items.forEach(function (item) {
this.dataset.data.push(item.age);
if (item.age < 2) {
this.dataset.bgColor.push(this.tempThis.green);
} else if (item.age < 5) {
this.dataset.bgColor.push(this.tempThis.yellow);
} else {
this.dataset.bgColor.push(this.tempThis.red);
}
}, {tempThis:this,dataset:dataset});
this.refreshGraph(dataset);
Related
In mapper object. I have map2 property map2 format is changed compared to others it has inner object. Due to that I put if block in my for loop to map inner object. But I feel its bad thing. If in future I add another property in mapper with nested object means. then I put another else if statement to handle this.. Any other correct way to code?
const mapper = {
map1: {
font_size: "font-size",
font_width: "font-width",
},
map2: {
"line_color": "background-color",
thickness: {
"false": "width",
"true": "height"
}
},
map3: {
border_radius: "border-radius"
},
map4 : {
border_radius: "border-radius",
font_width: "font-width"
},
map5 : {
border_radius: "border-radius",
font_size: "font-size",
}
}
function styleObjectFetch(mapObject,json) {
var x = {};
for (let eachProp of Object.keys(mapObject)) {
if (json.hasOwnProperty(eachStyleProp)) {
let value = mapObject[eachProp];
if (eachProp==="thickness") {
value = value[json.vertical];
}
x[value] = json[eachProp];
}
}
return x;
}
function fetchData(mapText,json)
{
if(mapText && mapper[mapText])
{
styleObjectFetch(mapper[mapText],json)
}
}
fetchData("map1",{
font_size : "30px",
thickness : "false"
})
I need more flexible and maintaineable code?
const person = {
name: "JONY SINS",
age: 25,
walk() {
let anotherPerson = {
right() {
console.log("right");
},
left() {
console.log("left");
},
};
console.log(this);
},
};
is it possible to call right() and left() function in this object
anotherPerson is a local variable inside the walk() method. This variable can only be accessed inside the method. You could call anotherPerson.left() from inside walk(), but you can't call it from outside the method.
If the method returned the variable, you could call walk() and then call left() or right() from the returned value.
const person = {
name: "JONY SINS",
age: 25,
walk() {
let anotherPerson = {
right() {
console.log("right");
},
left() {
console.log("left");
},
};
console.log("walk");
return anotherPerson;
},
};
let other = person.walk();
other.left();
other.right();
I have already sorted the objects like the link below. My next step is to be able to use the sorted object to display on my render rather than just console.log().
I'm not sure if putting it back into an object is the approach. Would appreciate if anyone can help me out. Thanks
var byLikes = [
{ name: 'herman', Like: 5 },
{ name: 'tabitha', Like: 3 },
{ name: 'juags', Like: 1 },
{ name: 'ukiq', Like: 4 },
{ name: 'limau', Like: 10 },
{ name: 'kwe', Like: 6 }
];
byLikes.sort(sortByLike);
function sortByLike(a, b) {
var result = 0;
if (a.Like > b.Like) { result = 1; }
if (b.Like > a.Like) { result = -1; }
return result;
}
byLikes.forEach(function (cat) {
console.log(cat);
});
res.render('reload', { imglikes: sortedlikedhere, postername: sortednamehere });
byLikes can be passed to render method as below. In the template, it can be accessed via imglikes property.
res.render('reload', {
imglikes: byLikes,
postername: sortednamehere
});
I have created a new web app that has 10 pages / forms and during the load of those pages 6 of the pages / forms call the same JavaScript methods and 4 call the same plus some additional methods. I can obviously call the the methods individually in the page but I'm wondering how I can do this in a more intelligent manner.
Currently all I'm doing is calling the methods at the bottom of the page like:
<script>
somemethod1(someparam1);
somemethod2(someparam1, someparam2);
somemethod3();
somemethod4();
somemethod5(someparam1);
</script>
It would be nicer to call something like:
<script>
Execute('somemethod1', 'somemethod2''somemethod3', 'somemethod4', 'somemethod5')();
</script>
I don't think its a good practice to do it. But sure, it'd be nicer to have Execute function like that if you don't need to pass any parameters.
You can do it like,
function test1() {
console.log('test1');
}
function test2() {
console.log('test2');
}
var Execute = function() {
for (var i = 0; i < arguments.length; i++) {
var funcName = arguments[i];
if (typeof window[funcName] == 'function') {
window[funcName]();
}
}
}
Execute('test1', 'test2')
However, as your question edited that you need to pass specific parameter/s to one or more of the functions, here's the intelligent way to do it.
test1(param1);
test2(param2, param1);
If you have uniform procedures and you need to call them in a certain order, like my example below for iteration nested arrays, then you could use the given proposal, which uses the following functions for the function before.
This might not work for other needs.
function Callback(array) {
this.array = array;
}
Object.defineProperties(Callback.prototype, {
start: {
get: function () {
return this.getCallback(0);
}
},
getCallback: {
value: function (i) {
var that = this;
return this.array[i].bind({ get next() { return that.getCallback(i + 1); } });
}
}
});
// example
function getSubmodel(model, submodel) {
var callback = new Callback([
function (a) { a.Categories.forEach(this.next); },
function (a) { a.forEach(this.next); },
function (a) { if (a.brandname === model) { a.models.forEach(this.next); } },
function (a) { if (a.name === submodel) { a.submodel.forEach(this.next); } },
function (a) { result.push(a.name); }
]),
result = [];
data.forEach(callback.start);
return result;
}
var data = [{
Storename: "Zig Zag Mobiles",
Shopid: "asdef1234",
Categories: [[{
models: [{
submodel: [{
price: null,
name: "Lumia 735 TS"
}, {
price: "3200",
name: "Lumia 510"
}], name: "Lumia"
}],
brandname: "Nokia",
}]]
}];
console.log(getSubmodel('Nokia', 'Lumia'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
it would be nicer to call something like Execute('somemethod1',
'somemethod2''somemethod3', 'somemethod4', 'somemethod5')();
Provided that you do not need to pass in argument(s) to the individual method you can do this:
[func1, func2, func3].map(function(func){
func();
});
Otherwise the way to do it is to just call the methods with the required arguments as you are already doing now unless there is really significant benefits to be gained from creating an abstraction where you can pass in both the method to be called and its associated arguments.
i have a function that loop all object properties and return value if it qualify certain condition
basically this is how i m doing
//an enum
var BillingType = Object.freeze({
PayMonthly: { key: 'Monthly', value: 1 },
PayYearly: { key: 'Yearly', value: 2 }
});
now to make it work i do this
for (var property in BillingType ) {
if (BillingType .hasOwnProperty(property)) {
if (value === BillingType [property].value) {
return BillingType [property].key;
}
}
}
it works fine but to make it generic for all enums i changed code to
getValue = function (value, object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
if (value === object[property].value) {
return object[property].key;
}
}
}
}
now when i try to call from other functions
enumService.getValue(1, 'BillingModel');
rather to loop all properties it start loop on its characters.
how can i convert string to object or m doing it totally wrong . any help will be appreciated
Regards
Your getValue looks fine, just call it using
enumService.getValue(1, BillingModel); // <-- no quotes
and here is a working fiddle: http://jsfiddle.net/LVc6G/
and here is the code of the fiddle:
var BillingType = Object.freeze({
PayMonthly: { key: 'Monthly', value: 1 },
PayYearly: { key: 'Yearly', value: 2 }
});
var getValue = function (value, object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
if (value === object[property].value) {
return object[property].key;
}
}
}
};
alert(getValue(1, BillingType));