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!