Programming

Introduction to JavaScript

steloflute 2012. 6. 20. 15:35
http://adamonio.us/wexcode/66/introduction-to-javascript/

 

Introduction to JavaScript

This tutorial does not require any prior experience in writing JavaScript, but does assume a basic understanding how Object Oriented Programming works, and a familiarity with using Developer Tools in Google Chrome or Firebug for Firefox. These tools are incredibly useful, and I recommend that you utilize them by running the examples in your console.

While what I’ve written is meant to be comprehensive, I did not include how to use JavaScript to manipulate the DOM. Instead, this tutorial will prepare you to use JavaScript libraries, such as d3.js.

JavaScript is a weak and dynamic typed language.

Strong Typed and Weak Typed Languages

A programming language is strong typed if its variable declaration uses data types. A programming language is weak typed if variables aren’t declared with a specific data type.

This doesn’t, however, mean that variables don’t have different data types:

typeof(4); // number
typeof(x); // undefined
typeof(true); // boolean
typeof("String"); // string
typeof( {} ); // object
typeof( [] ); // object
typeof( function() { alert("hi!"); } ); // function

More info: typeof()

Static and Dynamic Typing

A static typed language requires that, when declaring variables, you define what type the variable is. Once a variable has been bound to a type, you may only assign values of the same type to that variable. Javascript is a dynamically typed language, in which variables are not bound to any type. The following segment of code is completely valid:

var x = "String";
console.log(x);   // String
x = 5;
console.log(x);   // 5
x = true;
console.log(x);   // true
x = null;
console.log(x);   // null

More info: console.log()

Variables, Primitive Types, Operations, and Expressions

Variables are declared using the var keyword. Here are some of the primitive types of JavaScript.

string = "String"; // still valid without var, but it's better to use var
var boolean = true,
    number = 5;
var x = null;

typeof(string);  // string
typeof(boolean); // boolean
typeof(number);  // number
typeof(null);    // object
typeof(asdf);    // undefined    

To var, or not to var?

tl;dr If you’re in a function then using var will create a local variable. If you don’t use var it will look up the scope chain until it finds the variable or hits the global scope (at which point it will create it).

The assignment statement = returns the right side value of the assignment after assigning it (if you’re using var it returns undefined).

var f;          // undefined
typeof(f = 3);  // number
console.log(f); // 3

Operators and Statements

Comparison Operators

=== is the Strict Equal operator. This returns true when the operands are both of the same type and value. == is the Equal operator. The equal operator checks to see if the two operands are the same type, and if so, casts them to the same type and applies the Strict Equal operator. If an operand is a number or boolean, both are converted to a number; else if an operand is a string, then both are converted to a string. Strict Equal and Equal operands will return true for objects if the same object was assigned to both operands.

var num = 10,
    string = "10";

console.log( num == string );      // true
console.log( num === string );     // false

console.log( 0 == false );         // true
console.log( 0 === false );        // false

console.log( null == undefined) ;  // true
console.log( null === undefined ); // false

String Operators

Use + when concatenating Strings, and += when appending to existing strings.

var dob = "",
    month = "03",
    day = "24",
    year = "1992";

dob += month + "/" + day + "/" + year ;

console.log(dob); // 03/24/1992

Integer Parsing

When working with JSON and CSV files, data will be read as a string. If you are reading a list numbers you will later be using in computations, cast the strings to numbers using the + sign. You can also use the parseInt() function.

var string = "50";

console.log(string + 100);  // 50100
console.log(+string + 100); // 150
string = parseInt(string);
console.log(string + 100);  // 150

parseInt()

Functions

You can write functions in JavaScript like typical functions:

function doSomething(param) {
    return param;
}

console.log( doSomething(10) );  // 10

Passing parameters in JavaScript

Primitive types are passed in by value (with the exception of string, which I will explain in the next section). Everything else is passed in by reference. Even when passed in by reference, reassigning the value of the parameter will make the parameter a local variable.

function doSomething(param) {
    param = param.toUpperCase(); // param is now a local variable
    console.log(param);
}
var string = "test";

/* note that string will be passed in by reference */
doSomething(string);             // TEST
console.log(string);             // test

More info: string.toUpperCase()

Anonymous functions

Functions can also be assigned to variables as anonymous functions.

var sayNothing = function() {
    console.log("nothing");
};

typeof(sayNothing);          // function
var method = sayNothing;     // method = function() { console.log("nothing"); };
typeof(method);              // function
method();                    // nothing same as calling sayNothing()

Interestingly, this is equivalent to the following function:

function sayNothing() {
    console.log("nothing");
}

Hoisting

tl;dr The problem is that, whether you realise it or not, javascript invisibly moves all the var declarations to the top of the function scope.

Arrays, Objects, and Classes

Arrays

Creating an index-based array in JavaScript is very easy:

var array = [1, 2, 3, 4, 5];

// invoke the .toString() method
console.log(array);             // [1, 2, 3, 4, 5]

You can also create an empty array of a certain size using the Array() constructor:

var array = new Array(500);

More info: Array()

You can access the value of an array value based on their 0-based placement in the array using bracket notation:

var array = [1, 2, 3, 4, 5];

console.log( array[4] );        // 5

Bracket notation also allows you to access properties of objects such that array["foo"] returns the value of the foo property in array.

var array = [];

array[5] = "anything";
array["foo"] = "bar";

// iterate through all the properties
for ( var x in array ) {
    console.log(array[x]);
}

More info: for...in

Although their values are equivalent, the two variables are not equal when compared:

var array1 = [1, 2, 3, 4, 5];
var array2 = [1, 2, 3, 4, 5];

console.log( array1.toString() === array2.toString() ); // true
console.log( array1 == array2 );                        // false
console.log( array1 === array2 );                       // false

This is because arrays are actually objects, and objects are only ever equivalent by comparison when they are referencing the same instance:

var x = [1, 2, 3, 4, 5];
var y = x;

console.log( x == y );  // true
console.log( x === y ); // true

Although string is treated as an object when used as a parameter, it is treated like a primitive type when used in comparison:

var x = "string"; 
var y = "string";

console.log(x == y);  // true
console.log(x === y); // true

Objects

You can create a single instance of an object using { and }. Objects have properties, which can be set directly on the object. You can access these properties using either bracket notation or dot notation.

var object = {
    // All members are public
    string: "String",
    bool: true,
    num: 5
};
var attribute = "num";

console.log(object.string);     // String
console.log(object["bool"]);    // true

/* You must use bracket notation to access a property with a variable */
console.log(object[attribute]); // 5

More info: Object()

Create an empty object in JavaScript with {} or new Object()?

tl;dr

  1. Never use new Object(); — it’s klunky and looks silly.
  2. Always use []; — except when you need to quickly create an “empty” array with a predefined length.

As a dynamically typed language, you can also create properties for objects on the fly

var me = {
    name: "Adam",
    gender: "male"
};
me.funny = true;

for ( var thing in me ) {
    console.log(thing + ": " + me[thing]);
}

/* output would be:
name: Adam
gender: male
funny: true
*/

Writing your own classes

The function keyword is also used to create classes. Any function can be used as a class and is instantiated using the new operator. Public variables are referenced by setting them as a property of this. Private variables are locally declared using var.

/* This function is the constructor for MyClass */
function MyClass(num, b) {
    //public members
    this.string = "String";
    this.num = num;

    //local (private) members
    var bool = b;
}
var instance = new MyClass(5, false);

console.log(instance.string); // String

Variable scope and closure

Any variable defined using var is private, and thus inaccesible outside of the constructor function itself. You can create privileged methods using this to access and modify these private variables. A privileged method has the same accessibility as a public method.

function account(name, b) {
    this.name = name;
    var balance = b;

    // Privileged members
    this.getBalance = function() { 
        return balance;
    };
    this.transaction = function(amount) {
        balance += amount;
    };
}

var amex = new account("Adam", 1000);
console.log(amex.name);           // Adam
console.log(amex.balance);        // undefined
console.log( amex.getBalance() ); // 1000
amex.transaction(-100);
console.log( amex.getBalance() ); // 900

Functions can also define inner anonymous functions that have a similar function to classes. Although typically variables do not exist outside of the scope of the function they are defined in, any inner function that uses either the parameter or locally defined variables will retain the values. This is the main idea behind closure; variables exist beyond their typical lifecycle.

// The outer function defines a variable called "name"  
var display = function(name) {
    var before = "My name is ";

    return function() {
        return before + name;
    };
};

var print = display("Adam");
typeof(print); // function
console.log( print() );       // My name is Adam

Functions in Objects

Classes can also have functions, and can even take functions as parameters:

function MyClass(param, method) {
    this.method = method;
    this.other = function() {
        console.log(param);
    };
}

var instance = new MyClass("test", function() { console.log("anything"); });

console.log(instance.method); // function() { console.log("anything"); });
instance.method();            // anything
instance.other();             // test

Private members can have public getters and setters:

function account(initial) {
    var balance = initial;

    this.transaction = function(amount) {
        balance += amount;
    };
    this.getBalance = function() {
        return balance;
    };
}

Or you can write them both as a single method using the arguments object:

function Circle(r) {
    var radius = r;

    this.radius = function(r) {
        if ( arguments.length === 0 ) {
            return radius; 
        }
        radius = r;
    }
}

var shape = new Circle(5);
console.log( circle.radius() ); // 5
shape.radius(10);
console.log( circle.radius() ); // 10

More info: arguments

You cannot overload functions in JavaScript; this will merely overwrite them. Fortunately, JavaScript functions are variadic. The arguments object gives you full access to all variables passed into the function:

function add() {
    var sum = 0;
    if(!arguments.length) return sum;
    for ( var i = 0; i < arguments.length; i++ ) {
        sum += arguments[i];
    }
    return sum;
}

console.log( add() );        // 0
console.log( add(1) );       // 1
console.log( add(1, 2) );    // 3
console.log( add(1, 2, 3) ); // 6

Function Chaining

Function chaining is also very popular in JavaScript. The idea is that when modifying an object, rather than returning void, you return the modified object. This way, you can make multiple modifications on one line.

function Rectangle(w, h) {
    var width = w,
        height = h;

    this.width = function(w) {
        if(!arguments.length) return width;
        width = w;
        return this;
    };
    this.height = function(h) {
        if(!arguments.length) return height;
        height = h;
        return this;
    };
    this.getArea = function() {
        console.log(width * height);
    };
}

/* Without using function chaining */
var shape1 = new Rectangle(10, 10);
shape1.getArea(); // 100
shape1.width(5);
shape1.height(5);
shape1.getArea(); // 25

/* Using function chaining */
var shape2 = new Rectangle(10, 10);
shape2.getArea(); // 100
shape2.width(5).height(5).getArea(); // 25

Prototype Based Programming

Constructors create instances of classes as objects using the new keyword. Every time the constructor is called with the new keyword, memory is allocated for everything in the object. JavaScript provides a way for instances of the same class to share common methods. Using prototype, you can still access public methods and variables using this.

function Circle(r) {
    this.r = r;
}

// {ClassName}.prototype allows you to define public functions
// which only have access to public members of the class
Circle.prototype.radius = function(r) {
    if(!arguments.length) return this.r;
    this.r = r;
    return this;
};

var shape = new Circle(10);
console.log( shape.radius() ); // 10
shape.radius(4);
console.log(shape.radius());   // 4

Anything defined using prototype will be overridden if it is re-defined in the constructor or on the instance itself. Remember that the only place where private variables are accessible is in the constructor.

function Circle(rad) {
    var r = rad;

    this.radius = function(rad) {
        if(!arguments.length) return r;
        r = rad;
    };
}

Circle.prototype.area = function() {
    return this.radius() * this.radius() * Math.PI;
}

var c = new Circle(10);
console.log( c.area() ); // 314.159265358979

Static Variables

Static variables are also possible.

var Person = function(name) {
    this.name = name;
    ++Person.population;
};
Person.population = 0;

var me = new Person("Adam");
console.log("Name: " + me.name);                 // Name: Adam
console.log("Population: " + Person.population); // Population: 1

Inheritance

Prototype based programming also supports inheritance. In order to understand how inheritance works, we must look more in depth at how JavaScript handles prototypes.

The Prototype

Every object has a prototype. Prototypes are objects which contain a set of properties that are passed down to the object they are assigned to, and can be thought of as a parent class. The default prototype for all objects is Object(). An object’s prototype is not publicly accessible, but can be accessed using Object.getPrototypeOf(object).

var a = {};

Object.getPrototypeOf(a); // Object
a.prototype;              // undefined

More info: getPrototypeOf()

Constructors (and functions), on the other hand, have explicit prototype properties. The prototype property is the object which is assigned to the prototype of each instance.

function Person(name) {
    this.name = name;
}

Person.prototype instanceof Person; // true
Person.prototype instanceof Object; // true
var me = new Person("Adam");
Object.getPrototypeOf(me);          // Person

// Note that the prototype of any object created using a constructor
// is set to the prototype property of its constructor.
console.log( Person.prototype === Object.getPrototypeOf(me) ); // true

More info: instanceof

Prototype chain

By setting the prototype property of a constructor to a different object, you are creating what is called the prototype chain. Instead of directly inheriting from Object(), you inherit from this parent object first, and then inherit whatever that object’s prototype is.

var Animal = function(name) {
    this.name = name;

    this.breathe = function() {
        console.log(name + " took a breath");
    }
};

var Pet = function(name, owner) {
    Animal.call(this, name);
    this.owner = owner;

    console.log("Name is " + this.name);
};

// set the prototype of Pet to Animal
Pet.prototype = new Animal();

var dog = new Pet("Rover", "Adam"); // Name is Rover

console.log(dog instanceof Pet);    // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

console.log(dog.name);              // Rover
console.log(dog.owner);             // Adam
dog.breathe();                      // Rover took a breath

Accessors and Mutators

When accessing a property, JavaScript will return the top-most value in the prototype chain. Mutations always happen at the top-most level in the prototype chain.

var Person = function(name) {
    this.name = name;
    this.x = 10;
};
var Student = function(name, major) {
    Person.call(this, name);
    this.major = major;
    this.x = 15;
};
Student.prototype = new Person();

var me = new Student("Adam", "Computer Science");
console.log(me.x); // 15
me.x = 20;         /* same as this.x in constructor */
console.log(me.x); // 20