JavaScript is a high-level programming language like Ruby, but with a very different syntax and different ways of dealing with objects. It is also the only language understood by web browsers, so JavaScript is crucial in writing web applications that respond immediately to user actions, without having to wait for a server to send back a whole new HTML page.
By the end of this lesson, students should be able to:
- List the Javascript datatypes
- Manipulate JavaScript datatypes
- Write conditionals to manage control flow in javascript
- Describe the difference between loose and strict equality
- Read and write javascript arrays
- Read and write javascript objects
- Fork, clone your fork
cd
into your local copy- run
npm install
- run
bower install
- run
grunt test
- Follow along with the README and make the tests pass.
When developing in Ruby, we'd run code files by typing ruby some_file.rb
. We can do something similar with JavaScript by typing node some_file.js
. This runs the file through NodeJS, a framework that can run JavaScript outside of a web browser. This is useful when developing JavaScript applications that run on a server – but since we'll mostly be using JavaScript in the browser, we'll rarely use this method.
- Try it: Examine the contents of
app/js/testrun.js
. Then typenode testrun.js
in your terminal and you should see "It works!"
Browsers run JavaScript when they see a <script>
tag in a web page that points to the URL of a JavaScript file. The browser will make a separate request to download the file, run it, then proceed with parsing the rest of the page.
There is no "terminal" in the browser, but there is something called the console that shows output and errors from JavaScript programs. It also lets us type in and run JavaScript code one line at a time, like Ruby's pry
.
- Try it: Examine the contents of
app/testrun.html
. Then open this file in your browser. PressCmd+Opt+I
(Mac) orCtrl+Shift+I
(Linux) to open the console (depending on which browser you're using), and you should see "It works!"
Note: If you type node
in your terminal without specifying a filename, it will let you enter and run JavaScript one line at a time just like the browser console.
Like Ruby, in JavaScript everything is an object. Unlike Ruby, in JavaScript there is no such thing as a "class" – each object stands alone, and can have properties and behaviors unique to it. We can still create something that looks and acts a lot like a class, but we'll get into this in a later lesson.
There are some special objects in JavaScript called "primitives", also known as the standard data types.
The latest ECMAScript standard defines seven data types:
- Six data types that are primitives:
- Number
- String
- Boolean
- Undefined
- Null
- Symbol (new in ECMAScript 6)
- and Object
JavaScript is a loosely typed or a dynamic language. That means you don't have to declare the type of a variable ahead of time. The type will get determined automatically while the program is being processed. That also means that you can have the same variable as different types:
var foo = 42; // foo is a Number now
var foo = "bar"; // foo is a String now
var foo = true; // foo is a Boolean now
In app/js/app.js
follow along and write this code.
'use strict';
/* NUMBERS
———————————————————————————————————————————————————
Unlike in Ruby there is no special distinction between numbers with and
without decimal points. They are all just "numbers". */
var currentLevel = 17;
var price = 1499.99;
var fiveMinutes = 60 * 5; // all the basic math operations work
var threeHalves = 3 / 2; // this results in 1.5 -- no weird "integer division"
price = 1299.99; // only the first assignment needs a `var`
price += 100; // this kind of shortcut still works
/* There is only 1 integer that has two representations: 0 is represented
as -0 and +0. ("0" is an alias for +0).
*/
var infinity = 42 / +0;
var negativeInfinity = 42 / -0;
Run grunt test
. If there are any failing tests go back and fix your code.
In app/js/app.js
add this code.
/* STRINGS
———————————————————————————————————————————————————
Unlike in Ruby there is no difference between single quotes and double
quotes... since JavaScript has no string interpolation. Strings are also
"immutable", meaning we can't modify them in-place (no shoveling!) */
var greeting = 'Hello there!';
var firstName = "Jason";
var lastName = "Wharff";
var myName = firstName + ' ' + lastName; // clunky, but it's the only way
Run grunt test
. If there are any failing tests go back and fix your code.
In app/js/app.js
add this code.
/* BOOLEANS
———————————————————————————————————————————————————
Like in Ruby, we have the booleans true and false. */
var excited = true;
var testMode = false;
var excitedlyTesting = excited && testMode; // boolean && and || are here
var calm = !excited; // boolean "not" is also here
Run grunt test
. If there are any failing tests go back and fix your code.
In app/js/app.js
add this code.
// Unlike in Ruby, we have an extra nil-like value called "undefined". It's
// what you'll get if you access a variable that's not assigned yet, or call
// a function that doesn't return anything.
var mystery = undefined;
var spooky; // This does the same thing as above! The value is "undefined"
Run grunt test
. If there are any failing tests go back and fix your code.
In app/js/app.js
add this code.
/* NULL
———————————————————————————————————————————————————
The value null is a JavaScript literal representing null or an "empty" value,
i.e. no object value is present. It is one of JavaScript's primitive values.
We also have nil, but in JS it's called "null". A value that carries no value.
Like undefined, but defined (not automatically assigned).
It is falsy. */
var result = null; // the variable `result` is defined, but it's value is null.
console.log(typeof undefined);
Run grunt test
. If there are any failing tests go back and fix your code.
We already have some notable syntax differences from Ruby:
- Variable and function names use
lowerCamelCase
rather thansnake_case
. This is a much weaker convention in JavaScript than snake case in Ruby – although it's the style used by the language itself, many JavaScript developers use snake case anyway. - Lines of code usually end in a semicolon. We can leave these out, but JavaScript will be left to guess where they should be inserted, and sometimes it guesses wrong. The rules for when to use a semicolon are hard to remember, but your JSHint plugin for Sublime Text will steer you right.
- The first time we assign a variable (and only the first time), we must prefix the assignment with
var
. This is known as "declaring" the variable. As with semicolons, if you don't do this your program will still work sometimes, but not always. We'll get into why this is when we talk about functions.
A common theme in JavaScript is "things you can easily get wrong and have your program usually still work, but sometimes not". Attention to detail is your ally when writing JavaScript.
In app/js/app.js
add this code.
/* Control Flow in Javascript
———————————————————————————————————————————————————
Important differences from Ruby:
- Conditions must be enclosed in parentheses.
- Code blocks are always enclosed in braces. There is no `end` in JavaScript.
- The "else-if" syntax is two separate words, `else if`, rather than `elsif`.
- There is no `unless` in JavaScript. Use the "not" operator (`!`) instead. */
var holyNumber = function(holyNumber){
if(holyNumber > 3) {
return 'Four shalt thou not count. Five is right out.';
} else if(holyNumber < 3) {
return 'Count neither one nor two, excepting that thou then proceedest to three.';
} else if(holyNumber === 3) {
return 'Throw the holy hand grenade!';
} else {
return 'World ends';
}
};
holyNumber('4'); // 'Four shalt thou not count. Five is right out.'
holyNumber(4); // 'Four shalt thou not count. Five is right out.'
holyNumber('2'); // 'Count neither one nor two, excepting that thou then proceedest to three.'
holyNumber(2); // 'Count neither one nor two, excepting that thou then proceedest to three.'
holyNumber('3'); // 'World ends'
holyNumber(3); // 'Throw the holy hand grenade!'
Run grunt test
. Then look at the tests on lines 106–119. Note the difference in datatypes passed into the function call. Some are strings, some are numbers, but Javascript converts the datatypes when the ">" or "<" are used.
JavaScript also has while
loops (but no until
loops).
There are no tests for while loops. If you're interested in testing this code, run grunt serve
in the root of your repo, then uncomment this code in index.html
var input = '';
while(input !== 'stop') {
input = prompt('Enter "stop" to cut it out');
}
Instead of case
/when
, JavaScript has switch
/case
(just to trip you up).
In app/js/app.js
add this code.
/* Switch/Case statements
———————————————————————————————————————————————————
Note that `case` blocks are *not* enclosed in braces,
and each one also needs a `break` statement at the end –
otherwise code execution will "fall through" to the next
block and keep on going! Thankfully `switch` uses the
threequals for comparison, but due to its quirks and
inflexibility, you don't see it that often in real-world programs.
*/
var yearbook = function(year){
switch(year) {
case 'freshman':
return 'cannon fodder';
break;
case 'sophomore':
return 'mildly respectable';
break;
case 'junior':
return 'some influence';
break;
case 'senior':
return 'phenomenal cosmic power';
break;
default:
return 'mysterious stranger'
break;
}
};
Run grunt test
. If there are any failing tests go back and fix your code.
Notice above that even though holyNumber
is a string, we can use the >
and <
operators to compare it with numbers. This is because most JavaScript comparison operators, including ==
, are "loose" – meaning JavaScript will try to convert both sides of the operator to the same data type before evaluating. This has some counter-intuitive consequences.
In app/js/app.js
add this code.
// All of these are true
3 == '3'
0 == false
1 == true
2 != true
2 != false
0 == ''
0 == []
'' == []
'wat' == ['wat']
null == undefined
Fortunately, JavaScript also has the ===
operator, known as the "strict" equality operator or the "threequals", which does not attempt to convert data types and works much closer to Ruby's notion of equality. Always use this operator to check equality!
There is also a "strict" not-equal operator, !==
. Unfortunately, there are no strict greater-than or less-than operators, so we kind of have to live with the data type conversion when using those.
// Never use these operators
3 == '3' // true
1 != '1' // false
// Always use these operators
3 === '3' // false
1 !== '1' // true
Run grunt test
. If there are any failing tests go back and fix your code.
JavaScript arrays work mostly the same as Ruby arrays.
In app/js/app.js
add this code.
/* Arrays
———————————————————————————————————————————————————
Like all objects, arrays can have *functions* (or methods)
defined on them, like `.sort()`, that we can call. Functions
must always be called with parentheses, even if we're not
passing any arguments. Note `length` is not a function –
instead it is a *property* that is accessed directly, and we
cannot use parentheses to call it. The MDN reference will
tell you whether something is a property or a function. */
var colors = ['red', 'green', 'blue'];
var green = colors[1];
var colorsCount = colors.length;
var indexOfBlue = colors.indexOf('blue');
var lastColor = colors[colors.length - 1]; // we can't use negative indexes
colors.push('purple');
var purple = colors.pop();
colors.sort(); // now they're in alphabetical order
// These should be familiar from Ruby
var newColors = 'blue, orange, yellow'.split(', ');
var joinedColors = newColors.join(' and '); // 'blue and orange and yellow'
Run grunt test
. If there are any failing tests go back and fix your code.
In app/js/app.js
add this code.
/* Iterating through arrays
———————————————————————————————————————————————————
In Ruby we avoid `for` loops in favor of methods like
`each` or `map`, but in JavaScript they are seen frequently
for simple iteration.
*/
var colors = ['red', 'green', 'blue'];
var tmpColors = [];
for(var i = 0; i < colors.length; i++) {
tmpColors.push(colors[i] + ' is one of my favorite colors');
}
/* The three semicolon-separated components of a `for` loop are:
- A statement that will be executed once before the first iteration
- An expression evaluated at the start of each iteration – if `false`, the loop is terminated
- A statement that will be executed at the start of each iteration
Since `for` loops are awkward and error-prone, it's usually
preferable to use the `forEach` function instead. This requires
defining an anonymous function, which we'll get into later.
`forEach` can also receive the index and the array iterating upon as params
There are also `map` and `reduce` functions that do the same thing
as their Ruby equivalents. */
var tempColors = [];
colors.forEach(function(color, index, array){
tempColors.push(color + ' is favorite color number ' + index);
});
The three semicolon-separated components of a for
loop are:
- A statement that will be executed once before the first iteration
- An expression evaluated at the start of each iteration – if
false
, the loop is terminated - A statement that will be executed at the start of each iteration
Since for
loops are awkward and error-prone, it's usually preferable to use the forEach
function instead. This requires defining an anonymous function, which we'll get into later.
There are also map
and reduce
functions that do the same thing as their Ruby equivalents.
Run grunt test
. If there are any failing tests go back and fix your code.
In app/js/app.js
add this code.
There is no such thing as a hash in JavaScript. That said, consider the following:
var friend = {
name: 'Dan',
age: 26,
colors: ['purple', 'blue', 'teal'],
pets: [
{ name: 'Fattykins', species: 'cat', age: 6 },
{ name: 'Reginald', species: 'hamster', age: 2 }
]
};
Looks like a hash, right? Nope! It's actually a plain object. As we mentioned above, objects in JavaScript can have properties. In this example, name
, age
, and so on are not "keys" but property names. Each property has a value, like the keys in a Ruby hash have values.
Some important differences to note:
- Though the property names look like Ruby symbols, they're actually an alternate syntax for strings.
- This is the only syntax to define object properties – there is no "hash rocket".
- Bracket notation is not the only way we can access object properties. Take a look:
var secondColor = friend.colors[1];
var firstPetAge = friend.pets[0].age;
friend.colors.push('indigo');
This style is preferred over the bracket style where possible. It should also look familiar: We used the same syntax to get the .length
of an array, which we mentioned was a property.
In app/js/app.js
add this code.
/* Operating on Objects
———————————————————————————————————————————————————
Plain objects in JavaScript are extremely minimal and
have virtually no functions defined on them (unlike Ruby
hashes, which have dozens!). We can at least iterate
over the properties of any object using a `for...in` loop:
*/
var propArray = []
for(var prop in friend) {
propArray.push("My friend's " + prop + " is " + friend[prop]);
}
And we can remove properties of any object using the delete
operator:
console.log(friend.age); // 26
delete friend.age;
console.log(friend.age); // undefined
Since these are plain objects and not hashes, two objects are only considered equal if they are actually the same object (even if we compare with ==
):
var friend1 = { name: 'Dan', age: 26 };
var friend2 = { name: 'Dan', age: 26 };
console.log(friend1 === friend2); // false
friend2 = friend1;
console.log(friend1 === friend2); // true