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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>JS Objects: De"construct"ion (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://davidwalsh.name/javascript-objects-deconstruction">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. JS Objects: De"construct"ion (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://davidwalsh.name/javascript-objects-deconstruction">Source originale du contenu</a></h3>
  445. <h3>JS Objects: TL;DR</h3>
  446. <p>&#13;
  447. <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;
  448. <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;
  449. &#13;
  450. <h3>Due Thanks</h3>&#13;
  451. <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;
  452. &#13;
  453. <p>In part 1 of this article series (which you should totally go read if you haven't yet!), I revisited an idea not original to me: <a href="http://davidwalsh.name/javascript-objects">JS doesn't have "inheritance" in the traditional sense</a>, and what it does have is more appropriately labeled "behavior delegation" -- the ability of one object to delegate a method or property access which it <em>cannot</em> handle over to another object which <em>can</em> handle it.</p>&#13;
  454. &#13;
  455. <p>Then, in part 2, I addressed <a href="http://davidwalsh.name/javascript-objects-distractions">several distractions</a> which <strong><em>I think</em></strong> obfuscate JS's true object-oriented identity, including "custom types", "mixins", "polymorphism" (which we'll come back to again later), and even the new "class syntax" coming in ES6. I suggested that to understand (and leverage) better the <code>[[Prototype]]</code>, we needed to strip away the cruft. Here, I will attempt to do that.</p>&#13;
  456. &#13;
  457. <h2><del>Turtles</del> Objects all the way <del>down</del> up</h2>&#13;
  458. <p>The key realization, the punchline to this entire article series, is that <code>[[Prototype]]</code> is really only about linking one object to another object, for the purposes of delegating, if the first object <a href="http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.2">cannot handle a property or method access</a> but the second can. In other words, it's only objects, linked to other objects. That's really all JS has.</p>&#13;
  459. &#13;
  460. <p>In a sense, JS is the purest essence of a "object-oriented (OO)" language, in that it really <em>is</em> all about objects. In contrast to most other languages, JS is somewhat unique that you can actually create objects directly without the notion of classes or other abstractions. That's a powerful and brilliant feature!</p>&#13;
  461. &#13;
  462. <blockquote><p>People often bash JavaScript, but it’s one of the few prog languages that let you directly create objects. Others: Lua, Dylan, Cecil.</p>&#13;
  463. &#13;
  464. <p>— Axel Rauschmayer (@rauschma) <a href="https://twitter.com/rauschma/status/321712221400989696">April 9, 2013</a></p></blockquote>&#13;
  465. &#13;
  466. <p>JavaScript legitimately <em>is</em> "object-oriented", and perhaps we shouldn't have used that term for the other languages which imply a lot more than <strong><em>just</em></strong> "objects". Maybe "class-oriented" would have been more accurate, which would have freed us up to use "object-oriented" for JS. Of course, as I argued in part 1, <a href="http://davidwalsh.name/javascript-objects#spade-spade">what everybody means when they use some term, matters</a>, so it's far too late to redefine or bend the commonly accepted "object-oriented" to my own purposes, much as I'd like to.</p>&#13;
  467. &#13;
  468. <p>I'm mildly tempted, however, to just hijack the abbreviation of "OO" to mean "objects-only" instead of "object-oriented", but I bet that probably wouldn't get anywhere, either. So, for our purposes here, let's just say<strong>JavaScript is "object-based (OB)"</strong> to clarify against "object-oriented (OO)".</p>&#13;
  469. &#13;
  470. <p>Whatever we call it, we normally tap into this object mechanism by following the "OO way": we create a function which we use as a "constructor", and we call that function with <code>new</code> so that we can "instantiate" our "class", which we specify with the constructor function together with its subsequent <code>.prototype</code> additions... but all that is like a magician's sleight of hand that dazzles you <em>over here</em> to distract you from what's really going on <em>over there</em>.</p>&#13;
  471. &#13;
  472. <p>What really matters, at the end of the trick, is that <strong>two objects end up linked to each other via the<code>[[Prototype]]</code> chain</strong>.</p>&#13;
  473. &#13;
  474. <h2>Codez Plz</h2>&#13;
  475. <p>&#13;
  476. Before we can derive and understand that simpler view of "objects-only" or "object-based", we need to understand what actually gets created and linked when we build up some "inherited" objects in JavaScript. Not only are we going to see what happens by default, but what <em>doesn't</em> happen.</p>&#13;
  477. &#13;
  478. <p>Take this code for our main example:</p>&#13;
  479. &#13;
  480. <pre class="js">function Foo(who) {&#13;
  481. this.me = who;&#13;
  482. }&#13;
  483. &#13;
  484. Foo.prototype.identify = function() {&#13;
  485. return "I am " + this.me;&#13;
  486. };&#13;
  487. &#13;
  488. function Bar(who) {&#13;
  489. Foo.call(this,who);&#13;
  490. }&#13;
  491. &#13;
  492. Bar.prototype = Object.create(Foo.prototype);&#13;
  493. // NOTE: .constructor is borked here, need to fix&#13;
  494. &#13;
  495. Bar.prototype.speak = function() {&#13;
  496. alert("Hello, " + this.identify() + ".");&#13;
  497. };&#13;
  498. &#13;
  499. var b1 = new Bar("b1");&#13;
  500. var b2 = new Bar("b2");&#13;
  501. &#13;
  502. b1.speak(); // alerts: "Hello, I am b1."&#13;
  503. b2.speak(); // alerts: "Hello, I am b2."</pre>&#13;
  504. &#13;
  505. &#13;
  506. <p><strong>Note:</strong> Some people write <code>Bar.prototype = Object.create(Foo.prototype);</code> as <code>Bar.prototype = new Foo();</code>. Both approaches end up with the same linked objects, where <code>Bar.prototype</code> is an object linked via its<code>[[Prototype]]</code> to <code>Foo.prototype</code>. The only real difference is whether or not the <code>Foo</code> function is called during the creation of <code>Bar.prototype</code>. Depending on your circumstances and intent, you may or may not want that to happen, so let's consider them roughly interchangable but with different purposes.</p>&#13;
  507. &#13;
  508. <p>What we have is an object labeled <code>Foo.prototype</code> with an <code>identify()</code> method, and another object called<code>Bar.prototype</code> with a <code>speak()</code> method. <code>Bar.prototype</code> is a <em>new empty object</em> that is <code>[[Prototype]]</code>-linked to <code>Foo.prototype</code>. Then we have two objects <code>b1</code> and <code>b2</code>, who each are each respectively linked via their own <code>[[Prototype]]</code> to <code>Bar.prototype</code>. <code>b1</code> and <code>b2</code> also have an "owned property" directly on each of them called <code>me</code>, which respectively holds the values "b1" and "b2".</p>&#13;
  509. &#13;
  510. <p>Let's take a visual look at the relationships implied by the above code snippet:</p>&#13;
  511. &#13;
  512. <p><a href="http://davidwalsh.name/demo/JavaScriptObjects--Simplified.png" target="_blank"><img alt="" src="http://davidwalsh.name/demo/JavaScriptObjects--Simplified.png"/></a></p>&#13;
  513. &#13;
  514. <p><strong>Note:</strong> All the <code>[[Prototype]]</code> links in the diagram also mention <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/proto">a ".<strong>proto</strong>" property</a>. <code><strong>proto</strong></code> is a formerly non-standard property (which exists in most but not all JS environments) to expose the internal <code>[[Prototype]]</code>chain. As of ES6, however, it will be standardized.</p>&#13;
  515. &#13;
  516. <p>I left a whole bunch of detail out of that diagram, intentionally, so it was even remotely digestable. But of course, since JS is all objects, all the linkages and ancestry of each item can be fully traced. We'll come back to all the omitted parts of this diagram in a little bit.</p>&#13;
  517. &#13;
  518. <p>Note in this diagram that the function constructors all have a <code>.prototype</code> property pointing at an object. As we've been suggesting, the object is what we really care about, and in this way of viewing the JS object mechanism, the way we get that object is to look at a constructor function's <code>.prototype</code>. The function doesn't really serve any particularly important role.</p>&#13;
  519. &#13;
  520. <p>I know a whole bunch of you just screamed out, "sure it does! it runs constructor code to initialize the new object!" OK, you're technically right. <code>Foo()</code> has some code in it which is ultimately run against <code>b1</code> and <code>b2</code>.</p>&#13;
  521. &#13;
  522. <p>But the devil's always in the details. First, we don't need a constructor function to run such code. That's just one way of getting that outcome. And I'm going to suggest it's a more distracting approach.</p>&#13;
  523. &#13;
  524. <p>Secondly, <a href="http://max.berger.name/teaching/s06/ch13.jsp">unlike C++</a>, the base-class/superclass <code>Foo()</code> "constructor" doesn't automatically get called when you run the child-class <code>Bar()</code> "constructor" to make <code>b1</code> and <code>b2</code>. So, like Java, we have to manually call the<code>Foo()</code> function from <code>Bar()</code>, but unlike Java, we have to do so with a variation of the <a href="http://davidwalsh.name/javascript-objects-distractions#mixins">explicit "mixin" pattern</a> (I'd probably call it "implicit mixin" here) to make it work like we expect. That's an ugly detail that is very easy to forget or get wrong.</p>&#13;
  525. &#13;
  526. <p>So, where you'd probably argue with me that "constructor" functions are useful being automatically called at the construction of an object, I'd point out that this is true for only the immediate level, not for the entire "chain of inheritance", which means that automatic behavior is pretty limited/shallow in utility.</p>&#13;
  527. &#13;
  528. <h2>Polymorphism redux</h2>&#13;
  529. <p>Moreover, we see here the first hint of the <a href="http://davidwalsh.name/javascript-objects-distractions#polys">problems with relative polymorphism</a> in JS: <strong>you can't do it!</strong> I can't tell<code>Bar()</code> to automatically and relatively call his ancestor constructor(s), via a relative reference. I have to manually call (aka, "borrow") the <code>Foo()</code> function (it's not a constructor here, just a normal function call!) from inside of<code>Bar()</code>, and to make sure the <code>this</code> is bound correctly, I have to do the slightly more awkward <code>.call(this)</code>style of code. Ugh.</p>&#13;
  530. &#13;
  531. <p>What may not be obvious until you go back and look closer at the diagram above is that the <code>Foo()</code> function is<strong>not</strong> related in any useful/practical way to the <code>Bar()</code> function. The <code>Foo()</code> function does not even appear in the "inheritance" (aka "delegation") chain of <code>Bar.prototype</code> object. The fact that there are some lines you can follow on the graph for indirect relationships doesn't mean that those relationships are what you'd want to rely on in your code.</p>&#13;
  532. &#13;
  533. <p>The problem with polymorphism we're seeing here is not only for "constructor" functions. Any function at one level of the <code>[[Prototype]]</code> chain that wants to call up to an ancestor with the same name must do so via this manual implicit mixin approach just like we did inside of <code>Bar()</code> above. <strong>We have no effective way of making relative references up the chain.</strong></p>&#13;
  534. &#13;
  535. <p>Importantly, this means that not only do we establish the link between <code>Bar</code> and <code>Foo</code> once at "class" definition, but every single polymorphic reference also has to be hardcoded with the direct relationship. This significantly decreases the flexibility and maintainability of your code. As soon as you make a function hard-coded with implicit mixin to an "ancestor", now your function can't be "borrowed" as easily by other objects without those possible unintended side effects.</p>&#13;
  536. &#13;
  537. <p>OK, so let's say you agree with me at this point that polymoprhism in JS is more trouble than it's worth. <strong>Using constructor-based coding to wire JS objects to each other forces you into the <em>problems</em> of polymorphism</strong>.</p>&#13;
  538. &#13;
  539. <h2>.constructor</h2>&#13;
  540. <p>Another detail that's easy to miss is that an object's <code>.constructor</code> property really doesn't behave like we'd probably expect. It's correct at the <code>Foo()</code> level of the graph, but below that, at <code>Bar()</code> and <code>b1</code> / <code>b2</code>, notice that the implied linkage there shows <code>.constructor</code> references, strangely, still pointing at <code>Foo</code>.</p>&#13;
  541. &#13;
  542. <p>Actually, what this means is that the only time a <code>.constructor</code> property is added to an object is when that object is the <strong>default</strong> <code>.prototype</code> attached to a declared function, as is the case of <code>Foo()</code>. When objects are created via <code>new Fn()</code> or <code>Object.create(..)</code> calls, those objects <strong>don't</strong> get a <code>.constructor</code> added to them.</p>&#13;
  543. &#13;
  544. <p>Let me say that again: an object created by a constructor doesn't actually get a <code>.constructor</code> property to point to which constructor it was created by. This is an <strong>extremely common</strong> misconception.</p>&#13;
  545. &#13;
  546. <p>So if you reference <code>b1.constructor</code> for instance, then you're actually going to delegate up the chain a few links, to <code>Foo.prototype</code>. Of course, <code>Foo.prototype</code> has a <code>.constructor</code> property and it's pointing at <code>Foo</code> like you'd expect.</p>&#13;
  547. &#13;
  548. <p>What's that mean? In the above snippet, right after you perform <code>Bar.prototype = Object.create(Foo)</code> (or even if you'd done <code>Bar.prototype = new Foo()</code>), if you plan to rely on the <code>.constructor</code> property (which many do), you need to perform an extra step, right where I put the JS "Note:" comment:</p>&#13;
  549. &#13;
  550. <pre class="js">//...&#13;
  551. Bar.prototype = Object.create(Foo.prototype);&#13;
  552. Bar.prototype.constructor = Bar; // &lt;-- add this line!&#13;
  553. //...</pre>&#13;
  554. &#13;
  555. <p>Then <code>b1.constructor</code> references will delegate to that <code>Bar.prototype</code> level, and will "correctly" point at <code>Bar()</code>as you'd probably have expected. Ugh...<strong>more syntax gotchas</strong> that user-land libs always have to "fix" for us.</p>&#13;
  556. &#13;
  557. <p>Furthermore, the fact that <code>Foo.prototype</code> has a <code>.constructor</code> property pointing at <code>Foo</code> is strange, when you think about "constructor" the way most people do. It's nice that it gives objects created by <code>new Foo()</code> a way to delegate to a <code>.constructor</code> property access and find <code>Foo()</code>, but it's bizarre where <code>.constructor</code> actually lives.</p>&#13;
  558. &#13;
  559. <p>It implies that <code>Foo()</code> constructed <code>Foo.prototype</code>, but that's nonsense. <code>Foo()</code> had nothing to do with creating the default <code>Foo.prototype</code>. <code>Foo.prototype</code> defaults to an empty object that was <strong>actually constructed by the built-in <code>Object()</code> constructor</strong>.</p>&#13;
  560. &#13;
  561. <p>So we have to change how we think of what the <code>.constructor</code> property means. It does <strong>not</strong> mean "the constructor this object was created by". It <em>actually</em> means "the constructor which creates any objects that end up getting <code>[[Prototype]]</code> linked to this object." Subtle but super important difference to get straight.</p>&#13;
  562. &#13;
  563. <p>Point? These confusions only happen/matter if you're using constructor-style code, so it's <em>the choice of this style of code</em> that opts you into the problems. You don't <strong><em>have</em></strong> to live with that pain. There's a better, simpler way!</p>&#13;
  564. &#13;
  565. <h2>The Whole Pie</h2>&#13;
  566. <p>Now let's look at everything that's actually implied by the above snippet of code. Ready for the whole messy thing?</p>&#13;
  567. &#13;
  568. <p><a href="http://davidwalsh.name/demo/JavaScriptObjects--Full.png" target="_blank"><img alt="" src="http://davidwalsh.name/demo/JavaScriptObjects--Full.png"/></a></p>&#13;
  569. &#13;
  570. <p>Take a few minutes to just take all that in. Why show you such a complex diagram?</p>&#13;
  571. &#13;
  572. <p>This diagram actually shows you where some of JavaScript's functionality comes from, where before you might have just never considered how it all worked. For instance, have you wondered how all functions are able to use behavior such as <code>call()</code>, <code>apply()</code>, <code>bind()</code>, etc? You may have assumed each function has that behavior built-in, but as you can see from this diagram, functions <strong>delegate</strong> up their <code>[[Prototype]]</code> chain to handle those behaviors.</p>&#13;
  573. &#13;
  574. <p>While the behavior delegation part is sensible and useful, consider all the <em>implied complexity</em> of constructor-style coding as visualized here. It's pretty tough to trace all the different entities and diagrams and make much sense of it all. A lot of that complexity comes from the function constructors. (here's the same <a href="https://dl.dropboxusercontent.com/u/2474669/jsdiagrams/JavaScript%20Objects%20--%20Full%20--%20Direct%20Only.png">complete graph but with the implied relationship lines omitted</a>, if that helps to digest)</p>&#13;
  575. &#13;
  576. <p>If you take that diagram, and remove all the functions and any associated arrows (which we'll see in just a moment), you're left with "objects-only", and you'll have a <strong>much</strong> more simplified view of the JS objects world.</p>&#13;
  577. &#13;
  578. <h1 id="simpler-object-object">Simpler: Object -&gt; Object</h1>&#13;
  579. &#13;
  580. <blockquote><p>Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. --Antoine de Saint-Exupery</p></blockquote>&#13;
  581. <p>For refresher, the same prototype-style code from above:</p>&#13;
  582. &#13;
  583. <pre class="js">function Foo(who) {&#13;
  584. this.me = who;&#13;
  585. }&#13;
  586. &#13;
  587. Foo.prototype.identify = function() {&#13;
  588. return "I am " + this.me;&#13;
  589. };&#13;
  590. &#13;
  591. function Bar(who) {&#13;
  592. Foo.call(this,who);&#13;
  593. }&#13;
  594. &#13;
  595. Bar.prototype = Object.create(Foo.prototype);&#13;
  596. // NOTE: .constructor is borked here, need to fix&#13;
  597. &#13;
  598. Bar.prototype.speak = function() {&#13;
  599. alert("Hello, " + this.identify() + ".");&#13;
  600. };&#13;
  601. &#13;
  602. var b1 = new Bar("b1");&#13;
  603. var b2 = new Bar("b2");&#13;
  604. &#13;
  605. b1.speak(); // alerts: "Hello, I am b1."&#13;
  606. b2.speak(); // alerts: "Hello, I am b2."</pre>&#13;
  607. &#13;
  608. <p>Now, let's instead consider this alternative snippet of code, which accomplishes exactly the same, but it does so without any of the confusion/distraction of "constructor functions", <code>new</code>, <code>.prototype</code>, etc. It just creates several objects and links them together.</p>&#13;
  609. &#13;
  610. <pre class="js">var Foo = {&#13;
  611. init: function(who) {&#13;
  612. this.me = who;&#13;
  613. },&#13;
  614. identify: function() {&#13;
  615. return "I am " + this.me;&#13;
  616. }&#13;
  617. };&#13;
  618. &#13;
  619. var Bar = Object.create(Foo);&#13;
  620. &#13;
  621. Bar.speak = function() {&#13;
  622. alert("Hello, " + this.identify() + ".");&#13;
  623. };&#13;
  624. &#13;
  625. var b1 = Object.create(Bar);&#13;
  626. b1.init("b1");&#13;
  627. var b2 = Object.create(Bar);&#13;
  628. b2.init("b2");&#13;
  629. &#13;
  630. b1.speak(); // alerts: "Hello, I am b1."&#13;
  631. b2.speak(); // alerts: "Hello, I am b2."</pre>&#13;
  632. &#13;
  633. <p>Let's try to take some comparison looks between this snippet and the previous one. They both accomplish the same thing, but there's some important differences in how we get there.</p>&#13;
  634. &#13;
  635. <p>First of all, <code>Bar</code> and <code>Foo</code> are now <strong>just objects</strong>, they're not functions or constructors anymore. I left them as capital letters just for the symmetry and because some people feel better with them. They make it clear that the objects being linked are what we cared about all along, so instead of the indirectness of linking <code>Bar.prototype</code> to<code>Foo.prototype</code>, we just make <code>Foo</code> and <code>Bar</code> objects themselves and link <em>them</em>. <strong>AND</strong>, we only need one line of code to link them, instead of the extra ugly polymorphic linkage. Bam!</p>&#13;
  636. &#13;
  637. <p>Instead of calling function constructors like <code>new Bar(..)</code>, we <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create">use <code>Object.create(..)</code></a>, which is an ES5 helper that allows us to create a new object and optionally provide another object to <code>[[Prototype]]</code> link it to. We get the same outcome (object creation and linkage) as a constructor call, but without needing the constructor. BTW, there's a <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create#Polyfill">simple non-ES5 polyfill</a> for <code>Object.create(..)</code>, so you can safely use this style of code in all browsers without concern.</p>&#13;
  638. &#13;
  639. <p>Secondly, notice that because we're not worried about constructors anymore, we have eliminated any concerns about awkward polymorphisms forcing us to do manual implied mixins to call <code>Foo()</code> from <code>Bar()</code>. Instead, we put the code we wanted to run to initialize our objects into a <code>init()</code> method, on <code>Foo</code>, and we can now call<code>b1.init(..)</code> directly via the delegation chain and it "magically" just works like we want.</p>&#13;
  640. &#13;
  641. <p>So, we have a tradeoff here. We don't get automatic constructor calls, which means we create the object like<code>var b1 = Object.create(Bar)</code> and then we have to additionally call <code>b1.init("b1")</code>. That's "more code".</p>&#13;
  642. &#13;
  643. <p>But the benefits we get, <strong>which I think are much better and well worth it</strong>, are no awkwardness with the linkage between <code>Foo</code> and <code>Bar</code> -- instead we leverage <code>[[Prototype]]</code> delegation to get at the code reuse in<code>init()</code>. Also, no more verbose/repetitive <code>.prototype</code> references, and neither do we need to use<code>.call(this)</code> nearly as often (especially if we <strong>avoid</strong> polymorphism!).</p>&#13;
  644. &#13;
  645. <h2>Looks are everything</h2>&#13;
  646. <p>And to visualize the simplicity this approach brings us, here's the diagram when we remove the functions entirely and focus only on the objects:</p>&#13;
  647. &#13;
  648. <p><a href="http://davidwalsh.name/demo/JavaScriptObjects--OnlyObjects.png" target="_blank"><img alt="" src="http://davidwalsh.name/demo/JavaScriptObjects--OnlyObjects.png"/></a></p>&#13;
  649. &#13;
  650. <p>I don't know about you, but I just think that mental model is <strong>so much cleaner</strong>, and the bonus is that its semantics perfectly match the code.</p>&#13;
  651. &#13;
  652. <p>I have shown you simple enough code using only core JS syntax, that I don't need any helper libraries to wire up my objects. Of course, I <em>could</em> use one, but why? Simpler is better. KISS.</p>&#13;
  653. &#13;
  654. <blockquote><p>Any fool can make something complicated. It takes a genius to make it simple. --Woody Guthrie</p></blockquote>&#13;
  655. <p>For the record, I'm not <em>even remotely</em> the genius here. <a href="http://twitter.com/brendaneich">Brendan Eich</a>, creator of our language, was the genius for making something so powerful yet so simple.</p>&#13;
  656. &#13;
  657. <h2>Object self-reflection</h2>&#13;
  658. <p>Last thing to address: how does this simplification affect the process of reflecting on an object? In other words, can we inspect an object and find out about its relationships to other objects?</p>&#13;
  659. &#13;
  660. <p>For prototype-style code, reflection looks like this:</p>&#13;
  661. &#13;
  662. <pre class="js">b1 instanceof Bar; // true&#13;
  663. b2 instanceof Bar; // true&#13;
  664. b1 instanceof Foo; // true&#13;
  665. b2 instanceof Foo; // true&#13;
  666. Bar.prototype instanceof Foo; // true&#13;
  667. Object.getPrototypeOf(b1) === Bar.prototype; // true&#13;
  668. Object.getPrototypeOf(b2) === Bar.prototype; // true&#13;
  669. Object.getPrototypeOf(Bar.prototype) === Foo.prototype; // true</pre>&#13;
  670. &#13;
  671. <p>Notice that you're using <code>instanceof</code> and having to think in terms of the constructor functions that made your objects, and their <code>.prototype</code>s, rather than just reflecting on the objects themselves. Each of those reflections comes with slightly more mental tax as a result.</p>&#13;
  672. &#13;
  673. <p>And when there's only objects?</p>&#13;
  674. &#13;
  675. <pre class="js">Bar.isPrototypeOf(b1); // true&#13;
  676. Bar.isPrototypeOf(b2); // true&#13;
  677. Foo.isPrototypeOf(b1); // true&#13;
  678. Foo.isPrototypeOf(b2); // true&#13;
  679. Foo.isPrototypeOf(Bar); // true&#13;
  680. Object.getPrototypeOf(b1) === Bar; // true&#13;
  681. Object.getPrototypeOf(b2) === Bar; // true&#13;
  682. Object.getPrototypeOf(Bar) === Foo; // true</pre>&#13;
  683. &#13;
  684. <p>By contrast, reflection on objects is only about the objects. There's no awkward references to a constructor's<code>.prototype</code> property for the checks. You can just inspect if one object is related via <code>[[Prototype]]</code> to another object. Same capabilities as above, but with less mental tax.</p>&#13;
  685. &#13;
  686. <p>Moreover, as I mentioned in part 2, this sort of explicit object reflection is preferable and more robust/reliable than implicit detection through <a href="http://en.wikipedia.org/wiki/Duck_typing">duck typing</a>.</p>&#13;
  687. &#13;
  688. <h2>Object.wrapItUpAlready()</h2>&#13;
  689. <p>Take a deep breath! That was a lot to take in. If you've followed all 3 parts of the article series, I hope by now you see the bottom line: JS has objects and when we link them, we get powerful behavior delegation.</p>&#13;
  690. &#13;
  691. <p>There's just no need to pile on class-orientation on top of such a great system, because it ultimately just leads to the confusion and distraction that has kept JS' object mechanism shrouded and covered up by all these helper libraries and misunderstandings about JS syntax.</p>&#13;
  692. &#13;
  693. <p>If you stop thinking about inheritance, and instead think with the arrows headed the other way: delegation, your JS code will be simpler. Remember: it's just objects linked to objects!</p></p>
  694. </article>
  695. </section>
  696. <nav id="jumpto">
  697. <p>
  698. <a href="/david/blog/">Accueil du blog</a> |
  699. <a href="http://davidwalsh.name/javascript-objects-deconstruction">Source originale</a> |
  700. <a href="/david/stream/2019/">Accueil du flux</a>
  701. </p>
  702. </nav>
  703. <footer>
  704. <div>
  705. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  706. <p>
  707. Bonjour/Hi!
  708. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  709. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  710. </p>
  711. <p>
  712. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  713. </p>
  714. <p>
  715. Voici quelques articles choisis :
  716. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  717. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  718. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  719. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  720. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  721. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  722. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  723. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  724. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  725. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  726. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  727. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  728. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  729. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  730. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  731. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  732. </p>
  733. <p>
  734. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  735. </p>
  736. <p>
  737. Je ne traque pas ta navigation mais mon
  738. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  739. conserve des logs d’accès.
  740. </p>
  741. </div>
  742. </footer>
  743. <script type="text/javascript">
  744. ;(_ => {
  745. const jumper = document.getElementById('jumper')
  746. jumper.addEventListener('click', e => {
  747. e.preventDefault()
  748. const anchor = e.target.getAttribute('href')
  749. const targetEl = document.getElementById(anchor.substring(1))
  750. targetEl.scrollIntoView({behavior: 'smooth'})
  751. })
  752. })()
  753. </script>