JavaScript dynamically defining properties - javascript

I'm working on a JS framework and came across something odd (or I'm missing completely obvious)
I define properties on objects using Object.defineProperty. However using this within a for loop will result in some funky values. Somehow the last property added will always be the assigned value. For example if I'd assign something to attribute one it gets assigned to attribute three. Here's an example (and here's a fiddle http://jsfiddle.net/5xLdC/)
var Test = function(){};
var props = ['one', 'two', 'three'];
for(var i = 0; i < props.length; i++) {
Object.defineProperty(Test.prototype, props[i], {
get: function() {
return this['_'+props[i]];
},
set: function(val) {
this['_'+props[i]] = val;
}
});
}
var test = new Test();
test.one = 'one';
console.log(test.three) // => 'one'
If I'd wrap this up in a forEach loop it works perfectly fine. What I'm guess at is that they all hold the same get/set functions due to scope errors (on my part?).
Could anyone explain as to why this happens?
EDIT:
Also possible to solve with a IIFE:
get:(function(y) {
return function() { return this['_'+props[y]]; }
})(i)

get and set are functions that all reference the same variable, i. By the time you call the function outside the loop, i is 3. When you use forEach, the function defining the properties gets the index or key as a parameter, which is a different entity in each call.

Related

Knockout arrayForEach undefined property

I'm having trouble trying to get a number from each item in a knockout observable array and add the numbers together and assign it to another computed variable. Here's what I have right now...
Semesters: ko.observableArray([
{
semesterName: "Fall",
semesterCode: "300",
PlannedCourses: ko.observableArray([]),
totalCredits: ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(this.PlannedCourses, function (course) {
total += course.MinHours();
});
return total;
}),
},
...
What I'm trying to do is, in the totalCredits variable, I'm trying to iterate through the PlannedCourses array and get the MinHours variable for each item and add them together in the total variable. Then I return it to the totalCredits item in the Semesters array. The issue I'm having is getting the PlannedCourses variable in the ko.utils.arrayForEach part. I'm getting an undefined on it and I'm not sure why. I think it's a simple syntax error but I can't see what's wrong.
The PlannedCourses observable array is a dynamic object that is getting the list of PlannedCourses properly. It's defined in the context of itself but I'm not passing it to the totalCredits computed function properly.
I hope this is clear enough. Thank you for your help!
Note: All the rest of the code is working as intended. The only part that isn't working is the totalCredits computed function. I'm not sure if anything within the ko.utils.arrayForEach is working as I haven't been able to get that far.
You're going to need to change the way you populate your Semesters observable array to use a constructor function in order to get a reference to the correct scope for this:
function semester(name, code) {
this.Name = name;
this.Code = code;
this.PlannedCourses = ko.observableArray([]);
this.totalCredits = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(this.PlannedCourses(), function (course) {
//Note the change to "this.PlannedCourses()" above to get the underlying array
total += course.MinHours();
});
return total;
}, this); //now we can pass "this" as the context for the computed
}
See how we can now pass in an object to the second argument for ko.computed to use as the context for this in the inner function. For more information, see the knockout docs: Managing 'this'.
You then create new instances of semester when populating your array:
Semesters: ko.observableArray([
new semester("Fall", "300"),
new semester(...)
]);
This approach also means you have a consistent way of creating your semester objects (the computed is only defined once for one thing), rather than possibly incorporating typos etc in any repetition you may originally have had.
As others already mentioned your this is not what you think it is. In your case the context should be passed to the computed as follows:
totalCredits: ko.computed(function() {
// Computation goes here..
}, this)
Another approach could be to store the correct this to some local variable during the object creation (ex. var self = this; and then use self instead of this).
However, ko.utils.arrayForEach doesn't work with observable arrays but works on pure JavaScript arrays, so you should unwrap the observable array to access the elements of the underlying array:
ko.utils.arrayForEach(this.PlannedCourses(), function(course) {
// ...
});
// Or
ko.utils.arrayForEach(ko.unwrap(this.PlannedCourses), function(course) {
// ...
});
The scope (this) isn't what you think it is.
See http://knockoutjs.com/documentation/computedObservables.html
try adding your context, like the following:
Semesters: ko.observableArray([
{
semesterName: "Fall",
semesterCode: "300",
PlannedCourses: ko.observableArray([]),
totalCredits: ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(this.PlannedCourses, function (course) {
total += course.MinHours();
});
return total;
}, this), // new context passed in here
},
...
Doing this passes in the context of the array item itself into your computed function.
Edit:
you may need to access the Semesters object inside you loop, and add some way to reference the current item:
Semesters: ko.observableArray([
{
semesterName: "Fall",
semesterCode: "300",
PlannedCourses: ko.observableArray([]),
totalCredits: ko.computed(function(){
var total = 0;
for( var i = 0, len = Semesters().length; i < len; i++ ) {
// check current array item, possibly add an id?
if( Semesters()[i].semesterName === "Fall" &&
Semesters()[i].semesterCode === "300" ) {
ko.utils.arrayForEach(Semesters()[i].PlannedCourses, function (course) {
total += course.MinHours();
});
break; // done searching
}
}
return total;
})
},

javascript method is undefined

I'm trying to learn javascript. As part of that effort, I am writing a basic minimax AI. I have the following methods:
Computer.prototype.expand = function(node) {
/* adds all state action pairs to the node.successors array */
};
Computer.prototype.getMove = function(boardAr) {
console.log("getMove");
var b2 = boardAr.slice();
var i;
var action;
this.root = new TNode(b2, this.mark);
this.root.AIPlayedLast = false;
this.expand(this.root);
this.root.successors.forEach(this.minVal);
action = maxNode(root.successors);
this.draw(action);
registerMove(action, this.mark);
};
Computer.prototype.minVal = function(node) {
if (node.isTerminal) {
return;
} else {
this.expand(node);
node.successors.forEach(maxVal);
node.utility = this.minNode(node.successors).utility;
}
};
When the getMove method is called the subsequent call to expand goes as expected. But, when expand is called from the minVal method I get: Uncaught TypeError: undefined is not a function. I'm utterly perplexed by this. Any help/suggestions would be greatly appreciated.
I think the reason is in this row:
this.root.successors.forEach(this.minVal);
You pass minVal as contextless reference, it will not be called in a context of your Computer instance (this)
Here is how you can improve it:
var self = this;
this.root.successors.forEach(function() {
self.minVal.apply(self,arguments);
})
The simplest and quickest solution is just to change
this.root.successors.forEach(this.minVal);
to
this.root.successors.forEach(this.minVal.bind(this))
This solves the problem in the same as the other answers, but in a way some might consider more compact.
Or, you can pass a "this" to the forEach function as the second argument, a somewhat under-utilized feature of forEach:
this.root.successors.forEach(this.minVal, this)
This feature is also available on other Array prototype methods that take functions, including map, filter, some, every (but not reduce and reduceRight).
ES6 arrow functions handle this differently, so you can do
this.root.successors(forEach(e => this.minVal(e)));
The forEach() method might be called for each of the successors. So, you pass the Computer::minVal method (this.minVal), but with the TNode(?) as this-pointer. Try:
var that = this;
this.root.successors.forEach(function(node) {
that.minVal(node));
});

how to get a property name which represent a function in JS

This is some JS code
var methodArr = ['firstFunc','secondFunc','thirdFunc'];
for(var i in methodArr)
{
window[methodName] = function()
{
console.log(methodName);
}
}
My problem is that how to get the name of a function in JS.
In JS, use this.callee.name.toString() can get the function name. But in this situation, it is a null value. How can i get the 'funName' string?
Sorry, I didn't make it clear.
I want to create functions in a for loop, all these functions has almost the same implementation which need its name. But others can call these functions use different name.I want to know what methodName function is called.
it seems a scope problem.
Try this:
var methodArr = ['firstFunc','secondFunc','thirdFunc'];
for(var i in methodArr) {
var methodName = methodArr[i]; // <---- this line missed in your code?
window[methodName] = (function(methodName) {
return function() {
console.log(methodName);
}
})(methodName);
}
window['secondFunc'](); // output: secondFunc

Having a closure issue. Can't seem to resolve it. Please advise

I have this code:
_trackit: function(){
for(var key in this.items.sublinks){
switch(key){
case 'shoes':
for(var innerkey in this.items.sublinks[key]){
(function(){
$(innerkey).observe('click', (function(e){
Event.stop(e);
someClass.click_link( this.items.sublinks[key][innerkey],false)
}));
)(this);
}
break;
}
}
}
The hash I am passing in has a size of 2. But as you would guess both of the links (since the hash maps to links), are passing the last hash value to come through (someClass.click_link <- in here this value, this.item.sublinks[key][innerkey]).
I've tried using an innerfuction etc... but something is messing up. If I go to "inner function deep", then this.items returns undefined.
Any help?
Since you're passing this in as an argument, you just need to create a parameter for it — call it, say, _this — and then you can refer to _this instead of this inside the function:
(function(_this, innerkey){
$(innerkey).observe('click', (function(e){
Event.stop(e);
someClass.click_link( _this.items.sublinks[key][innerkey],false)
}));
)(this, innerkey);
(There are other ways as well, but the above seems to be the way you were going for when you passed this in as an argument? And it's a perfectly respectable way to do it.)
Edited to add: Per Rob W's comment, I've edited the above to add innerkey as a parameter as well, since otherwise the inner function(e){...} expression will refer to the same innerkey variable as the outer function — a variable which, as a loop variable, is likely to have changed by the time the inner function actually runs. Passing it as a parameter gives the inner expression a new innerkey variable that's equal to what innerkey was when the inner function was created.
As others mentioned, you need to have an argument to receive the "this" you are passing. You will also need to pass copies of the "key" and "innerkey" variables, in order to avoid the closures inside for loops bug.
var make_event_listener = function(that, key, innerKey){
return function(e){
Event.stop(e);
someClass.click_link( that.items.sublinks[key][innerkey], false)
};
};
//...
for(var innerkey in this.items.sublinks[key]){
$(innerkey).observe('click', make_event_listener(this, key, innerKey) );
}
//...
OF course, you can use an anonymous version of make_event_listener instead but I find this way more readable.
The second call to "this" references the actual element being clicked. Change it to:
_trackit: function () {
var self = this;
for (var key in this.items.sublinks) {
switch (key) {
case 'shoes':
for (var innerkey in this.items.sublinks[key]) {
(function () {
$(innerkey).observe('click', (function (e) {
Event.stop(e);
someClass.click_link(self.items.sublinks[key][innerkey], false)
}));)(this);
}
break;
}
}
}
}

How can I call javascript function using a foreach on a JSON object?

My problem is pretty easy to understand. I have a JSON object (see code) and I will automatically call all functions of this object in the order that those appears. .
var installer = {
a : function() {
...
}
b : function() {
...
}
};
for(var func in installer) {
fn.call(document);
};
Have you any idea why the previous code doesn't work ? I'm sorry, I'm a beginner in javascript.
Thanks in advance !
Regards.
You don't have a variable called fn, and you are also missing commas at the end of your function definitions.
Additionally, your functions will not be called in order because JavaScript orders your object properties arbitrarily. You may want to consider using an array or, as I have done below, specify an array that determines the order.
var installer = {
a : function() {
...
},
b : function() {
...
},
};
var order = [ "a", "b" ];
for(var i = 0; i < order.length; i++) {
installer[order[i]].call(document);
}
You declare var func as the variable to loop through the members of installer, yet you use fn.call(...). Where did fn come from?
Should you be able to do: installer[func].call(document) instead of fn.call(document).
Also your functions declared in the installer object don't take any arguments, yet you're passing document as an argument.
[updated code to add missing .call to installer[func](document)]

Categories

Resources