2006-03-06 Simon Willison's Re-intro to JavaScript ORA E-Tech 2006 ------------------ Note takers: Gabe Hollombe (gabe@avantbard.com) is light green ------------------ Recommends: BOOKS - JavaScript the Definitive Guide. It's old but still worth getting. But there's a new ver coming out later this year. - DHTML Utopia - DOM Scripting TOOLS - Firefox (duh) - Jesse Ruderman's Shell tool www.squarefree.com JS initially named LiveScript renamed to JS by Sun, heh. - designed to be embeded inside environments like browsers, acrobat, widget engines, etc - semi-colons optional but it's silly not to use them - always use var to declare variables (else they're global) TYPES - Numbers - 64-bit IEEE754 vals (floats) - No such thing as an integer (0.1 + 0.2 = 0.300000000004) - luckily, lots of Math functions. Math object has mucho helpers. - parseInt converts string to num - special numbers NaN (not a number), isNaN(NaN) -> true Special vals for Infinity and -Infinity - Strings - sequences of unicode chars (16 bit) - "\u0020" - char is a str of length 1 - of course, lots of str methods too on the string object - null = deliberately no value - undefined = no val assigned yet - Bools - true or false, duh - everything else is truthy or falsy - 0, "", NaN, null, undefined are falsy - everything else is truthy - Objects - Function - Array - Date - RegExp - vars defined with var keyword var a; var name = "foo"; - if you dont use var keyword you get a global var which is bad bad - never omit var even if you want a global var - Operators for nums + - * / % - compound assignment +=, -=, etc - increment and decrement a++, ++a - string concats: "hello" + "world" - type coercion: "3" + 4 + 5 = "345" vs 3 + 4 + "5" = 75 - adding empty string to something converts to str - some equality tests will use type coercion - === checks type, == will type coerce - 1 === true -> false - true === true -> true - typeof() gives you a string with type, sometimes - objects, arrays, null all return 'object' - undefined returns 'undefined' SWITCH - put a fallthrough at the beginning of switch is a good idea - switch(1 + 3) is allowed, comparisons take place using === LOGICAL OPERATIONS - && and || are short-circut evaluated - one-line conditionals can be helpful but easily abused: var ok = (age > 18) ? "yes" : "no"; EXCEPTIONS - just like Java - try, catch, finally - throw throw new Error("An error!"); throw "Another error!"; - finally not commonly used OBJECTS - name value pairs (dicts in python, hashes in perl/ruby, hash tables in c++, assoc arrays in php) - extremely versitile data structure - name part is string, value part can be anything - basic creation var obj = new Object(); //old style var obj = {}; //new style; object literal syntax, this is more convenient - prop access //one way, not so cool obj.name = "Simon"; var name = obj.name; //-OR- //below is better because you can use strings so it can be decided at run-time obl["name"] = "Simon"; var name = obj["name"]; - obj litteral syntax in action var obj = { name: "Carrot", "for" : "Max", details: { color: "orange", size: 12 } } //access is easy >obj.details.color -> "orange" >obj["details"]["size"] -> 12 - can iterate over keys of an object for (var attr in obj) { print (attr + ' = ' obj[attr]); } //dont use this with arrays, there are safer ways of iterating over them ARRAYS - special type of object: keys are whole numbers, not strings - use [] syntax just like objects > var a = new Array(); > a[0] = "dog"; > a.length -> 1 - better: array litteral notation > var a = ["dog", "cat"]; - safest way to append item to array: > a[a.length] = item; //length prop always returns one more than the highest index - iterating for (var i=0; ia -> 4 >b -> 2 - recursive functions are allowed, too - arguments.callee returns the current function object so we can make recursive func calls w/out knowing the function's name. useful for anony funcs - arguments.callee keeps track of number of times its been called (arguments.callee.count) CONSTRUCTORS - can attach funcs to objects - can we do it in an elegant way? function Person(first, last) { //use InitialCaps to denote it's a constuctor (convention) this.first = first; this.last = last; this.FullName = function() { return this.first + ' ' + this.last } } > var s = new Person("Gabe", "Hollombe"); // OR BETTER, USE PROTOTYPE function Person(first, last) { this.first = first; this.last = last; } Person.prototype.fullName = function() { //return a full name } - if you ask for a property in an object and it doesnt exist in the object itself, js then checks to see if there's a prototype object with that property - instanceof operator used to tyest type of an object, based on its constructor var a = [1,2,3]; a.instanceof Array -> true a.instanceof Object -> true a.instanceof String -> false //it's not a string INHERITANCE - you can add new methods to objects/classes at runtime; just declare ClassObject.prototype.newFunction() - the object doesnt change but it's prototype does, so this works - we can use this to extend core js objects > var s = "Simon"; > s.reversed(); -> ERROR > String.prototype.reverse() = function() { //return a reversed string //use 'this' to reference the string object being operated on } > s.reversed() -> nomiS - even string litterals get prototyped! >"Abcd".reversed() -> "dcbA" - you can also redefine existing methods (probably a bad idea, but it's possible) - only time this is useful is in cases where some browser implementation doesnt support a feature of the lang you want to use. just override it's normal behavior - you can check browser and if it's one you want to change behavior of, then override the default behavior - probably better to actually test the method's behavior and only override if it's not what you expect - all prototype chains terminate at Object.prototype - Object methods include toString() which we can override - can we edit Object.prototype to edit behavior of ALL objects? at peril - anything you add to object.prototype will be added to the enumeration chain; for(attr in obj) (boo urns!) - design classes with inheritance in mind - constructor shouldn't do anything (that might break if you creatre empty instance for use as a prototype) - use one of the many workarounds: - prototype's Class.create() - the stuff you get by searching for "javascript inheritance" on the web =-) HIGHER ORDER FUNCTIONS - functions are first-class objects (can pass to other functions, store in vars, return it from a function) - closures let you encapsulate a remembered value inside an instance of a function; a combination of the function AND the scope in which it was created. - so, functions can have state, through closures - functions in js have a scope chain (similar to prototype chain) - example 'gotcha' function openLinksInNewWindows() { for (var i=0; i < document.links.length; i++) { document.links[i].onclick = function() { window.open(document.links[i].href); //WONT WORK //BETTER: window.open(this.href); //this will, using the 'this' keyword refers to itself return false; } } } // THIS WONT WORK BECAUSE i is a shared scope amongst all these functions so by the time this loop finishes, i won't exist anymore and none of the document.links[i] references will be valid. use this.href instead will work SINGLETON - javascript shares a single global namespace - easy to step on other people's functions (if you start integrating your code with others' code) - example: var simon = (function() { var myVar = 5; //private var function init(x) { //... can access myVar and doPrivate } function doPrivate(x) { //... invisible to outside world } function doSomething(x, y) { //... can access myvar and doPrivate } return { 'init': init, 'doSomething': doSomething } })(); >simon.init(x); >simon.doSomething(x, y); - singleton lets you wrap up a complex app with dozens of functions in a single private namespace; a closure - so, you get the choice to expose only the functions that make up your app's external interface MEMORY LEAKS - IE sucks because it uses different GC's for JS and for the DOM; can't handle circular references between them - any object that IE gives to the JS host environment is actually a COM object function leak() { var div = document.getElementById('d'); div.obj = { 'leak' : div } } //call enough times and IE will crash due to the circular reference - if you can remember, assign your DOM var references to null when you're done with your function, thus, no circ reference and no leak - better: use one of the js libs that allow easy attaching/detaching events. just remember to be aware of the problem. PERFORMANCE - de-reference complex lookups var ele = document.getElementById('id').style; ele.width = "100%"; ele.color = "red"; LIBRARIES - dojotoolkit.org - nice because it's designed to run outside of browsers as well - mochikit.com - written by python programmer to make js more pythonic - prototype.conio.net - most famous because they did ajax integration first, and rails uses it by default - modifies Object.properties so iterating with for attr in obj will probably not behave as you expect - developer.yahoo.new/yui/ - script.aculo.us SUM UP - everything in js is an Object, even functions - every object is always mutable, even built-ins - the dot operator is equivilent to de-referencing by hash (foo.bar === foo["bar"]) - the new keyword creates an object that class constructors run inside of, thereby imprinting them - functions are _always_ closures (combine w/ previous rule to create OOP) - the 'this' keyword is relative to the execution context, not the declaration context (object.method ('this' references the object, vs running method directly, 'this' will refer to the window object) Slides availabe at http://simon.incutio.com/ by the end of the day EXTRAS - simon thinks you could build macros with 'eval' but you shouldn't - performance-wise, js is interpereted into a syntax tree. each eval call drops out of execution to parse it, then back to execution (duh) - design-wise, debugging tools won't work when you have code in strings (like venkman) - 'eval' uses can probably be more elegantly solved with closures (build a brand new functions with different behaviors) - one useful case for eval is when you're doin AJAX and you need to eval the response text if the AJAX call is returning js, of course (like JSON) - javascript threadding model, or lack thereof - things like animation where you want to delay things (move x by y pixels every z seconds) can be a pain to implement elegantly - setInterval and setTimeout are available - or, declare and use function.prototype.callLater()