Everything is Permitted: Extending Built-ins


Published on

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.

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Everything is Permitted: Extending Built-ins

  1. Everything is Permitted: Extending Built-ins Andrew Dupont http://andrewdupont.net
  2. Remy Sharp (flickr.com/photos/remysharp)
  3. “ 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
  4. “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
  5. “Break any of these rules sooner than say anything outright barbarous.”George Orwell,“Politics and the English Language”
  6. “Commandments”are no way to write code.
  7. Alex (flickr.com/photos/littlebirdfeet/)
  8. @DonaldGlover Donald GloverFuck what you heard, SHORTS AREDOPE. Just wear them right. Not mid leglike you’re at a fuckin Creed concert.30 Jul via Echofon
  9. Code has social customs.
  10. IN THE BEGINNING (ca. 2005)
  11. Prototype 1.1
  12. Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};
  13. var whiteHouse = { washington: adams };whiteHouse.extend({ adams: jefferson });for (var president in whiteHouse) console.log(president);//=> washington, adams, extend
  14. Object.prototype is verbotenhttp://erik.eae.net/archives/2005/06/06/22.13.54/
  15. You can do anything you want, assuming it’s all your code
  16. Object.prototype.extend became Object.extend
  17. 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; };}
  18. Prototype 1.4 introduced Enumerable
  19. for...in loops on arrays are usually dumb
  20. JavaScript “Associative Arrays” Considered Harmful http://is.gd/js_considered_harmful
  21. Prototype 1.5 introduced “Extended” elements
  22. // 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);
  23. This was slow in IE
  24. @kangax kangaxAlso `typeofdocument.querySelectorAll(*).item` is string in IE8. Well, thatsjust silly.16 Dec 08 via web
  25. What’s wrong with extending the DOMhttp://perfectionkills.com/whats-wrong-with-extending-the-dom/
  26. Maintainable JavaScript: Don’t modify objects you don’t own http://is.gd/zakas_maintainable_javascript
  27. Who owns built-ins?
  28. We all do.
  29. Social customs are a proven way to manage shared property.
  30. Case in point: RUBY
  31. Ruby and JavaScriptare Smalltalk-influenced
  32. Rails gave RubyActive Support
  33. 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
  34. Symbol#to_procresult = names.map {|name| name.upcase }# becomes...result = names.map(&:upcase)
  35. http://brockman.se/2004/method-references/
  36. Prototype 1.4Function.prototype.bind = function() { var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); };};
  37. EcmaScript 5.1 Specification
  38. Obviously,there were fights along the way
  39. “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 doesnt hurt anyone else.”Zed Shaw,“The Chainsaw Infanticide Logger Manuever”http://is.gd/chainsaw_infanticide
  40. “Dont slip a concrete dildo into someones box of Fruit Loops. They wont 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 its the concrete dildo?’) at least they know to ask.”The Higgs Bozohttp://is.gd/concrete_dildo
  41. “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.”Yehuda Katzhttp://is.gd/cQar9O
  42. Lessons learned by Rubyists:
  43. 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::Object end
  44. Make core extensions as atomic as possible.require active_support/core_ext/numeric/bytes3.megabytes#=> 31457283.days.ago# NoMethodError: undefined method `days for 3:Fixnum
  45. 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) end end
  46. Unsolved problems:
  47. Only one library gets the privilege to extend built-ins.
  48. Libraries I require will o en decide for themselves which core extensions will be used.{}.blank?# NoMethodError: undefined method `blank? for {}:Hashrequire rails{}.blank?#=> true
  49. Can we extend built-ins safely right now?
  50. Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};
  51. Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};
  52. ES5 to the rescue?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]; } } } });
  53. var whiteHouse = { washington: adams };whiteHouse.extend({ adams: jefferson });for (var president in whiteHouse) console.log(president);//=> washington, adams
  54. But wait…var whiteHouse = { extend: adams };whiteHouse.extend({ adams: jefferson });// TypeError: Property extend of object #<Object>// is not a function
  55. And also…console.log(extend);//=> [Function]
  56. Object.prototype is still verboten
  57. If you extend other built-ins,consider turning off enumerability.
  58. What about Node?
  59. node-timehttps://github.com/TooTallNate/node-time
  60. var time = require(time);var date = new Date();date.setTimeZone(America/Chicago);console.log(date.toString());console.log(date.getTimezone());console.log(date.getTimezoneAbbr());Output:Tue Apr 26 2011 02:45:21 GMT-0500 (CDT)America/ChicagoCDT
  61. What about the browser?
  62. 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 Sharp http://remysharp.com/2010/10/08/what-is-a-polyfill/
  63. 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; };}
  64. 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; };}
  65. Let’s start with bind.
  66. Prototype 1.7.1 will have spec compliancefor all methods defined by ES5.
  67. Will we be able toextend built-ins safely someday?
  68. The future:CLASSBOXES
  69. “[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.”So ware Composition Group,University of Bernhttp://scg.unibe.ch/research/classboxes
  70. A classbox-like system called “refinements” has been proposed for Ruby 2.0. module TimeExtensions refine Numeric do def minutes; self * 60; end end end 2.minutes #=> NoMethodError using TimeExtensions 2.minutes #=> 120
  71. Could we have classboxes in JavaScript?
  72. Strawman: Scoped Object Extensionsmodule 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
  74. Questions?