Skip to content
JamesNewton edited this page Aug 30, 2019 · 25 revisions

You can try these in the console of your browser while in debug mode, or eval them in DDE.

true == 1 and false == 0 so true - true == false. True and false are just numbers. Anything that comes out 0 is false, and anything that isn't zero is true. Inside a computer, /everything/ is a number, even strings, or objects, or programs. Everything. The job of a language developer is to turn human ideas into numbers with the least surprise. Let's see how the developers of Javascript did:

9 + "1" returns 91 but 9 - "1" returns 8. LOL. I love this one. This happens because the "+" operator has two different meanings to humans: Add and Concatenate. Add is just good old math adding 2 numbers. Concatenate is tacking one thing after another; like "9" + "1" is "91". So if "+" gets handed a number and a string, should it add or concatenate? Well... every number can be converted to a string, but not every string can be converted to a number, so... 9 gets converted to "9" and the "1" gets tacked on. But why the 8 as a result in the second case? Well, minus only knows how to subtract or negate numbers. So it tries to convert "1" to a number, and when that works, it continues and returns 8. Now, -'s other function is negate, so if you like you can do 9- -"1" and get 10. But not 9 --"1" because -- is decrement. By the way, 9 - "A" or anything else that can't be converted to a number returns NaN ("Not A Number"). Best just use parseInt() or parseFloat(). e.g. 9 + parseInt("1") will always get you 10. And 0.1 + parseFloat("0.2") will... oh.. no it won't. LOL. See the next item:

(0.1+0.5==0.6) will return true but (0.1+0.2==0.3) will return false. This actually makes perfect sense once you understand that computers do math in binary and humans read numbers in decimal. Converting from one to the other introduces small errors. And some values cause more errors than others. 0.2 is a really difficult value. 0.1+0.2 ends up becoming 0.30000000000000004 which is why it doesn't equal 0.3. This video does more to explain why:
https://www.youtube.com/watch?v=PZRI1IfStY0
To get around this, you must round your answers. Math.round((0.1+0.2)*1000)/1000 gives you 0.3

[]+[] returns "". So 2 empty arrays become an empty string. This is because the "+" operator only knows how to work on two things: Numbers and strings. So when you hand it something else, it tries to convert it to a string. An empty array gets converted to an empty string, and two of them concatenated together are still empty.

('b' + 'a' + + 'a' + 'a').toLowerCase() Can you figure out what that evaluates to? Try it in the debug console. (Press Ctrl + Shift + I, and enter it into the console, press enter)

value vs reference variables have values. Some types of variables have a value which is a reference to another thing, which might have a name or not. For example: x = 10 sets the value of x to 10. No worries. y = x sets y to the value x currently holds, so y is also 10. If we say y = 20 then y is now 20, but x is still 10. No surprises.

Lets try again with something less "primitive". x = {value: 10} sets the value of x to an address which references a new object, {} makes block objects, containing a label "value" which has the value 10. x.value is 10, but x "is" {value:10} or at least points to the thing that is. Now, y = x will show y.value == 10 and y points to this {value:10} thing. But if we do y.value = 20 and then look at x.value, we will find it has now become 20. Why? Because "thing" was only made once. Both x and y only pointed to it. There was only ever one .value and we changed it's value via y. We did NOT change y.

If we want a completely independent y from our x, we need to duplicate, or "clone" the object. And that, is a bloody nightmare:
https://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object
But for general use in most cases, y = JSON.parse(JSON.stringify(x)) is a good bet. JSON is worth learning.

String.indexOf(substring) does not bool well The indexOf method for a string object returns an integer value, starting from zero, indicating the beginning of the substring within the String object. If the substring is not found, a -1 is returned. If the substring matches the start of the string, 0 is returned, and so on.

You might try to use this to test if a substring is present in a string, but note that -1 is NOT false (only 0 is) so text.indexOf("t") is false if the first character in the string is "t" but true if the second (or following) character is "t" and it is true if "t" is not found in the string.

if ("test".indexOf("t")) {console.log("true")}
undefined
if ("test".indexOf("e")) {console.log("true")}
true
if ("test".indexOf("z")) {console.log("true")}
true

Turns out when used with a number, the Tilde operator (bitwise not) effective does ~N => -(N+1). This expression evaluates to 0 only when N == -1. We can leverage this by putting ~ in front of the indexOf(...) function to do a boolean check if an item exists in a String or an Array.

if (~strvar.indexOf(substring)) { //substring was found 
if (!~strvar.indexOf(substring)) { //substring was NOT found 

typeof(typeof(12)) is "string". Because "number" is a string. And the typeof 12 is "number"

Math.max() returns -Infinity and Math.min() returns Infinity. Seems a bit backwards huh? So first, you have to realize this is not how these were supposed to be used; something like Math.max(1,2,3) is more reasonable and does, in fact, return 3. But how do you write a program to find the maximum of a set of numbers? At some point, you need to have a line like if (current_max < next_value) current_max = next_value; right? And you call that for each value you get and the biggest one shakes out. But what value do you start current_max off with? If you set it to 10, then called your function with 1, 2, 3 what would happen? So how about starting at zero? Well, Math.max(-1,-2,-3) will give you zero instead of -3 then. So you need to start with a REALLY small number... like... negative infinity. And if you don't pass it any numbers, it just returns that starting value. A longer version

Short Circuits

'cat' && 'dog' what does that return? Why? How might that be useful?

'cat' || 'dog' what does that return? Why? How might that be useful?

hints: && is logical AND which needs to know that both values are true. || is logical OR and doesn't need to know the second value if the first is true. 'cat' and 'dog' both evaluate to true.

  • var attrib = obj && obj.subobject && obj.subobject.attribute || 'default' if attribute is just one level down, use var attrib = obj ? obj.attribute : 'default'
  • var thing = getThing(parameter) || reporterror(message) Keeps the focus on the expected action but still handles occasional errors. e.g.
  • var contents = fs.open('filename') || process.exit(1) (actually only useful in node)

Hidden parameters:

There are a number of methods which don't make it clear how many parameters they can take or how many parameters they call other functions with. e.g.
https://medium.com/dailyjs/parseint-mystery-7c4368ef7b21
which shows that ['1', '7', '11'].map(parseInt) returns [1, NaN, 3] instead of the [1, 7, 11] you might expect. This happens because map (and forEach) actually calls the function you give it with THREE parameters: Value, Index, and Array. So parseInt actually gets called 3 times, with parameters:

 '1', 0, ['1', '7', '11']
 '7', 1, ['1', '7', '11']
'11', 2, ['1', '7', '11']

On top of that, parseInt, which we often just hand a single parameter of a string we want converted to an integer, actually takes TWO parameters: Value and Radix. The radix or base is "the number of unique digits, including the digit zero, used to represent numbers in a positional numeral system." So for binary, the radix is 2 because 0 and 1 are two digits. For decimal, the radix is 10 (1-9 and 0). On top of that, if the radix passed to parseInt is "falsy" it defaults to base 10, decimal. And... parseInt will try to guess the radix from the string! e.g. '0x0F' is 15 (because heXadecimal... It's K&R's fault following a brilliant hack by Ken Thompson), but that's a different gotcha.

So... map is passing 3 parameters to parseInt, which is taking the first 2 (value and index) and trying to use the index as the radix, which actually works for index 0 because 0 is false and so it's on base 10; and we get a 1. Then it tries to process '7' with radix 1, which can't work because the only value in a number system with radix 1 is '0'; so NaN. Then '11' in binary (base 2) is 3 in decimal. [1, NaN, 3].

Why have map (forEach, etc...) pass Value, Index, and Array? Why not just pass the Value. Well, then you wouldn't be able to change the array. E.g. if you wanted to add one to each value in an array:

var numbers = [65, 44, 12, 4];
numbers.forEach( function(item, index, arr) { arr[index] = item * 10; } )

Advanced:

Guess at the output of this script:

var x ={x:'x'},
    y ={y:'y'},
    z ={z:'z'};
x[y] = 'y';
x[z] = 'z';
console.log(x[y]);

Nope, it prints out z. Why? Well, you have to think about the type of the variables. {} makes an object. And [key] creates an entry with the string representation of the key. For example, x['y']='y' actually does what you expect; x becomes {x:'x', y:'y'}. But take a look at console.log(y.toString());; that is what is being used to create the key: [Object object] because y is an object, and that is how Javascript converts objects to strings. So what x actually becomes is {x:"x","[object Object]":"y"} Note that you have to put [Object object] in quotes because it has a space in the middle, but the result is the same. So that is an object with a key of 'x' which has a value of 'x' and a key of '[Object object]' which has a value of 'y'. Now, when you do the x[z] = 'z'; you are replacing the '[Object object]' keys value. So the result of that is {x:"x","[object Object]":"z"}

Guess the output of const a = [4, 3, 2].push(1). You might think it's an error because you can't modify a const. e.g. const a = 1; a = 2; generates an "Assignment to a constant variable" error. You might think it's [4,3,2] (if the error were ignored) or [4,3,2,1] (if you could push to a const array). You might think its undefined because let, const, or var don't return anything.
Nope: It's 4. And a is now [4,3,2,1].

  • JavaScript CAN push to a const array. Because const locs the value, NOT the attributes of a variable. Arrays are objects. Pushing a new value modifies the attributes of the array; the value of the array is the pointer to the memory where the array "values" (attributes) are stored. You can't a = 2 for example.
  • .push(1) gets called after the array is assigned to a. const gets finished first.
  • .push(n) returns the length of the array. note: .pop() returns the value, not the length.

JavaScript makes a lot more sense when you realize that EVERYTHING is an object. Their prototypes make them act differently, but they are still objects. e.g. an array is just an object with a .__proto__ attribute which includes an Array() constructor, a .push method, and so on. And that arrays .__proto__ has a .__proto__ with a constructor of Object() and all the object methods.


Challenges

Write a function that takes in an array of strings and returns an English list. e.g.

console.log(englishList(["one","two","three","four"])) // "one, two, three and four"
console.log(englishList(["one","two","three"])) // "one, two and three"
console.log(englishList(["one","two"])) // "one and two"
console.log(englishList(["one"])) // "one"
console.log(englishList([])) // ""

One possible answer (there are many ways to do this)

function englishList(a) {
  if (a.length>1)
    return a.slice(0,-1).join(", ")+" and "+a[a.length-1]
  return a.join()
  }
  • Note that slice with a negative second parameter stops that many characters from the end.
  • Why is there no '{' or '}' after the if? It would work fine with them, why does it work without? Why leave them off?

JavaScript cheatsheet

See also:

Clone this wiki locally