Jim Cheung

(notes from JavaScript for PHP Developers)

TOC

Chapter 1, Introduction

JavaScript = ECMAScript + DOM + BOM

inside PHP you can embed JavaScript using the V8Js PHP class.

Learning Environment

JavaScript doesn't have a concept of input and output. I/O is provided by the environment that JavaScript programs run in.

Chapter 2, JavaScript Syntax

Variables

type is derived from the value, use var for all types.

declaring a variable without initializing with a value, undefined is assigned.

redeclaring an existing variable doesn't set the variable value back to undefined

var a = 1;
var a; // a is still 1

Values

There are five primitive value types in JavaScript: (Everything else is an object)

string: there is no difference between single and double qutoes.

boolean: literals true and false without qutoes

number: there are no floats, ints, doubles and so on; they are all numbers.

typeof Introspection

you can use the operator typeof to find the type of value.

typeof is not a function but a operator. (typeof() works because in this context, () is as grouping operator.

typeof operator always returns a string.

null and undefined

undefined type has only one value – undefined, function that doesn't return a value explicitly, it returns undefined

when using typeof, it returns "undefined" not equals undefined

null type has only one value – null, useful when you want to make an explicit differentiation between undeclared and uninitialized variables.

surprisingly, typeof returns "object". (you can expect typeof null to return "null" in one of the next version of ECMAScript.

Arrays

var a = [1, 2, 3];

Arrays in JavaScript are objects:

typeof a; // "object"

common usage

a.length; // like count() in PHP
// append to array
a.[a.length] = "new element";
// or
a.push("new element");

// just like PHP
a.unshift("as first element");
a.shift();
a.pop()

Associative Arrays

There are no associative arrays in JavaScript. When you need one, use an object.

var assoc = {one: 1, two: 2};

quotes around the properties (keys) are commonly omitted, when the keys are valid identifiers.

// adding
assoc.three = 3;
// removing
delete assoc.two;
// accessing
assoc.one; // 1
// or
assoc["one"]; // 1

Conditions

if (..) {
    ..
} else if (..) { // can not use elseif
    ..
} else {
    ..
}

Ternary Operator

(num % 2) ? "odd" : "even";

stacking more than one ternary operator, not a good idea, always do with parentheses.

Strict Comparision

values that cast to false in nonstrict comparisons:

All objects are truthy, empty arrays always cast to true in loose comparisons.

Switch

unlike PHP, the case are evaluated using strict comparison.

try-catch

var msg = "";
try {
    throw new Error("ouch");
} catch(e) {
    msg = e.message;
} finally {
    msg += " - finally";
}
msg; // "ouch - finally"

comparing to PHP, noticeable differences are:

catch block is an exception to the variable scope rule, it doesn't have block scope, just function scope. variables defined inside catch block will bleed outside of it. however, error object e is the only exception:

try {
    throw new Error();
} catch (e) {
    var oops = 1;
}
typeof e; // "undefined"
typeof oops; // "number"

try-catch has some performance implications when catch block is executed. should be avoided in performance-critical code paths.

while loop, for loop, just as PHP

for-in loop

var clothes = {
    shirt: "black",
    pants: "jeans",
    shoes: "none" // no trailing comma, because of old IEs
};
var out = '';
for (var key in clothes) {
    out += key + ': ' + clothes[key] + "\n";
}

here, you don't have direct access to the next property's value inside the loop, so you get the value by using clothes[key] not clothes.key

also the order of properties is not guaranteed and depends on implementations. use regular array if you need order.

in

if ("shirt" in clothes) // true
if (clothes['shirt']) // true
if (clothes.shirt) // true

the difference is that in only tests for the presence of the property; it doesn't look at the value at all. Testing with clothes.shirt returns true only if the property exists and it has a non-falsy value.

String Concatenation

var alphabet = "a" + "b" + "c";
// + operator is used for both adding numbers and concatenating strings
// alternative concatenating ways
['a', 'b', 'c'].join('');
"".concat('a', 'b', 'c');
String.prototype.concat('a', 'b', 'c'); 

Type Casting

use + to quickly change the types of variables

+"1"; // convert string "1" to number 1
"" + 1; // convert number 1 to string

use !! to convert a boolean

!!1; // true
!!""; // false

void

void operator takes any expression as an operand and returns undefined

var a = void 1;
typeof a === "undefined"; // true

for href in links, don't use href="javascript:void(0)", but href="javascript:undefined"

Comma Operator

it's the lowest precedence operator in JavaScript, it simply returns the value of the last operand passed to it

var a = ("hello", "there");
a; // there

Chapter 3, Functions

Default Parameters

There's no syntax that allows you to have a default value of a function parameter. but you can do

function sum(a, b) {
    b = b || 2;
    // also sometimes written as
    // b || (b = 2)
    
    // longer version using typeof
    b = typeof b === "undefined" ? 2 : b;
    return a + b;
}

Any number of arguments

there are no required parameters, you need to enforce it in the body of your function.

use arguments array-like object to access all arguments

function sum(/* nothing here */) {
    for (var i = 0, result = 0; i < arguments.length; i++) {
        result += arguments[i];
    }
    return result;
}

arguments is an object, to convert it to normal array (which you can use pop(), push() etc.)

function sum() {
    var args = Array.prototype.slice.call(arguments);
    typeof arguments.push; // "undefined"
    typeof args.push; // "function"
}

arguments.length trick

// just like PHP
// function sum($a = 1, $b = 2, $c = 3, $d = 4)
function sum(a, b, c, d) {
    // note, no break needed
    switch(arguments.length) {
        case 0: a = 1;
        case 1: b = 2;
        case 2: c = 3;
        case 3: d = 4;
    }
    return a + b + c + d;
}

Return Values

function always returns an value, if it doesn't, undefined will be returned.

Functions are Objects

function sum(a, b) {
    return a + b;
}
sum.length; // 2, the number of expected arguments
sum.call(null, 3, 4); // 7
sum.apply(null, [3, 4]); // 7
// equal to PHP
call_user_func('sum', 3, 4);
call_user_func_array('sum', array(3, 4));

A Different Syntax

functions are objects, so they can be assigned to variables just like all other data types.

var sum = function (a, b) {
    return a + b;
}; // <-- trailing semicolon is needed

this syntax is called a function expression, as opposed to the other syntax called function declaration

there's a semantic difference in the meaning of the function keyword depending on the context. in a function expression, it's an operator; in a function declaration, it's a statement.

named function expression (NFE)

var sum = function plum(a, b) {
    return a + b;
};
sum(1, 2); // 3;
plum(1, 2); // Error: plum() is not defined
// it helps debugging
sum.name; // "plum"
// but it cause some problem in older IEs, better to set the name matching to the variable
var sum = function sum(a, b) {
    return a + b;
};

Scope

there is no block scope in JavaScript, only function scope.

global variables are thoes defined outside of any function.

only exception is the error object in a catch block.

Hoisting

when the program execution enters a new scope, all variables defined anywhere in the function are moved, or hoisted, to top of the scope.

var a = 1; // global scope
function hoistingText() {
    console.log(a);
    var a = 2; // hoisted
    console.log(a);
}
hoistingText(); // logs "undefined", then 2

declare variables on top of the function to avoid the confusion.

Hoisting Functions

// global scope
function declare() {}
var express = function() {}
(function() {
    // local scope
    console.log(typeof declare); // "function"
    console.log(typeof express); // "undefined"
    function declare() {}
    var express = function() {}
    console.log(typeof declare); // "function"
    console.log(typeof express); // "function"
    }()
);

only var is hoisted, while the function declaration is hoisted together with its value (implementation)

Closures

A closure is a function together with its environment of nonlocal variables. In JavaScript, every function is a closure.

var global_ten = 10;
function papa() {
    var hundred_more = 100;
    var sum = function(a, b) {
        return a + b + global_ten + hundred_more;
    };
    return sum(9, 11);
}
papa(); // 130

papa() has access to the variables of the environment in which it was defined.

sum() has access to the parent's papa() environment (which it was defined).

there parent-child relationships form a chain you can call the scope chain

In JavaScript, you always have references to the variables in closure scope.

Scope Chain

Every time has JavaScript interpreter enters a new function, it looks around to see what local variables are available. It gathers those and puts them in a special variables object.

If there's another function defined inside the first and the interpreter enters its body, another variables object is created for the inner function. The inner function also gets a special scope property, which points to the variables object of the outer function. The outer function is where the inner function was defined, as if in a dictionary. That's why you hear the term "lexical" ("lexical definition" being synonymous with "dictionary definition") to highlight the difference between definition of a function and its execution. The scope property is set during the function definition, not execution.

The variables object and scope property are invisible from the programmer's perspective.

(more about scope, read the book again)

But it's important to understand that when retaining the scope, only references to the environment are retained, not specific values.

Immediate Functions

var immediate_one = function () {
    return 1;
}(); // <-- trailing ()
// syntax error
function myfunc() {
    return 1;
}();
// valid
(function myfunc() {
    return 1;
})();

// also valid
(function myfunc() {
    return 1;
}());

put it in a grouping operator () returns the function declaration into a function expression.

Chapter 4, Object-Oriented Programming

when you call a function with the new operator, it always returns an object. otherwise, every function without an explicit return returns undefined.

like PHP, () are optional when no argument is passing to the constructor.

returning custom object other than this cause the instanceof operator and the constructor property to not work as expected:

function Dog() {
    return {
        noname: "Anonymous"
    };
}
var fido = new Dog();
fido instanceof Dog; // false
fido.constructor === object; // true

when using this inside body but calling without new, the namespace remains global, global variables will be created

function Dog() {
    this.thisIsTheName = "Fido";
    return 1;
}

var fido = new Dog();
var one = Dog();
typeof fido; // "object"
fido.thisIsTheName; // "Fido"
typeof one; // "number"
one.thisIsTheName; // undefined
// when calling without new, global var created
thisIsTheName; // "Fido"

this dangerous behavior is fixed in ECMAScript 5's strict mode.

Enforcing Constructors

function Dog() {
    // check if the caller forgot "new"
    if (!(this instanceof Dog)) {
        return new Dog();
    }
    ...
}

Prototypes

the prototype property is always created for each function, constructor or otherwise.

creating object with new gives object access to all the properties added to the prototype property. otherwise, prototype and everything in it is ignored.

identify own properties and prototype properties, use hasOwnProperty()

literal.hasOwnProperty('mine'); // true
literal.hasOwnProperty('toString'); // false

proto

objects have prototypes, but they don't have a prototype property -- only functions do. However, many environment offer a special __proto__ property for each object.

Inheritance via the Prototype

// first constructor
function NormalObject() {
    this.name = 'normal';
    this.getName = function () { 
        return this.name;
    }
}
// second constructor
function PreciousObject() {
    this.shiny = true;
    this.round = true;
}
// the inheritance part
PreciousObject.prototype = new NormalObject();

another way, using object literal

var Normal = {
    name: 'normal',
    getName: function() {
        return this.name;
    }
};
var PreciousObject = function() {};
PreciousObject.prototype = Normal;
var child = new PreciousObject; 

Beget Object

function begetObject(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
// usage
var shiny = begetObject(normal);

"Classical" extend()

function extend(Child, Parent) {
    var F = function() {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
}
// usage
function Parent() {
    this.name = "Papa";
}
Parent.prototype.family = "Bear";
function Child() {}
extend(Child, Parent);
new Child().name;   // undefined
new Child().family; // Bear
Child.prototype.parent === Parent; // true

Borrowing Methods

using call() and apply() gives you an opportunity to reuse code without having to deal with inheritance at all.

var object = {
    name: "normal",
    sayName: function() {
        return "My name is " + this.name;
    }
};
var precious = {
    shiny: true,
    name: "iPhone"
};
object.sayName.call(precious); // "My name is iPhone"

and combine apply() with classical extend(), can call parent methods by

function Child() {
    Child.prototype.parent.apply(this, arguments);
}

Chapter 5, The Build-In API

each environment has a global object.

some environments have a global variable that refer to the global object. Browsers call it window. you can access it using this in global program code or in a function, as long as the function is not called with new and you're not in ES5 strict mode.

so, window.navigator is the same as navigator, unless you have a local variable with the same name.

Global Properties

there are three build-in properties of the global object

Global Functions

there 9 global functions, 4 related to numbers, 4 to encoding/decoding URLs, and eval()

Numbers

note, parseInt() takes a second argument to specify how you want the string to be parsed. always pass 10 to make sure your input is treated as a decimal:

parseInt(08); // 8
parseInt("08"); // 0
parseInt("08", 10); // 8

(in strict mode, ES5 parseInt("08") now returns 8)

code example, convert CSS hexadecimal color to rgb

var red = '#ff0000', r, g, b;
r = red[1] + red[2]; // "ff"
g = red[3] + red[4]; // "00"
b = red[5] + red[6]; // "00"
r = parseInt(r, 16); // parse as hexadecimal
g = parseInt(g, 16);
b = parseInt(b, 16);

Encoding URLs

var url = 'http://phpied.com/?search=js php';
encodeURIComponent(url); // "http%3A%2F%2Fphpied.com%2F%3Fsearch%3Djs%20php"
encodeURI(url); // "http://phpied.com/?search=js%20php"

note, encodeURI() doesn't encode &, which may be a problem.

Build-In Constructors

4 groups

two more global objects worth mentioning:

Object

var o = new Obejct();
// same
var o = {};

Array

(works just like PHP functions)

RegExp

var re = new RegExp('[a-z]'); // constructor
var re = RegExp('[a-z]'); // constructor without 'new'
var re = /[a-z]/; // literal

modifiers:

you can use exec() and test() to match strings, exec() returns matches, test() only returns true/false

Function

// function constructor, just like eval()
var f = new Function('a, b', 'return a + b');
f(4, 5); // 9

One use of Function() to replace eval() because Function() creates a local scope and doesn't bleed variables after evaluation.

Function() not only doesn't bleed variables into the global space, but it also has nothing in its scope chain except the global scope.

Chapter 6, ECMAScript 5

Strict Mode

once per function or once per program:

"use strict;"

old browsers will ignore this string statement, but an engine that understands strict mode will treat your code more strictly.

Property Attributes

a property can have a value attribute as well as 3 boolean attributes:

var stealth_descriptor = {
    value: "can't touch this",
    enumerable: false, // won't show up in for-in loops
    writable: false, // can't change my value
    configurable: false // can't delete or change my attributes
};

New Object APIs

Object.create()

this method can be used to create a new object and at the same time:

var human = {name: "John"};
var programmer = Object.create(
    human,
    {
        secret: stealth_descriptor,
        skill: {value: "Code ninja"}
    }
);

Restricting Object Mutations

var pizza = {
    tomatoes: true,
    cheese: true
};
// you can add, change or delete
pizza.pepperoni = 'lots';
pizza.cheese = 'mozzarella';
delete pizza.pepperoni; // true
// prevent extensions
Object.isExtensible(pizza); // true
Object.preventExtensions(pizza); // returns the 'pizza' object
Object.isExtensible(pizza); // false
pizza.broccoli = 'eww'; // Error
typeof pizza.broccoli; // "undefined"
// prevent deletions with seal()
Object.isSealed(pizza); // false
Object.seal(pizza); // return the 'pizza' object
Object.isSealed(pizza); // true
delete pizza.cheese; // Error
pizza.cheese; // "mozzarella"
// you can still change property value
pizza.cheese = 'ricotta';
// but once you freeze() an object, all properties become nonwritable
Object.isFrozen(pizza); // false
Object.freeze(pizza); // returns the 'pizza' object
Object.isFrozen(pizza); // true
pizza.cheese = "gorgonzola"; // Error
pizza.cheese; // "ricotta"
// none of these actions can be turned back on

Looping Alternatives

// get keys
var pizza = {tomatoes: true, cheese: true};
Object.key(pizza); // ["tomatoes", "cheese"]
Object.getOwnPropertyNames(pizza); // ["tomatoes", "cheese"]

keys() gives you all enumerable properties, getOwnPropertyNames() reutrns all own properties, enumerable or not.

Object.getPrototypeOf()

in ES5, you can directly ask "Who is your prototype?"

var pizza_v20 = Object.create(
    pizza,
    { ... }
);
Object.getPrototypeOf(pizza_v20) === pizza; // true

this is the same as using __proto__ (__proto__ is not standard)

Array Additions

Array.isArray() gives you a nonhacky way to distinguish between arrays and objects.

the hacky way (for ES3)

if (!Array.isArray) {
    Array.isArray = function (candidate) {
        return Object.prototype.toString.call(candidate) === '[object Array]';
    };
}

Array.prototype.forEach() to loop over all array elements without a loop construct.

['a', 'b', 'c'].forEach(function() {
    console.log(arguments);
});

Array.prototype.filter(), just like PHP's array_filter()

function testVowels(char) {
    return (/aeiou/i).test(char);
}
var input = ['a', 'b', 'c', 'd', 'e'];
var output = input.filter(testVowels);
output.join(', '); // "a, e"

two new methods every() and some() to test array content

function isEven(num)
    return num %2 === 0;
}
[1, 2, 4].every(isEven); // false
[1, 2, 4].some(isEven); // true

Array.prototype.map(), Array.prototype.reduce() and Array.prototype.reduceRight(), just like PHP's array_map and array_reduce()

String Trimming

String.prototype.trim()

" hello ".trim(); // "hello"

New in Date

Date.now() is a shorter way to do (new Date()).getTime() or +new Date(). it returns current timestamp

Function.prototype.bind()

var breakfast = {
    drink: "coffee",
    eat: "bacon",
    my: function() {
        return this.drink + " & " + this.eat;
    }
};
breakfast.my(); // "coffee & bacon"
var morning = breakfast.my;
morning(); // "undefined & undefined"
// make it work
var morning = breakfast.my.bind(breakfast);
morning(); // "coffee & bacon"

JSON

JSON.stringify({helo: "world"}); // like PHP's json_encode()
JSON.parse('{"hello":"world"}'); // like PHP's json_decode()

Shims

"shims" or "polyfills" are a way to emulate the new APIs. check online.

Chapter 7, JavaScript Patterns

Private Properties

ES5 can use property descriptors, in ES3, use a closure and not expose the variables you want to keep private.

var my = (function() {
    var hidden_private = 42;
    return {
        hi: 1 + hidden_private,
        bye: 2 + hidden_private
    };
}());
my.bye; // 44

Private Methods

like regular variables, put it it inside a closure.

var my = (function() {
    var hidden_private = 42;
    function special() {
        return Number(public_api.hi) + hidden_private;
    }
    var public_api = {
        hi: 1,
        get: function() {
            return special();
        }
    };
    return public_api;
}());
my.hi; // 1
my.get(); // 43
my.hi = 100; // 100
my.get(); // 142

Exposing Private Objects

last example, another way to do so is

var public_api = {
    hi: 1
    get: special
};

however, because object is passed by reference, code outside the closure can modify the Properties of that object.

my.get.call() // same as my.get()
my.get.call = "not so special" // overwrite the call method
my.get.call() // TypeError: call is not a function

Returning Private Arrays

var my = (function() {
    var days = ["Mo" ... "Su"];
    return {
        getDays: function() {
            return days;
        }
    };
})();

days is private to outside the closure, but they can add access the array indirectly

my.getDays().push('Js'); 
my.getDays(); // ["Mo" ... "Su", "Js"]

a way to protect the array is return a copy of it

getDays: function() {
    return days.slice();
}
my.getDays().push('Js'); 
my.getDays(); // ["Mo" ... "Su"]

Deep Copy via JSON

last example, if need to return a deep copy of array:

return {
    getStuff: function() {
        return JSON.parse(JSON.stringify(stuff));
    }
};

Namespaces

there are no namespaces in JavaScript, simply use properties of an object for the purpose

var App = {};
App.my = { .. }
App.my.more = { .. }

Modules

there is no concept of modules in JavaScript, but the name module pattern is often used to refer to a combination of serval things:

App.namespace('my.shiny').module = (function() {
    // short name for dependencies
    var shiny = App.my.shiny;
    // local/private vars
    var i, j;
    // private functions
    function hidden() { ... }
    // public API
    return {
        willYouUseMe: function() {
            return "Yups";
        }
    };
}());
// usage
App.my.shiny.module.willYouUseMe(); // "Yups"

CommonJS Modules

following snippet is a bare-bones CommonJS module:

// private vars
var i, j;
// private functions
function hidden() { ... }
// public API
exports.sayHi = function() {
    return "hi!";
};
// usage
var hi = require("./hi.js");
hi.sayHi(); // "hi!"

(rest of the book are some design patterns examples and resources)

(end)