everything is permitted: extending built-ins

82
Everything is Permitted: Extending Built-ins Andrew Dupont http://andrewdupont.net

Upload: andrew-dupont

Post on 15-Jan-2015

5.064 views

Category:

Technology


2 download

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

Page 1: Everything is Permitted: Extending Built-ins

Everything is Permitted:Extending Built-ins

Andrew Duponthttp://andrewdupont.net

Page 2: Everything is Permitted: Extending Built-ins

Remy Sharp (flickr.com/photos/remysharp)

Page 3: Everything is Permitted: Extending Built-ins

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

Page 4: Everything is Permitted: Extending Built-ins

“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

Page 5: Everything is Permitted: Extending Built-ins

George Orwell,“Politics and the English Language”

“Break any of these rules sooner than say anything outright barbarous.”

Page 6: Everything is Permitted: Extending Built-ins

“Commandments”are no way to write code.

Page 7: Everything is Permitted: Extending Built-ins

Alex (flickr.com/photos/littlebirdfeet/)

Page 8: Everything is Permitted: Extending Built-ins

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

Page 9: Everything is Permitted: Extending Built-ins

Code has social customs.

Page 10: Everything is Permitted: Extending Built-ins

IN THE BEGINNING(ca. 2005)

Page 11: Everything is Permitted: Extending Built-ins

Prototype 1.1

Page 12: Everything is Permitted: Extending Built-ins

Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};

Page 13: Everything is Permitted: Extending Built-ins

var whiteHouse = { washington: 'adams' };whiteHouse.extend({ adams: 'jefferson' });

for (var president in whiteHouse) console.log(president);//=> washington, adams, extend

Page 14: Everything is Permitted: Extending Built-ins
Page 15: Everything is Permitted: Extending Built-ins

http://erik.eae.net/archives/2005/06/06/22.13.54/Object.prototype is verboten

Page 16: Everything is Permitted: Extending Built-ins
Page 17: Everything is Permitted: Extending Built-ins
Page 18: Everything is Permitted: Extending Built-ins

You can do anything you want,assuming it’s all your code

Page 19: Everything is Permitted: Extending Built-ins

Object.prototype.extendbecame

Object.extend

Page 20: Everything is Permitted: Extending Built-ins

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; };}

Page 21: Everything is Permitted: Extending Built-ins

Prototype 1.4 introducedEnumerable

Page 22: Everything is Permitted: Extending Built-ins

for...in loops on arraysare usually dumb

Page 23: Everything is Permitted: Extending Built-ins

http://is.gd/js_considered_harmfulJavaScript “Associative Arrays” Considered Harmful

Page 24: Everything is Permitted: Extending Built-ins

Prototype 1.5 introduced“Extended” elements

Page 25: Everything is Permitted: Extending Built-ins

// 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');

Page 26: Everything is Permitted: Extending Built-ins

This was slow in IE

Page 27: Everything is Permitted: Extending Built-ins

Also `typeof document.querySelectorAll('*').item` is 'string' in IE8. Well, that's just silly.

kangax@kangax

16 Dec 08 via web

Page 28: Everything is Permitted: Extending Built-ins

What’s wrong with extending the DOMhttp://perfectionkills.com/whats-wrong-with-extending-the-dom/

Page 29: Everything is Permitted: Extending Built-ins

http://is.gd/zakas_maintainable_javascriptMaintainable JavaScript: Don’t modify objects you don’t own

Page 30: Everything is Permitted: Extending Built-ins

Who owns built-ins?

Page 31: Everything is Permitted: Extending Built-ins

We all do.

Page 32: Everything is Permitted: Extending Built-ins

Social customs are a proven wayto manage shared property.

Page 33: Everything is Permitted: Extending Built-ins

Case in point:RUBY

Page 34: Everything is Permitted: Extending Built-ins

Ruby and JavaScriptare Smalltalk-influenced

Page 35: Everything is Permitted: Extending Built-ins

Rails gave RubyActive Support

Page 36: Everything is Permitted: Extending Built-ins

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

Page 37: Everything is Permitted: Extending Built-ins

result = names.map {|name| name.upcase }# becomes...result = names.map(&:upcase)

Symbol#to_proc

Page 39: Everything is Permitted: Extending Built-ins
Page 40: Everything is Permitted: Extending Built-ins
Page 41: Everything is Permitted: Extending Built-ins

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

Page 42: Everything is Permitted: Extending Built-ins

EcmaScript 5.1 Specification

Page 43: Everything is Permitted: Extending Built-ins

Obviously,there were fights along the way

Page 44: Everything is Permitted: Extending Built-ins

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

Page 45: Everything is Permitted: Extending Built-ins

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

Page 46: Everything is Permitted: Extending Built-ins

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

Page 47: Everything is Permitted: Extending Built-ins

Lessons learned by Rubyists:

Page 48: Everything is Permitted: Extending Built-ins

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

Page 49: Everything is Permitted: Extending Built-ins

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

Page 50: Everything is Permitted: Extending Built-ins

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

Page 51: Everything is Permitted: Extending Built-ins

Unsolved problems:

Page 52: Everything is Permitted: Extending Built-ins

Only one library gets the privilegeto extend built-ins.

Page 53: Everything is Permitted: Extending 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.

Page 54: Everything is Permitted: Extending Built-ins

Can we extend built-inssafely right now?

Page 55: Everything is Permitted: Extending Built-ins

Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};

Page 56: Everything is Permitted: Extending Built-ins

Object.prototype.extend = function(object) { for (var property in object) { this[property] = object[property]; } return this;};

Page 57: Everything is Permitted: Extending Built-ins

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?

Page 58: Everything is Permitted: Extending Built-ins

var whiteHouse = { washington: 'adams' };whiteHouse.extend({ adams: 'jefferson' });

for (var president in whiteHouse) console.log(president);//=> washington, adams

Page 59: Everything is Permitted: Extending Built-ins
Page 60: Everything is Permitted: Extending Built-ins

But wait…

var whiteHouse = { extend: 'adams' };whiteHouse.extend({ adams: 'jefferson' });

// TypeError: Property 'extend' of object #<Object>// is not a function

Page 61: Everything is Permitted: Extending Built-ins
Page 62: Everything is Permitted: Extending Built-ins

console.log(extend);//=> [Function]

And also…

Page 63: Everything is Permitted: Extending Built-ins
Page 64: Everything is Permitted: Extending Built-ins

Object.prototype is still verboten

Page 65: Everything is Permitted: Extending Built-ins

If you extend other built-ins,consider turning off enumerability.

Page 66: Everything is Permitted: Extending Built-ins

What about Node?

Page 67: Everything is Permitted: Extending Built-ins

node-timehttps://github.com/TooTallNate/node-time

Page 68: Everything is Permitted: Extending Built-ins

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:

Page 69: Everything is Permitted: Extending Built-ins

What about the browser?

Page 70: Everything is Permitted: Extending Built-ins

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/

Page 71: Everything is Permitted: Extending Built-ins

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; };}

Page 72: Everything is Permitted: Extending Built-ins

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; };}

Page 73: Everything is Permitted: Extending Built-ins

Let’s start with bind.

Page 74: Everything is Permitted: Extending Built-ins

will have spec compliancefor all methods defined by ES5.

Prototype 1.7.1

Page 75: Everything is Permitted: Extending Built-ins

Will we be able toextend built-ins safely someday?

Page 76: Everything is Permitted: Extending Built-ins

The future:

CLASSBOXES

Page 77: Everything is Permitted: Extending Built-ins

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

Page 78: Everything is Permitted: Extending Built-ins

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

Page 79: Everything is Permitted: Extending Built-ins

Could we have classboxesin JavaScript?

Page 80: Everything is Permitted: Extending Built-ins

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'

Page 81: Everything is Permitted: Extending Built-ins

SOMEDAY

Page 82: Everything is Permitted: Extending Built-ins

Questions?