(notes from JavaScript for PHP Developers)
TOC
- Chapter 1, Introduction
- Chapter 2, JavaScript Syntax
- Chapter 3, Functions
- Chapter 4, Object-Oriented Programming
- Chapter 5, The Build-In API
- Chapter 6, ECMAScript 5
- Chapter 7, JavaScript Patterns
Chapter 1, Introduction
JavaScript = ECMAScript + DOM + BOM
inside PHP you can embed JavaScript using the V8Js
PHP class.
- Functions in JavaScript are Objects. Arrays and regular expressions are objects, too.
- Functions provide scope, and local variable scope is achieved by wrapping the code in a function.
- Closures are used heavily.
- Prototypes are an important concept. It's one of the ways JavaScript accomplishes code reuse and inheritance.
- JavaScript doesn't have a concept of classes.
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
- boolean
- number
- null
- undefined
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
===
means strict comparison (the expressions are compared by both value and type)==
means loose comparison (only values are compared and, if needed, cast happens)
values that cast to false
in nonstrict comparisons:
- Empty string
""
- The number
0
- The value
false
null
undefined
- Special numberic value
NaN
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:
- An
Error
object is thrown, notException
- Type is not decalred when catching
- A
message
property is accessed as opposed to calling agetMessage()
method finally
exists in PHP after v5.5
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
NaN
:1 * 'a'; // NaN
Infinity
:1 / 0; // Infinity
undefined
Global Functions
there 9 global functions, 4 related to numbers, 4 to encoding/decoding URLs, and eval()
Numbers
isNaN()
isFinite()
parseInt()
parseFloat()
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
encodeURIComponent()
decodeURIComponent()
encodeURI()
decodeURI()
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
Object()
,Array()
,RegExp()
andFunction()
, rarely used objects, because they have shorter literal syntax versions.String()
,Number()
andBoolean()
, rarely used because they are just wrappers around primitive types, in JavaScript, you can jump easily between primitive type and objectified wrapper.Date()
, has tons of methodsError()
, generic one, there are also more specific error constructors (e.g.SyntaxError()
,ReferenceError()
, etc.)
two more global objects worth mentioning:
Math
, not a constructor but a global object,Math.min(3, 5); // 3
JSON
, not a constructor but an object that holds methods for encoding/decoding JSON strings.
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:
g
globalm
multilinei
ignorecase
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:
enumerable
writable
configurable
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()
Object.defineProperty()
Object.defineProperties()
Object.getOwnPropertyDescriptor()
Object.create()
this method can be used to create a new object and at the same time:
- define inheritance
- define properties
- define attributes of the properties
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:
- Namespaces to define each module as a property of a single global object.
- An immediate function what wraps the whole module definition and provides privacy
- The immediate function returns an object, which reveals a public API
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!"
- no need for an immediate function wrapper
- everything you do is local by default
- anything you add to the special
exports
object becomes your module's public API
(rest of the book are some design patterns examples and resources)
(end)