expert javascript tricks of the masters
DESCRIPTION
A presentation I gave at Dr. Dobbs India in the cities of Bangalore and Pune.TRANSCRIPT
Tricks of the masters
Expert JavaScript:
JavaScript can be strange
JavaScript can be strange
• Syntactically similar to C++ and Java
“Java is to JavaScript as car is to carpet”• Prototypal inheritance
• Classless. Objects inherit from objects.
• Loosely typed• Type coercion. “Truthy” / “falsy” values.
• Objects are mutable
NodeLists
NodeLists
Get all the anchors in a page:
var anchors1 = document.getElementsByTagName('a');[<a href="#"> Item 1 </a>, <a href="#"> Item 2 </a>]
var anchors2 = document.querySelectorAll('a');[<a href="#"> Item 1 </a>, <a href="#"> Item 2 </a>]
NodeLists
User interaction causes more anchors to be added to the DOM.
NodeLists
One of our NodeLists is automatically updated:
console.log(anchors1);[<a href="#"> Item 1 </a>, <a href="#"> Item 2 </a>,<a href="#"> Item 3 </a>,<a href="#"> Item 4 </a>]
console.log(anchors2);[<a href="#"> Item 1 </a>, <a href="#"> Item 2 </a>]
Scope and Closures
Scope and Closures
JavaScript does not have block scope.
Sort of.
Scope and Closures
Variables are scoped to functions
var foo = "bar"; //global
function widget() {
var foo = "123"; //scoped to widget
}
Scope and Closures
Variables are scoped to functions
var message = 'Hello? Is anybody there?';
function helloWorld() {
var message = 'Hello, world!';
return message;
} // returns "Hello, world!"
Scope and Closures
Functions don't need to be named.
A common scoping technique is to use the Immediately-Invoked Function Expression (aka IIFE)
Scope and Closures
Immediately-Invoked Function Expression:
(function () {
var message = 'Hello, world!',
conferenceName = 'Dr. Dobbs India',
attendees = 'Awesome';
}());
Scope and Closures
Immediately-Invoked Function Expression:
(function ($) {
$('p'); //works
}(jQuery));
$('p'); //does not work
Scope and Closures
Functions also create closures.
Closure = Anything declared within a function is aware of anything else declared within that function.
Scope and Closures
Functions also create closures.
var public = 'Everybody sees this.';
function family() {
var father, mother;
function kids() {
var child1, child2;
}
}
Scope and Closures
Building a game leveraging closures:
var game = (function () {
var secret = 5;
return function (num) {
var total = secret * num;
console.log('You say ' + num + ', I say ' + total + '. What is my secret?');
};
}());
Scope and Closures
Building a game leveraging closures:
> game(5)
You say 5, I say 25. What is my secret?
> game(10)
You say 10, I say 50. What is my secret?
> secret
ReferenceError: secret is not defined
Scope and Closures
Block scope with let*
if (x) {
let myVar;
let yourVar = 123;
}
* Not yet available everywhere.
Scope and Closures
Block scope with let*
var myVar = 5;
let(myVar = 6) {
alert(myVar); // 6
}
alert(myVar); // 5
* Not yet available everywhere.
Memoization
Memoizationsquare()
Memoizationvar square = function (num) {
return num * num;
};
Memoization
Problem:
What if the operation is more complex and is called multiple times?
Memoization
Trick:
Cache the result.
Memoizationvar square = (function () {
var memo = [];
return function (num) {
var sqr = memo[num];
if (typeof sqr === 'undefined') {
console.log('Calculating...');
memo[num] = sqr = num * num;
}
return sqr;
}
}());
Memoizationvar fetchData = (function () {
var data = null, timestamp = new Date().getTime();
return function (url, callback) {
if (timestamp - new Date().getTime() > 60000) {
$.ajax(url, {success: function (d) {
timestamp = new Date().getTime();
data = d;
callback(data);
});
} else {
callback(data);
}
}
}());
Function Overloading
Function overloading
calculateAverage()
Function overloading
> calculateAverage([10, 20, 30, 40, 50])
30
Function overloading
function calculateAverage(values) {
var total = 0;
values.forEach(function (val) {
total += val;
});
return total / values.length;
}
Function overloading
> calculateAverage(10, 20, 30, 40, 50)
30
Function overloading
How many arguments are being passed?
Function overloading
Use the arguments object.
function returnArgs() { return arguments;}
> returnArgs('hello', 'world');["hello", "world"]
Function overloading
arguments is not an Array.
Does not have any Array properties except length. Not very useful.
Function overloading
arguments can be converted into an array:
var args = Array.prototype.slice.call(arguments);
Function overloadingfunction calculateAverage(values) {
var total = 0;
if (!Array.isArray(values)) {
values = Array.prototype.slice.call(arguments);
}
values.forEach(function (val) {
total += val;
});
return total / values.length;
}
Function overloading
> calculateAverage('10, 20, 30, 40, 50')
30
Function overloadingfunction calculateAverage(values) {
var total = 0;
if (typeof values === 'string') {
values = values.split(',');
} else if (typeof values === 'number') {
values = Array.prototype.slice.call(arguments);
}
values.forEach(function (val) {
total += parseInt(val, 10);
});
return total / values.length;
}
Async Code Execution
Async Code Execution
What does this code do?
// Load appointments for April$.ajax('/calendar/2014/04/');
// Show the calendarshowCalendar();
Shows an empty calendar.
Async Code Execution
Ajax requests are asynchronous.
The JavaScript engine doesn’t wait.
// Load appointments for April$.ajax('/calendar/2014/04/');
// Show the calendarshowCalendar();
Async Code Execution
Event-driven programming where callback functions are assigned as event handlers.
When X happens, do Y. Where X is the event and Y is the code to execute.
Async Code Execution
Assigning an Ajax event handler:
// Load appointments for April$.ajax('/calendar/2014/04/', {
complete: showCalendar});
Async Code Execution
Event handler assignment:
document.onclick = function (e) {
e = e || event;
console.log('Mouse coords: ', e.pageX, ' / ', e.pageY);
}
Async Code Execution
Event handler assignment:document.addEventListener('click', function (e) {
e = e || event;
console.log('Mouse coords: ', e.pageX, ' / ', e.pageY);
});
document.addEventListener('click', function (e) {
e = e || event;
var target = e.target || e.srcElement;
console.log('Click target: ', target);
});
Async Code Execution
Event handler assignment:
var xhr = new XMLHttpRequest();
xhr.onload = function () {
console.log(this.responseText);
}
xhr.open('get', ’/weather_data');
xhr.send();
Async Code Execution
Event handler context:
document.onclick = function (e) {
// `this` refers to document object
e = e || event;
console.log('Mouse coords: ', e.pageX, ' / ', e.pageY);
}
Async Code Execution
Event handler context:
xhr.onload = function () {
// `this` refers to xhr object
console.log(this.responseText);
}
Async Code Execution
this refers to XMLHttpRequest object:
Execution Context
Execution Context
Need to execute callback in different contexts where this doesn’t refer to the same thing.
Execution Context
Use apply, call or bind to change execution context.
Execution Context
Usage:
doSomething.apply(context, args);
doSomething.call(context, arg1, arg2, ... argN);
var doIt = doSomething.bind(context, arg1, arg2, … argN);
Execution Context
Let’s bootstrap our earlier Ajax example to demonstrate.
Execution Context
Ajax event handler assignment from earlier:
var xhr = new XMLHttpRequest();
xhr.onload = function () {
console.log(this.responseText);
}
xhr.open('get', '/weather_data');
xhr.send();
Execution Context
Bootstrap data rendered into the page:<script>
var bootstrapData = {
"responseText": {
"weather": {
"high": "35 ºC",
"low": "24 ºC",
"precipitation": "0%",
"humidity": "78%",
"wind": "13 km/h"
}
}
};
</script>
Execution Context
Start with refactoring our callback:
function parseData () {
console.log(this.responseText);
}
var xhr = new XMLHttpRequest();
xhr.onload = parseData;
xhr.open('get', '/weather_data');
xhr.send();
Execution Context
Set Ajax code to poll every 15 seconds:
function parseData () {console.log(this.responseText);}
var xhr = new XMLHttpRequest();
xhr.onload = parseData;
setInterval(function () {
xhr.open('get', '/weather_data');
xhr.send();
}, 15000);
Execution Context
bootstrapData is a global variable:<script>
var bootstrapData = {
"responseText": {
"weather": {
"high": "35 ºC",
"low": "24 ºC",
"precipitation": "0%",
"humidity": "78%",
"wind": "13 km/h"
}
}
};
</script>
Execution Context
Parse bootstrap data immediately on page load:
function parseData () {
console.log(this.responseText);
}
parseData.call(bootstrapData);
Prototypal Inheritance
Prototypal Inheritance
“All objects in JavaScript are descended from Object”
var myObject = new Object(); ObjectObject
myObjectmyObject
Prototypal Inheritance
“All objects in JavaScript are descended from Object”
Object.prototype.hello = 'world';
var myObject = new Object();
Object.helloObject.hello
myObject.hello
myObject.hello
Prototypal Inheritance
In JavaScript, functions can act like objects.
Prototypal Inheritance
They have a prototype property allowing inheritance.
function Person() {};Person.prototype.name = "Bob";
Prototypal Inheritance
You can create an instance of one using the new keyword:
var person1 = new Person();console.log(person1.name); // Bob
Prototypal Inheritance
Live link with its parent:
function Animal() {}
var dog = new Animal();
Animal.prototype.speak = function (msg) {
console.log(msg)
};
Prototypal Inheritance
Live link with its parent:
function Animal() {}
var dog = new Animal();
Animal.prototype.speak = function (msg) {
console.log(msg)
};
dog.speak('woof');
requestAnimationFrame, CSS transforms
Optimization
Optimization
Improving event handlers with delegation
Optimization
Rather than assigning individual handlers:
var a = document.getElementById('save');
a.onclick = function () {
// save code
};
Optimization
Assign one for all of the same type:
document.onclick = function (e) {
e = e || event;
var target = e.target || e.srcElement;
if (target.id === 'save') {
// save code
}
};
Optimization
Improving animation
Optimization
Improving animation
setInterval(function () {
someElement.style.left = (leftValue + 1) + "px";
}, 0);
Optimization
Improve animation with requestAnimationFrame
Key differences: •Animation stops when running in hidden tab•Browser optimized repaint/reflow
Optimization
Animation stops when running in hidden tab
•Uses less GPU/CPU•Uses less memory
Translation = longer battery life
Optimization
Browser optimized repaint/reflow
Changes to CSS and the DOM cause parts or all of the document to be recalculated.
Optimization
Source: https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame
requestAnimationFrame compatibility
Optimization
Source: https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame
requestAnimationFrame compatibility
Security
Security
Avoid eval
Security
Use literals
[] instead of new Array(){} instead of new Object()
Security
Use literals
Array = function () {
// I am pretending to be an array.
// I am doing something evil with your data.
}
Security
External scripts make your application insecure.
Thank you!
Happy to answer your questions throughout the conference and afterwards!
twitter.com/ara_p