On Github seutje / such-java-much-script
Steve De Jonghe
typeof 'foo'; // "string" - Good typeof String('foo'); // "string" - Not so good typeof new String('foo'); // "object" - Bad typeof (new String('foo')).valueOf(); // "string" - Awkward (new String('foo')) === 'foo'; // false - o.O
It lies!
typeof null; // "object" typeof []; // "object" typeof /regex/; // "function" in some browsers
Force the Object.toString method to do your bidding!
Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call(/regex/); // "[object RegExp]"
(More on prototypes and function methods later!)
If you need this, you probably have a bigger problem.
0.2 + 0.2 + 0.2; // 0.6000000000000001 WAT?!
(0.2 + 0.2 + 0.2).toFixed(2); // 0.60
parseInt(42.56789, 10); // 42(!)
var foo = new Number(0); foo === 0; // "false" - foo is an obj, 0 is an int. foo == 0; // "true" - so foo is falsy? !!foo; // "true" - I guess not o.O
parseInt('042'); // Some browsers will default to octal 66. parseInt('042', 10); // Force parsing as decimal. parseInt('12foo34', 10); // 12 (!)
var poo = '💩'; poo.length; // 2 poo.split('').reverse().join(''); // "��" poo === '\uD83D\uDCA9'; // true
var a = 'se\xF1orita'; // "señorita" var b = 'sen\u0303orita'; // "señorita" a == b; // false a.length; // 8 b.length; // 9
var foo = 'foo'; foo[0]; // 'f' foo[0] = 'p'; // Nice try, no setters tho! foo.split('f').join('p'); // 'poo' (like PHPs explode/implode) foo.length; // 3 foo.bar = 'baz'; // Nice try, primitives can't have properties tho! foo.bar; // undefined
This is often referred to as "Array-like", but should not be considered a fully-fledged array.
var foo = new String('foo'); // Secretly an object wrapper pretending to be a string foo === 'foo'; // false foo == 'foo'; // true foo.lol = 'lol'; // now it actually sticks! foo.lol; // 'lol'
With strings and numbers, the + operator will coerce everything to strings
'123' + 4; // '1234' 1 + '234'; // '1234'
Likewise, the - operator will coerce everything to a number
'123' - 4; // 119 1 - '234' ; // -233
Using the unary + operator, we can easily cast to a number.
This works similar to parseFloat(), except it will return NaN if any part of the string could not be parsed.
'1.23' + '1.23'; // '1.231.23' (string) (+'1.23') + (+'1.23'); // 2.46 (parens optional) +'1.23' + + '1.23'; // 2.46 +'1.23'; // 1.23 +'1.23.23'; // NaN (!!) +'1.999999999999999'; // 1.999999999999999 +'1.9999999999999999'; // 2
This does not save you from NaNtyNaN problems though, always check user input!
The following values are considered not "truthy" and will coerce to false:undefined, null, NaN, 0, ''
Gotcha: !!'0'; // "true"
// someInit will not be executed unless someVar is falsy var foo = someVar || someInit(); // Useful for defaults! var bar = function (x) { x = x || myDefaults; }; // Or checks! var ready = someInit() && attachThatNeedsToRunAfterInit();
var foo = ['poo']; foo.length; // 1 foo.bar = 'bar'; foo.length; // 1 foo.bar; // 'bar' (foo['bar'] also works) foo; // ['poo']
var foo = ['lala', 'hihi']; foo.push('hoho'); // 3 (Array.push returns new length) foo[7] = 'lolapi'; foo.length; // 8 foo[6]; // undefined
var foo = ['bar']; foo.lol = 'gotcha!'; for (var i in foo) { console.log(foo[i]); // 'bar', 'gotcha!' } for (var i = 0, len = foo.length; i < len; i++) { console.log(foo[i]); // 'bar' }
Don't care about IE8?Use Array.forEach(callback) (note: unbreakable!)
Object.prototype.lol = 'lolapi'; var foo = {'meh': 'poo'}; for (var key in foo) { console.log(key); // 'meh', 'lol' if (foo.hasOwnProperty(key)) { console.log(key); // Just 'meh' } }More on inheritence later!
function foo() { var bar = 'lol'; } foo(); console.log(bar); // undefined
var bar = 'lol'; function foo(bar) { return bar; } console.log(foo()); // undefined
var x = 'bar'; (function (x) { x === 'foo'; // true })('foo'); x === 'bar'; // true // Roughly equivalent (without accessible leftovers) var foo = function(x) {} foo('foo');
(function (x, y) { x === 'foo'; // true y === window; // true if in a browser and not hosed outside scope var foo = "I won't leak to the global scope!"; y.bar = "I will!"; // same as window.bar })('foo', this); foo; // undefined bar; // "I will!"
(function(window){ // Scoped variable. var myPrivate = 'foo'; // Intentionally leaked getter function to the global scope. window.getPrivate = function() { // function is defined within same scope so it can access the var return myPrivate; }; })(this); getPrivate(); // 'foo' myPrivate; // ReferenceError: myPrivate is not defined
function compute (x) { return function (y) { return x*y; } } compute(2)(3); // 6
function maths(x) { return { compute: function(y) { return x * y; }, add: function(y) { return x + y; }, abs: Math.abs(x) } } maths(2).compute(3); // 6 maths(2).add(3); // 5 maths(-2).abs; // 2
var foo = { a: 'lol', b: function() { return this.a; } }; foo.b(); // 'lol' var bar = foo.b; bar(); // undefined
Here, bar is actually window.bar, so the window object becomes the context, and there is no window.a.
var foo = function() { return this; } // Create a clone of the function with a fixed context of 'bar!'. var bar = foo.bind('bar!'); foo(); // returns reference to the window object. bar(); // 'bar!' // Call foo in the context of 'lol' foo.call('lol'/* , arg1, arg2 */); // 'lol' foo.apply('lol'/* , [arg1, arg2] */); // 'lol'
// Defined at run-time. foo(); // TypeError: undefined is not a function var foo = function () {};
// Defined at parse-time for this block. foo(); // no error function foo() {};
// Essentially an anonymous function stored in a variable. var foo = function () { }; foo.name; // '' (empty string)
// A named function. function foo() { }; foo.name; // 'foo'
// But wait! var foo = function bar() { }; foo.name; // 'bar' bar.name; // ReferenceError: bar is not defined // WAT?!
var foo = function bar() { bar.meh = function() { return bar.name; }; }; foo(); // Calling it makes it give itself the "meh" method. foo.meh(); // "bar"
// We can return ourselves! var foo = function bar() { // Do some things... return bar; }; foo.name; // 'bar' foo().name; // 'bar' foo()()()()()()()(); // ...
Variable and function declarations are invisibly moved to the top of their scope
one(); // TypeError "one is not a function" two(); // Works. three(); // TypeError "three is not a function" four(); // ReferenceError "four is not defined" var one = function() {}; // Anonymous function expression. function two() {}; // Function declaration. var three = function four() {}; // Named function expression. one(); // Works. two(); // Works. three(); // Works. four(); // ReferenceError "four is not defined"
Actual interpretation
var one, three; // Variable name is "hoisted" to top of scope. function two() {}; // Entire function declaration gets hoisted. one(); // TypeError "one is not a function" two(); // Works. three(); // TypeError "three is not a function" four(); // ReferenceError "four is not defined" one = function() {}; // Anonymous function expression. three = function four() {}; // Named function expression. one(); // Works. two(); // Works. three(); // Works. four(); // ReferenceError "four is not defined"
// Remember, we're also an object. var foo = function bar(op) { // Fixed internal reference! bar.doStuff = function() { // Do stuff return bar; }; bar.doMoreStuff = function() { // Do more stuff return bar; }; if (op && bar[op]) { // if an op was passed and it exists return bar[op](); // call it and return the return } return bar; // Return ourselves }; foo().doStuff().doMoreStuff(); // or foo('doStuff')('doMoreStuff')...
// Logs 'HOLAS!' every second. var foo = function() { console.log('HOLAS!'); window.setTimeout(foo, 1000); }; window.setTimeout(foo, 1000); // Bad: var bar = function() { console.log('What if I took more than 1 second to run?'); }; window.setInterval(bar, 1000);
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { doStuffWithData(JSON.parse(xhr.responseText)); } } }; xhr.open('GET', '/lol', true); // xhr.setRequestHeader('Content-Type', 'some/mime-type'); xhr.send();
// Define it as a constructor. function Foo() { // capital is just convention this.bar = function() { return this; }; // Note the "this" }; var one = new Foo(); // Note the "new" var two = new Foo(); one === two; // false, they are 2 separate instances one.bar() === two.bar(); // false one.bar() === one; // true one instanceof Foo; // true Foo(); // note the absence of "new" window.bar; // We just hosed the global scope
// Set up constructor. var Foo = function() { this.bar = 0; }; // Define its base object. Foo.prototype = { addBar: function() { this.bar++; } }; var foo = new Foo(); // foo > Foo.prototype > Object.prototype > null foo.addBar(); foo.bar; // 1 Object.getPrototypeOf(foo).bar; // undefined (not available in IE<9) Foo.prototype.meh = 'lol'; foo.meh; // 'lol'
var Foo = function() { this.bar = 0; this.addBar = function() { return ++this.bar; }; }; var Bar = function() { this.addBar = function(i) { this.bar = this.bar + i; return this.bar; }; }; Bar.prototype = new Foo(); // Set Bar's prototype to new instance of Foo var foo = new Foo(); // Foo.prototype is essentially an empty object var bar = new Bar(); // bar > Bar.prototype (Foo) > Foo.prototype ({}) > Object.prototype > null bar.addBar(7); // 7 foo.addBar(7); // 1 bar.bar; // 7 Bar.prototype.meh = 'lol'; bar.meh; // 'lol' foo.meh; // undefined
var Foo = function() { this.bar = 0; this.addBar = function() { return ++this.bar; }; }; var Bar = function() { this.addBar = function(i) { this.bar = this.bar + i; return this.bar; }; this.callSuper = function(op, arg) { return Object.getPrototypeOf(this)[op].call(this, arg); }; } Bar.prototype = new Foo(); var foo = new Foo(); var bar = new Bar(); bar.callSuper('addBar', 7); // 1
function create(parent) { // aka "justMakeMeAnObjectThatInheritsFromThis" var Constructor = function () {}; // Empty constructor function Constructor.prototype = parent; // Attach prototype object // Return new instance, inheriting from parent return new Constructor(); } var a = { foo: 'lol' }; var b = create(a); b.foo; // 'lol' a.bar = 'baz'; b.bar; // 'baz' b.meh = 'nope'; a.meh; // undefined
Object.create(proto /*[, props ]*/)
The Object.create() method creates a new object with the specified prototype object and properties.
proto: The object which should be the prototype of the newly-created object.
props: An object whose enumerable own properties specify property descriptors to be added to the newly-created object, with the corresponding property names.
if (typeof Object.create != 'function') { (function () { var F = function () {}; Object.create = function (o) { if (arguments.length > 1) { throw Error('Second argument not supported');} if (o === null) { throw Error('Cannot set a null [[Prototype]]');} if (typeof o != 'object') { throw TypeError('Argument must be an object');} F.prototype = o; return new F(); }; })(); }
var extend = function(protoProps, staticProps) { var parent = this; var child; if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } _.extend(child, parent, staticProps); var Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; if (protoProps) _.extend(child.prototype, protoProps); child.__super__ = parent.prototype; return child; };
// [ <html element> ] document.children; // [ <head element>, <body element> ] document.children[0].children; // [ <element with class someClass>, <...> ] document.getElementsByClassName('someClass'); // equivalent (note: IE8 only supports CSS2 selectors) document.querySelectorAll('.someClass');
Remember: You cannot query for an element if it hasn't been parsed yet. Place scripts at the bottom or use things like jQuery's $(document).ready(function() { ... })
<html> <head> <script> //TypeError: Cannot read property 'querySelector' of null var foo = document.body.querySelector('.foo'); </script> </head> <body> <div class="foo">foo</div> <script> // Works fine var foo = document.body.querySelector('.foo'); </script> </body> </html>
As mentioned before, array-like does not mean it's an array.
var foo = document.querySelectorAll('.foo'); foo.forEach; // undefined - does not have array method. // Invoke array method in the context of our collection. Array.prototype.forEach.call(foo, function(el) { // Pass in anonymous function console.log(el); // logs out each element separately. });
// Define a handler for our events. var handler = function(event) { console.log(this, event.target); }, body = window.document.body, // Cache some element references. form = body.querySelector('form'), button = form.querySelector('input[type="button"]'); // Add our event listener to the cached references. body.addEventListener('click', handler); form.addEventListener('click', handler); button.addEventListener('click', handler);
// Handler that calls stopPropagation on the event. var handler = function(event) { console.log(this, event.target); event.stopPropagation(); }, body = window.document.body, // Cache some element references. form = body.querySelector('form'), button = form.querySelector('input[type="submit"]'); // Add our event listener to the cached references. body.addEventListener('click', handler); form.addEventListener('click', handler); button.addEventListener('click', handler);
// Handler that calls preventDefault on the event. var handler = function(event) { event.preventDefault(); }, form = window.document.body.querySelector('form'); // The handler will effectively prevent // the native submit event from happening. form.addEventListener('submit', handler);
(function(window) { var handler = function(event) { // Only act if the actual target is '.foo' if (event.target.className === 'foo') { event.preventDefault(); console.log('Do something!'); } }; // Attach the handler to the entire body element. window.document.body.addEventListener('click', handler); })(this);
Like jQuery's $.fn.on(), $.fn.delegate, ...
// Bad: var ajaxData = $.get('some/url', function(data) { return data; }); console.log(ajaxData); // XMLHttpRequest object o.O // Instead: $.get('some/url', function(data) { console.log(data); }); // Or use jQuery's new promises! $.get('some/url').done(function(data) { console.log(data); })
(function($) { // initialize returnData in this scope var returnData, // Example function to shout out the data shoutOut = function() { if (returnData) { console.log(returnData); } }; // Grab the data, assign the variable and call the function. $.get('some/url', function(data) { returnData = data; // Store it. shoutOut(); // Shout it. }); })(jQuery)
var callback = function() { /* do something*/ }; $.get('some/url', callback);
What goes on internally:
Drupal.Foo = function(el) { this.el = el; /* do fancy stuff with this.el */ }; Drupal.Foo.prototype.destroy = function () { /* undo fancy stuff to this.el */ } Drupal.behaviors.foo = { attach: function(context) { $('.some-selector', context).once('foo').each(this.attachHandler.bind(this)); }, attachHandler: function(i, el) { var $el = $(el), instance = new Drupal.Foo(el); $el.data('foo', instance); this.instances = this.instances || []; this.instances.push(instance); }, detach: function(context) { $('.some-selector.foo-processed', context).each(this.detachHandler.bind(this)); }, detachHandler: function(i, el) { var $el = $(el), instance = $el.data('foo'), index = this.instances.indexOf(instance); instance.destroy(); this.instances.splice(index, 1); } };
// Probably the most annoying piece of JS in Drupal 7 Drupal.jsAC.prototype.hidePopup = function(keycode) { /* hidden some irrelevant stuff */ // Hide popup. var popup = this.popup; if (popup) { this.popup = null; $(popup).fadeOut('fast', function() { $(popup).remove(); // <- doesn't hide, but actually removes! }); } this.selected = false; $(this.ariaLive).empty(); };
// Remember, a function is just a variable holding a reference // We can just change the reference to point to something else Drupal.jsAC.prototype.hidePopup = function() { /* do nothing */ } // Now it won't disappear and we can theme it!
// But what if we still wanna use the original? (function(window){ // Scope a reference! var _alert = window.alert; window.alert = function (arg) { // Do some extra stuff, maybe manipulate the arg console.log('Alert called!', arg); // Call the original function with proper arguments and "this" context _alert.call(this, arg); } })(this)
Thanks for not throwing rotten fruit, much appreciated!