I’ll start with the code:
var s = ["hi"];
console.log(s);
s[0] = "bye";
console.log(s);
Simple, right? In response to this, the Firefox console says:
[ "hi" ]
[ "bye" ]
Wonderful, but Chrome’s JavaScript console (7.0.517.41 beta) says:
[ "bye" ]
[ "bye" ]
Have I done something wrong, or is Chrome’s JavaScript console being exceptionally lazy about evaluating my array?
Thanks for the comment, tec. I was able to find an existing unconfirmed Webkit bug that explains this issue: https://bugs.webkit.org/show_bug.cgi?id=35801 (EDIT: now fixed!)
There appears to be some debate regarding just how much of a bug it is and whether it's fixable. It does seem like bad behavior to me. It was especially troubling to me because, in Chrome at least, it occurs when the code resides in scripts that are executed immediately (before the page is loaded), even when the console is open, whenever the page is refreshed. Calling console.log when the console is not yet active only results in a reference to the object being queued, not the output the console will contain. Therefore, the array (or any object), will not be evaluated until the console is ready. It really is a case of lazy evaluation.
However, there is a simple way to avoid this in your code:
var s = ["hi"];
console.log(s.toString());
s[0] = "bye";
console.log(s.toString());
By calling toString, you create a representation in memory that will not be altered by following statements, which the console will read when it is ready. The console output is slightly different from passing the object directly, but it seems acceptable:
hi
bye
From Eric's explanation, it is due to console.log() being queued up, and it prints a later value of the array (or object).
There can be 5 solutions:
1. arr.toString() // not well for [1,[2,3]] as it shows 1,2,3
2. arr.join() // same as above
3. arr.slice(0) // a new array is created, but if arr is [1, 2, arr2, 3]
// and arr2 changes, then later value might be shown
4. arr.concat() // a new array is created, but same issue as slice(0)
5. JSON.stringify(arr) // works well as it takes a snapshot of the whole array
// or object, and the format shows the exact structure
You can clone an array with Array#slice:
console.log(s); // ["bye"], i.e. incorrect
console.log(s.slice()); // ["hi"], i.e. correct
A function that you can use instead of console.log that doesn't have this problem is as follows:
console.logShallowCopy = function () {
function slicedIfArray(arg) {
return Array.isArray(arg) ? arg.slice() : arg;
}
var argsSnapshot = Array.prototype.map.call(arguments, slicedIfArray);
return console.log.apply(console, argsSnapshot);
};
For the case of objects, unfortunately, the best method appears to be to debug first with a non-WebKit browser, or to write a complicated function to clone. If you are only working with simple objects, where order of keys doesn't matter and there are no functions, you could always do:
console.logSanitizedCopy = function () {
var args = Array.prototype.slice.call(arguments);
var sanitizedArgs = JSON.parse(JSON.stringify(args));
return console.log.apply(console, sanitizedArgs);
};
All of these methods are obviously very slow, so even more so than with normal console.logs, you have to strip them off after you're done debugging.
This has been patched in Webkit, however when using the React framework this happens for me in some circumstances, if you have such problems just use as others suggest:
console.log(JSON.stringify(the_array));
Looks like Chrome is replacing in its "pre compile" phase any instance of "s" with pointer to the actual array.
One way around is by cloning the array, logging fresh copy instead:
var s = ["hi"];
console.log(CloneArray(s));
s[0] = "bye";
console.log(CloneArray(s));
function CloneArray(array)
{
var clone = new Array();
for (var i = 0; i < array.length; i++)
clone[clone.length] = array[i];
return clone;
}
the shortest solution so far is to use array or object spread syntax to get a clone of values to be preserved as in time of logging, ie:
console.log({...myObject});
console.log([...myArray]);
however be warned as it does a shallow copy, so any deep nested non-primitive values will not be cloned and thus shown in their modified state in the console
This is already answered, but I'll drop my answer anyway. I implemented a simple console wrapper which doesn't suffer from this issue. Requires jQuery.
It implements only log, warn and error methods, you will have to add some more in order for it to be interchangeable with a regular console.
var fixedConsole;
(function($) {
var _freezeOne = function(arg) {
if (typeof arg === 'object') {
return $.extend(true, {}, arg);
} else {
return arg;
}
};
var _freezeAll = function(args) {
var frozen = [];
for (var i=0; i<args.length; i++) {
frozen.push(_freezeOne(args[i]));
}
return frozen;
};
fixedConsole = {
log: function() { console.log.apply(console, _freezeAll(arguments)); },
warn: function() { console.warn.apply(console, _freezeAll(arguments)); },
error: function() { console.error.apply(console, _freezeAll(arguments)); }
};
})(jQuery);
I have created an array (I think):
var recipeArray: List<RecipeTemplate> = mutableListOf() //RecpeTemplate is a class
I have written out the basic Firebase code and verified that it works using console.log to print out data from the database.
In onChildAdded() I now want to loop the database and add the data to the array. This is what I've got:
override fun onChildAdded(snapshot: DataSnapshot?, p1: String?) {
val children = snapshot!!.children
/*children.mapNotNullTo(recipeArray) {
it.getValue(RecipeTemplate)<RecipeTemplate::class>
} NOT SURE ABOUT THIS ONE*/
children.forEach{
var tempRecipe: RecipeTemplate? = null
tempRecipe!!.recipeHeader = it.object["recipeHeaderFirebase"]
tempRecipe!!.recipeText = it.object["recipeIngredientsTextFirebase"]
tempRecipe!!.recipeImage = it.object["recipeImageFirebase"]
}
}
is a sample of fb data.
Not sure about the last part: it.object. I figure it should be something else...
To iterate try the following:
override fun onChildAdded(snapshot: DataSnapshot?, p1: String?) {
val children = snapshot!!.children
children.forEach {
println(it.toString())
}
}
This will iterate inside the direct children of your reference and retrieve the data.
I am learning how to build apps and working with Swift for this project.
I had a buddy help me pull data in from a website and it looks like he created classes with variables and mapped them to certain extensions (IE "Username") so when I call the variable data such as profile I would call it. The below uses luck_30 able to store "Stats.luck_30"
luck_30.text = profile.luck_30
So inside one of my variables that is in this "Profile" class is setup into an array. I can pull the array out of the class, but I can't seem to do for while statement replacing the [#] with a variable from the for command.
func aliveWorkers(profile: Profile) -> NSNumber{
var myworkers : Array = profile.workers!
//this test works and returns the proper value
var testworker: NSNumber = myworkers[0].alive!
println("The satus of the test worker is " + testworker.description)
/* This code is giving error "Could not find member alive" it does not ifor var
for ifor in myworkers{
var thisworker: NSNumber = myworkers[ifor].alive! as NSNumber
}
*/
return 42
}
Your variable ifor is not a counter, it is an actual object. You could do something like this:
for worker in myWorkers {
let workerIsAlive = worker.alive!
}
Alternatively, if you need the index,
for i in 0 ..< myWorkers.count {
let worker = myWorkers[i]
let workerIsAlive = worker.alive!
}
If you need both:
for (i, worker) in enumerate(myWorkers) {
let workerIsAlive = worker.alive!
}
And as a matter of style, I would stay away from NSNumber and use Int or Bool or whatever the data actually is. Also, it looks like the alive variable should not be optional, as you're unwrapping it everywhere. To avoid "mysterious" crashes later, you may want to think about making it a non-optional type.
when using a for in loop, your loop variable isn't an index, its the objects you're looping through. so..
func aliveWorkers() {
var myworkers = [1, 2, 3]
//this test works and returns the proper value
let testworker = myworkers[0]
print("The satus of the test worker is \(testworker)")
for ifor in myworkers {
print(ifor)
}
}
Notice a few things... you don't need to use + to concatenate those strings. you can just use string interpolation. \(variable) inserts the value of variable in the string.
Try to use let instead of var when you don't change the variable. You don't need to explicitly define type on variables either.
I'm trying to create a function in Xcode that I can call every time I hit a button to iterate through an array in sequence which then updates the value of the button title.
I can't seem to crack the challenge. I've tried various iterations of while loops and if statements but everytime I run it I end straight up at the last value in the array. Here's the code I've got at the moment, I tried to add a break clause to stop the function from automatically iterating through the whole array but it's now throwing up an error message saying that the code after the return statement will never be executed:
So, I've created an instance of a button within my viewController as follows:
#IBAction func repCount() {
repCountButton.setTitle("\(repCounter.repCount())", forState: UIControlState.Normal)
I'm hoping that this will then update the title of the button with what I return from the repCount function that is called every time the button is pressed.
I've set up the function in a separate Swift file called repCounter and my code for the repCount function is as follows:
var repArray = [1,2,3,4,5]
var repArrayIndex: Int = 0
func repCount () -> String {
if repArrayIndex < repArray.count {
while repArrayIndex < repArray.count {
return "\(repArray[repArrayIndex])"
break
}
repArrayIndex++
} else {
return "\(repArray[0])"
}
}
What I'd like this to do is to cycle through the array every time it is called and once it's got to the end of the array to start cycling from the beginning of the array again.
Thanks in advance for any help!
I'm not at a computer where I can pull up XCode to test it, but I think the version below will do what you want. It isn't the most elegant code, but it is very straightforward. You have to do all the index juggling before the return statement since once the code hits a return, nothing following it will be executed.
I added some code to reset the index once it reaches the end of the array.
var repArray = [1,2,3,4,5]
var repArrayIndex: Int = 0
func repCount () -> String {
while repArrayIndex < repArray.count {
var curIndex = repArrayIndex
repArrayIndex = repArrayIndex + 1;
if repArrayIndex >= repArray.count {
repArrayIndex = 0
}
return "\(repArray[curIndex])"
}
return "\(repArray[0])"
}
Another option to getting this count, without iterating, is to do
// From Swift 1.2
func repCount () -> String {
return count(repArray)
}
// Before Swift 1.2
func repCount () -> String {
return countElements(repArray)
}
If you insist on iterating there are multiple options, see Iterating over an array, one which could be:
var count = 0
for rep in repArray {
count++
}
Or you could for the round trip iteration provided in the other answer, but why do it the hard way, when you don't need to? Or is there something you haven't told us?
In one of my controller functions in my angular.js application, I'm trying to use Google's RSS Feed API to fetch feed entries to populate in my scope.items model.
It's behaving quite strangely, with the console output in the innermost part always being '2' but while the console output in the outermost loop being '0' and '1' (which is correct since that is the length of my items array).
I'm thinking it could have something to do with the Google API thing being an async request and that it hasn't finished before I try to manipulate it (?). Still doesn't make sense that my iterator variable would become '2' though!
Code:
function ItemListCtrl($scope, Item) {
$scope.items = Item.query({}, function() { // using a JSON service
for (var i = 0; i < $scope.items.length; i++) { // the length is 2 here.
$scope.items[i].entries = [];
console.log(i); // gives '0' and '1' as output in the iterations.
var feed = new google.feeds.Feed($scope.items[i].feedUrl);
feed.load(function(result) {
console.log(i); // gives '2' as output in both the iterations.
if (!result.error) {
for (var j = 0; j < result.feed.entries.length; j++) {
$scope.items[i].entries[j] = result.feed.entries[j];
}
}
});
}
});
}
The callback function is executed asynchonously, after the loop over the items has ended. And at the end of the loop, i is equal to 2, since there are 2 items in your items array.
See Javascript infamous Loop issue? for another example of the same behavior, and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures#Creating_closures_in_loops.3A_A_common_mistake for an more in-depth explanation.