everything is permitted: extending built-ins
DESCRIPTION
Adding methods to built-in objects: it’s one of JavaScript’s most powerful features. It’s also a great way to offend the sensibilities of your colleagues. We all hear that it’s irresponsible, that it’s sloppy, that it’s flat-out bad practice and should be avoided. I’m tired of this one-sided battle. In this talk, I’m going to push back against whatever blog post you read that told you that extending built-ins was unconditionally and universally bad. I’m gonna go all Howard Beale on your asses.TRANSCRIPT
Everything is Permitted:Extending Built-ins
Andrew Duponthttp://andrewdupont.net
Remy Sharp (flickr.com/photos/remysharp)
There’s a whole lot of know-nothing advocacy that’s still happening in the JS/webdev/design world these days, and it annoys me to no end. I’m not sure how our community got so religious and fact-disoriented, but it has got to stop.”
“
Alex Russell
“If you use this book as a guide, by all means leave the road when you wish. That is precisely the use of a road: to reach individually chosen points of departure. By all means break the rules, and break them beautifully, deliberately and well. That is one of the ends for which they exist.”
Robert Bringhurst,The Elements of Typographic Style
George Orwell,“Politics and the English Language”
“Break any of these rules sooner than say anything outright barbarous.”
“Commandments”are no way to write code.
Alex (flickr.com/photos/littlebirdfeet/)
Fuck what you heard, SHORTS ARE DOPE. Just wear them right. Not mid leg like you’re at a fuckin Creed concert.
Donald Glover@DonaldGlover
Echofon30 Jul via
Code has social customs.
IN THE BEGINNING(ca. 2005)
Prototype 1.1
Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};
var whiteHouse = { washington: 'adams' };whiteHouse.extend({ adams: 'jefferson' });
for (var president in whiteHouse) console.log(president);//=> washington, adams, extend
http://erik.eae.net/archives/2005/06/06/22.13.54/Object.prototype is verboten
You can do anything you want,assuming it’s all your code
Object.prototype.extendbecame
Object.extend
if (!Array.prototype.push) { Array.prototype.push = function() { var startLength = this.length; for (var i = 0; i < arguments.length; i++) this[startLength + i] = arguments[i]; return this.length; };}
Prototype 1.4 introducedEnumerable
for...in loops on arraysare usually dumb
http://is.gd/js_considered_harmfulJavaScript “Associative Arrays” Considered Harmful
Prototype 1.5 introduced“Extended” elements
// Prototype 1.4Element.addClassName('some_element', 'active');Element.show();$('some_element').setAttribute('title', 'Active item');
// Prototype 1.5$('some_element').addClassName('active').show(). setAttribute('title', 'Active item');
This was slow in IE
Also `typeof document.querySelectorAll('*').item` is 'string' in IE8. Well, that's just silly.
kangax@kangax
16 Dec 08 via web
What’s wrong with extending the DOMhttp://perfectionkills.com/whats-wrong-with-extending-the-dom/
http://is.gd/zakas_maintainable_javascriptMaintainable JavaScript: Don’t modify objects you don’t own
Who owns built-ins?
We all do.
Social customs are a proven wayto manage shared property.
Case in point:RUBY
Ruby and JavaScriptare Smalltalk-influenced
Rails gave RubyActive Support
ActiveSupport::CoreExtensions::Numeric
1.day.ago#=> Thu Apr 21 01:57:24 -0500 2011
(4.years + 12.days).from_now#=> Mon May 04 01:58:30 -0500 2015
7.5.megabytes#=> 7864320.0
result = names.map {|name| name.upcase }# becomes...result = names.map(&:upcase)
Symbol#to_proc
http://brockman.se/2004/method-references/
Function.prototype.bind = function() { var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); };};
Prototype 1.4
EcmaScript 5.1 Specification
Obviously,there were fights along the way
Zed Shaw,“The Chainsaw Infanticide Logger Manuever”
“I only re-open a class after I’ve tried every other option like subclassing, wrapping, etc. If nothing else works or is too much effort for the modification, then I document the hell out of my modification and try desperately to localize the change so that it doesn't hurt anyone else.”
http://is.gd/chainsaw_infanticide
The Higgs Bozo
“Don't slip a concrete dildo into someone's box of Fruit Loops. They won't be happy with your Morning Breakfast Surprise. Put the concrete dildo in a clearly labeled box, with instructions. Then when someone encounters a problem (‘Hey, something is screwing me here. Maybe it's the concrete dildo?’) at least they know to ask.”
http://is.gd/concrete_dildo
Yehuda Katz
“In my mind, in Ruby < 2.0, there’s a category of library which is ‘provide a number of useful core extensions.’ The three major ones are ActiveSupport, Extlib and Facets. In general, applications need to choose one, but not more of these libraries to avoid conflicts.”
http://is.gd/cQar9O
Lessons learned by Rubyists:
Make it obvious when you’re defining core extensions.
module SomeLibrary module CoreExtensions module Object def foo # ... end end end end
class Object include SomeLibrary::CoreExtensions::Objectend
Make core extensions as atomic as possible.
require 'active_support/core_ext/numeric/bytes'
3.megabytes#=> 3145728
3.days.ago# NoMethodError: undefined method `days' for 3:Fixnum
If you must monkey-patch, do so seamlessly.Don’t break the old method’s contract.
class Array alias_method :foo_original :foo def foo(arg) puts "Adding advice to the 'foo' method" foo_original(arg) endend
Unsolved problems:
Only one library gets the privilegeto extend built-ins.
{}.blank?# NoMethodError: undefined method `blank?' for {}:Hash
require 'rails'{}.blank?#=> true
Libraries I require will o"en decide for themselveswhich core extensions will be used.
Can we extend built-inssafely right now?
Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};
Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};
Object.defineProperty(Object.prototype, 'extend', { writable: true, configurable: true, enumerable: false, value: function() { for (var i = 0, len = arguments.length, source; i < len; i++) { source = arguments[i]; for (var property in source) { if (source.hasOwnProperty(property)) this[property] = source[property]; } } } });
ES5 to the rescue?
var whiteHouse = { washington: 'adams' };whiteHouse.extend({ adams: 'jefferson' });
for (var president in whiteHouse) console.log(president);//=> washington, adams
But wait…
var whiteHouse = { extend: 'adams' };whiteHouse.extend({ adams: 'jefferson' });
// TypeError: Property 'extend' of object #<Object>// is not a function
console.log(extend);//=> [Function]
And also…
Object.prototype is still verboten
If you extend other built-ins,consider turning off enumerability.
What about Node?
node-timehttps://github.com/TooTallNate/node-time
var time = require('time');
var date = new Date();date.setTimeZone('America/Chicago');
console.log(date.toString());console.log(date.getTimezone());console.log(date.getTimezoneAbbr());
Tue Apr 26 2011 02:45:21 GMT-0500 (CDT)America/ChicagoCDT
Output:
What about the browser?
We’ll be needing ES5 polyfills
“A polyfill … is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively.”
Remy Sharphttp://remysharp.com/2010/10/08/what-is-a-polyfill/
if (!Object.keys) { Object.keys = function(object) { if (object !== Object(object)) throw new TypeError('Object.keys called on non-object'); var results = []; for (var property in object) { if (object.hasOwnProperty(property)) results.push(property); }
return results; };}
if (!Function.prototype.bind) { Function.prototype.bind = function(object) { var slice = Array.prototype.slice, args = slice.call(arguments, 1), self = this;
var nop = function() {}; var bound = function() { return self.apply( this instanceof nop ? this : (object || {}), args.concat(slice.call(arguments)) ); }; nop.prototype = self.prototype; bound.prototype = new nop(); return bound; };}
Let’s start with bind.
will have spec compliancefor all methods defined by ES5.
Prototype 1.7.1
Will we be able toextend built-ins safely someday?
The future:
CLASSBOXES
So"ware Composition Group,University of Bern
“[W]e present classboxes, a module system for object-oriented languages that allows method addition and replacement. Moreover, the changes made by a classbox are only visible to that classbox (or classboxes that import it), a feature we call local rebinding.”
http://scg.unibe.ch/research/classboxes
A classbox-like system called “refinements”has been proposed for Ruby 2.0.
module TimeExtensions refine Numeric do def minutes; self * 60; end endend
2.minutes #=> NoMethodError
using TimeExtensions2.minutes #=> 120
Could we have classboxesin JavaScript?
Strawman: Scoped Object Extensions
module NumericExtensions { export extension Time = Number.prototype { days: function() {}, /* ... */
ago: function() {}, fromNow: function() {} }}
function setExpiringCookie(key, value) { import NumericExtensions.Time; var expires = (3).days().fromNow(); setCookie(key, value, expires);}
(3).days().fromNow();//=> TypeError: Object 3 has no method 'days'
SOMEDAY
Questions?