Created
January 11, 2012 08:44
-
-
Save markmarkoh/1593764 to your computer and use it in GitHub Desktop.
javascript dev training
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Javascript is an incredibly fun and (relatively) easy language. It's also | |
| really easy to do badly... in some ways js is out to get you, so if you're | |
| ready for it, you can avoid the pitfalls and have a great time. | |
| Ideally this talk gets you thinking of Javascript as the unique, | |
| quasi-dangerous little beast that is. If you respect the ways in which it | |
| is your enemy, it will become one of your best friends. | |
| This presentation isn't intended to teach javascript (though you'll probably | |
| learn some). It's focused on understanding topics which are taken for | |
| granted and pointing out pitfalls. | |
| */ | |
| /****** | |
| * | |
| * 1. Javascript's syntax is LYING to you. | |
| * | |
| ******/ | |
| /* | |
| Javascript is probably not like any programming language you have expertise in; | |
| it has more in common with LISP than Java. | |
| It is highly functional. It is also very objected oriented but uses a | |
| "prototype" system rather than a class system. More on prototypes later... | |
| for now, here's an object: | |
| */ | |
| var saysSomething = { | |
| somethingToSay: 'Something', | |
| speak: function() { | |
| console.log(this.somethingToSay); | |
| } | |
| }; | |
| saysSomething.speak(); | |
| /* | |
| Not very familiar looking is it? Note that we don't need a class. | |
| We just whip an object up inline. This is called an "object literal". | |
| This syntax is sweet. But enough praising javascript, | |
| let's get to some of its dirty little secrets. | |
| */ | |
| /* | |
| Scoping: Remember how Javascript LIES to you? The scoping is bonkers. | |
| */ | |
| var inTheIfBlock = 'OUTSIDE'; | |
| var fakeCondition = true; | |
| if (fakeCondition) { | |
| var inTheIfBlock = 'INSIDE'; | |
| console.log(inTheIfBlock); | |
| } | |
| console.log(inTheIfBlock); | |
| // console.logs 'INSIDE' twice. What? | |
| /* | |
| Scopes in Javascript are defined by functions. To 'fix' the above code, | |
| we could write: | |
| (Side note: don't do this. For demonstration purposes only) | |
| */ | |
| var inTheIfBlock = 'OUTSIDE'; | |
| var fakeCondition = true; | |
| if (fakeCondition) { | |
| (function() { | |
| var inTheIfBlock = 'INSIDE'; | |
| console.log(inTheIfBlock); | |
| })(); | |
| } | |
| console.log(inTheIfBlock); | |
| /* | |
| ... a bit ugly, but demonstrates the point, and introduces us | |
| to the "immediately invoked function expressions", or "IFFYs". | |
| */ | |
| /* | |
| In general, all code should be scoped, and anything that is put in the global | |
| namespace should be put there consciously. The two examples above are stuffing | |
| a bunch of useless stuff in the global namespace. You'll see the following a | |
| lot; it's much better: | |
| */ | |
| (function() { | |
| var myVar = "mine all mine"; | |
| })(); | |
| console.log(myVar); // undefined | |
| /* | |
| But scoping is WONKY so watch out for this: | |
| */ | |
| (function() { | |
| /* on noes... forgot to put 'var', this code will still work | |
| (and maybe even as expected, but you are leaking globals!) */ | |
| myVar = "mine all mine"; // implicit global! | |
| // Who on earth thought this was a good "feature"??? | |
| })(); | |
| console.log(myVar); | |
| /* If your intention was to make myVar global, consciously use the global scope variable, window */ | |
| (function() { | |
| window.myVar = "mine all mine"; // EXPLICIT global! | |
| })(); | |
| /* | |
| Think that's wonky? Check this out: | |
| */ | |
| var foo = 'hello'; | |
| (function() { | |
| var foo = foo || 'world'; | |
| console.log(foo); | |
| })(); | |
| /* | |
| This always says 'world'. What??? | |
| */ | |
| /* | |
| This is an artifact of something called 'hoisting'. Javascript will always | |
| scan the whole scope to see what variables are declared before executing | |
| anything, so what it's seeing looks a lot more like this: | |
| */ | |
| var foo = 'hello'; | |
| (function() { | |
| var foo; //which is now undefined | |
| foo = foo || 'world'; | |
| console.log(foo); | |
| })(); | |
| /* | |
| Some people argue that this means you should always explicitly declare your | |
| variables at the beginning of a scope. | |
| Kinda hard to argue back... | |
| */ | |
| /* | |
| More hoisting while we're on the topic: | |
| */ | |
| (function() { | |
| foo(); // expresses pity | |
| bar(); // error: bar is not a function... yet. | |
| function foo() { | |
| console.log('I pity the foo'); | |
| } | |
| var bar = function() { | |
| console.log('I pity the bar?') | |
| } | |
| })(); | |
| /* | |
| Functions which are declared in the first form get their whole definition | |
| hoisted (unlike the vars we say earlier). These are called "function declarations". | |
| Functions declared in the second form behave like all other vars. These are called "function expressions". | |
| The other nice thing about the second form is that it reinforces the fact that functions | |
| are just values, and can be treated like any other value (which is AWESOME). | |
| */ | |
| /****** | |
| * | |
| * 2. What the heck is a "closure"? | |
| * | |
| ******/ | |
| /* | |
| Closures are awesome... but can perhaps be tricky to really *get*. Lots of | |
| people write lots of js without getting it, but they are a danger to themselves | |
| and others. | |
| Ideally, this is a really simple concept: when a function is defined, | |
| the state of the world at the time of the function is saved along with it. | |
| This has a lot of really neat implications. | |
| So, uh, why "Closure"? | |
| Wiki says: because a function is "closed over" its free variables (?). | |
| Let's be honest: it's a pretty terrible name for the concept. | |
| With closures, scopes always keep access to the outer scope, in which they were defined. | |
| (there are hundreds of attempted definitions of a closure, this is my favorite). | |
| Some examples: | |
| */ | |
| (function() { | |
| var outerVar = "this is outterVar"; | |
| // functions always create scope (and thus a closure) | |
| window.functionCreatingClosure = function() { | |
| outerVar = outerVar + "; this was appended by the function"; | |
| console.log(outerVar); | |
| }; | |
| // prints "this is outterVar; this was appended by the function" | |
| functionCreatingClosure(); | |
| // also prints "this is outterVar; this was appended by the function" | |
| console.log(outerVar); | |
| outerVar = "hey! who changed my value?"; | |
| // prints "hey! who changed my value?; this was appended by the function"; | |
| functionCreatingClosure(); | |
| // one last change before outterVar goes out of scope | |
| outerVar = "last change"; | |
| })(); | |
| // At this point outerVar is totally out of scope. It's gone. | |
| // However, the reference to it in functionCreatingClosure persists | |
| // prints "last change; this was appended by the function" | |
| functionCreatingClosure(); | |
| /****** | |
| * | |
| * 3. Object disoriented code | |
| * | |
| ******/ | |
| /* | |
| Objects in Javascript probably don't work the way you'd expect. | |
| They have no class (they're vulgar?). | |
| You can make them look vaguely familiar... | |
| */ | |
| // a "constructor" | |
| var MyConstructor = function() { | |
| this.aProperty = 'a property'; | |
| this.oops = 'oops!'; | |
| }; | |
| // creating a new instance | |
| var myInstance = new MyConstructor(); | |
| console.log(myInstance.oops); | |
| /* | |
| This "familiar" approach is deficient in a lot of ways... | |
| - no encapsulation; everything is public | |
| - much, much worse... what's wrong with this picture: | |
| */ | |
| var myInstance = MyConstructor(); | |
| console.log(myInstance.oops); // error??? | |
| console.log(oops); // aw crud... this outputs "oops!" | |
| /* | |
| Often object literals will give you everything you need. | |
| */ | |
| var myObj = { | |
| myProp: 'stuff', | |
| myOtherProp: 'otherStuff', | |
| someFunc: function() { | |
| return 'get func-y'; | |
| } | |
| }; | |
| /* | |
| Since you often you need a bunch of these, note that they make a great | |
| return type from light "factories" | |
| */ | |
| var createMyObj = function() { | |
| var _private = "secrets"; //this is how we do private variables in JS. | |
| //after this function returns, nobody except for | |
| //the returned object has access to _private | |
| return { | |
| myProp: 'stuff', | |
| myOtherProp: 'otherStuff', | |
| someFunc: function() { | |
| return _private; | |
| } | |
| } | |
| }; | |
| /* | |
| Disgression 1: JSON. | |
| Discussing object literals brings us to "JSON" (Javascript Object Notation). | |
| JSON is a data interchange format, analgous to XML. It's really popular and | |
| useful, especially in js for the following reasons: | |
| * it's lightweight. It contains not much more that the data to be transfered | |
| * it's built out of a subset of object literal syntax, so it is really easy | |
| to translate a JSON description of an object into an actual object | |
| (using JSON.parse, NOT eval) | |
| * it's really rather human readable, unlike some formats which only | |
| claim to be (I'm looking at you XML...) | |
| * it's easy to create, especially from js by calling JSON.stringify | |
| One pitfall is that there is often confusion between JSON and actual objects. | |
| Crockford himself even refers to a JSON representation of an object as a | |
| "JSON object", but I've found it to be fruitful to keep stricter lines | |
| between JSON, which is a string representation of data, and javascript | |
| objects, which are actual program data. | |
| More info can be found in The Good Parts, and at http://json.org/ | |
| */ | |
| /* | |
| Digression 2: Prototypes. | |
| So, if there's no classes, how can you define types in js? Answer: prototypes. | |
| */ | |
| var myString = 'test'; // an object of type String. | |
| // You could say "new String('test')... actually, no you can't. Just don't. | |
| // There are some methods String defines | |
| console.log('test'.toUpperCase()); | |
| // and some methods String does NOT define. | |
| console.log('test'.makeAwesome()); // error | |
| // we can enhance all things of type String by modifying its "prototype" | |
| String.prototype.makeAwesome = function() { | |
| return this + ' is AWESOME!'; | |
| }; | |
| console.log('test'.makeAwesome()); // has the desired effect | |
| var myNewString = 'test 2'; | |
| console.log('test 2'.makeAwesome()); // also has the desired effect | |
| /**** IMPORTANT! For illustrative purposes only. | |
| Please do not modify the String prototype. ****/ | |
| /* A useful prototype example */ | |
| function Person(name, age) { | |
| this.name = name; | |
| this.age = age; | |
| this.getAge = function() { | |
| return this.age; | |
| } | |
| this.setAge = function(age) { | |
| this.age = age; | |
| return this; | |
| } | |
| } | |
| var person = new Person('Mark', 101); | |
| person.getAge(); | |
| person.setAge(13); | |
| /* ^^^ A typical getter, you could imagine adding setters and more methods. | |
| The problem here is every time we create a Person object, we are recreating all the getters/setters. | |
| This is inefficient. 1,000 Person objects means 1,000 copies of all the methods. | |
| We can do better: */ | |
| function Person(name, age) { | |
| this.name = name; | |
| this.age = age; | |
| } | |
| Person.prototype.getAge = function() { | |
| return this.age; | |
| } | |
| Person.prototype.setAge = function(age) { | |
| this.age = age; | |
| return this; | |
| } | |
| var person = new Person('Mark', 101); | |
| person.getAge(); | |
| person.setAge(13); | |
| /* Viola! By adding methods to the Person prototype, every Person object will use the Prototype's version of getAge/setAge. */ | |
| /* | |
| The discouraged "new MyConstructor" syntax creates objects based the same | |
| prototype. | |
| The Object.create method is a nice alternative which is available in modern | |
| browser. | |
| Here is an implementation of Object.create courtesy of Crockford which | |
| pulls together a lot of the stuff we've just been looking at: | |
| (This is an example of what people call a "polyfill") | |
| */ | |
| (function() { | |
| if (typeof Object.create !== 'function') { | |
| Object.create = function(o) { | |
| // create a trivial function. This has a prototype associated with it which is applied | |
| // any objects created by hitting this function with 'new' | |
| var F = function() {}; | |
| // set that prototype to the given object | |
| F.prototype = o; | |
| // new we can answer with a new instance of type o | |
| return new F(); | |
| } | |
| } | |
| })(); | |
| /* | |
| We could get a lot more complicated on the subject of Objects, and there's | |
| a lot more detail on this business in The Good Parts, but, honestly, | |
| it rarely even needs to get this complicated. Often object literals alone | |
| take care of everything you need. | |
| */ | |
| /* | |
| We won't quite get to the really fun part here: monkeying with web pages. | |
| But here's a few words on it: | |
| The thing that we "monkey with" is the "DOM" (the "Document Object Model"). | |
| From the javascript's perspective, it's literally the object which represents | |
| the web page the script is being run on. This lets you inspect and change the | |
| current page in pretty much any way imaginable. | |
| We ideally use jQuery for all of our DOM manipulation, since there a wild | |
| differences in the DOM cross-browser. jQuery normalizes those differences | |
| behind a really friendly API, making stuff more pleasant and robust. | |
| We should probably do a whole other session on jQuery, but it's pretty easy | |
| to pick up, and the docs are great (http://docs.jquery.com/). In a sense, | |
| chatting about the wonkyness of javascript is a great introduction to jQuery, | |
| since jQuery is just javascript. | |
| */ | |
| /* | |
| Finally, let's close off with some more "bad parts" and "awful parts" from | |
| Crockford's Good Parts (we've already seen a few of these around scoping and | |
| global variables and such above... there's more gems though...) | |
| */ | |
| /* | |
| Equality operators: ===, !== vs. ==, != | |
| The second set are the 'evil twins'. They attempt to coerce values, | |
| but do a real crap job of it. | |
| Evidence: | |
| */ | |
| '' == 0 // true | |
| 0 == '0' // true | |
| // therefore '' equals '0'? Right? Right??? Wrong: | |
| '' == '0' // false. | |
| '' == 0 // true | |
| 0 == ' \t\r\n ' // true | |
| // same punchline as above: | |
| '' == ' \t\r\n ' // false | |
| /* | |
| "The lack of transitivity is alarming" -- Douglas Crockford | |
| It's really just best to try to never use the 'evil twins'. | |
| Brendan Eich, the creator of Javascript, at TXJS 2011, apologized to the Javascript community for ==. | |
| */ | |
| /* | |
| Semicolon insertion. This does not do what you expect: | |
| */ | |
| var getStatus = function() { | |
| return | |
| { | |
| status: true | |
| } | |
| }; | |
| /* | |
| Always explicitly put in your semi-colons | |
| */ | |
| /* | |
| NaN is bonkers (note that this also demonstrates that typeof is bonkers. | |
| There's a whole bad part in the book for that which we omit here). | |
| */ | |
| typeof NaN === 'number' // evals to 'true'. So, er, something which is | |
| // "Not a Number" is a number. | |
| /* | |
| isFinite behaves better since it reject both NaN and infinity, but it will try | |
| to coerce values to numbers, so it isn't a good test for numberness on its own. | |
| Crockford suggests this: | |
| */ | |
| var isNumber = function(value) { | |
| return typeof value === 'number' && isFinite(value); | |
| }; | |
| /* | |
| eval is evil. | |
| */ | |
| eval('myValue = myObject.' + myKey + ';'); | |
| // vs. | |
| myValue = myObject[myKey]; | |
| /* | |
| The eval version is harder to read and guaranteed to be slower since it needs | |
| to compile the code it is given. | |
| It's also an atrocious security hole. It'll execute whatever it is given, | |
| and in all but the simplest apps, it can be hard to verify all the ways that | |
| a string that you're eval-ing may have been modified. | |
| Also: if you pass setTimeout or setInterval a string, you're eval-ing. Give | |
| them functions instead. | |
| */ | |
| /* | |
| Q: So, how does one avoid all these pitfalls? Is it better to just give up on | |
| javascript? | |
| A: That's not going to happen. We may have had our hands forced by the fact | |
| that it's pretty much the only client side code we can run in the browser, | |
| but also it REALLY FUN to code in, so let's not throw the towel in. | |
| With a bit of discipline, you'll soon be living | |
| with the shame of loving javascript. | |
| */ | |
| /* | |
| Resources: | |
| * https://developer.mozilla.org/en/JavaScript/Guide --- Good reference | |
| * Javascript: The Good Parts --- relatively advanced, but definitely worth | |
| * eventually absorbing | |
| * *Not* random Googling (there is an overwhelming amount of bad Javascript | |
| * advice on the web) See w3fools.com for some interesting insight into | |
| * this, and some pointers to other resources | |
| * Special thanks to Rebecca Murphey for inspiration for some of the topics | |
| * here and a few of the code snippets which she generously licensed under | |
| * the WTFPL. (see https://gist.github.com/576723 and http://blog.rebeccamurphey.com/in-search-of-javascript-developers-a-gist) | |
| * Awesome resource for the language: http://bonsaiden.github.com/JavaScript-Garden/ | |
| * | |
| * | |
| * Paul Irish's "10 things I learned from reading the jQuery source" is also a rad video: http://paulirish.com/2010/10-things-i-learned-from-the-jquery-source/ | |
| */ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment