A place to cache linked articles (think custom and personal wayback machine)
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

4 лет назад
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. title: JS Objects: Inherited a Mess
  2. url: http://davidwalsh.name/javascript-objects
  3. hash_url: 8a69114624560f6a2ec42877c6747fdd
  4. <h3>JS Objects: TL;DR</h3>&#13;
  5. <p>JavaScript has been plagued since the beginning with misunderstanding and awkwardness around its "prototypal inheritance" system, mostly due to the fact that "inheritance" isn't how JS works at all, and trying to do that only leads to gotchas and confusions that we have to pave over with user-land helper libs. Instead, embracing that JS has "behavior delegation" (merely delegation links between objects) fits naturally with how JS syntax works, which creates more sensible code without the need of helpers.</p>&#13;
  6. <p>When you set aside distractions like mixins, polymorphism, composition, classes, constructors, and instances, and only focus on the objects that link to each other, you gain a powerful tool in behavior delegation that is easier to write, reason about, explain, and code-maintain. Simpler is better. JS is "objects-only" (OO). Leave the classes to those other languages!</p>&#13;
  7. &#13;
  8. <h3>Due Thanks</h3>&#13;
  9. <p>I'd like to thank the following amazing devs for their generous time in feedback/tech review of this article series: David Bruant, Hugh Wood, Mark Trostler, and Mark McDonnell. I am also honored that David Walsh wanted to publish these articles on his fantastic blog.</p>&#13;
  10. &#13;
  11. <h3>Complete Series</h3>&#13;
  12. &#13;
  13. </div>&#13;
  14. &#13;
  15. <blockquote><p>year 2013: Haskell people are still writing monad tutorials, JavaScript people are still trying to explain inheritance.</p>&#13;
  16. &#13;
  17. <p>— Vyacheslav Egorov (@mraleph) <a rel="nofollow" href="https://twitter.com/mraleph/status/322843655381590016">April 12, 2013</a></p></blockquote>&#13;
  18. &#13;
  19. <p>As sad a criticism on JS as that quote is, it's quite true. (I have no perspective on Haskell or Monads, so I'm only talking about the JS and inheritance part). Of all the confusing and, depending on your biases, "bad", parts of this JS language, the behaviors of <code>this</code> and the <code>[[Prototype]]</code> chain have remained some of the most elusive to explain and use accurately.</p>&#13;
  20. &#13;
  21. <p>As a bit of background, I've been developing JS full time since 2007. The first major epiphany I had back then was the understanding of how closures work, and how they enable the classic module pattern. The first open-source project I wrote (early 2008) was <a rel="nofollow" href="https://github.com/flensed/flXHR">flXHR</a>, a cross-domain Ajax <a rel="nofollow" href="https://twitter.com/SlexAxton/status/257543702124306432">prollyfill</a> using the standard Ajax (XHR) interface (via a hidden flash element) that relied heavily upon the module pattern.</p>&#13;
  22. &#13;
  23. <p>It's quite possibly my "ah-ha!" moment around the module pattern that satisfied me enough that I never really felt a strong need to also apply the "inheritance" pattern to my JS design.</p>&#13;
  24. &#13;
  25. <p>Nevertheless, like most JS developers, I've read lots of blogs and books over the years that have tried (and mostly failed) to explain the appeal and mystery that is "JavaScript inheritance" (aka, "prototypal inheritance").</p>&#13;
  26. &#13;
  27. <p>But if it's so hard to understand, and even harder to actually do correctly, the <em>point</em> yet eludes me. And apparently I'm not alone in that frustration.</p>&#13;
  28. &#13;
  29. <h2>OO in JavaScript</h2>&#13;
  30. <p>In traditional <a rel="nofollow" href="http://en.wikipedia.org/wiki/Object-oriented_programming">Object-oriented languages</a>, the <a rel="nofollow" href="http://www.cs.bu.edu/teaching/cpp/inheritance/intro/">syntax of classes</a> matches the semantics. You can express the object-oriented concepts of classes, inheritance, and polymorphism directly and explicitly using the language's syntax. There's no need to use some helper library to fake your way into OO-like behavior through work-arounds of other language facilities.</p>&#13;
  31. &#13;
  32. <p>JavaScript on the other hand has <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript">a set of syntax that looks somewhat OO</a>, but which behaves in frustratingly different ways (which we will cover throughout this article series). As a result, the common way that you implement OO patterns in JS is through any of a variety of user-land helper libraries which let you express the desired semantic relationships between your "objects". The reason most JS developers use them is because the underlying JS syntax makes those semantic expressions awkward. It's nice to just let a library handle paving over the confusing syntax hiccups.</p>&#13;
  33. &#13;
  34. <p>Libraries like jQuery are useful because they hide the ugly details of dealing with <strong>cross-browser differences in JS engines</strong>. But these OO-helper libraries are different: they're going to great lengths to <strong>hide the true nature of JavaScript's OO mechanisms</strong>, instead masking them in a set of patterns that are more familiar to other languages.</p>&#13;
  35. &#13;
  36. <p>At this point of understanding, we should really ask ourselves: is the difficulty of <a rel="nofollow" href="http://javascript.crockford.com/inheritance.html">expressing classes and inheritance in pure JavaScript</a> a failure of the language (one which can <a rel="nofollow" href="http://prototypejs.org/learn/class-inheritance">temporarily</a> be <a rel="nofollow" href="http://mootools.net/docs/core/Class/Class">solved</a> with <a rel="nofollow" href="http://ejohn.org/blog/simple-javascript-inheritance/">user libraries</a>and ultimately solved by <a rel="nofollow" href="http://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes">additions to the language like <code>class { .. }</code></a> syntax), <a rel="nofollow" href="https://c9.io/site/blog/2011/11/the-time-has-come-to-add-classes-to-javascript/">as many</a> devs <a rel="nofollow" href="http://www.nczonline.net/blog/2012/10/16/does-javascript-need-classes/">feel</a>, or is it something deeper? Is it indicative of a more fundamental disparity, that we're trying to <a rel="nofollow" href="http://www.kirupa.com/html5/objects_classes_javascript.htm">do something in JS</a> that it's<a rel="nofollow" href="http://webreflection.blogspot.com/2010/01/better-javascript-classes.html">just not meant to do</a>?</p>&#13;
  37. &#13;
  38. <p><a rel="nofollow" href="http://www.2ality.com/2011/11/javascript-classes.html">Not everyone</a> drank the JS classes kool-aid, so the rest of this article series will favor a different perspective.</p>&#13;
  39. <h2>Blueprint</h2>&#13;
  40. <p>One of the most common metaphors used in <a rel="nofollow" href="http://www.tutorialspoint.com/cplusplus/cpp_inheritance.htm">traditional class/inheritance OO</a> is that the <a rel="nofollow" href="http://msdn.microsoft.com/en-us/library/ee5edha0%28v=vs.80%29.aspx">class represents a "blueprint"</a> for a house to be built, but once you instantiate that class, you are basically copying all the characteristics from the blueprint into the actual built house. This metaphor partially matches, to an extent, what actually happens at a language level when the code is compiled, in that it sort-of flattens the definition of a class (sans "virtual" methods) inheritance hierarchy into the instance.</p>&#13;
  41. &#13;
  42. <p>Of course, a main pillar of <a rel="nofollow" href="http://www.alexonlinux.com/how-inheritance-encapsulation-and-polymorphism-work-in-cpp">inheritance-oriented coding is overriding and polymorphism</a>, which allows an object to<em>automatically</em> access the most descendant definition for a method, but also to use <code>super</code>-style relative references to access ancestor (aka "virtual") versions of the <em>same-named</em> method. In those cases, the compiler maintains lookup tables for the virtual methods, but it flattens out the non-virtual parts of the class/inheritance definition. The compiler can determine a lot about what needs to be preserved and not and highly optimize the definition structure it creates in the compiled code.</p>&#13;
  43. &#13;
  44. <p>For our purposes, we can think of traditional class-inheritance as basically a flattening "copy" of behavior down the chain to the instance. Here's a diagram to illustrate the inheritance relationship between a parent/base class<code>Foo</code>, and child class <code>Bar</code>, and then instances of each, respectively named <code>foo1</code>, <code>foo2</code>, <code>bar1</code>, and<code>bar2</code>. Visually, the arrows (aka, "copying") point from left-to-right and top-to-bottom:</p>&#13;
  45. &#13;
  46. <p><a rel="nofollow" href="http://davidwalsh.name/demo/InheritanceArrows.png" target="_blank"><img alt="Inheritance Arrows" src="http://davidwalsh.name/demo/InheritanceArrows.png"/></a></p>&#13;
  47. <h2>What's in a name?</h2>&#13;
  48. <p>Despite the borrowed implications of the common name "prototypal inheritance", JavaScript's mechanism works quite differently, which we'll see in just a moment.</p>&#13;
  49. &#13;
  50. <p>Both <a rel="nofollow" href="http://dictionary.reference.com/browse/inheritance?s=t">definitionally</a> ("...characteristics transmitted from parent to offspring") and behaviorally (as described above), "inheritance" is most closely associated with the idea of "copying" from parent to child.</p>&#13;
  51. &#13;
  52. <p>When you then take "inheritance" and apply it to a mechanism which has some very different behavior, you are asking for the confusion which has plagued "JavaScript inheritance" <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript">documentation</a>, <a rel="nofollow" href="http://blog.rjzaworski.com/2013/03/composition-in-javascript/">education</a>, and <a rel="nofollow" href="http://tobyho.com/2011/11/11/js-object-inheritance/">usage</a> for nearly 2 decades.</p>&#13;
  53. &#13;
  54. <p>To try to wade through this mess, let's set aside the label "inheritance" and its implications for JS, and hopefully we can arrive at something that is both conceptually more accurate and functionally more useful.</p>&#13;
  55. <h2>A.B.D's: Always Be Delegating</h2>&#13;
  56. <p>JavaScript's OO-like property mechanism for <a rel="nofollow" href="http://www.ecma-international.org/ecma-262/5.1/#sec-8.6">objects</a> is notated by <code>[[Prototype]]</code>, which is the internal characteristic of <strong>any object</strong> called its prototype-chain -- a special link to another object. It's kind of like a scope mechanism, in that the <code>[[Prototype]]</code> linkage <a rel="nofollow" href="http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.2">describes which alternate object should be referred to</a> if you request a property or method on your object which doesn't exist.</p>&#13;
  57. &#13;
  58. <p>In other words, you're indicating an object to <strong>delegate</strong> behavior to if that behavior isn't defined on the object in question.</p>&#13;
  59. &#13;
  60. <p>The above class-oriented <code>Foo</code> and <code>Bar</code> example, expressed in JS, relates object <code>Bar.prototype</code> to<code>Foo.prototype</code>, and then the <code>foo1</code>, <code>foo2</code>, <code>bar1</code> and <code>bar2</code> objects to their respective <code>[[Prototype]]</code>s. The arrows (not copies but live-links) point in a right-to-left, bottom-to-top fashion in JS:</p>&#13;
  61. &#13;
  62. <p><a rel="nofollow" href="https://dl.dropboxusercontent.com/u/2474669/jsdiagrams/DelegationArrows.png" target="_blank"><img alt="Delegation Arrows" src="http://davidwalsh.name/demo/DelegationArrows.png"/></a></p>&#13;
  63. &#13;
  64. <p>"Behavior delegation" is a more accurate term to describe JavaScript's <code>[[Prototype]]</code>. This is not just a matter of word semantics, it's a fundamentally different type of functionality.</p>&#13;
  65. &#13;
  66. <p>If you try to illustrate behavior delegation in terms of the "blueprint" metaphor, you quickly see how it totally breaks down. There's no way that my home, lacking a guest bedroom, could simply refer to another house, or to the original blueprints, to provide a bedroom for my mother-in-law when she comes to visit. Though the outcomes you can achieve have <strong><em>some</em></strong> respective similarities, the concepts of "inheritance" and "behavior delegation" <em>are quite different</em>.</p>&#13;
  67. &#13;
  68. <p>Some devs insist that "delegation" is just the dynamic version of "inheritance", like two sides of the same coin, but I see them <strong>as orthagonal systems</strong>.</p>&#13;
  69. &#13;
  70. <h3>How to delegate?</h3>&#13;
  71. <p>We'll revisit this later in the article series, but <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create"><code>Object.create(..)</code> was added to ES5</a> to assist with creating an object and then optionally linking its <code>[[Prototype]]</code> to another object. The link that is created is a delegation link, as opposed to an inheritance-by-copy.</p>&#13;
  72. &#13;
  73. <p><strong>Note:</strong> Once an object has its <code>[[Prototype]]</code> chain set at its creation, it <em>should</em> for the most part be considered set in stone and not changeable. Technically, browsers which support <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/proto">the <code>__proto__</code> property</a>, a public representation of the internal link, allow you to change at any time where an object is linked to. However, this practice is littered with landmines and generally frowned upon -- it's almost certainly something you'd want to<strong>avoid</strong> in your code.</p>&#13;
  74. &#13;
  75. <h2>Spade a Spade</h2>&#13;
  76. <p>You've seen how the mechanisms in JavaScript are comparitively different from the mechanisms in other languages. But is it ok to just hand-waive over these differences so we can keep using the term "inheritance" for JS?</p>&#13;
  77. &#13;
  78. <p>The fact is, it's just <strong>not</strong> an accurate usage of the term. By insisting that JavaScript has "inheritance", we're really saying that the meaning of the word "inheritance" doesn't matter, or is rather soft.</p>&#13;
  79. &#13;
  80. <p>JS doesn't statically analyze what parts of an inheritance chain it can safely flatten out <strong>and copy</strong>, it maintains links to the entire delegation chain throughout runtime, <strong>as distinct objects</strong>, which means our code can take advantage of <a rel="nofollow" href="http://michaux.ca/articles/early-mixins-late-mixins">a variety of powerful "late binding" dynamic patterns</a>.</p>&#13;
  81. &#13;
  82. <p>If we keep trying to mimic inheritance in JavaScript (syntax hurdles be damned), we get <em>distracted</em> and <strong>miss out on <em>all that power</em> that was built into our language from the start</strong>.</p>&#13;
  83. &#13;
  84. <p>I say: let's call it what it is, and stop trying to pile on JavaScript these other concepts that the "inheritance" label implies.&#13;
  85. </p><h2>So What?</h2>&#13;
  86. <p>So far, I've tried to identify some misconceptions about JS's <code>[[Prototype]]</code> mechanism and how "inheritance" is not a helpful label.</p>&#13;
  87. &#13;
  88. <p>You may still be skeptical why it <em>actually</em> matters what we call this OO-like mechanism in JS? In the <a href="http://davidwalsh.name/javascript-objects-distractions">next part</a> of the article series, I'm going to address many of the trappings of traditional "class-based" programming which I think are distractions that lead us to missing out on the essence of how JS objects interoperate. In fact, we could even say that <a rel="nofollow" href="http://www.infoq.com/presentations/Classes-Are-Premature-Optimization">classes/inheritance are a premature optimization</a> for JavaScript.</p>&#13;
  89. &#13;
  90. <p>Clearing those distractions out of the way leads us to <a href="http://davidwalsh.name/javascript-objects-deconstruction">part 3</a>, where we'll see a simpler and more robust pattern for our JS code, and more importantly, <strong>our code will actually match our semantics</strong> <em>without</em> us having to jump through hoops to hide the ugly mismatches.</p>