Repository: javascript-tutorial/fr.javascript.info Branch: master Commit: c02fa1d6e2ca Files: 1171 Total size: 3.9 MB Directory structure: gitextract_jgp5_eg_/ ├── .gitattributes ├── .gitignore ├── 1-js/ │ ├── 01-getting-started/ │ │ ├── 1-intro/ │ │ │ └── article.md │ │ ├── 2-manuals-specifications/ │ │ │ └── article.md │ │ ├── 3-code-editors/ │ │ │ └── article.md │ │ ├── 4-devtools/ │ │ │ ├── article.md │ │ │ └── bug.html │ │ └── index.md │ ├── 02-first-steps/ │ │ ├── 01-hello-world/ │ │ │ ├── 1-hello-alert/ │ │ │ │ ├── index.html │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 2-hello-alert-ext/ │ │ │ │ ├── alert.js │ │ │ │ ├── index.html │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 02-structure/ │ │ │ └── article.md │ │ ├── 03-strict-mode/ │ │ │ └── article.md │ │ ├── 04-variables/ │ │ │ ├── 1-hello-variables/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-declare-variables/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-uppercast-constant/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 05-types/ │ │ │ ├── 1-string-quotes/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 06-alert-prompt-confirm/ │ │ │ ├── 1-simple-page/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 07-type-conversions/ │ │ │ └── article.md │ │ ├── 08-operators/ │ │ │ ├── 1-increment-order/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-assignment-result/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-primitive-conversions-questions/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-fix-prompt/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 09-comparison/ │ │ │ ├── 1-comparison-questions/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 10-ifelse/ │ │ │ ├── 1-if-zero-string/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-check-standard/ │ │ │ │ ├── ifelse_task2/ │ │ │ │ │ └── index.html │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-sign/ │ │ │ │ ├── if_sign/ │ │ │ │ │ └── index.html │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-rewrite-if-question/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-rewrite-if-else-question/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 11-logical-operators/ │ │ │ ├── 1-alert-null-2-undefined/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-alert-or/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-alert-1-null-2/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-alert-and/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-alert-and-or/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-check-if-in-range/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 7-check-if-out-range/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 8-if-question/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 9-check-login/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 12-nullish-coalescing-operator/ │ │ │ └── article.md │ │ ├── 13-while-for/ │ │ │ ├── 1-loop-last-value/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-which-value-while/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-which-value-for/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-for-even/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-replace-for-while/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-repeat-until-correct/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 7-list-primes/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 14-switch/ │ │ │ ├── 1-rewrite-switch-if-else/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-rewrite-if-switch/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 15-function-basics/ │ │ │ ├── 1-if-else-required/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-rewrite-function-question-or/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-min/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-pow/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 16-function-expressions/ │ │ │ └── article.md │ │ ├── 17-arrow-functions-basics/ │ │ │ ├── 1-rewrite-arrow/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 18-javascript-specials/ │ │ │ └── article.md │ │ └── index.md │ ├── 03-code-quality/ │ │ ├── 01-debugging-chrome/ │ │ │ ├── article.md │ │ │ ├── debugging.view/ │ │ │ │ ├── hello.js │ │ │ │ └── index.html │ │ │ └── head.html │ │ ├── 02-coding-style/ │ │ │ ├── 1-style-errors/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 03-comments/ │ │ │ └── article.md │ │ ├── 04-ninja-code/ │ │ │ └── article.md │ │ ├── 05-testing-mocha/ │ │ │ ├── 3-pow-test-wrong/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ ├── beforeafter.view/ │ │ │ │ ├── index.html │ │ │ │ └── test.js │ │ │ ├── index.html │ │ │ ├── pow-1.view/ │ │ │ │ ├── index.html │ │ │ │ └── test.js │ │ │ ├── pow-2.view/ │ │ │ │ ├── index.html │ │ │ │ └── test.js │ │ │ ├── pow-3.view/ │ │ │ │ ├── index.html │ │ │ │ └── test.js │ │ │ ├── pow-4.view/ │ │ │ │ ├── index.html │ │ │ │ └── test.js │ │ │ ├── pow-full.view/ │ │ │ │ ├── index.html │ │ │ │ └── test.js │ │ │ ├── pow-min.view/ │ │ │ │ ├── index.html │ │ │ │ └── test.js │ │ │ └── pow-nan.view/ │ │ │ ├── index.html │ │ │ └── test.js │ │ ├── 06-polyfills/ │ │ │ └── article.md │ │ └── index.md │ ├── 04-object-basics/ │ │ ├── 01-object/ │ │ │ ├── 2-hello-object/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-is-empty/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-const-object/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-sum-object/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 8-multiply-numeric/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ ├── source.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 02-object-copy/ │ │ │ └── article.md │ │ ├── 03-garbage-collection/ │ │ │ └── article.md │ │ ├── 04-object-methods/ │ │ │ ├── 4-object-property-this/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 7-calculator/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 8-chain-calls/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 06-constructor-new/ │ │ │ ├── 1-two-functions-one-object/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-calculator-constructor/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-accumulator/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 07-optional-chaining/ │ │ │ └── article.md │ │ ├── 08-symbol/ │ │ │ └── article.md │ │ ├── 09-object-toprimitive/ │ │ │ └── article.md │ │ └── index.md │ ├── 05-data-types/ │ │ ├── 01-primitives-methods/ │ │ │ ├── 1-string-new-property/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 02-number/ │ │ │ ├── 1-sum-interface/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-why-rounded-down/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-repeat-until-number/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-endless-loop-error/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 8-random-min-max/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 9-random-int-min-max/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 03-string/ │ │ │ ├── 1-ucfirst/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-check-spam/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-truncate/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-extract-currency/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 04-array/ │ │ │ ├── 1-item-value/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 10-maximal-subarray/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-create-array/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-call-array-this/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-array-input-sum/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 05-array-methods/ │ │ │ ├── 1-camelcase/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 10-average-age/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 11-array-unique/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 12-reduce-object/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-filter-range/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-filter-range-in-place/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-sort-back/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-copy-sort-array/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-array-get-names/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-calculator-extendable/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 7-map-objects/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 8-sort-objects/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 9-shuffle/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 06-iterable/ │ │ │ └── article.md │ │ ├── 07-map-set/ │ │ │ ├── 01-array-unique-map/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-filter-anagrams/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 03-iterable-keys/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 08-weakmap-weakset/ │ │ │ ├── 01-recipients-read/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-recipients-when-read/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 09-keys-values-entries/ │ │ │ ├── 01-sum-salaries/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-count-properties/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 10-destructuring-assignment/ │ │ │ ├── 1-destruct-user/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-max-salary/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 11-date/ │ │ │ ├── 1-new-date/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-get-week-day/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-weekday/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-get-date-ago/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-last-day-of-month/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-get-seconds-today/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 7-get-seconds-to-tomorrow/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 8-format-date-relative/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 12-json/ │ │ │ ├── 1-serialize-object/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-serialize-event-circular/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ └── index.md │ ├── 06-advanced-functions/ │ │ ├── 01-recursion/ │ │ │ ├── 01-sum-to/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-factorial/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 03-fibonacci-numbers/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 04-output-single-linked-list/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 05-output-single-linked-list-reverse/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ ├── 02-rest-parameters-spread/ │ │ │ └── article.md │ │ ├── 03-closure/ │ │ │ ├── 1-closure-latest-changes/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 10-make-army/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ ├── source.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-closure-variable-access/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-counter-independent/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-counter-object-independent/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-function-in-if/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-closure-sum/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 7-let-scope/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 8-filter-through-function/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ ├── source.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 9-sort-by-field/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ ├── source.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 04-var/ │ │ │ └── article.md │ │ ├── 05-global-object/ │ │ │ └── article.md │ │ ├── 06-function-object/ │ │ │ ├── 2-counter-inc-dec/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ ├── source.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-sum-many-brackets/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ ├── source.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 07-new-function/ │ │ │ └── article.md │ │ ├── 08-settimeout-setinterval/ │ │ │ ├── 1-output-numbers-100ms/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-rewrite-settimeout/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-settimeout-result/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 09-call-apply-decorators/ │ │ │ ├── 01-spy-decorator/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ ├── source.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-delay/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 03-debounce/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── debounce.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 04-throttle/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 10-bind/ │ │ │ ├── 2-write-to-object-after-bind/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-second-bind/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-function-property-after-bind/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-question-use-bind/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-ask-partial/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ ├── 12-arrow-functions/ │ │ │ └── article.md │ │ └── index.md │ ├── 07-object-properties/ │ │ ├── 01-property-descriptors/ │ │ │ └── article.md │ │ ├── 02-property-accessors/ │ │ │ └── article.md │ │ └── index.md │ ├── 08-prototypes/ │ │ ├── 01-prototype-inheritance/ │ │ │ ├── 1-property-after-delete/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-search-algorithm/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-proto-and-this/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-hamster-proto/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 02-function-prototype/ │ │ │ ├── 1-changing-prototype/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-new-object-same-constructor/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 03-native-prototypes/ │ │ │ ├── 1-defer-to-prototype/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-defer-to-prototype-extended/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 04-prototype-methods/ │ │ │ ├── 2-dictionary-tostring/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-compare-calls/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ └── index.md │ ├── 09-classes/ │ │ ├── 01-class/ │ │ │ ├── 1-rewrite-to-class/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── source.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 02-class-inheritance/ │ │ │ ├── 1-class-constructor-error/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-clock-class-extended/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── clock.js │ │ │ │ │ ├── extended-clock.js │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ ├── clock.js │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 03-static-properties-methods/ │ │ │ ├── 3-class-extend-object/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 04-private-protected-properties-methods/ │ │ │ └── article.md │ │ ├── 05-extend-natives/ │ │ │ └── article.md │ │ ├── 06-instanceof/ │ │ │ ├── 1-strange-instanceof/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 07-mixins/ │ │ │ ├── article.md │ │ │ └── head.html │ │ └── index.md │ ├── 10-error-handling/ │ │ ├── 1-try-catch/ │ │ │ ├── 1-finally-or-code-after/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 2-custom-errors/ │ │ │ ├── 1-format-error/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ └── index.md │ ├── 11-async/ │ │ ├── 01-callbacks/ │ │ │ ├── article.md │ │ │ └── one.js │ │ ├── 02-promise-basics/ │ │ │ ├── 01-re-resolve/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-delay-promise/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 03-animate-circle-promise/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ ├── 03-promise-chaining/ │ │ │ ├── 01-then-vs-catch/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ ├── getMessage.js │ │ │ ├── head.html │ │ │ ├── one.js │ │ │ ├── three.js │ │ │ ├── two.js │ │ │ └── user.json │ │ ├── 04-promise-error-handling/ │ │ │ ├── 01-error-async/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ ├── getMessage.js │ │ │ ├── head.html │ │ │ ├── one.js │ │ │ ├── three.js │ │ │ ├── two.js │ │ │ └── user.json │ │ ├── 05-promise-api/ │ │ │ ├── article.md │ │ │ ├── head.html │ │ │ ├── iliakan.json │ │ │ ├── one.js │ │ │ └── two.js │ │ ├── 06-promisify/ │ │ │ └── article.md │ │ ├── 07-microtask-queue/ │ │ │ └── article.md │ │ ├── 08-async-await/ │ │ │ ├── 01-rewrite-async/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-rewrite-async-2/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 03-async-from-regular/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ └── index.md │ ├── 12-generators-iterators/ │ │ ├── 1-generators/ │ │ │ ├── 01-pseudo-random-generator/ │ │ │ │ ├── _js.view/ │ │ │ │ │ ├── solution.js │ │ │ │ │ └── test.js │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 2-async-iterators-generators/ │ │ │ ├── article.md │ │ │ └── head.html │ │ └── index.md │ ├── 13-modules/ │ │ ├── 01-modules-intro/ │ │ │ ├── article.md │ │ │ ├── say.view/ │ │ │ │ ├── index.html │ │ │ │ └── say.js │ │ │ ├── scopes-working.view/ │ │ │ │ ├── hello.js │ │ │ │ ├── index.html │ │ │ │ └── user.js │ │ │ └── scopes.view/ │ │ │ ├── hello.js │ │ │ ├── index.html │ │ │ └── user.js │ │ ├── 02-import-export/ │ │ │ └── article.md │ │ ├── 03-modules-dynamic-imports/ │ │ │ ├── article.md │ │ │ └── say.view/ │ │ │ ├── index.html │ │ │ └── say.js │ │ └── index.md │ ├── 99-js-misc/ │ │ ├── 01-proxy/ │ │ │ ├── 01-error-nonexisting/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 02-array-negative/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 03-observable/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 02-eval/ │ │ │ ├── 1-eval-calculator/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 03-currying-partials/ │ │ │ └── article.md │ │ ├── 04-reference-type/ │ │ │ ├── 2-check-syntax/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-why-this/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 05-bigint/ │ │ │ └── article.md │ │ ├── 06-unicode/ │ │ │ └── article.md │ │ └── index.md │ └── index.md ├── 2-ui/ │ ├── 1-document/ │ │ ├── 01-browser-environment/ │ │ │ └── article.md │ │ ├── 02-dom-nodes/ │ │ │ ├── article.md │ │ │ ├── elk.html │ │ │ └── head.html │ │ ├── 03-dom-navigation/ │ │ │ ├── 1-dom-children/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 3-navigation-links-which-null/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-select-diagonal-cells/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ ├── 04-searching-elements-dom/ │ │ │ ├── 1-find-elements/ │ │ │ │ ├── solution.md │ │ │ │ ├── table.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 05-basic-dom-node-properties/ │ │ │ ├── 2-lastchild-nodetype-inline/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-tree-info/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 3-tag-in-comment/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-where-document-in-hierarchy/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 06-dom-attributes-and-properties/ │ │ │ ├── 1-get-user-attribute/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-yellow-links/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 07-modifying-document/ │ │ │ ├── 1-createtextnode-vs-innerhtml/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 10-clock-setinterval/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 11-append-to-list/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 12-sort-table/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 4-clear-elem/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 5-why-aaa/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 6-create-list/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 7-create-object-tree/ │ │ │ │ ├── build-tree-dom.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── innerhtml.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── solution.md │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 8-tree-count/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 9-calendar-table/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 08-styles-and-classes/ │ │ │ ├── 2-create-notification/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 09-size-and-scroll/ │ │ │ ├── 1-get-scroll-height-bottom/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-scrollbar-width/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 4-put-ball-in-center/ │ │ │ │ ├── ball-half/ │ │ │ │ │ └── index.html │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 6-width-vs-clientwidth/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ ├── cssWidthScroll.view/ │ │ │ │ └── index.html │ │ │ └── metric.view/ │ │ │ └── index.html │ │ ├── 10-size-and-scroll-window/ │ │ │ └── article.md │ │ ├── 11-coordinates/ │ │ │ ├── 1-find-point-coordinates/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 2-position-at/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 3-position-at-absolute/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 4-position-inside-absolute/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ └── index.md │ ├── 2-events/ │ │ ├── 01-introduction-browser-events/ │ │ │ ├── 01-hide-other/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 02-hide-self-onclick/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 03-which-handlers-run/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 04-move-ball-field/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 05-sliding-menu/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 06-hide-message/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── messages.css │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── messages.css │ │ │ │ └── task.md │ │ │ ├── 07-carousel/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── style.css │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── style.css │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ ├── 02-bubbling-and-capturing/ │ │ │ ├── article.md │ │ │ ├── both.view/ │ │ │ │ ├── example.css │ │ │ │ ├── index.html │ │ │ │ └── script.js │ │ │ ├── bubble-target.view/ │ │ │ │ ├── example.css │ │ │ │ ├── index.html │ │ │ │ └── script.js │ │ │ └── capture.view/ │ │ │ ├── example.css │ │ │ ├── index.html │ │ │ └── script.js │ │ ├── 03-event-delegation/ │ │ │ ├── 1-hide-message-delegate/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── messages.css │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── messages.css │ │ │ │ └── task.md │ │ │ ├── 2-sliding-tree/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 3-sortable-table/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 4-behavior-tooltip/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── bagua.view/ │ │ │ ├── bagua.css │ │ │ └── index.html │ │ ├── 04-default-browser-action/ │ │ │ ├── 1-why-return-false-fails/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ ├── 2-catch-link-navigation/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 3-image-gallery/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── gallery.css │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ ├── gallery.css │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── menu.view/ │ │ │ ├── index.html │ │ │ ├── menu.css │ │ │ └── menu.js │ │ ├── 05-dispatch-events/ │ │ │ └── article.md │ │ └── index.md │ ├── 3-event-details/ │ │ ├── 1-mouse-events-basics/ │ │ │ ├── 01-selectable-list/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── head.html │ │ ├── 3-mousemove-mouseover-mouseout-mouseenter-mouseleave/ │ │ │ ├── 1-behavior-nested-tooltip/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 2-hoverintent/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── hoverIntent.js │ │ │ │ │ ├── index.html │ │ │ │ │ ├── style.css │ │ │ │ │ └── test.js │ │ │ │ ├── source.view/ │ │ │ │ │ ├── hoverIntent.js │ │ │ │ │ ├── index.html │ │ │ │ │ ├── style.css │ │ │ │ │ └── test.js │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ ├── mouseenter-mouseleave-delegation-2.view/ │ │ │ │ ├── index.html │ │ │ │ ├── script.js │ │ │ │ └── style.css │ │ │ ├── mouseenter-mouseleave-delegation.view/ │ │ │ │ ├── index.html │ │ │ │ ├── script.js │ │ │ │ └── style.css │ │ │ ├── mouseleave-table.view/ │ │ │ │ ├── index.html │ │ │ │ ├── script.js │ │ │ │ └── style.css │ │ │ ├── mouseleave.view/ │ │ │ │ ├── index.html │ │ │ │ ├── script.js │ │ │ │ └── style.css │ │ │ ├── mouseoverout-child.view/ │ │ │ │ ├── index.html │ │ │ │ ├── script.js │ │ │ │ └── style.css │ │ │ ├── mouseoverout-fast.view/ │ │ │ │ ├── index.html │ │ │ │ ├── script.js │ │ │ │ └── style.css │ │ │ └── mouseoverout.view/ │ │ │ ├── index.html │ │ │ ├── script.js │ │ │ └── style.css │ │ ├── 4-mouse-drag-and-drop/ │ │ │ ├── 1-slider/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── style.css │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── style.css │ │ │ │ └── task.md │ │ │ ├── 2-drag-heroes/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── soccer.css │ │ │ │ │ └── soccer.js │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── soccer.css │ │ │ │ │ └── soccer.js │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ ├── ball.view/ │ │ │ │ └── index.html │ │ │ ├── ball2.view/ │ │ │ │ └── index.html │ │ │ ├── ball3.view/ │ │ │ │ └── index.html │ │ │ └── ball4.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── 6-pointer-events/ │ │ │ ├── article.md │ │ │ ├── ball-2.view/ │ │ │ │ └── index.html │ │ │ ├── ball.view/ │ │ │ │ └── index.html │ │ │ ├── multitouch.view/ │ │ │ │ └── index.html │ │ │ ├── slider-html.view/ │ │ │ │ ├── index.html │ │ │ │ └── style.css │ │ │ └── slider.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── 7-keyboard-events/ │ │ │ ├── 2-check-sync-keydown/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── keyboard-dump.view/ │ │ │ ├── index.html │ │ │ ├── script.js │ │ │ └── style.css │ │ ├── 8-onscroll/ │ │ │ ├── 1-endless-page/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 2-updown-button/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── 3-load-visible-img/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ └── index.md │ ├── 4-forms-controls/ │ │ ├── 1-form-elements/ │ │ │ ├── 1-add-select-option/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 2-focus-blur/ │ │ │ ├── 3-editable-div/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── my.css │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── my.css │ │ │ │ └── task.md │ │ │ ├── 4-edit-td-click/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── bagua.css │ │ │ │ │ ├── index.html │ │ │ │ │ ├── my.css │ │ │ │ │ └── script.js │ │ │ │ ├── source.view/ │ │ │ │ │ ├── bagua.css │ │ │ │ │ ├── index.html │ │ │ │ │ ├── my.css │ │ │ │ │ └── script.js │ │ │ │ └── task.md │ │ │ ├── 5-keyboard-mouse/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 3-events-change-input/ │ │ │ ├── 1-deposit-calculator/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ └── article.md │ │ ├── 4-forms-submit/ │ │ │ ├── 1-modal-dialog/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── style.css │ │ │ │ ├── source.view/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── style.css │ │ │ │ └── task.md │ │ │ └── article.md │ │ └── index.md │ ├── 5-loading/ │ │ ├── 01-onload-ondomcontentloaded/ │ │ │ ├── article.md │ │ │ ├── readystate.view/ │ │ │ │ ├── iframe.html │ │ │ │ └── index.html │ │ │ └── window-onbeforeunload.view/ │ │ │ └── index.html │ │ ├── 02-script-async-defer/ │ │ │ ├── article.md │ │ │ ├── long.js │ │ │ └── small.js │ │ ├── 03-onload-onerror/ │ │ │ ├── 1-load-img-callback/ │ │ │ │ ├── solution.md │ │ │ │ ├── solution.view/ │ │ │ │ │ └── index.html │ │ │ │ ├── source.view/ │ │ │ │ │ └── index.html │ │ │ │ └── task.md │ │ │ ├── article.md │ │ │ └── crossorigin.view/ │ │ │ └── error.js │ │ └── index.md │ ├── 99-ui-misc/ │ │ ├── 01-mutation-observer/ │ │ │ └── article.md │ │ ├── 02-selection-range/ │ │ │ └── article.md │ │ ├── 03-event-loop/ │ │ │ ├── 2-micro-macro-queue/ │ │ │ │ ├── solution.md │ │ │ │ └── task.md │ │ │ └── article.md │ │ └── index.md │ └── index.md ├── 3-frames-and-windows/ │ ├── 01-popup-windows/ │ │ └── article.md │ ├── 03-cross-window-communication/ │ │ ├── article.md │ │ ├── postmessage.view/ │ │ │ ├── iframe.html │ │ │ └── index.html │ │ └── sandbox.view/ │ │ ├── index.html │ │ └── sandboxed.html │ ├── 06-clickjacking/ │ │ ├── article.md │ │ ├── clickjacking-visible.view/ │ │ │ ├── facebook.html │ │ │ └── index.html │ │ ├── clickjacking.view/ │ │ │ ├── facebook.html │ │ │ └── index.html │ │ ├── protector.view/ │ │ │ ├── iframe.html │ │ │ └── index.html │ │ └── top-location.view/ │ │ ├── iframe.html │ │ └── index.html │ └── index.md ├── 4-binary/ │ ├── 01-arraybuffer-binary-arrays/ │ │ ├── 01-concat/ │ │ │ ├── _js.view/ │ │ │ │ ├── solution.js │ │ │ │ ├── source.js │ │ │ │ └── test.js │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 02-text-decoder/ │ │ └── article.md │ ├── 03-blob/ │ │ └── article.md │ ├── 04-file/ │ │ └── article.md │ └── index.md ├── 5-network/ │ ├── 01-fetch/ │ │ ├── 01-fetch-users/ │ │ │ ├── _js.view/ │ │ │ │ ├── solution.js │ │ │ │ ├── source.js │ │ │ │ └── test.js │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── article.md │ │ └── post.view/ │ │ └── server.js │ ├── 02-formdata/ │ │ ├── article.md │ │ └── post.view/ │ │ └── server.js │ ├── 03-fetch-progress/ │ │ ├── article.md │ │ └── progress.view/ │ │ ├── index.html │ │ └── long.txt │ ├── 04-fetch-abort/ │ │ ├── article.md │ │ └── demo.view/ │ │ └── server.js │ ├── 05-fetch-crossorigin/ │ │ ├── 1-do-we-need-origin/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 06-fetch-api/ │ │ ├── article.md │ │ └── post.view/ │ │ ├── index.html │ │ └── server.js │ ├── 07-url/ │ │ └── article.md │ ├── 08-xmlhttprequest/ │ │ ├── article.md │ │ ├── example.view/ │ │ │ ├── index.html │ │ │ └── server.js │ │ ├── hello.txt │ │ ├── phones-async.view/ │ │ │ ├── index.html │ │ │ ├── phones.json │ │ │ └── server.js │ │ ├── phones.json │ │ ├── phones.view/ │ │ │ ├── index.html │ │ │ ├── phones.json │ │ │ └── server.js │ │ └── post.view/ │ │ ├── index.html │ │ └── server.js │ ├── 09-resume-upload/ │ │ ├── article.md │ │ └── upload-resume.view/ │ │ ├── index.html │ │ ├── server.js │ │ └── uploader.js │ ├── 10-long-polling/ │ │ ├── article.md │ │ └── longpoll.view/ │ │ ├── browser.js │ │ ├── index.html │ │ └── server.js │ ├── 11-websocket/ │ │ ├── article.md │ │ ├── chat.view/ │ │ │ ├── index.html │ │ │ └── server.js │ │ └── demo.view/ │ │ └── server.js │ ├── 12-server-sent-events/ │ │ ├── article.md │ │ └── eventsource.view/ │ │ ├── index.html │ │ └── server.js │ └── index.md ├── 6-data-storage/ │ ├── 01-cookie/ │ │ ├── article.md │ │ └── cookie.js │ ├── 02-localstorage/ │ │ ├── 1-form-autosave/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ └── index.html │ │ │ ├── source.view/ │ │ │ │ └── index.html │ │ │ └── task.md │ │ ├── article.md │ │ └── sessionstorage.view/ │ │ ├── iframe.html │ │ └── index.html │ ├── 03-indexeddb/ │ │ ├── article.md │ │ └── books.view/ │ │ └── index.html │ └── index.md ├── 7-animation/ │ ├── 1-bezier-curve/ │ │ └── article.md │ ├── 2-css-animations/ │ │ ├── 1-animate-logo-css/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ └── index.html │ │ │ ├── source.view/ │ │ │ │ └── index.html │ │ │ └── task.md │ │ ├── 2-animate-logo-bezier-css/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ └── index.html │ │ │ └── task.md │ │ ├── 3-animate-circle/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ └── index.html │ │ │ ├── source.view/ │ │ │ │ └── index.html │ │ │ └── task.md │ │ ├── 4-animate-circle-callback/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ └── index.html │ │ │ └── task.md │ │ ├── article.md │ │ ├── boat.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── digits-negative-delay.view/ │ │ │ ├── index.html │ │ │ ├── script.js │ │ │ └── style.css │ │ ├── digits.view/ │ │ │ ├── index.html │ │ │ ├── script.js │ │ │ └── style.css │ │ ├── step-end.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── step-list.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── step.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── train-linear.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── train-over.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ └── train.view/ │ │ ├── index.html │ │ └── style.css │ ├── 3-js-animation/ │ │ ├── 1-animate-ball/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ ├── index.html │ │ │ │ └── style.css │ │ │ ├── source.view/ │ │ │ │ ├── index.html │ │ │ │ └── style.css │ │ │ └── task.md │ │ ├── 2-animate-ball-hops/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ ├── index.html │ │ │ │ └── style.css │ │ │ └── task.md │ │ ├── article.md │ │ ├── back.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── bounce-easeinout.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── bounce-easeout.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── bounce.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── circ.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── elastic.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── move-raf.view/ │ │ │ └── index.html │ │ ├── move.view/ │ │ │ └── index.html │ │ ├── quad.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── quint.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── text.view/ │ │ │ ├── index.html │ │ │ └── style.css │ │ └── width.view/ │ │ ├── animate.js │ │ └── index.html │ └── index.md ├── 8-web-components/ │ ├── 1-webcomponents-intro/ │ │ ├── .vs/ │ │ │ ├── 1-webcomponents-intro/ │ │ │ │ └── v15/ │ │ │ │ └── .suo │ │ │ └── VSWorkspaceState.json │ │ └── article.md │ ├── 2-custom-elements/ │ │ ├── 1-live-timer/ │ │ │ ├── solution.md │ │ │ ├── solution.view/ │ │ │ │ ├── index.html │ │ │ │ ├── live-timer.js │ │ │ │ └── time-formatted.js │ │ │ ├── source.view/ │ │ │ │ ├── index.html │ │ │ │ ├── live-timer.js │ │ │ │ └── time-formatted.js │ │ │ └── task.md │ │ ├── article.md │ │ └── head.html │ ├── 3-shadow-dom/ │ │ └── article.md │ ├── 4-template-element/ │ │ └── article.md │ ├── 5-slots-composition/ │ │ ├── article.md │ │ └── menu.view/ │ │ └── index.html │ ├── 6-shadow-dom-style/ │ │ └── article.md │ ├── 7-shadow-dom-events/ │ │ └── article.md │ └── index.md ├── 9-regular-expressions/ │ ├── 01-regexp-introduction/ │ │ └── article.md │ ├── 02-regexp-character-classes/ │ │ └── article.md │ ├── 03-regexp-unicode/ │ │ └── article.md │ ├── 04-regexp-anchors/ │ │ ├── 1-start-end/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 05-regexp-multiline-mode/ │ │ └── article.md │ ├── 06-regexp-boundary/ │ │ ├── 1-find-time-hh-mm/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 07-regexp-escaping/ │ │ └── article.md │ ├── 08-regexp-character-sets-and-ranges/ │ │ ├── 1-find-range-1/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 2-find-time-2-formats/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 09-regexp-quantifiers/ │ │ ├── 1-find-text-manydots/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 2-find-html-colors-6hex/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 10-regexp-greedy-and-lazy/ │ │ ├── 1-lazy-greedy/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 3-find-html-comments/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 4-find-html-tags-greedy-lazy/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 11-regexp-groups/ │ │ ├── 01-test-mac/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 02-find-webcolor-3-or-6/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 03-find-decimal-numbers/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 04-parse-expression/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 12-regexp-backreferences/ │ │ └── article.md │ ├── 13-regexp-alternation/ │ │ ├── 01-find-programming-language/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 02-find-matching-bbtags/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 03-match-quoted-string/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 04-match-exact-tag/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 14-regexp-lookahead-lookbehind/ │ │ ├── 1-find-non-negative-integers/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ ├── 2-insert-after-head/ │ │ │ ├── solution.md │ │ │ └── task.md │ │ └── article.md │ ├── 15-regexp-catastrophic-backtracking/ │ │ └── article.md │ ├── 16-regexp-sticky/ │ │ └── article.md │ ├── 17-regexp-methods/ │ │ └── article.md │ └── index.md ├── AUTHORING.md ├── LICENSE.md ├── README.md ├── changes.sketch ├── css.md ├── figures.sketch ├── property-accessors.md ├── script/ │ └── clean-unused-png.php └── todo.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf *.svg binary ================================================ FILE: .gitignore ================================================ *.diff *.err *.orig *.log *.rej *.swo *.swp *.vi *~ *.sass-cache # OS or Editor folders .DS_Store .idea .cache .project .settings .tmproj .nvmrc sftp-config.json Thumbs.db tags /svgs ================================================ FILE: 1-js/01-getting-started/1-intro/article.md ================================================ # Une Introduction à JavaScript Voyons ce qui est spécial à propos de JavaScript, ce qu'il nous permet de faire et avec quelles autres technologies il s'accorde bien. ## Qu'est-ce que JavaScript ? _JavaScript_ a été initialement créé pour "rendre les pages web vivantes". Les programmes dans ce langage sont appelés _scripts_. Ils peuvent être écrits directement dans une page HTML et exécutés automatiquement au chargement des pages. Les scripts sont fournis et exécutés en texte brut. Ils n'ont pas besoin d'une préparation spéciale ou d'une compilation pour fonctionner. De par cet aspect, JavaScript est très différent d'un autre langage appelé [Java](). ```smart header="Pourquoi est-il appelé JavaScript ?" Quand JavaScript a été créé, il portait initialement un autre nom : "LiveScript". Mais à cette époque le langage Java était très populaire, il a donc été décidé que positionner un nouveau langage en tant que "petit frère" de Java pourrait aider. Mais au fur et à mesure de son évolution, JavaScript est devenu un langage totalement indépendant, avec ses propres spécifications appelées [ECMAScript](https://fr.wikipedia.org/wiki/ECMAScript), aujourd'hui il n'a aucun rapport avec Java. ``` Aujourd'hui, JavaScript peut s'exécuter non seulement dans le navigateur, mais également sur un serveur, ou encore sur n'importe quel appareil dans lequel existe un programme appelé [le moteur JavaScript](https://fr.wikipedia.org/wiki/Moteur_JavaScript). Le navigateur a un moteur intégré, parfois il peut être également appelé "la machine virtuelle JavaScript". Différents moteurs ont différents "nom de code", par exemple : - [V8]() -- dans Chrome et Opera. - [SpiderMonkey](https://fr.wikipedia.org/wiki/SpiderMonkey) -- dans Firefox. - … Il existe d'autres noms de code comme "Chakra" pour IE, "JavaScriptCore", "Nitro" et "SquirrelFish" pour Safari etc. Les termes ci-dessus sont bons à retenir, car ils sont utilisés dans les articles destinés aux développeurs sur Internet. Nous les utiliserons aussi. Par exemple, si "une fonctionnalité X est prise en charge par V8", cela fonctionne probablement dans Chrome, Edge et Opera. ```smart header="Comment fonctionnent les moteurs ?" Les moteurs sont compliqués. Mais le fonctionnement de base est facile à comprendre. 1. Le moteur (intégré si c’est un navigateur) lit ("analyse") le script. 2. Ensuite, il convertit ("compile") le script en langage machine. 3. Enfin le code machine s'exécute, très rapidement. Le moteur applique des optimisations à chaque étape du processus. Il surveille même le script compilé en cours d'exécution, analyse les données qui le traversent et applique des optimisations au code machine en fonction de ces informations. ``` ## Que peut faire JavaScript dans le navigateur ? Le JavaScript moderne est un langage de programmation "sûr". Il ne fournit pas d'accès de bas niveau à la mémoire ou au processeur, parce qu'il a été initialement conçu pour les navigateurs qui n'en ont pas besoin. Les fonctionnalités dépendent grandement de l'environnement qui exécute JavaScript. Par exemple, [Node.js](https://fr.wikipedia.org/wiki/Node.js) prend en charge les fonctions qui permettent à JavaScript de lire / écrire des fichiers arbitrairement, d'exécuter des requêtes réseau, etc. JavaScript intégré au navigateur peut faire tout ce qui concerne la manipulation des pages Web, l'interaction avec l'utilisateur et le serveur Web. Par exemple, JavaScript dans le navigateur est capable de : - Ajouter un nouveau code HTML à la page, modifier le contenu existant, modifier les styles. - Réagir aux actions de l'utilisateur, s'exécuter sur des clics de souris, des mouvements de pointeur, des appuis sur des touches. - Envoyer des requêtes sur le réseau à des serveurs distants, télécharger et envoyer des fichiers (technologies [AJAX]() et [COMET]()). - Obtenir et définir des cookies, poser des questions au visiteur, afficher des messages. - Se souvenir des données du côté client ("stockage local"). ## Qu'est-ce que JavaScript ne peut pas faire dans le navigateur ? Les capacités de JavaScript dans le navigateur sont limitées pour la sécurité de l'utilisateur. L'objectif est d'empêcher une page Web malveillante d'accéder à des informations privées ou de nuire aux données de l'utilisateur. Les exemples de telles restrictions sont : - JavaScript sur une page Web ne peut pas lire/écrire des fichiers arbitrairement sur le disque dur, les copier ou exécuter des programmes. Il n'a pas d'accès direct aux fonctions du système d'exploitation. Les navigateurs modernes lui permettent de fonctionner avec des fichiers, mais l'accès est limité et n'est fourni que si l'utilisateur effectue certaines actions, comme «déposer» un fichier dans une fenêtre de navigateur ou le sélectionner via une balise ``. Il existe des moyens d’interagir avec une webcam/microphone et d’autres périphériques, mais ils nécessitent une autorisation explicite de l’utilisateur. Ainsi, une page contenant du JavaScript ne permet pas d'activer une caméra Web, d'observer l'environnement et d'envoyer les informations à la [NSA](https://fr.wikipedia.org/wiki/National_Security_Agency). - Différents onglets / fenêtres ne se connaissent généralement pas. Parfois, ils se croisent, par exemple lorsqu'une fenêtre utilise JavaScript pour ouvrir l'autre. Mais même dans ce cas, le JavaScript d'une page ne peut pas accéder à l'autre si elle provient de sites différents (provenant d'un autre domaine, protocole ou port). C'est ce qu'on appelle la "politique de même origine" (“Same Origin Policy”). Pour contourner cette sécurité, _les deux pages_ doivent se mettre d'accord et contenir un code JavaScript spécial qui gère l'échange de données. Nous verrons cela plus loin dans ce tutoriel. Cette limitation concerne également la sécurité de l'utilisateur. Une page de `http://autresite.com` qu'un utilisateur a ouvert ne doit pas pouvoir accéder à un autre onglet du navigateur avec l'URL `http://gmail.com` et y voler des informations. - JavaScript peut facilement communiquer sur le net avec le serveur d'où provient la page. Mais sa capacité à recevoir des données d'autres sites / domaines est paralysée. Bien que possible, il nécessite un accord explicite (exprimé dans les en-têtes HTTP) du côté distant. Encore une fois, ce sont des limites de sécurité. ![Schéma des limitations du JavaScript dans un navigateur](limitations.svg) De telles limites n'existent pas si JavaScript est utilisé en dehors du navigateur, par exemple sur un serveur. Les navigateurs modernes permettent également l’installation de plug-ins / extensions susceptibles d’obtenir des autorisations étendues. ## Qu'est-ce qui rend JavaScript unique ? Il y a au moins trois bonnes choses à propos de JavaScript : ```compare + Intégration complète avec HTML / CSS. + Les choses simples sont faites simplement. + Pris en charge par tous les principaux navigateurs et activé par défaut. ``` JavaScript est la seule technologie de navigateur qui combine ces trois éléments. C’est ce qui rend JavaScript unique. C’est pourquoi il l’outil le plus répandu pour créer des interfaces de navigateur. Cela dit, JavaScript permet également de créer des serveurs, des applications mobiles, etc. ## Les langages "par dessus" JavaScript La syntaxe de JavaScript ne convient pas aux besoins de tous. Différentes personnes veulent des fonctionnalités différentes. Il faut s’y attendre, parce que besoins sont différents pour tous. Donc, récemment, une pléthore de nouveaux langages sont apparus, qui sont _transpilés_ (convertis) en JavaScript avant leur exécution dans le navigateur. Les outils modernes rendent la [transpilation](https://fr.wiktionary.org/wiki/transpilation) très rapide et transparente, permettant aux développeurs de coder dans un autre langage et de le convertir automatiquement "sous le capot". Les exemples de ce genre de langages : - [CoffeeScript](http://coffeescript.org/) est un "sucre syntaxique" pour JavaScript, il introduit une syntaxe plus courte, permettant d’écrire du code plus précis et plus clair. Habituellement, les développeurs Ruby l'aiment bien. - [TypeScript](http://www.typescriptlang.org/) se concentre sur l'ajout de "typage strict des données" pour simplifier le développement et la prise en charge de systèmes complexes. Il est développé par Microsoft. - [Flow](http://flow.org/) ajoute également la saisie de données, mais de manière différente. Développé par Facebook. - [Dart](https://www.dartlang.org/) est un langage autonome doté de son propre moteur qui s'exécute dans des environnements autres que les navigateurs (comme les applications mobiles), mais peut aussi être transpilé en JavaScript. Développé par Google. - [Brython](https://brython.info/) est un transpiler (ou transpileur en bon français) Python vers JavaScript qui permet d'écrire des applications en Python pur sans JavaScript. - [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) est un langage de programmation moderne, concis et sûr qui peut cibler le navigateur ou Node.js Il en existe évidemment bien plus, cela dit, même si nous utilisons un de ces langages transpilés, nous devrions également connaître le langage JavaScript, pour bien comprendre ce que nous faisons. ## Résumé - JavaScript a été initialement créé en tant que langage de navigateur uniquement, mais il est désormais également utilisé dans de nombreux autres environnements. - En ce moment, JavaScript occupe une position unique en tant que langage de navigateur le plus largement adopté avec une intégration complète avec HTML/CSS. - De nombreux langages sont _transpilés_ en JavaScript et fournissent certaines fonctionnalités. Il est recommandé d'y jeter un coup d'œil, au moins brièvement, après avoir maîtrisé JavaScript. ================================================ FILE: 1-js/01-getting-started/2-manuals-specifications/article.md ================================================ # Manuels et spécifications Ce livre est un *tutoriel*. Il vise à vous aider à apprendre progressivement le langage. Mais une fois que vous maîtriserez les bases, vous aurez besoin d’autres ressources. ## Spécification [The ECMA-262 specification](https://www.ecma-international.org/publications/standards/Ecma-262.htm) contient les informations les plus détaillées et formalisées sur JavaScript. C'est elle qui définit le langage. Une nouvelle version de la spécification est publiée chaque année. La dernière version en cours est disponible à cette adresse : . Pour en savoir plus sur les fonctionnalités à venir, y compris celles qui sont "presque standards" (appelées aussi "stage 3"), vous pouvez consulter les propositions à cette adresse : . De plus, si vous développez pour le navigateur, d'autres spécifications sont couvertes dans la [seconde partie](info:browser-environment) du tutoriel. ## Manuels - **La référence MDN (Mozilla) JavaScript** est le principal manuel avec des exemples et d’autres informations. C’est une excellente source pour obtenir des informations détaillées sur les fonctions linguistiques, les méthodes, etc. On peut la trouver à cette adresse : . Cependant, il est souvent préférable d’utiliser une recherche sur Internet. Utilisez simplement "MDN [terme]" dans la requête, par exemple pour rechercher la fonction `parseInt`. ## Tableaux de compatibilité JavaScript est un langage en développement, de nouvelles fonctionnalités sont ajoutées régulièrement. Pour voir si elles sont supportées dans les moteurs, au sein des navigateurs et autres, voir : - - tables de prise en charge par fonctionnalité, par exemple pour voir quels moteurs supportent les fonctions de cryptographie modernes : . - - un tableau avec les fonctionnalités linguistiques et les moteurs qui les prennent en charge ou non. Toutes ces ressources sont utiles dans le quotidien des développeurs, parce qu'elles contiennent des informations précieuses sur les fonctionnalités du langage, leur support, etc. Veuillez vous en souvenir (ou de cette page) pour les cas où vous avez besoin d'informations détaillées sur une fonctionnalité particulière. ================================================ FILE: 1-js/01-getting-started/3-code-editors/article.md ================================================ # Les éditeurs de code Un éditeur de code est l'endroit où les programmeurs passent la plus grande partie de leur temps. Il existe deux archétypes: IDE et les éditeurs légers. Beaucoup de personnes se sentent à l'aise pour choisir un outil de chaque type. ## IDE Le terme [IDE](https://fr.wikipedia.org/wiki/Environnement_de_développement) (Integrated Development Environment) signifie un éditeur puissant avec de nombreuses fonctionnalités qui fonctionne généralement sur un "projet entier". Comme son nom l’indique, ce n’est pas seulement un éditeur, mais un environnement de développement complet. Un IDE charge le projet (peut contenir de nombreux fichiers), permet la navigation entre les fichiers, fournit une auto-complétion basée sur l'ensemble du projet (pas seulement le fichier ouvert), s'intègre à un système de gestion de version (comme [git](https://git-scm.com/)), un environnement de test et d’autres éléments au niveau du projet. Si vous n'avez pas encore pensé à sélectionner un IDE, examinez les variantes suivantes : - [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). - [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, payant). Pour Windows, il existe également "Visual Studio", à ne pas confondre avec "Visual Studio Code ". "Visual Studio" est un IDE payant et puissant, disponible sur Windows et Mac, bien adapté à la plate-forme .NET. C’est aussi bon en JavaScript. Il y a aussi une version gratuite [Visual Studio Community](https://www.visualstudio.com/vs/community/). La plupart des IDE sont payants, mais ont une période d'essai. Leur coût est généralement négligeable par rapport au salaire d’un développeur qualifié, alors choisissez le meilleur pour vous. ## Les éditeurs légers "Les éditeurs légers" ne sont pas aussi puissants que les IDE, mais ils sont rapides, élégants et simples. Ils sont principalement utilisés pour ouvrir et éditer instantanément un fichier. La principale différence entre un "éditeur léger" et un "IDE" réside dans le fait qu’un environnement de développement intégré fonctionne au niveau du projet. Il charge donc beaucoup plus de données au démarrage, analyse la structure du projet si nécessaire, etc. Un éditeur léger est beaucoup plus rapide si nous avons besoin d'un seul fichier. En pratique, les éditeurs légers peuvent avoir beaucoup de plug-ins, y compris des analyseurs de syntaxe au niveau des répertoires et des autocompléteurs. Il n’y a donc pas de frontière stricte entre un éditeur léger et un IDE. Il existe de nombreuses options, par exemple : - [Sublime Text](http://www.sublimetext.com) (multiplateforme, payant). - [Notepad++](https://notepad-plus-plus.org/) (Windows, gratuit). - [Vim](https://www.vim.org/) et [Emacs](https://www.gnu.org/software/emacs/) sont également cool, si vous savez comment les utiliser. ## Ne discutons pas Les éditeurs des listes ci-dessus sont ceux que moi-même ou mes amis, que je considère comme de bons développeurs, utilisent depuis longtemps et en sont satisfaits. Il y a d'autres grands éditeurs dans notre vaste monde. Veuillez choisir celui que vous aimez le plus. Le choix d'un éditeur, comme tout autre outil, est individuel et dépend de vos projets, de vos habitudes, de vos préférences personnelles. L'avis personnel de l'auteur : - J'utilise [Visual Studio Code](https://code.visualstudio.com/) si je développe principalement du frontend. - Sinon, s'il s'agit principalement d'un autre langage/plate-forme et partiellement d'un frontend, envisagez d'autres éditeurs, tels que XCode (Mac), Visual Studio (Windows) ou la famille Jetbrains (Webstorm, PHPStorm, RubyMine, etc., selon le langage). ================================================ FILE: 1-js/01-getting-started/4-devtools/article.md ================================================ # La console de développement Le code est sujet aux erreurs. Vous êtes susceptible de faire des erreurs … Oh, de quoi je parle ? Vous allez absolument faire des erreurs, du moins si vous êtes un humain, pas un [robot](https://fr.wikipedia.org/wiki/Bender_Tordeur_Rodr%C3%ADguez). Mais dans le navigateur, un utilisateur ne voit pas les erreurs par défaut. Ainsi, si quelque chose ne va pas dans le script, nous ne verrons pas ce qui ne va pas et nous ne pourrons pas le réparer. Pour voir les erreurs et obtenir beaucoup d’informations utiles sur les scripts, les navigateurs intègrent des "outils de développement". Le plus souvent, les développeurs se tournent vers Chrome ou Firefox pour le développement, car ces navigateurs disposent des meilleurs outils de développement. D'autres navigateurs fournissent également des outils de développement, parfois dotés de fonctions spéciales, mais jouent généralement le rôle de "rattrapage" pour Chrome ou Firefox. Donc, la plupart des gens ont un navigateur "favori" et passent à d'autres si un problème est spécifique au navigateur. Les outils de développement sont très puissants ; ils possèdent de nombreuses fonctionnalités. Pour commencer, nous allons apprendre à les ouvrir, à examiner les erreurs et à exécuter des commandes JavaScript. ## Google Chrome Ouvrez la page [bug.html](bug.html). Il y a une erreur dans le code JavaScript. Elle est invisible à un visiteur habituel, alors ouvrons les outils de développement pour la voir. Appuyez sur `key:F12` ou, si vous êtes sur Mac, utilisez la combinaison `key:Cmd+Opt+J`. Les outils de développement s'ouvriront sous l'onglet Console par défaut. Cela ressemble un peu à ceci : ![chrome](chrome.png) L'aspect exact des outils de développement dépend de votre version de Chrome. Cela change de temps en temps, mais devrait être similaire. - Ici, nous pouvons voir le message d'erreur de couleur rouge. Dans ce cas, le script contient une commande "lalala" inconnue. - Sur la droite, il y a un lien cliquable vers le code source bug.html:12 avec le numéro de ligne où l'erreur s'est produite. Sous le message d'erreur, il y a un symbole bleu `>`. Il marque une "ligne de commande" où l'on peut taper des commandes JavaScript et appuyer sur `key:Enter` pour les exécuter. Nous pouvons maintenant voir les erreurs et c’est suffisant pour le début. Nous reviendrons plus tard sur les outils de développement et approfondirons le débogage plus loin dans le chapitre . ```smart header="Multi-line input" Habituellement, quand on met une ligne de code dans la console, puis qu'on appuie sur `key:Enter`, elle s'exécute. Pour insérer plusieurs lignes, appuyez sur `key:Shift+Enter`. De cette façon, on peut saisir de longs fragments de code JavaScript. ``` ## Firefox, Edge et autres La plupart des autres navigateurs utilisent `key:F12` pour ouvrir les outils de développement. Leur apparence est assez similaire. Une fois que vous savez comment utiliser l'un d'entre eux (vous pouvez commencer avec Chrome), vous pouvez facilement passer à un autre. ## Safari Safari (navigateur Mac, non pris en charge par Windows / Linux) est un peu spécial ici. Nous devons d'abord activer le menu "Développement". Ouvrez les préférences et accédez au volet "Avancé". Il y a une case à cocher en bas : ![safari](safari.png) Maintenant `key:Cmd+Opt+C` peut activer la console. Notez également que le nouvel élément de menu supérieur nommé "Développement" est apparu. Il a beaucoup de commandes et d'options. ## Résumé - Les outils de développement nous permettent de voir les erreurs, d'exécuter des commandes, d'examiner des variables et bien plus encore. - Ils peuvent être ouverts avec `key:F12` pour la plupart des navigateurs sous Windows. Chrome pour Mac nécessite la combinaison `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (doit être activé avant). Nous avons maintenant notre environnement prêt. Dans la section suivante, nous passerons à JavaScript. ================================================ FILE: 1-js/01-getting-started/4-devtools/bug.html ================================================ There is an error in the script on this page. ================================================ FILE: 1-js/01-getting-started/index.md ================================================ # Une introduction A propos du langage JavaScript et de l'environnement pour le développeur. ================================================ FILE: 1-js/02-first-steps/01-hello-world/1-hello-alert/index.html ================================================ ================================================ FILE: 1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md ================================================ [html src="index.html"] ================================================ FILE: 1-js/02-first-steps/01-hello-world/1-hello-alert/solution.view/index.html ================================================ ================================================ FILE: 1-js/02-first-steps/01-hello-world/1-hello-alert/task.md ================================================ importance: 5 --- # Afficher une alerte Créez une page qui affiche un message "Je suis JavaScript!". Faites-le dans un bac à sable ou sur votre disque dur, peu importe, assurez-vous simplement que cela fonctionne. [demo src="solution"] ================================================ FILE: 1-js/02-first-steps/01-hello-world/2-hello-alert-ext/alert.js ================================================ alert("I'm JavaScript!"); ================================================ FILE: 1-js/02-first-steps/01-hello-world/2-hello-alert-ext/index.html ================================================ ================================================ FILE: 1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md ================================================ The HTML code: [html src="index.html"] pour le fichier `alert.js` dans le même dossier : [js src="alert.js"] ================================================ FILE: 1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md ================================================ importance: 5 --- # Afficher une alerte avec un script externe Prendre la solution de l'exercice précédent . Modifiez-le en extrayant le contenu du script dans un fichier externe `alert.js`, résidant dans le même dossier. Ouvrez la page, assurez-vous que l'alerte fonctionne. ================================================ FILE: 1-js/02-first-steps/01-hello-world/article.md ================================================ # Hello, world! Cette partie du tutoriel est à propos du coeur de JavaScript, le langage lui même. Mais nous avons besoin d'un environnement de travail pour exécuter nos scripts et, étant donné que ce guide est en ligne, le navigateur est un bon choix. Nous allons nous efforcer d'utiliser les commandes spécifiques au navigateur (comme `alert`) au minimum afin de ne pas y consacrer du temps si vous prévoyez de vous concentrer sur un autre environnement tel que Node.JS. Par ailleurs, les détails du navigateur sont expliqués dans [la partie suivante](/ui) du didacticiel. Alors, voyons d'abord comment intégrer un script à une page Web. Pour les environnements côté serveur, vous pouvez simplement l'exécuter avec une commande comme `"node mon.js"` pour Node.JS. ## La balise "script" Les programmes JavaScript peuvent être insérés dans n'importe quelle partie d'un document HTML à l'aide de la balise ` */!*

...After the script.

``` ```online Vous pouvez exécuter l'exemple en cliquant sur le bouton "Play" dans son coin supérieur droit. ``` La balise ` ``` Cette astuce n’est pas utilisée dans le JavaScript moderne. Ces commentaires ont été utilisés pour masquer le code JavaScript des anciens navigateurs qui ne savaient pas comment traiter une balise ` ``` Ici, `/chemin/vers/script.js` est un chemin absolu du script depuis la racine du site. On peut également fournir un chemin relatif à partir de la page en cours. Par exemple `src="script.js"` signifierait un fichier `"script.js"` dans le dossier courant. Nous pouvons également donner une URL complète, par exemple : ```html ``` Pour joindre plusieurs scripts, utilisez plusieurs tags : ```html … ``` ```smart En règle générale, seuls les scripts les plus simples sont mis en HTML. Les plus complexes résident dans des fichiers séparés. L’avantage d’un fichier séparé est que le navigateur le télécharge puis le stocke dans son [cache](https://fr.wikipedia.org/wiki/Cache_web). Après cela, les autres pages qui veulent le même script le récupéreront à partir du cache au lieu de le télécharger à nouveau. Le fichier n'est donc téléchargé qu'une seule fois. Cela économise du trafic et rend les pages plus rapides. ``` ````warn header="Si `src` est défini, le contenu de la balise script est ignoré." Une seule balise ` ``` Nous devons choisir : soit un script ` ``` ```` ## Résumé - Nous pouvons utiliser une balise ` ``` ================================================ FILE: 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md ================================================ importance: 4 --- # Une simple page Créez une page Web qui demande un nom et l'affiche [demo] ================================================ FILE: 1-js/02-first-steps/06-alert-prompt-confirm/article.md ================================================ # Interaction: alert, prompt, confirm Comme nous allons utiliser le navigateur comme environnement de démonstration, voyons quelques fonctions pour interagir avec l'utilisateur : `alert`, `prompt` et `confirm`. ## alert Celui-ci, nous l'avons déjà vu. Il affiche un message et attend que l'utilisateur appuie sur "OK". Par exemple : ```js run alert("Hello"); ``` La mini-fenêtre avec le message s'appelle une *fenêtre modale*. Le mot "modal" signifie que le visiteur ne peut pas interagir avec le reste de la page, appuyer sur d'autres boutons, etc., tant qu'il n'a pas traité la fenêtre. Dans ce cas -- jusqu'à ce qu'ils appuient sur "OK". ## prompt La fonction `prompt` accepte deux arguments : ```js no-beautify result = prompt(title, [default]); ``` Elle affiche une fenêtre modale avec un message texte, un champ de saisie pour le visiteur et les boutons OK/Annuler. `title` : Le texte à afficher au visiteur. `default` : Un deuxième paramètre facultatif, la valeur initiale du champ d'entrée. ```smart header="Les crochets dans la syntaxe `[...]`" Les crochets autour de `default` dans la syntaxe ci-dessus indiquent que le paramètre est facultatif, non requis. ``` Le visiteur peut taper quelque chose dans le champ de saisie d'invite et appuyer sur OK. Ensuite, nous obtenons ce texte dans le `result`. Ou ils peuvent annuler l'entrée en appuyant sur Annuler ou en appuyant sur la touche `key:Esc`, puis nous obtenons `null` comme `result`. L'appel à `prompt` renvoie le texte du champ de saisie ou `null` si l'entrée a été annulée. Par exemple : ```js run let age = prompt('How old are you?', 100); alert(`You are ${age} years old!`); // You are 100 years old! ``` ````warn header="Dans IE : fournissez toujours un `default`" Le second paramètre est facultatif, mais si nous ne le fournissons pas, Internet Explorer insérera le texte `"undefined"` dans l'invite. Exécutez ce code dans Internet Explorer pour voir : ```js run let test = prompt("Test"); ``` Donc, pour que les invites semblent bonnes dans IE, nous vous recommandons de toujours fournir le deuxième argument : ```js run let test = prompt("Test", ''); // <-- pour IE ``` ```` ## confirm La syntaxe : ```js result = confirm(question); ``` La fonction `confirm` affiche une fenêtre modale avec une `question` et deux boutons : OK et Annuler. Le résultat est `true` si vous appuyez sur OK et `false` dans le cas contraire. Par exemple : ```js run let isBoss = confirm("Are you the boss?"); alert( isBoss ); // true si OK est pressé ``` ## Résumé Nous avons couvert 3 fonctions spécifiques au navigateur pour interagir avec les visiteurs : `alert` : affiche un message. `prompt` : affiche un message demandant à l'utilisateur de saisir du texte. Il renvoie le texte ou `null`, si vous cliquez sur le bouton Annuler ou sur `key:Esc`. `confirm` : affiche un message et attend que l'utilisateur appuie sur "OK" ou "Annuler". Il retourne `true` pour OK et `false` pour Annuler/`key:Esc`. Toutes ces méthodes sont modales: elles suspendent l'exécution du script et ne permettent pas au visiteur d'interagir avec le reste de la page tant que la fenêtre n'a pas été fermée. Il existe deux limitations partagées par toutes les méthodes ci-dessus : 1. L'emplacement exact de la fenêtre modale est déterminé par le navigateur. Habituellement, c'est au centre. 2. L'aspect exact de la fenêtre dépend également du navigateur. Nous ne pouvons pas le modifier. C'est le prix de la simplicité. Il existe d'autres moyens d'afficher des fenêtres plus jolies et une interaction plus riche avec le visiteur, mais si l'aspect graphique n'ont pas beaucoup d'importance, ces méthodes fonctionnent très bien. ================================================ FILE: 1-js/02-first-steps/07-type-conversions/article.md ================================================ # Les conversions de types La plupart du temps, les opérateurs et les fonctions convertissent automatiquement les valeurs qui leur sont attribuées dans le bon type. Par exemple, `alert` convertit automatiquement toute valeur en chaîne de caractères pour l'afficher. Les opérations mathématiques convertissent les valeurs en nombres. Il y a aussi des cas où nous devons convertir explicitement une valeur pour corriger les choses. ```smart header="On ne parle pas encore des objets" Dans ce chapitre, nous ne couvrons pas encore les objets. Ici, nous étudions d'abord les primitives. Plus tard, après avoir appris les objets, nous verrons comment la conversion d’objets fonctionne dans le chapitre . ``` ## String Conversion La conversion `String` se produit lorsque nous avons besoin de la forme chaîne de caractères d'une valeur. Par exemple, `alert(value)` le fait pour afficher la valeur. Nous pouvons également utiliser un appel de fonction `String(value)` pour ça : ```js run let value = true; alert(typeof value); // boolean *!* value = String(value); // maintenant la valeur est une chaîne de caractères "true" alert(typeof value); // string */!* ``` La conversion `String` est assez évidente. Un `false` devient `"false"`, `null` devient `"null"` etc. ## Numeric Conversion La conversion numérique dans les fonctions et expressions mathématiques s'effectue automatiquement. Par exemple, lorsque la division `/` est appliqué à des non-numéros : ```js run alert( "6" / "2" ); // 3, les chaînes de caractères sont converties en nombres ``` Nous pouvons utiliser une fonction `Number(value)` pour convertir explicitement une valeur : ```js run let str = "123"; alert(typeof str); // string let num = Number(str); // devient un nombre 123 alert(typeof num); // nombre ``` Une conversion explicite est généralement requise lorsque nous lisons une valeur à partir d'une source basée sur des chaînes de caractères, par exemple un champ texte, mais qu'un nombre doit être entré. Si la chaîne de caractères n'est pas un nombre valide, le résultat de cette conversion est `NaN`, par exemple : ```js run let age = Number("une chaîne de caractères arbitraire au lieu d'un nombre"); alert(age); // NaN, la conversion a échoué ``` Règles de conversion numériques : | Valeur | Devient ... | |--------------------------------------|-------------| | `undefined` | `NaN` | | `null` | `0` | | true et false | `1` et `0` | | `string` | Les espaces blancs du début et de la fin sont supprimés. Ensuite, si la chaîne restante est vide, le résultat est `0`. Sinon, le nombre est «lu» dans la chaîne. Une erreur donne `NaN`. Exemples: ```js run alert( Number(" 123 ") ); // 123 alert( Number("123z") ); // NaN (erreur de lecture d'un nombre à "z") alert( Number(true) ); // 1 alert( Number(false) ); // 0 ``` Veuillez noter que `null` et `undefined` se comportent différemment ici : `null` devient un zéro, alors qu'`undefined` devient `NaN`. La plupart des opérateurs mathématiques effectuent également une telle conversion, nous le verrons dans le chapitre suivant. ## Boolean Conversion La conversion booléenne est la plus simple. Cela se produit dans les opérations logiques (plus tard, nous nous intéresserons aux tests de condition et à d’autres types de tests), mais nous pouvons également l’effectuer manuellement avec l’appel de `Boolean(value)`. La règle de conversion : - Les valeurs qui sont intuitivement "vides", comme `0`, une chaîne de caractères vide, `null`, `undefined` `NaN` deviennent `false`. - Les autres valeurs deviennent `true`. Par exemple : ```js run alert( Boolean(1) ); // true alert( Boolean(0) ); // false alert( Boolean("hello") ); // true alert( Boolean("") ); // false ``` ````warn header="Veuillez noter que la chaîne de caractères avec un zero `\"0\"` est `true`" Certains langages (à savoir PHP) traitent `"0"` comme faux. Mais en JavaScript, une chaîne non vide est toujours `true`. ```js run alert( Boolean("0") ); // true alert( Boolean(" ") ); // espaces, également vrai (toute chaîne de caractères non vide est vraie) ``` ```` ## Résumé Les trois conversions de types les plus utilisées sont : to string, to number et to boolean. **`La conversion en String`** -- Se produit lorsque nous sortons quelque chose, peut être effectué avec `String(value)`. La conversion en chaîne de caractères est généralement évidente pour les valeurs primitives. **`La conversion en Number`** -- Se produit dans les opérations mathématiques, peut être effectué avec `Number(value)`. La conversion vers `number` suit les règles suivantes : | Valeur | Devient ... | |-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| | `undefined` | `NaN` | | `null` | `0` | | true / false | `1 / 0` | | `string` | La chaîne de caractères est lue "tel quel", les espaces des deux côtés sont ignorés. Une chaîne vide devient `0`. Une erreur donne `NaN`. | **`La conversion en Boolean`** -- Se produit dans des opérations logiques, ou peut être effectué avec `Boolean(value)`. La conversion vers `boolean` suit les règles suivantes : | Valeur | Devient ... | |---------------------------------------|-------------| | `0`, `null`, `undefined`, `NaN`, `""` | `false` | | tout autre valeur | `true` | La plupart de ces règles sont faciles à comprendre et à mémoriser. Les exceptions notables où les gens font généralement des erreurs sont : - `undefined` est `NaN` en tant que number, non `0`. - `"0"` et les espaces dans les chaines de caractères comme `" "` sont "true" en booléen. Les objets ne sont pas couverts ici, nous y reviendrons plus tard dans le chapitre qui est consacré exclusivement aux objets, après avoir appris plus de choses basiques sur JavaScript. ================================================ FILE: 1-js/02-first-steps/08-operators/1-increment-order/solution.md ================================================ La réponse est : - `a = 2` - `b = 2` - `c = 2` - `d = 1` ```js run no-beautify let a = 1, b = 1; alert( ++a ); // 2, la forme préfixe renvoie la nouvelle valeur alert( b++ ); // 1, la forme postfixe renvoie l'ancienne valeur alert( a ); // 2, incrémenté une fois alert( b ); // 2, incrémenté une fois ``` ================================================ FILE: 1-js/02-first-steps/08-operators/1-increment-order/task.md ================================================ importance: 5 --- # Les formes postfixes et préfixes Quelles sont les valeurs finales de toutes les variables `a`, `b`, `c` et `d` après le code ci-dessous ? ```js let a = 1, b = 1; let c = ++a; // ? let d = b++; // ? ``` ================================================ FILE: 1-js/02-first-steps/08-operators/2-assignment-result/solution.md ================================================ La réponse est : - `a = 4` (multiplié par 2) - `x = 5` (calculé comme 1 + 4) ================================================ FILE: 1-js/02-first-steps/08-operators/2-assignment-result/task.md ================================================ importance: 3 --- # Résultat d'affectation Quelles sont les valeurs de `a` et `x` après le code ci-dessous ? ```js let a = 2; let x = 1 + (a *= 2); ``` ================================================ FILE: 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md ================================================ ```js no-beautify "" + 1 + 0 = "10" // (1) "" - 1 + 0 = -1 // (2) true + false = 1 6 / "3" = 2 "2" * "3" = 6 4 + 5 + "px" = "9px" "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN " -9 " + 5 = " -9 5" // (3) " -9 " - 5 = -14 // (4) null + 1 = 1 // (5) undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` 1. L'addition avec une chaîne de caractères `"" + 1` converti `1` vers une chaîne de caractères : `"" + 1 = "1"`, ensuite nous avons `"1" + 0`, la même règle est appliquée. 2. La soustraction `-` (comme la plupart des opérations mathématiques) ne fonctionne qu'avec des nombres, il convertit une chaîne de caractères vide `""` vers `0`. 3. L'addition avec un string ajoute le number `5` au string. 4. La soustraction est toujours convertie en nombres, donc elle fait de `" -9 "` un number `-9` (en ignorant les espaces qui l'entourent). 5. `null` devient `0` après la conversion numérique. 6. `undefined` devient `NaN` après la conversion numérique. 7. Les caractères d'espacement sont coupés au début et à la fin de la chaîne de caractères lorsque celle-ci est convertie en nombre. Ici toute la chaîne se compose d'espaces, tels que `\t`, `\n` et d'un espace "normal" entre eux. Ainsi, de manière similaire à une chaîne de caractères vide, elle devient `0`. ================================================ FILE: 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md ================================================ importance: 5 --- # Les conversions de type Quels sont les résultats de ces expressions ? ```js no-beautify "" + 1 + 0 "" - 1 + 0 true + false 6 / "3" "2" * "3" 4 + 5 + "px" "$" + 4 + 5 "4" - 2 "4px" - 2 " -9 " + 5 " -9 " - 5 null + 1 undefined + 1 " \t \n" - 2 ``` Réfléchissez bien, notez et comparez avec la réponse. ================================================ FILE: 1-js/02-first-steps/08-operators/4-fix-prompt/solution.md ================================================ La raison en est que le prompt renvoie l'entrée utilisateur sous forme de chaîne de caractères. Les variables ont donc respectivement les valeurs `"1"` et `"2"`. ```js run let a = "1"; // prompt("First number?", 1); let b = "2"; // prompt("Second number?", 2); alert(a + b); // 12 ``` Ce que nous devons faire est de convertir les chaînes de caractères en nombres avant `+`. Par exemple, en utilisant `Number()` ou en les préfixant avec `+`. Par exemple, juste avant `prompt` : ```js run let a = +prompt("First number?", 1); let b = +prompt("Second number?", 2); alert(a + b); // 3 ``` Ou dans l'`alert`: ```js run let a = prompt("First number?", 1); let b = prompt("Second number?", 2); alert(+a + +b); // 3 ``` Nous utilisons à la fois unaire et binaire `+` dans le dernier code. Ça a l'air drôle, non ? ================================================ FILE: 1-js/02-first-steps/08-operators/4-fix-prompt/task.md ================================================ importance: 5 --- # Corrigez l'addition Voici un code qui demande à l'utilisateur deux nombres et affiche leur somme. Cela ne fonctionne pas correctement. La sortie dans l'exemple ci-dessous est `12` (pour les valeurs d'invite par défaut). Pourquoi ? Réparez-le. Le résultat doit être `3`. ```js run let a = prompt("First number?", 1); let b = prompt("Second number?", 2); alert(a + b); // 12 ``` ================================================ FILE: 1-js/02-first-steps/08-operators/article.md ================================================ # Opérateurs de base, mathématiques De nombreux opérateurs nous sont connus de l'école. Ce sont les additions `+`, multiplications `*`, soustractions `-` et ainsi de suite. Dans ce chapitre, nous nous concentrons sur les aspects qui ne sont pas couverts par l'arithmétique scolaire. ## Termes: "unaire", "binaire", "opérande" Avant de continuer, saisissons la terminologie commune. - Un opérande est ce à quoi les opérateurs sont appliqués. Par exemple, dans la multiplication `5 * 2`, il y a deux opérandes : l'opérande gauche est `5` et l'opérande droit est `2`. Parfois, les gens disent "arguments" au lieu de "opérandes". - Un opérateur est unaire s'il a un seul opérande. Par exemple, la négation unaire `-` inverse le signe du nombre : ```js run let x = 1; *!* x = -x; */!* alert( x ); // -1, le moins unaire a été appliqué ``` - Un opérateur est *binaire* s'il a deux opérandes. La même négation existe également dans la forme binaire : ```js run no-beautify let x = 1, y = 3; alert( y - x ); // 2, le moins binaire soustrait des valeurs ``` D'un point de vue formel, dans les exemples ci-dessus, nous avons deux opérateurs différents qui partagent le même symbole : l'opérateur de négation, un opérateur unaire qui inverse le signe, et l'opérateur de soustraction, un opérateur binaire qui soustrait un nombre d'un autre. ## Opérations mathématiques Les opérations mathématiques suivantes sont supportées : - Addition `+`, - Soustraction `-`, - Multiplication `*`, - Division `/`, - Reste `%`, - Exponentiation `**`. Les quatre premières sont assez simples, tandis que `%` et `**` nécessitent quelques explications. ### Reste % (Modulo) L'opérateur reste `%`, malgré son apparence, n'est pas lié aux pourcentages. Le résultat de `a % b` est le [reste](https://fr.wikipedia.org/wiki/Reste) de la division entière de `a` par `b`. Par exemple : ```js run alert( 5 % 2 ); // 1, le reste de 5 divisé par 2 alert( 8 % 3 ); // 2, le reste de 8 divisé par 3 alert( 8 % 4 ); // 0, le reste de 8 divisé par 4 ``` ### Exponentiation ** L'opérateur d'exponentiation `a ** b` multiplie `a` par lui-même `b` fois. En mathématiques à l'école, nous écrivons cela ab. Par exemple : ```js run alert( 2 ** 2 ); // 2² = 4 alert( 2 ** 3 ); // 2³ = 8 alert( 2 ** 4 ); // 2⁴ = 16 ``` Tout comme en mathématiques, l'opérateur d'exponentiation est également défini pour les nombres non entiers. Par exemple, une racine carrée est une exponentiation de `½` : ```js run alert( 4 ** (1/2) ); // 2 (la puissance de 1/2 équivaut à une racine carrée) alert( 8 ** (1/3) ); // 2 (la puissance de 1/3 équivaut à une racine cubique) ``` ## Concaténation de chaînes de caractères, binaire `+` Découvrons les fonctionnalités des opérateurs JavaScript qui vont au-delà de l'arithmétique scolaire. Habituellement, l'opérateur `+` additionne des chiffres. Mais si le binaire `+` est appliqué aux chaînes de caractères, il les fusionne (concatène) : ```js let s = "my" + "string"; alert(s); // mystring ``` Notez que si l'un des opérandes est une chaîne de caractères, l'autre est automatiquement converti en chaîne de caractères. Par exemple : ```js run alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` Peu importe que le premier opérande soit une chaîne de caractères ou le second. La règle est simple : si l'un des opérandes est une chaîne de caractères, convertissez l'autre également en une chaîne de caractères. Cependant, notez que les opérations se déroulent de gauche à droite. S'il y a deux nombres suivis d'une chaîne, les nombres seront ajoutés avant d'être convertis en chaîne : ```js run alert(2 + 2 + '1' ); // "41" et non "221" ``` Ici, les opérateurs travaillent les uns après les autres. Le premier `+` additionne deux nombres, donc il renvoie `4`, puis le `+` suivant ajoute la chaîne de caractères `1`, donc c'est comme `4 + '1' = 41`. ```js run alert('1' + 2 + 2); // "122" and not "14" ``` Ici, le premier opérande est une chaîne de caractères, le compilateur traite également les deux autres opérandes comme des chaînes de caractères. Le `2` est concaténé à `'1'`, donc c'est comme `'1'+ 2 = "12"` et `"12" + 2 = "122"`. Le binaire `+` est le seul opérateur qui prend en charge les chaînes de caractères de cette manière. D'autres opérateurs arithmétiques ne fonctionnent qu'avec des nombres et convertissent toujours leurs opérandes en nombres. Voici l'exemple pour la soustraction et la division : ```js run alert( 6 - '2' ); // 4, convertit '2' en nombre alert( '6' / '2' ); // 3, convertit les deux opérandes en nombres ``` ## Conversion numérique, unaire + Le plus `+` existe sous deux formes. La forme binaire que nous avons utilisée ci-dessus et la forme unaire. L’unaire plus ou, en d’autres termes, l’opérateur plus `+` appliqué à une seule valeur, ne fait rien avec les nombres, mais si l’opérande n’est pas un nombre, alors il est converti en nombre. Par exemple : ```js run // Aucun effet sur les nombres let x = 1; alert( +x ); // 1 let y = -2; alert( +y ); // -2 *!* // Convertit les non-nombres alert( +true ); // 1 alert( +"" ); // 0 */!* ``` En fait, il fait la même chose que `Number(...)`, mais il est plus court. La nécessité de convertir des chaînes de caractères en nombres est très fréquente. Par exemple, si nous obtenons des valeurs à partir de champs de formulaire HTML, il s’agit généralement de chaînes de caractères. Et si on veut les additionner ? Le binaire plus les ajouterait comme des chaînes de caractères : ```js run let apples = "2"; let oranges = "3"; alert( apples + oranges ); // "23", le binaire plus concatène les chaînes de caractères ``` Si nous voulons les traiter comme des nombres, nous devons d'abord les convertir et ensuite seulement nous pouvons les additionner : ```js run let apples = "2"; let oranges = "3"; *!* // les deux valeurs converties en nombres avant le binaire plus alert( +apples + +oranges ); // 5 */!* // c'est équivalent à cette variante plus longue // alert( Number(apples) + Number(oranges) ); // 5 ``` Du point de vue du mathématicien, l’abondance des `+` peut sembler étrange. Mais du point de vue du programmeur, il n’y a rien de spécial : les plus unaires sont appliqués en premier, ils convertissent les chaînes de caractères en nombres, puis le plus binaire les additionne. Pourquoi les plus unaires sont-ils appliqués aux valeurs avant les binaires ? Comme nous allons le voir, c’est à cause de *leur précédence supérieure*. ## Précédence des opérateurs Si une expression à plusieurs opérateurs, l’ordre d’exécution est défini par leur *priorité* ou, en d’autres termes, il existe un ordre de priorité implicite entre les opérateurs. De l'école, nous savons tous que la multiplication dans l'expression `1 + 2 * 2` devrait être calculée avant l'addition. C’est exactement cela la précédence. La multiplication est dite avoir une *précédence supérieure* à l'addition. Les parenthèses outrepassent toute priorité, donc si nous ne sommes pas satisfaits de l'ordre par défaut, nous pouvons les utiliser, comme : `(1 + 2) * 2`. Il y a beaucoup d'opérateurs en JavaScript. Chaque opérateur a un numéro correspondant à sa priorité de précédence. Celui qui est plus haut sur le tableau s'exécute en premier. Si la priorité est la même, l'ordre d'exécution est de gauche à droite. Un extrait du [tableau de précédence](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (vous n'avez pas besoin de vous en souvenir, mais notez que les opérateurs unaires ont une priorité plus élevée que les binaires correspondants) : | Précédence | Nom | Symbole | |------------|-----------------|---------| | ... | ... | ... | | 14 | plus unaire | `+` | | 14 | négation unaire | `-` | | 13 | exponentiation | `**` | | 12 | multiplication | `*` | | 12 | division | `/` | | 11 | addition | `+` | | 11 | soustraction | `-` | | ... | ... | ... | | 2 | affectation | `=` | | ... | ... | ... | Comme on peut le voir, le "plus unaire" a une priorité de `14`, ce qui est supérieur à `11` pour "l'addition" (plus binaire). C’est pourquoi, dans l’expression `"+apples + +oranges"`, les plus unaires fonctionnent avant l’addition. ## Affectation Notons qu’une affectation `=` est aussi un opérateur. Il est répertorié dans le tableau des précédences avec la très faible priorité de `2`. C’est pourquoi lorsque nous assignons une variable, comme `x = 2 * 2 + 1`, les calculs sont effectués en premier, puis le `=` est évalué, stockant le résultat dans `x`. ```js let x = 2 * 2 + 1; alert( x ); // 5 ``` ### Assignment = retourne une valeur Le fait que `=` soit un opérateur, pas une construction de langage "magique" a une implication intéressante. Tous les opérateurs en JavaScript renvoient une valeur. C'est évident pour `+` et `-`, mais aussi vrai pour `=`. L'appel `x = valeur` écrit la `valeur` dans `x` *puis la renvoie*. Voici un exemple qui utilise une affectation dans le cadre d'une expression plus complexe : ```js run let a = 1; let b = 2; *!* let c = 3 - (a = b + 1); */!* alert( a ); // 3 alert( c ); // 0 ``` Dans l'exemple ci-dessus, le résultat de l'expression`(a = b + 1)` est la valeur qui a été affectée à `a` (c'est-à-dire `3`). Il est ensuite utilisé pour d'autres évaluations. Drôle de code, n'est-ce pas? Nous devons comprendre comment cela fonctionne, car parfois nous le voyons dans les bibliothèques JavaScript. Cependant, n'écrivez pas le code comme ça. De telles astuces ne rendent certainement pas le code plus clair ou lisible. ### Affectations chaînées Une autre caractéristique intéressante est la possibilité de chaîner des affectations : ```js run let a, b, c; *!* a = b = c = 2 + 2; */!* alert( a ); // 4 alert( b ); // 4 alert( c ); // 4 ``` Les affectations en chaîne sont évaluées de droite à gauche. D'abord, l'expression la plus à droite `2 + 2` est évaluée puis assignée aux variables de gauche : `c`, `b` et `a`. À la fin, toutes les variables partagent une seule valeur. Encore une fois, pour des raisons de lisibilité, il est préférable de diviser ce code en quelques lignes : ```js c = 2 + 2; b = c; a = c; ``` C'est plus facile à lire, en particulier lors de la numérisation rapide du code. ## Modification sur place Nous avons souvent besoin d'appliquer un opérateur à une variable et d'y stocker le nouveau résultat. Par exemple : ```js let n = 2; n = n + 5; n = n * 2; ``` Cette notation peut être raccourcie en utilisant les opérateurs `+=` et `*=` : ```js run let n = 2; n += 5; // maintenant n = 7 (identique à n = n + 5) n *= 2; // maintenant n = 14 (identique à n = n * 2) alert( n ); // 14 ``` Il existe des opérateurs de "modification et assignation" courts pour tous les opérateurs arithmétiques et binaires : `/=`, `-=` etc. Ces opérateurs ont la même précédence qu'une affectation normale. Ils s'exécutent donc après la plupart des autres calculs : ```js run let n = 2; n *= 3 + 5; // la partie de droite est évaluée en premier (identique à n *= 8) alert( n ); // 16 ``` ## Incrémentation / décrémentation L'augmentation ou la diminution d'un nombre par `1` compte parmi les opérations numériques les plus courantes. Il y a donc des opérateurs spéciaux pour cela : - **Incrémentation** `++` augmente une variable de 1 : ```js run no-beautify let counter = 2; counter++; // fonctionne de la même manière que counter = counter + 1, mais c'est plus court alert( counter ); // 3 ``` - **Décrémentation** `--` diminue une variable de 1 : ```js run no-beautify let counter = 2; counter--; // fonctionne de la même manière que counter = counter - 1, mais c'est plus court alert( counter ); // 1 ``` ```warn L'incrémentation / décrémentation ne peut être appliquée qu'à une variable. Une tentative pour l'utiliser sur une valeur comme `5++` donnera une erreur. ``` Les opérateurs `++` et `--` peuvent être placés à la fois après et avant la variable. - Lorsque l'opérateur va après la variable, cela s'appelle une "forme postfixe" : `counter++`. - La "forme préfixe" est celle où l'opérateur se place devant la variable : `++counter`. Ces deux opérateurs font la même chose : augmenter le `counter` de `1`. Y a-t-il une différence ? Oui, mais nous ne pouvons le voir que si nous utilisons la valeur renvoyée de `++/--`. Soyons clairs. Comme nous le savons, tous les opérateurs renvoient une valeur. L'incrémentation / décrémentation n'est pas une exception ici. La forme préfixe renvoie la nouvelle valeur, tandis que la forme postfixe renvoie l'ancienne valeur (avant l'incrémentation / décrémentation). Pour voir la différence, voici un exemple : ```js run let counter = 1; let a = ++counter; // (*) alert(a); // *!*2*/!* ``` Ici, dans la ligne `(*)`, l'appel du *préfixe* `++counter` incrémente le compteur et retourne la nouvelle valeur qui est `2`. Ainsi, l'`alert` affiche `2`. Maintenant, utilisons la forme postfixe : ```js run let counter = 1; let a = counter++; // (*) changé ++counter pour counter++ alert(a); // *!*1*/!* ``` Dans la ligne `(*)`, la forme postfixe `counter++` incrémente également `counter`, mais renvoie l'*ancienne* valeur (avant l'incrémentation). Donc, l'`alert` montre `1`. Pour résumer : - Si le résultat de l'incrémentation/décrémentation n'est pas utilisé, alors il n'y a pas de différence dans la forme à utiliser : ```js run let counter = 0; counter++; ++counter; alert( counter ); // 2, les lignes ci-dessus ont fait la même chose ``` - Si nous souhaitons augmenter la valeur et utiliser le résultat de l'opérateur immédiatement, nous avons besoin de la forme préfixe : ```js run let counter = 0; alert( ++counter ); // 1 ``` - Si nous souhaitons incrémenter, mais utiliser la valeur précédente, alors nous avons besoin de la forme postfixe : ```js run let counter = 0; alert( counter++ ); // 0 ``` ````smart header="Incrémentation / décrémentation parmi d'autres opérateurs" Les opérateurs `++/--` peuvent également être utilisés dans une expression. Leur précédence est plus élevée que la plupart des autres opérations arithmétiques. Par exemple : ```js run let counter = 1; alert( 2 * ++counter ); // 4 ``` A comparer avec : ```js run let counter = 1; alert( 2 * counter++ ); // 2, counter++ renvoie "l'ancienne" valeur ``` Bien que techniquement acceptable, une telle notation rend le code moins lisible. Une ligne fait plusieurs choses -- pas bien. Lors de la lecture du code, un scan oculaire "vertical" rapide peut facilement manquer un tel `counter++`, et il n’est pas évident que la variable augmente. Le style "une ligne -- une action" est conseillé : ```js run let counter = 1; alert( 2 * counter ); counter++; ``` ```` ## Opérateurs binaires Les opérateurs binaires traitent les arguments comme des nombres entiers de 32 bits et travaillent au niveau de leur représentation binaire. Ces opérateurs ne sont pas spécifiques à JavaScript. Ils sont pris en charge dans la plupart des langages de programmation. La liste des opérateurs : - AND ( `&` ) - OR ( `|` ) - XOR ( `^` ) - NOT ( `~` ) - LEFT SHIFT ( `<<` ) - RIGHT SHIFT ( `>>` ) - ZERO-FILL RIGHT SHIFT ( `>>>` ) Ces opérateurs sont très rarement utilisés, lorsque nous devons jouer avec des nombres au niveau le plus bas (bit à bit). Nous n'aurons pas besoin de ces opérateurs de si tôt, car le développement Web les utilise peu, mais dans certains domaines particuliers, comme la cryptographie, ils sont utiles. Vous pouvez lire le chapitre [Opérateurs binaires](https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/Expressions_et_Op%C3%A9rateurs#Op%C3%A9rateurs_binaires) sur MDN en cas de besoin. ## Virgule L'opérateur virgule `,` est l'un des opérateurs les plus rares et les plus inhabituels. Parfois, il faut écrire un code plus court, il faut donc le connaître pour comprendre ce qui se passe. L'opérateur virgule nous permet d'évaluer plusieurs expressions en les divisant par une virgule `,`. Chacun d'eux est évalué, mais seulement le résultat de la dernière est renvoyé. Par exemple : ```js run *!* let a = (1 + 2, 3 + 4); */!* alert( a ); // 7 (le résultat de 3 + 4) ``` Ici, la première expression `1 + 2` est évaluée mais son résultat n'est pas utilisé, puis `3 + 4` est évalué et renvoyé comme résultat. ```smart header="La virgule a une très faible précédence" Veuillez noter que l'opérateur virgule a une précédence très basse, inférieure à `=`, donc les parenthèses sont importantes dans l'exemple ci-dessus. Sans eux : `a = 1 + 2, 3 + 4` évalue d'abord `+`, additionnant les nombres dans `a = 3, 7`, ensuite l'opérateur d'affectation `=` assigne `a = 3`, et le reste est ignoré. C'est comme `(a = 1 + 2), 3 + 4`. ``` Pourquoi avons-nous besoin d'un tel opérateur qui jette tout sauf la dernière partie ? Parfois, les gens l'utilisent dans des constructions plus complexes pour placer plusieurs actions sur une seule ligne. Par exemple : ```js // trois opérations en une seule ligne for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { ... } ``` Ces astuces sont utilisées dans de nombreux frameworks JavaScript, c’est pourquoi nous les mentionnons. Mais généralement, ils n'améliorent pas la lisibilité du code, nous devrions bien réfléchir avant de les utiliser. ================================================ FILE: 1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md ================================================ ```js no-beautify 5 > 4 → true "apple" > "pineapple" → false "2" > "12" → true undefined == null → true undefined === null → false null == "\n0\n" → false null === +"\n0\n" → false ``` Quelques raisons : 1. Évidemment, c'est vrai. 2. Comparaison du dictionnaire, donc fausse. `"a"` est plus petit que `"p"`. 3. Encore une fois, comparaison du dictionnaire, le premier caractère de `"2"` est plus grand que le premier caractère de `"1"`. 4. Les valeurs `null` et `undefined` sont exclusivement égale entre elles. 5. L'égalité stricte est stricte. Des types différents des deux côtés conduisent à `false`. 6. Similaire à `(4)`, `null` n'est égale qu'à `undefined`. 7. Egalité stricte de différents types. ================================================ FILE: 1-js/02-first-steps/09-comparison/1-comparison-questions/task.md ================================================ importance: 5 --- # Comparaisons Quel sera le résultat pour les expressions suivantes : ```js no-beautify 5 > 4 "apple" > "pineapple" "2" > "12" undefined == null undefined === null null == "\n0\n" null === +"\n0\n" ``` ================================================ FILE: 1-js/02-first-steps/09-comparison/article.md ================================================ # Comparaisons Il y a de nombreux opérateurs de comparaison que nous connaissons des mathématiques : - Plus grand/petit que : a > b, a < b. - Plus grand/petit ou égal à : a >= b, a <= b. - Égalité : `a == b` (veuillez noter le signe de la double égalité `==` signifie un test d’égalité. Un seul symbole `a = b` signifierait une affectation). - Pas égal : en maths la notation est , mais en JavaScript elle est écrite comme une assignation avec un signe d’exclamation : a != b. Dans cet article, nous en apprendrons plus sur les différents types de comparaisons, sur la façon dont JavaScript les fait, y compris sur les particularités importantes. À la fin, vous trouverez une bonne recette pour éviter les problèmes liés aux "bizarreries JavaScript". ## Booléen est le résultat Tout comme tous les autres opérateurs, une comparaison renvoie une valeur de type booléenne. - `true` -- signifie "oui", "correct" ou "vrai". - `false` -- signifie "non", "incorrect" ou "faux". Par exemple : ```js run alert( 2 > 1 ); // true (correct) alert( 2 == 1 ); // false (faux) alert( 2 != 1 ); // true (correct) ``` Un résultat de comparaison peut être affecté à une variable, comme toute valeur : ```js run let result = 5 > 4; // attribue le résultat de la comparaison alert( result ); // true ``` ## Comparaison de chaînes de caractères Pour voir quelle chaîne de caractères est plus grande que l'autre, on utilise l'ordre dit "dictionnaire" ou "lexicographique". En d'autres termes, les chaînes de caractères sont comparées lettre par lettre. Par exemple : ```js run alert( 'Z' > 'A' ); // true alert( 'Glow' > 'Glee' ); // true alert( 'Bee' > 'Be' ); // true alert( '9' > '10' ); // true ``` L'algorithme pour comparer deux chaînes de caractères est simple : 1. Compare les premiers caractères des deux chaînes de caractères. 2. Si le premier est supérieur (ou inférieur), la première chaîne de caractères est supérieure (ou inférieure) à la seconde. Nous en avons fini. 3. Sinon, si les premiers caractères sont égaux, comparez les deuxièmes caractères de la même manière. 4. Répéter jusqu'à la fin d'une chaîne de caractères. 5. Si les deux chaînes de caractères se sont terminées simultanément, alors elles sont égales. Sinon, la chaîne la plus longue est plus grande. Dans l'exemple ci-dessus, la comparaison `'Z' > 'A'` obtient le résultat à la première étape. La deuxième comparaison `'Glow'` et `'Glee'` nécessite plus d'étapes car les chaînes de caractères sont comparées caractère par caractère : 1. `G` est identique à `G`. 2. `l` est identique à `l`. 3. `o` est plus grand que `e`. On stop ici. La première chaîne de caractères est plus grande. ```smart header="Pas vraiment un dictionnaire, mais plutôt l'ordre Unicode" L'algorithme de comparaison ci-dessus est à peu près équivalent à celui utilisé dans les dictionnaires ou les annuaires téléphoniques. Mais ce n’est pas exactement la même chose. Par exemple, cette notion est importante à comprendre. Une lettre majuscule `"A"` n'est pas égale à la lettre minuscule `"a"`. Lequel est le plus grand ? En fait, le minuscule `"a"` l'est. Pourquoi ? Parce que le caractère minuscule a un index plus grand dans la table de codage interne (Unicode). Nous reviendrons sur les détails spécifiques et leurs conséquences dans le chapitre . ``` ## Comparaison de différents types Lorsque les valeurs comparées appartiennent à différents types, elles sont converties en nombres. Par exemple : ```js run alert( '2' > 1 ); // true, la chaîne '2' devient un numéro 2 alert( '01' == 1 ); // true, chaîne '01' devient un numéro 1 ``` Pour les valeurs booléennes, `true` devient `1` et `false` devient `0`. Par exemple : ```js run alert( true == 1 ); // true alert( false == 0 ); // true ``` ````smart header="Une conséquence amusante" Il est possible que dans le même temps : - Deux valeurs sont égales. - L'un d'eux est `vrai` comme booléen et l'autre est `faux` comme booléen. Par exemple : ```js run let a = 0; alert( Boolean(a) ); // false let b = "0"; alert( Boolean(b) ); // true alert(a == b); // true! ``` Du point de vue de JavaScript, c'est tout à fait normal. Un contrôle d'égalité convertit en utilisant la conversion numérique (par conséquent, `"0"` devient `0`), tandis que la `conversion booléenne` utilise un autre ensemble de règles. ```` ## Égalité stricte Une vérification d'égalité régulière `==` a un problème. Elle ne peut pas faire la différence entre `0` et `false` : ```js run alert( 0 == false ); // true ``` La même chose avec une chaîne de caractères vide : ```js run alert( '' == false ); // true ``` C’est parce que les opérandes de différents types sont convertis en un nombre par l’opérateur d’égalité `==`. Une chaîne de caractères vide, tout comme `false`, devient un zéro. Que faire si nous voulons différencier `0` de `faux` ? **Un opérateur d'égalité stricte `===` vérifie l'égalité sans conversion de type.** En d'autres termes, si `a` et `b` sont de types différents, alors `a === b` renvoie immédiatement `false` sans tenter de les convertir. Essayons : ```js run alert( 0 === false ); // false, parce que les types sont différents ``` Il existe également un opérateur de "strict non-égalité" `!==`, par analogie à la non-égalité `!=`. L’opérateur de vérification de l’égalité stricte est un peu plus long à écrire, mais rend évident ce qui se passe et laisse moins d’espace pour les erreurs. ## Comparaison avec null et undefined Il existe un comportement non intuitif lorsque `null` ou `undefined` sont comparés à d’autres valeurs. Pour un contrôle de strict égalité `===` : Ces valeurs sont différentes car chacune d’entre elles appartient à un type distinct. ```js run alert( null === undefined ); // false ``` Pour un contrôle d'égalité non strict `==` : Il y a une règle spéciale. Ces deux là forment "un beau couple" : ils sont égaux (au sens de `==`), mais pas à d'autres valeurs. ```js run alert( null == undefined ); // true ``` Pour les maths et autres comparaisons `<`, `>`, `<=`, `>=` : Les valeurs `null`/`undefined` sont converties en un nombre : `null` devient `0`, alors qu'`undefined` devient `NaN`. Voyons maintenant des choses amusantes qui se produisent lorsque nous appliquons ces règles. Et, ce qui est plus important, comment ne pas tomber dans un piège avec ces caractéristiques. ### L'étrange résultat : null vs 0 Comparons `null` avec un zéro : ```js run alert( null > 0 ); // (1) false alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) *!*true*/!* ``` Ouais, mathématiquement c'est étrange. Le dernier résultat indique que "`null` est supérieur ou égal à zéro". Alors que l'une des comparaisons au dessus devrait être correcte, mais les deux sont fausses. La raison est qu'une vérification d'égalité (`==`) et les comparaisons (`<`, `>`, `<=`, `>=`) fonctionnent différemment. Les comparaisons convertissent `null` en un nombre, donc le traitent comme `0`. C'est pourquoi (3) `null >= 0` est vrai et (1) `null > 0` est faux. D’un autre côté, la vérification de l’égalité `==` pour `undefined` et `null` est définie de telle sorte que, sans aucune conversion, ils sont égaux et ne correspondent à rien d’autre. C'est pourquoi (2) `null == 0` est faux. ### Un undefined incomparable La valeur `undefined` ne doit pas du tout participer aux comparaisons : ```js run alert( undefined > 0 ); // false (1) alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3) ``` Pourquoi est-ce qu'il n'aime pas le zéro ? Toujours faux! Nous avons ces résultats parce que : - Les comparaisons `(1)` et `(2)` renvoient `false` car `undefined` est converti en `NaN` et `NaN` est une valeur numérique spéciale qui renvoie `false` pour toutes les comparaisons. - Le contrôle d'égalité `(3)` renvoie `false`, car `undefined` est uniquement égal à `null` et à aucune autre valeur. ### Éviter les problèmes Pourquoi avons-nous observé ces exemples? Devrions-nous nous souvenir de ces particularités tout le temps ? Eh bien pas vraiment. En fait, ces notions délicates deviennent progressivement familières au fil du temps, mais il existe un moyen solide d’éviter tout problème avec elles. Il suffit de traiter toute comparaison avec `null`/`undefined` (à l'exception de la stricte égalité `===`) avec un soin exceptionnel. N'utilisez pas de comparaisons `=>`, `>`, `<`, `<=` avec une variable qui peut être `null`/`undefined`, sauf si vous êtes vraiment sûr de ce que vous faites. Si une variable peut avoir de telles valeurs, vérifiez-les séparément. ## Résumé - Les opérateurs de comparaison renvoient une valeur logique. - Les chaînes de caractères sont comparées lettre par lettre dans l'ordre "dictionnaire". - Lorsque des valeurs de différents types sont comparées, elles sont converties en nombres (à l'exclusion d'un contrôle d'égalité strict). - Les valeurs `null` et `undefined` sont égales (`==`) et ne correspondent à aucune autre valeur. - Soyez prudent lorsque vous utilisez des comparaisons telles que `>` ou `<` avec des variables pouvant parfois être `null`/`undefined`. Faire une vérification séparée pour `null`/`undefined` est une bonne idée. ================================================ FILE: 1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md ================================================ **Oui, il sera affiché.** Toute chaîne de caractères à l'exception d'une chaîne vide (et `"0"` n'est pas vide) devient vraie dans le contexte logique. Nous pouvons exécuter et vérifier: ```js run if ("0") { alert( 'Hello' ); } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md ================================================ importance: 5 --- # if (une chaîne de caractères avec zéro) Est-ce que `alert` sera affiché ? ```js if ("0") { alert( 'Hello' ); } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html ================================================ ================================================ FILE: 1-js/02-first-steps/10-ifelse/2-check-standard/solution.md ================================================ [html run src="ifelse_task2/index.html"] ================================================ FILE: 1-js/02-first-steps/10-ifelse/2-check-standard/task.md ================================================ importance: 2 --- # Le nom de JavaScript En utilisant la construction `if..else`, écrivez le code qui demande : 'Quel est le nom "officiel" de JavaScript?' Si le visiteur entre "ECMAScript", alors éditez une sortie "Bonne réponse !", Sinon -- retourne "Ne sait pas ? ECMAScript!" ![](ifelse_task2.svg) [demo src="ifelse_task2"] ================================================ FILE: 1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html ================================================ ================================================ FILE: 1-js/02-first-steps/10-ifelse/3-sign/solution.md ================================================ ```js run let value = prompt('Type a number', 0); if (value > 0) { alert( 1 ); } else if (value < 0) { alert( -1 ); } else { alert( 0 ); } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/3-sign/task.md ================================================ importance: 2 --- # Afficher le signe En utilisant `if..else`, écrivez le code qui obtient un numéro via le `prompt`, puis l'affiche en `alert` : - `1`, si la valeur est supérieure à zéro, - `-1`, si inférieur à zéro, - `0`, si est égal à zéro. Dans cet exercice, nous supposons que l'entrée est toujours un nombre. [demo src="if_sign"] ================================================ FILE: 1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md ================================================ ```js let result = (a + b < 4) ? 'Below' : 'Over'; ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md ================================================ importance: 5 --- # Réécrire 'if' en '?' Réécrivez ce `if` en utilisant l'opérateur conditionnel `'?'` : ```js let result; if (a + b < 4) { result = 'Below'; } else { result = 'Over'; } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/solution.md ================================================ ```js let message = (login == 'Employee') ? 'Hello' : (login == 'Director') ? 'Greetings' : (login == '') ? 'No login' : ''; ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md ================================================ importance: 5 --- # Réécrire 'if..else' en '?' Réécrire ce `if..else` en utilisant plusieurs opérateurs ternaires `'?'`. Pour plus de lisibilité, il est recommandé de diviser le code en plusieurs lignes. ```js let message; if (login == 'Employee') { message = 'Hello'; } else if (login == 'Director') { message = 'Greetings'; } else if (login == '') { message = 'No login'; } else { message = ''; } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/article.md ================================================ # Branche conditionnelle : if, '?' Parfois, nous devons effectuer différentes actions en fonction d'une condition. Pour ce faire, nous pouvons utiliser l'instruction `if` et l'opérateur conditionnel `? `, également appelé opérateur "point d'interrogation". ## L'instruction "if" L'instruction `if(...)` évalue une condition entre parenthèses et, si le résultat est `true`, exécute un bloc de code. Par exemple : ```js run let year = prompt('In which year was ECMAScript-2015 specification published?', ''); *!* if (year == 2015) alert( 'You are right!' ); */!* ``` Dans l'exemple ci-dessus, la condition est une vérification d'égalité simple : `year == 2015`, mais elle peut être beaucoup plus complexe. S'il y a plus d'une instruction à exécuter, nous devons envelopper notre bloc de code entre accolades : ```js if (year == 2015) { alert( "That's correct!" ); alert( "You're so smart!" ); } ``` Il est recommandé d'entourer votre bloc de code avec des accolades `{}` à chaque fois avec `if`, même s’il n’y a qu’une seule instruction. Cela améliore la lisibilité. ## Conversion booléenne L'instruction `if (…)` évalue l'expression entre parenthèses et la convertit en type booléen. Rappelons les règles de conversion du chapitre : - Un nombre `0`, une chaîne de caractères vide `""`, `null`, `undefined` et `NaN` deviennent `false`. À cause de cela, on dit de ces valeurs qu'elles sont "falsy". - Les autres valeurs deviennent `true`, on dit qu'elles sont "truthy". Donc, le code sous cette condition ne sera jamais exécuté : ```js if (0) { // 0 est falsy ... } ``` … Et à l'intérieur de cette condition - il fonctionne toujours : ```js if (1) { // 1 est truthy ... } ``` Nous pouvons également transmettre une valeur booléenne pré-évaluée à `if`, comme ici : ```js let cond = (year == 2015); // l'égalité évalue à vrai ou faux if (cond) { ... } ``` ## La clause "else" L'instruction `if` peut contenir un bloc optionnel `else`. Il s'exécute lorsque la condition est fausse. Par exemple : ```js run let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); if (year == 2015) { alert( 'You guessed it right!' ); } else { alert( 'How can you be so wrong?' ); // toute autre valeur que 2015 } ``` ## Plusieurs conditions : "else if" Parfois, nous aimerions tester plusieurs variantes d'une condition. Il y a une clause `else if` pour cela. Par exemple : ```js run let year = prompt('En quelle année la spécification ECMAScript-2015 a-t-elle été publiée ?', ''); if (year < 2015) { alert( 'Too early...' ); } else if (year > 2015) { alert( 'Too late' ); } else { alert( 'Exactly!' ); } ``` Dans le code ci-dessus, JavaScript vérifie `year < 2015`. S'il est falsy, il passe à l'année de condition suivante `year > 2015` et c'est toujours `false` il passe à la dernière instruction et affiche la dernière alerte. Il peut y avoir plus de blocks `else if`. Le dernier `else` est optionnel. ## Opérateur ternaire '?' Parfois, nous devons attribuer une variable en fonction d'une condition. Par exemple : ```js run no-beautify let accessAllowed; let age = prompt('How old are you?', ''); *!* if (age > 18) { accessAllowed = true; } else { accessAllowed = false; } */!* alert(accessAllowed); ``` L'opérateur dit "ternaire" ou "point d'interrogation" nous permet de le faire plus rapidement et plus simplement. L'opérateur est représenté par un point d'interrogation `?`. Appelé aussi "ternaire" parce que l'opérateur a trois opérandes. C'est en fait le seul et unique opérateur en JavaScript qui en a autant. La syntaxe est : ```js let result = condition ? value1 : value2 ``` La `condition` est évaluée, si elle est vraie, alors `value1` est retournée, sinon -- `value2`. Par exemple : ```js let accessAllowed = (age > 18) ? true : false; ``` Techniquement, nous pouvons omettre les parenthèses autour de `age > 18`. L'opérateur point d'interrogation a une faible précédence, il s'exécute donc après la comparaison `>`. Cet exemple fera la même chose que le précédent : ```js // l'opérateur de comparaison "age > 18" s'exécute en premier quoiqu'il en soit // (pas besoin de l'envelopper entre parenthèses) let accessAllowed = age > 18 ? true : false; ``` Mais les parenthèses rendent le code plus lisible, il est donc recommandé de les utiliser. ````smart Dans l'exemple ci-dessus, il est possible d'éviter l'opérateur ternaire, parce que la comparaison elle-même renvoie un `true/false`: ```js // la même chose let accessAllowed = age > 18; ``` ```` ## Multiple '?' Une séquence d'opérateurs point d'interrogation `?` permettent de renvoyer une valeur qui dépend de plusieurs conditions. Par exemple : ```js run let age = prompt('age?', 18); let message = (age < 3) ? 'Hi, baby!' : (age < 18) ? 'Hello!' : (age < 100) ? 'Greetings!' : 'What an unusual age!'; alert( message ); ``` Il peut être difficile au début de comprendre ce qui se passe. Mais après un examen plus approfondi, nous constatons que ce n’est qu’une séquence de tests ordinaire. 1. Le premier point d'interrogation vérifie si `age < 3`. 2. Si vrai -- retourne `'Hi, baby!'`, Sinon, il continue avec l'expression après les deux points ":" suivants et vérifie si `age < 18`. 3. Si vrai -- retourne `'Hello!'`, Sinon, il continue avec l'expression après les deux points ":" suivants et vérifie si `age < 100`. 4. Si vrai -- retourne `'Greetings!'`, Sinon, l'expression continue après les derniers deux-points et retourne `'What an unusual age!'`. La même logique utilisant `if..else` : ```js if (age < 3) { message = 'Hi, baby!'; } else if (age < 18) { message = 'Hello!'; } else if (age < 100) { message = 'Greetings!'; } else { message = 'What an unusual age!'; } ``` ## Utilisation non traditionnelle de '?' Parfois, le point d'interrogation `?` est utilisé en remplacement de `if` : ```js run no-beautify let company = prompt('Which company created JavaScript?', ''); *!* (company == 'Netscape') ? alert('Right!') : alert('Wrong.'); */!* ``` Selon si la condition `company == 'Netscape'` est vraie ou non, la première ou la deuxième partie après `?` s'exécute et affiche l'alerte correspondante. Nous n’attribuons pas de résultat à une variable ici. L'idée est d'exécuter un code différent en fonction de la condition. **Il n'est pas recommandé d'utiliser l'opérateur ternaire de cette manière.** La notation semble être plus courte qu'un `if`, ce qui plaît à certains programmeurs. Mais c'est moins lisible. Voici le même code avec `if` pour comparaison : ```js run no-beautify let company = prompt('Which company created JavaScript?', ''); *!* if (company == 'Netscape') { alert('Right!'); } else { alert('Wrong.'); } */!* ``` Nos yeux scrutent le code verticalement. Les constructions qui couvrent plusieurs lignes sont plus faciles à comprendre qu'un jeu d'instructions horizontal long. L'idée d'un point d'interrogation `?` est de renvoyer l'une ou l'autre valeur selon la condition. Veuillez l'utiliser pour cela exactement. Il y a `if` pour exécuter différentes branches du code. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md ================================================ La réponse est `2`, c'est la première valeur vraie. ```js run alert( null || 2 || undefined ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md ================================================ importance: 5 --- # Quel est le résultat de OR ? Qu'est-ce que le code ci-dessous va sortir ? ```js alert( null || 2 || undefined ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md ================================================ La réponse : d'abord `1` puis `2`. ```js run alert( alert(1) || 2 || alert(3) ); ``` Règle importante à retenir : **L'appel de l'`alert` ne renvoie pas de valeur. Ou, en d'autres termes, il retourne `undefined`.** 1. Le premier OR `||` évalue son opérande gauche `alert(1)`. Cela affiche le premier message avec `1`. 2. L'`alert` retourne `undefined`, donc OR passe au deuxième opérande en recherchant une valeur vraie. 3. Le deuxième opérande `2` est vrai, donc l'exécution est interrompue, `2` est renvoyé puis affiché par l'alerte externe. Il n'y aura pas de `3`, car l'évaluation n'atteint pas l'`alert (3)`. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/2-alert-or/task.md ================================================ importance: 3 --- # Quel est le résultat des alertes OR ? Qu'est-ce que le code ci-dessous va sortir ? ```js alert( alert(1) || 2 || alert(3) ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md ================================================ La réponse : `null`, car c'est la première valeur `false` de la liste. ```js run alert(1 && null && 2); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md ================================================ importance: 5 --- # Quel est le résultat de AND ? Qu'est-ce que ce code va afficher ? ```js alert( 1 && null && 2 ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md ================================================ La réponse : `1`, et ensuite `undefined`. ```js run alert( alert(1) && alert(2) ); ``` L’appel de l'`alert` renvoie undefined (il affiche juste un message, donc il n’ya pas de retour significatif dans le code). À cause de cela, `&&` évalue l'opérande gauche (sortie 1), et s'arrête immédiatement, car `undefined` est une valeur `false`. Et comme `&&` recherche la première valeur fausse et la retourne, donc il s'arrête là. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/4-alert-and/task.md ================================================ importance: 3 --- # Quel est le résultat des alertes AND ? Qu’est-ce que ce code va afficher ? ```js alert( alert(1) && alert(2) ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md ================================================ La réponse : `3`. ```js run alert( null || 2 && 3 || 4 ); ``` La priorité de AND `&&` est supérieure à OR `||`, elle s'exécute donc en premier. Le résultat de `2 && 3 = 3`, donc l'expression devient : ``` null || 3 || 4 ``` Maintenant, le résultat est la première valeur vraie : `3`. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md ================================================ importance: 5 --- # Le résultat de OR AND OR Quel sera le résultat ? ```js alert( null || 2 && 3 || 4 ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/6-check-if-in-range/solution.md ================================================ ```js if (age >= 14 && age <= 90) ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md ================================================ importance: 3 --- # Vérifiez la plage entre Ecrivez une condition `"if"` pour vérifier que l’`age` est compris entre `14` et `90` ans inclus. "Inclus" signifie que l’`age` peut atteindre les `14` ou `90` ans. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/7-check-if-out-range/solution.md ================================================ La première variante : ```js if (!(age >= 14 && age <= 90)) ``` La seconde variante : ```js if (age < 14 || age > 90) ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md ================================================ importance: 3 --- # Vérifiez à l'extérieur de la plage Ecrivez une condition `if` pour vérifier que l’`age` n’est PAS compris entre `14` et `90` ans inclus. Créez deux variantes: la première utilisant NOT `!`, La seconde - sans ce dernier. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/8-if-question/solution.md ================================================ La réponse: le premier et le troisième vont s'exécuter. Details: ```js run // S'éxécute // le résultat de -1 || 0 = -1, vrai if (-1 || 0) alert( 'first' ); // Ne s'éxécute pas // -1 && 0 = 0, faux if (-1 && 0) alert( 'second' ); // S'éxécute // L'opérateur && a une précédence plus élevée que || // donc -1 && 1 s'exécute en premier, nous donnant la chaîne : // null || -1 && 1 -> null || 1 -> 1 if (null || -1 && 1) alert( 'third' ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/8-if-question/task.md ================================================ importance: 5 --- # Une question à propos de "if" Lesquelles de ces `alert`es vont s'exécuter ? Quels seront les résultats des expressions à l'intérieur de `if (...)` ? ```js if (-1 || 0) alert( 'first' ); if (-1 && 0) alert( 'second' ); if (null || -1 && 1) alert( 'third' ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/9-check-login/solution.md ================================================ ```js run demo let userName = prompt("Who's there?", ''); if (userName === 'Admin') { let pass = prompt('Password?', ''); if (pass === 'TheMaster') { alert( 'Welcome!' ); } else if (pass === '' || pass === null) { alert( 'Canceled' ); } else { alert( 'Wrong password' ); } } else if (userName === '' || userName === null) { alert( 'Canceled' ); } else { alert( "I don't know you" ); } ``` Notez les retraits verticaux à l'intérieur des blocs `if`. Ils ne sont techniquement pas nécessaires, mais rendent le code plus lisible. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/9-check-login/task.md ================================================ importance: 3 --- # Check the login Écrivez le code qui demande une connexion avec `prompt`. Si le visiteur entre `"Admin"`, puis `prompt` pour un mot de passe, si l'entrée est une ligne vide ou `key:Esc` -- affichez "Canceled", s'il s'agit d'une autre chaîne de caractères -- alors affichez "I don't know you". Le mot de passe est vérifié comme suit : - S'il est égal à "TheMaster", alors affichez "Welcome!", - Une autre chaînede caractères -- affichez "Wrong password", - Pour une chaîne de caractères vide ou une entrée annulée, affichez "Canceled". Le schéma : ![](ifelse_task.svg) Veuillez utiliser des blocs `if` imbriqués. Attention à la lisibilité globale du code. Astuce: passer une entrée vide à un prompt renvoie une chaîne de caractères vide `''`. En pressant `key:ESC` lors d'un prompt cela retourne `null`. [demo] ================================================ FILE: 1-js/02-first-steps/11-logical-operators/article.md ================================================ # Opérateurs logiques Il y a trois opérateurs logiques en JavaScript : `||` (OR), `&&` (AND), `!` (NOT), `??` (Coalescence des nulles). Nous couvrons ici les trois premiers, l'opérateur `??` est dans l'article suivant. Bien qu'ils soient appelés "logiques", ils peuvent être appliqués à des valeurs de tout type, pas seulement booléennes. Le résultat peut également être de tout type. Voyons les détails. ## || (OR) L'opérateur "OR" est représenté avec deux symboles de ligne verticale : ```js result = a || b; ``` En programmation classique, le OU logique est destiné à manipuler uniquement les valeurs booléennes. Si l'un de ses arguments est `true`, alors il renvoie `true`, sinon il renvoie `false`. En JavaScript, l'opérateur est un peu plus compliqué et puissant. Mais voyons d’abord ce qui se passe avec les valeurs booléennes. Il existe quatre combinaisons logiques possibles : ```js run alert( true || true ); // true alert( false || true ); // true alert( true || false ); // true alert( false || false ); // false ``` Comme on peut le voir, le résultat est toujours `true` sauf pour le cas où les deux opérandes sont `false`. Si un opérande n'est pas booléen, il est converti en booléen pour l'évaluation. Par exemple, un nombre `1` est traité comme `true`, un nombre `0` - comme `false` : ```js run if (1 || 0) { // fonctionne comme si ( true || false ) alert( 'truthy!' ); } ``` La plupart du temps, OR `||` est utilisé dans une instruction `if` pour tester si `l'une` des conditions données est correcte. Par exemple : ```js run let hour = 9; *!* if (hour < 10 || hour > 18) { */!* alert( 'The office is closed.' ); } ``` Nous pouvons passer plus de conditions : ```js run let hour = 12; let isWeekend = true; if (hour < 10 || hour > 18 || isWeekend) { alert( 'The office is closed.' ); // c'est le weekend } ``` ### OR "||" cherche la première valeur vraie [#or-cherche-la-premiere-valeur-vraie] La logique décrite ci-dessus est quelque peu classique. Maintenant, apportons les fonctionnalités "supplémentaires" de JavaScript. L'algorithme étendu fonctionne comme suit. En cas de multiples valeurs liées par OR : ```js result = value1 || value2 || value3; ``` L'opérateur OR `||` fait ce qui suit : - Évaluez les opérandes de gauche à droite. - Pour chaque opérande, il le convertit en booléen. Si le résultat est `true`, arrêtez et retournez la valeur d'origine de cet opérande. - Si tous les autres opérandes ont été évalués (c’est-à-dire que tous étaient `false`), renvoyez le dernier opérande. Une valeur est renvoyée sous sa forme d'origine, sans conversion. En d'autres termes, une chaîne de OR `||` renvoie la première valeur `true` ou la dernière valeur si aucune valeur `true` n'a été trouvée. Par exemple : ```js run alert( 1 || 0 ); // 1 (1 est vrai) alert( null || 1 ); // 1 (1 est la première valeur vraie) alert( null || 0 || 1 ); // 1 (la première valeur vraie) alert( undefined || null || 0 ); // 0 (tous faux, renvoie la dernière valeur) ``` Cela conduit à des usages intéressants par rapport à un "OR pur, classique, booléen uniquement". 1. **Obtenir la première valeur vraie dans la liste des variables ou des expressions.** Par exemple, nous avons les variables `firstName`, `lastName` et `nickName`, toutes optionnelles (c'est-à-dire peut être indéfini ou avoir des valeurs fausses). Utilisons OR `||` pour choisir celui qui contient les données et l'afficher (ou `Anonymous` si rien n'est défini) : ```js run let firstName = ""; let lastName = ""; let nickName = "SuperCoder"; *!* alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder */!* ``` Si toutes les variables étaient fausses, ce serait `"Anonymous"` qui apparaîtrait. 2. **Évaluation des courts-circuits.** Une autre caractéristique de l'opérateur OR `||` est l'évaluation dite de "court-circuit". Cela signifie que `||` traite ses arguments jusqu'à ce que la première valeur de vérité soit atteinte, puis la valeur est renvoyée immédiatement, sans même toucher l'autre argument. L'importance de cette fonctionnalité devient évidente si un opérande n'est pas seulement une valeur, mais une expression avec un effet secondaire, comme une affectation de variable ou un appel de fonction. Dans l'exemple ci-dessous, seul le deuxième message est imprimé : ```js run no-beautify *!*true*/!* || alert("not printed"); *!*false*/!* || alert("printed"); ``` Dans la première ligne, l'opérateur OR `||` arrête l'évaluation immédiatement après avoir vu `true`, de sorte que `alert` n'est pas exécuté. Parfois, les gens utilisent cette fonctionnalité pour exécuter des commandes uniquement si la condition sur la partie gauche est fausse. ## && (AND) L'opérateur AND est représenté avec deux esperluettes `&&` : ```js result = a && b; ``` En programmation classique, AND retourne `true` si les deux opérandes sont `true` et `false` dans les autres cas : ```js run alert( true && true ); // true alert( false && true ); // false alert( true && false ); // false alert( false && false ); // false ``` Un exemple avec `if`: ```js run let hour = 12; let minute = 30; if (hour == 12 && minute == 30) { alert( 'Time is 12:30' ); } ``` Tout comme pour OR, toute valeur est autorisée en tant qu'opérande de AND : ```js run if (1 && 0) { // évalué comme true && false alert( "Ne marchera pas, car le résultat est faux" ); } ``` ## AND "&&" cherche la première valeur fausse En cas de multiples valeurs liées par AND : ```js result = value1 && value2 && value3; ``` L'opérateur AND `&&` effectue les opérations suivantes : - Évalue les opérandes de gauche à droite. - Pour chaque opérande, il le convertit en booléen. Si le résultat est `false`, arrêtez et retournez la valeur d'origine de cet opérande. - Si tous les autres opérandes ont été évalués (c’est-à-dire tous étaient vrais), retournez le dernier opérande. En d'autres termes, une chaîne de AND `&&` renvoie la première valeur `false` ou la dernière valeur si aucune valeur `false` n'a été trouvée. Les règles ci-dessus sont similaires à OR. La différence est que AND retourne la première valeur `false` tandis que OR renvoie la première valeur `true`. Exemples : ```js run // si le premier opérande est vrai, // AND retourne le second opérande : alert( 1 && 0 ); // 0 alert( 1 && 5 ); // 5 // si le premier opérande est faux, // AND le retourne. Le deuxième opérande est ignoré alert( null && 5 ); // null alert( 0 && "no matter what" ); // 0 ``` Nous pouvons également transmettre plusieurs valeurs à la suite sur une même ligne. Voyez comment le premier faux est retourné : ```js run alert( 1 && 2 && null && 3 ); // null ``` Lorsque toutes les valeurs sont vraies, la dernière valeur est renvoyée : ```js run alert( 1 && 2 && 3 ); // 3, la dernière ``` ````smart header="La précédence de AND `&&` est supérieure à OR `||`" La priorité de l'opérateur AND `&&` est supérieure à OR `||`. Donc, le code `a && b || c && d` est essentiellement le même que si `&&` était entre parenthèses: `(a && b) || (c && d)`. ```` ````warn header="Ne remplacez pas `if` par `||` ou `&&`" Parfois, les gens utilisent l'opérateur AND `&&` comme "plus court pour écrire `if`". Par exemple : ```js run let x = 1; (x > 0) && alert( 'Greater than zero!' ); ``` L'action dans la partie droite de `&&` ne s'exécutera que si l'évaluation lui parvient. C'est-à-dire que seulement si `(x > 0)` est vrai. Donc, nous avons une analogie pour : ```js run let x = 1; if (x > 0) alert( 'Greater than zero!' ); ``` Bien que la variante avec `&&` semble plus courte, `if` est plus évidente et a tendance à être un peu plus lisible. Nous recommandons donc d'utiliser chaque construction dans son but : utilisez `if` si nous voulons `if` et utilisez `&&` si nous voulons ET. ```` ## ! (NOT) L'opérateur booléen NOT est représenté par un point d'exclamation `!`. La syntaxe est assez simple : ```js result = !value; ``` L'opérateur accepte un seul argument et effectue les opérations suivantes : 1. Convertit l'opérande en type booléen : `true/false`. 2. Renvoie la valeur inverse. Par exemple : ```js run alert( !true ); // false alert( !0 ); // true ``` Un double NOT `!!` est parfois utilisé pour convertir une valeur en type booléen : ```js run alert( !!"non-empty string" ); // true alert( !!null ); // false ``` C'est-à-dire que le premier NOT convertit la valeur en booléen et retourne l'inverse, et que le second NOT l'inverse encore. À la fin, nous avons une conversion valeur à booléen simple. Il existe un moyen un peu plus verbeux de faire la même chose -- une fonction `Boolean` intégrée : ```js run alert( Boolean("non-empty string") ); // true alert( Boolean(null) ); // false ``` La précédence de NOT `!` est la plus élevée de tous les opérateurs binaire, il est donc toujours exécuté en premier, avant les autres. ================================================ FILE: 1-js/02-first-steps/12-nullish-coalescing-operator/article.md ================================================ # L'opérateur de coalescence des nuls '??' [recent browser="new"] L'opérateur de coalescence des nuls est écrit sous la forme de deux points d'interrogation `??`. Comme il traite `null` et `undefined` de la même manière, nous utiliserons un terme spécial ici, dans cet article. Par souci de brièveté, nous dirons qu'une expression est "définie" lorsqu'elle n'est ni `null` ni `undefined`. Le résultat de `a ?? b` est : - si `a` est défini, alors `a`, - si `a` n'est pas défini, alors `b`. En d'autres termes, `??` renvoie le premier argument s'il n'est pas `null`/`undefined`. Sinon, le second. L'opérateur de coalescence des nuls n'est pas complètement nouveau. C'est juste une belle syntaxe pour obtenir la première valeur "defined" des deux. Nous pouvons réécrire `result = a ?? b` en utilisant les opérateurs que nous connaissons déjà, comme ceci : ```js result = (a !== null && a !== undefined) ? a : b; ``` Maintenant, il devrait être absolument clair ce que fait `??`. Voyons où cela aide. Le cas d'utilisation courant de `??` est de fournir une valeur par défaut. Par exemple, nous affichons ici `user` si sa valeur n'est pas `null`/`undefined`, sinon `Anonymous` : ```js run let user; alert(user ?? "Anonymous"); // Anonymous (user is undefined) ``` Voici l'exemple avec `user` attribué à un nom : ```js run let user = "John"; alert(user ?? "Anonymous"); // John (user is not null/undefined) ``` Nous pouvons également utiliser une séquence de `??` pour sélectionner la première valeur dans une liste qui n'est pas `null`/`undefined`. Disons que nous avons les données d'un utilisateur dans les variables `firstName`, `lastName` ou `nickName`. Tous peuvent être indéfinis, si l'utilisateur décide de ne pas entrer de valeurs correspondantes. Nous aimerions afficher le nom d'utilisateur à l'aide de l'une de ces variables, ou afficher "Anonyme" si toutes sont `null`/`undefined`. Utilisons l'opérateur `??` pour cela : ```js run let firstName = null; let lastName = null; let nickName = "Supercoder"; // affiche la première valeur définie : *!* alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder */!* ``` ## Comparaison avec || L'opérateur OR `||` peut être utilisé de la même manière que `??`, comme il a été décrit dans le [chapitre précédent](info:logical-operators#or-finds-the-first-truthy-value). Par exemple, dans le code ci-dessus, nous pourrions remplacer `??` par `||` et toujours obtenir le même résultat : ```js run let firstName = null; let lastName = null; let nickName = "Supercoder"; // affiche la première valeur vraie : *!* alert(firstName || lastName || nickName || "Anonymous"); // Supercoder */!* ``` Historiquement, l'opérateur OR `||` était là en premier. Il existe depuis le début de JavaScript, donc les développeurs l'utilisaient à de telles fins depuis longtemps. D'un autre côté, l'opérateur de coalescence des nuls `??` n'a été ajouté à JavaScript que récemment, et la raison en était que les gens n'étaient pas tout à fait satisfaits de `||`. La différence importante entre eux est que : - `||` renvoie la première valeur *vraie*. - `??` renvoie la première valeur *définie*. En d'autres termes, `||` ne fait pas la distinction entre `false`, `0`, une chaîne vide `" "` et `null`/`undefined`. Ce sont tous les mêmes -- des valeurs fausses. Si l'un de ceux-ci est le premier argument de `||`, alors nous obtiendrons le deuxième argument comme résultat. Dans la pratique cependant, nous pouvons vouloir utiliser la valeur par défaut uniquement lorsque la variable est `null`/`undefined`. Autrement dit, lorsque la valeur est vraiment inconnue/non définie. Par exemple, considérez ceci : ```js run let height = 0; alert(height || 100); // 100 alert(height ?? 100); // 0 ``` - L'expression `height || 100` vérifie que `height` est une valeur fausse, et c'est `0`, elle est fausse en effet. - donc le résultat de `||` est le deuxième argument, `100`. - L'expression `height ?? 100` vérifie que `height` est `null`/`undefined`, et ce n'est pas le cas, - donc le résultat est `height` "tel quel", c'est-à-dire `0`. En pratique, la hauteur zéro est souvent une valeur valide, qui ne doit pas être remplacée par la valeur par défaut. Alors `??` fait ce qu'il faut. ## Priorité La priorité de l'opérateur `??` est la même que celle de `||`. Elle est égale à `3` dans le [tableau MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). Cela signifie que, tout comme `||`, l'opérateur de coalescence des nuls `??` est évalué avant `=` et `?`, mais après la plupart des autres opérations, telles que `+`, `*`. Nous devrons donc peut-être ajouter des parenthèses dans des expressions comme celle-ci : ```js run let height = null; let width = null; // important : utilisez des parenthèses let area = (height ?? 100) * (width ?? 50); alert(area); // 5000 ``` Sinon, si nous omettons les parenthèses, alors que `*` a une priorité plus élevée que `??`, il s'exécuterait en premier, conduisant à des résultats incorrects. ```js // sans parenthèses let area = height ?? 100 * width ?? 50; // ...fonctionne de cette façon (pas ce que nous voulons) : let area = height ?? (100 * width) ?? 50; ``` ### En utilisant ?? avec && ou || Pour des raisons de sécurité, JavaScript interdit d'utiliser `??` avec les opérateurs `&&` et `||`, à moins que la priorité ne soit explicitement spécifiée entre parenthèses. Le code ci-dessous déclenche une erreur de syntaxe : ```js run let x = 1 && 2 ?? 3; // Syntax error ``` La limitation est sûrement discutable, mais elle a été ajoutée à la spécification du langage dans le but d'éviter les erreurs de programmation, quand les gens commencent à passer de `||` à `??`. Utilisez des parenthèses explicites pour la contourner : ```js run *!* let x = (1 && 2) ?? 3; // fonctionne */!* alert(x); // 2 ``` ## Résumé - L'opérateur de coalescence des nuls `??` fournit un moyen court de choisir une valeur "définie" à partir d'une liste. Il est utilisé pour attribuer des valeurs par défaut aux variables : ```js // configurer height = 100, si height est null ou undefined height = height ?? 100; ``` - L'opérateur `??` a une priorité très faible, un peu plus élevée que `?` et `=`, pensez donc à ajouter des parenthèses lors de son utilisation dans une expression. - Il est interdit de l'utiliser avec `||` ou `&&` sans parenthèses explicites. ================================================ FILE: 1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md ================================================ La réponse : `1`. ```js run let i = 3; while (i) { alert( i-- ); } ``` Chaque itération de boucle diminue `i` de `1`. La vérification `while(i)` arrête la boucle lorsque `i = 0`. Par conséquent, les étapes de la boucle forment la séquence suivante ("boucle décomposée") : ```js let i = 3; alert(i--); // affiche 3, diminue i à 2 alert(i--) // affiche 2, diminue i à 1 alert(i--) // affiche 1, diminue i à 0 // terminé, la vérification while(i) termine la boucle ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/1-loop-last-value/task.md ================================================ importance: 3 --- # Dernière valeur de boucle Quelle est la dernière valeur affichée par ce code ? Pourquoi ? ```js let i = 3; while (i) { alert( i-- ); } ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/2-which-value-while/solution.md ================================================ L'exercice montre comment les formes postfix/prefix peuvent conduire à des résultats différents lorsqu’ils sont utilisés dans des comparaisons. 1. **De 1 à 4** ```js run let i = 0; while (++i < 5) alert( i ); ``` La première valeur est `i=1`, parce que `++i` incrémente d'abord `i` puis renvoie la nouvelle valeur. La première comparaison est donc `1 < 5` et `alert` indique `1`. Ensuite, viennent `2,3,4…` -- les valeurs apparaissent les unes après les autres. La comparaison utilise toujours la valeur incrémentée, car `++` est avant la variable. Enfin, `i=4` est incrémenté à `5`, la comparaison `while(5 < 5)` échoue et la boucle s'arrête. Donc `5` n'est pas affiché. 2. **De 1 à 5** ```js run let i = 0; while (i++ < 5) alert( i ); ``` La première valeur est encore `i=1`. La forme postfixée de `i++` incrémente `i` puis renvoie *l'ancienne* valeur, la comparaison `i++ < 5` utilisera donc `i=0` (contrairement à `++i < 5`). Mais l'appel d'`alert` est séparé. C’est une autre instruction qui s’exécute après l’incrémentation et la comparaison. Donc, on obtient `i=1`. Ensuite viennent `2,3,4…` Arrêtons-nous sur `i=4`. Le préfixe sous forme `++i` l'incrémenterait et utiliserait `5` dans la comparaison. Mais ici nous avons la forme postfixée `i++`. Donc, `i` augmente à `5`, mais renvoie l'ancienne valeur. Par conséquent, la comparaison est en réalité `while(4 < 5)` -- true, et le contrôle continue à `alert`. La valeur `i=5` est la dernière, car à l'étape suivante `while(5 <5)` est faux. ================================================ FILE: 1-js/02-first-steps/13-while-for/2-which-value-while/task.md ================================================ importance: 4 --- # Quelles valeurs affiche la boucle while ? A votre avis, quelles sont les valeurs affichées pour chaque boucle ? Notez-les puis comparer avec la réponse. Les deux boucles affichent-elles les mêmes valeurs dans l'`alert` ou pas ? 1. Le préfixe sous forme `++i` : ```js let i = 0; while (++i < 5) alert( i ); ``` 2. Le postfixe sous forme `i++` : ```js let i = 0; while (i++ < 5) alert( i ); ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/3-which-value-for/solution.md ================================================ **La réponse: de `0` à `4` dans les deux cas.** ```js run for (let i = 0; i < 5; ++i) alert( i ); for (let i = 0; i < 5; i++) alert( i ); ``` Cela peut être facilement déduit de l'algorithme de `for` : 1. Exécute une fois `i = 0` avant tout (début). 2. Vérifie l'état `i < 5` 3. Si `true` -- execute le corps de la boucle `alert(i)`, et ensuite `i++` L'incrément `i++` est séparé de la vérification de condition (2). C’est juste une autre déclaration. La valeur renvoyée par l’incrémentation n’est pas utilisée ici, il n’y a donc pas de différence entre `i++` et `++i`. ================================================ FILE: 1-js/02-first-steps/13-while-for/3-which-value-for/task.md ================================================ importance: 4 --- # Quelles valeurs sont affichées par la boucle "for" ? Pour chaque boucle, notez les valeurs qui vont s'afficher. Ensuite, comparez avec la réponse. Les deux boucles `alert` les mêmes valeurs ou pas ? 1. La forme postfix : ```js for (let i = 0; i < 5; i++) alert( i ); ``` 2. La forme préfix : ```js for (let i = 0; i < 5; ++i) alert( i ); ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/4-for-even/solution.md ================================================ ```js run demo for (let i = 2; i <= 10; i++) { if (i % 2 == 0) { alert( i ); } } ``` Ici nous utilisons l'opérateur “modulo” `%` pour obtenir le reste et vérifier si c'est pair ou pas. ================================================ FILE: 1-js/02-first-steps/13-while-for/4-for-even/task.md ================================================ importance: 5 --- # Extraire les nombres pairs dans la boucle Utilisez la boucle `for` pour afficher les nombres pairs de `2` à `10`. [demo] ================================================ FILE: 1-js/02-first-steps/13-while-for/5-replace-for-while/solution.md ================================================ ```js run let i = 0; while (i < 3) { alert( `number ${i}!` ); i++; } ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/5-replace-for-while/task.md ================================================ importance: 5 --- # Remplacer "for" par "while" Réécrivez le code en modifiant la boucle `for` en `while` sans modifier son comportement (la sortie doit rester la même). ```js run for (let i = 0; i < 3; i++) { alert( `number ${i}!` ); } ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md ================================================ ```js run demo let num; do { num = prompt("Enter a number greater than 100?", 0); } while (num <= 100 && num); ``` La boucle `do..while` se répète tant que les deux vérifications sont vrai : 1. La vérification de `num <= 100` - c’est-à-dire que la valeur entrée n’est toujours pas supérieure à `100`. 2. La vérification que `&& num` est `false` lorsque `num` est `null` ou une chaîne vide. Ensuite, la boucle `while` s'arrête aussi. P.S. Si `num` est `null`, alors `num <= 100` est `true`. Par conséquent, sans la seconde vérification, la boucle ne s’arrêterait pas si l’utilisateur cliquait sur CANCEL. Les deux vérifications sont obligatoires. ================================================ FILE: 1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md ================================================ importance: 5 --- # Répéter jusqu'à ce que l'entrée soit correcte Ecrivez une boucle qui demande un nombre supérieur à `100`. Si le visiteur saisit un autre numéro, demandez-lui de le saisir à nouveau. La boucle doit demander un numéro jusqu'à ce que le visiteur saisisse un nombre supérieur à `100` ou annule l'entrée/entre une ligne vide. Ici, nous pouvons supposer que le visiteur ne saisit que des chiffres. Il n’est pas nécessaire de mettre en œuvre un traitement spécial pour une entrée non numérique dans cette tâche. [demo] ================================================ FILE: 1-js/02-first-steps/13-while-for/7-list-primes/solution.md ================================================ Il existe de nombreux algorithmes pour cette tâche. Utilisons une boucle imbriquée : ```js Pour chaque i dans l'intervalle { vérifier si i a un diviseur de 1..i si oui => la valeur n'est pas un nombre premier si non => la valeur est un nombre premier, affichez-le } ``` Un code utilisant un label : ```js run let n = 10; nextPrime: for (let i = 2; i <= n; i++) { // Pour chaque i... for (let j = 2; j < i; j++) { // cherche un diviseur .. if (i % j == 0) continue nextPrime; // pas un premier, on passe au prochain i } alert( i ); // un premier } ``` Il y a beaucoup d’espace pour l’optimiser. Par exemple, nous pourrions rechercher les diviseurs de `2` à la racine carrée de `i`. Quoi qu’il en soit, si nous voulons être vraiment efficaces pour les grands intervalles, nous devons changer d’approche et nous baser sur des mathématiques avancées et des algorithmes complexes comme [Crible quadratique](https://fr.wikipedia.org/wiki/Crible_quadratique), [Crible algébrique](https://fr.wikipedia.org/wiki/Crible_alg%C3%A9brique) etc. ================================================ FILE: 1-js/02-first-steps/13-while-for/7-list-primes/task.md ================================================ importance: 3 --- # Extraire des nombres premiers Un nombre entier supérieur à 1 est appelé un [Nombre premier](https://fr.wikipedia.org/wiki/Nombre_premier) s'il ne peut être divisé sans reste par rien d'autre que 1 et lui-même. En d’autres termes, `n > 1` est un nombre premier s’il ne peut être divisé de manière égale par autre chose que `1` et `n`. Par exemple, `5` est un nombre premier, car il ne peut pas être divisé sans reste par `2`, `3` et `4`. **Écrivez un code qui produit les nombres premiers dans l’intervall e 2 à n.** Pour `n = 10`, le résultat sera `2`,`3`,`5`,`7`. P.S. Le code devrait fonctionner pour n'importe quel `n` et aucune valeur fixe ne doit être codé en dur. ================================================ FILE: 1-js/02-first-steps/13-while-for/article.md ================================================ # Boucles : while et for Nous avons souvent besoin d'effectuer des actions similaires plusieurs fois de suite. Par exemple, lorsque nous devons extraire des marchandises d'une liste les unes à la suite des autres. Ou exécutez simplement le même code pour chaque numéro de 1 à 10. *Les boucles* permettent de répéter plusieurs fois la même partie du code. ```smart header="Les boucles for..of et for..in" Une petite annonce pour les lecteurs avertis. Cet article ne couvre que les boucles de base : `while`, `do..while` et `for(..;..;..)`. Si vous êtes venu à cet article à la recherche d'autres types de boucles, voici les pointeurs : - Voir [for..in](info:object#forin) pour boucler sur les propriétés de l'objet. - Voir [for..of](info:array#loops) et [iterables](info:iterable) pour boucler sur des tableaux et des objets itérables. Sinon, lisez la suite. ``` ## La boucle "while" La boucle `while` a la syntaxe suivante : ```js while (condition) { // code // appelé "loop body" ("corps de boucle") } ``` Tant que la `condition` est vraie, le `code` du corps de la boucle est exécuté. Par exemple, la boucle ci-dessous affiche `i` tant que `i < 3` : ```js run let i = 0; while (i < 3) { // affiche 0, puis 1, puis 2 alert( i ); i++; } ``` Une unique exécution du corps de la boucle est appelée **une itération**. La boucle dans l'exemple ci-dessus fait trois itérations. S'il n'y avait pas d'`i++` dans l'exemple ci-dessus, la boucle se répèterait (en théorie) pour toujours. En pratique, le navigateur fournit des moyens d’arrêter ces boucles, et pour JavaScript côté serveur, nous pouvons tuer le processus. Toute expression ou variable peut être une condition de boucle, pas seulement une comparaison. Ils sont évalués et convertis en un booléen par `while`. Par exemple, le moyen le plus court d'écrire `while (i != 0)` pourrait être `while (i)` : ```js run let i = 3; *!* while (i) { // quand i devient 0, la condition devient fausse et la boucle s'arrête */!* alert( i ); i--; } ``` ````smart header="Les accolades ne sont pas requis pour un corps à une seule ligne" Si le corps de la boucle a une seule déclaration, nous pouvons omettre les accolades `{…}` : ```js run let i = 3; *!* while (i) alert(i--); */!* ``` ```` ## La boucle "do…while" La vérification de la condition peut être déplacée *sous* le corps de la boucle en utilisant la syntaxe `do..while` : ```js do { // corps de la boucle } while (condition); ``` La boucle exécute d'abord le corps, puis vérifie la condition et, tant que c'est vrai, l'exécute encore et encore. Par exemple : ```js run let i = 0; do { alert( i ); i++; } while (i < 3); ``` Cette forme de syntaxe est rarement utilisée, sauf lorsque vous souhaitez que le corps de la boucle s'exécute **au moins une fois**, quelle que soit la condition. Habituellement, l'autre forme est préférée : `while(…) {…}`. ## La boucle "for" La boucle `for` est plus complexe, mais c’est aussi la boucle la plus utilisée. Cela ressemble à ceci : ```js for (début; condition; étape) { // ... corps de la boucle ... } ``` Apprenons la signification de ces parties par l'exemple. La boucle ci-dessous exécute `alert(i)` pour `i` en partant de `0` jusqu'à `3` (mais non compris) : ```js run for (let i = 0; i < 3; i++) { // affiche 0, puis 1, puis 2 alert(i); } ``` Examinons la déclaration `for` partie par partie : | partie | | | |-----------|------------|----------------------------------------------------------------------------------------| | début | `let i = 0` | Exécute une fois en entrant dans la boucle. | | condition | `i < 3` | Vérifié avant chaque itération de la boucle, en cas d'échec, la boucle s'arrête. | | corps | `alert(i)` | Exécute encore et encore tant que la condition est vraie | | étape | `i++` | Exécute après le corps à chaque itération | L'algorithme de boucle général fonctionne comme ceci : ``` Exécuter le début → (si condition → exécuter le corps et exécuter l'étape) → (si condition → exécuter le corps et exécuter l'étape) → (si condition → exécuter le corps et exécuter l'étape) → ... ``` C'est-à-dire que `begin` est exécuté une fois, puis itéré : après chaque test de `condition`, `body` et `step` sont exécutés. Si vous débutez dans les boucles, il pourrait être utile de revenir à l'exemple et de reproduire comment elle s'exécute pas à pas sur une feuille de papier. Voici ce qui se passe exactement dans notre cas : ```js // for (let i = 0; i < 3; i++) alert(i) // exécute début let i = 0 // si condition → exécuter le corps et exécuter l'étape if (i < 3) { alert(i); i++ } // si condition → exécuter le corps et exécuter l'étape if (i < 3) { alert(i); i++ } // si condition → exécuter le corps et exécuter l'étape if (i < 3) { alert(i); i++ } // ... fini, parce que maintenant i == 3 ``` ````smart header="Déclaration de variable en ligne" Ici, la variable "counter" `i` est déclarée directement dans la boucle. Cela s'appelle une déclaration de variable "en ligne". De telles variables ne sont visibles que dans la boucle. ```js run for (*!*let*/!* i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } alert(i); // erreur, pas de variable ``` Au lieu de définir une variable, nous pouvons en utiliser une existante : ```js run let i = 0; for (i = 0; i < 3; i++) { // utiliser une variable existante alert(i); // 0, 1, 2 } alert(i); // 3, visible, car déclaré en dehors de la boucle ``` ```` ### Sauter des parties Toute partie de `for` peut être ignorée. Par exemple, nous pouvons omettre `le début` si nous n'avons rien à faire au début de la boucle. Comme ici : ```js run let i = 0; // nous avons i déjà déclaré et assigné for (; i < 3; i++) { // pas besoin de "début" alert( i ); // 0, 1, 2 } ``` Nous pouvons également supprimer la partie `étape` : ```js run let i = 0; for (; i < 3;) { alert( i++ ); } ``` La boucle est devenue identique à `while (i < 3)`. Nous pouvons tout supprimer, créant ainsi une boucle infinie : ```js for (;;) { // répète sans limites } ``` Veuillez noter que les deux les points-virgules `;` de `for` doivent être présents, sinon ce serait une erreur de syntaxe. ## Briser la boucle Normalement, la boucle sort quand la condition devient fausse. Mais nous pouvons forcer la sortie à tout moment. Il y a une directive spéciale appelée `break` pour cela. Par exemple, la boucle ci-dessous demande à l'utilisateur une série de chiffres, mais "se casse" quand aucun numéro n'est entré : ```js run let sum = 0; while (true) { let value = +prompt("Entrez un nombre", ''); *!* if (!value) break; // (*) */!* sum += value; } alert( 'Sum: ' + sum ); ``` La directive `break` est activée sur la ligne `(*)` si l'utilisateur entre une ligne vide ou annule l'entrée. Il arrête la boucle immédiatement, en passant le contrôle à la première ligne après la boucle. À savoir, `alert`. La combinaison "boucle infinie + `break` au besoin" est idéale pour les situations où la condition doit être vérifiée non pas au début / à la fin de la boucle, mais au milieu, voire à plusieurs endroits du corps. ## Continuer jusqu'à la prochaine itération [#continue] La directive `continue` est une "version plus légère" de `break`. Cela n'arrête pas toute la boucle. Au lieu de cela, elle arrête l'itération en cours et force la boucle à en démarrer une nouvelle (si la condition le permet). Nous pouvons l’utiliser si nous avons terminé l’itération en cours et aimerions passer à la suivante. La boucle ci-dessous utilise `continue` pour ne produire que des valeurs impaires : ```js run no-beautify for (let i = 0; i < 10; i++) { // si vrai, saute le reste du corps *!*if (i % 2 == 0) continue;*/!* alert(i); // 1, ensuite 3, 5, 7, 9 } ``` Pour les valeurs paires de `i`, la directive `continue` arrête l'exécution du corps en passant le contrôle à la prochaine itération de `for` (avec le nombre suivant). Donc, l'`alert` n'est appelée que pour les valeurs impaires. ````smart header="La directive `continue` aide à réduire le niveau d'imbrication" Une boucle affichant des valeurs impaires pourrait ressembler à ceci : ```js run for (let i = 0; i < 10; i++) { if (i % 2) { alert( i ); } } ``` D'un point de vue technique, c'est identique à l'exemple du dessus. Certes, nous pouvons simplement envelopper le code dans un bloc `if` au lieu de `continue`. Mais comme effet secondaire, nous avons obtenu un niveau d'imbrication supplémentaire (l'appel de l'`alert` à l'intérieur des accolades). Si le code à l'intérieur du `if` est plus long que quelques lignes, la lisibilité globale peut en être réduite. ```` ````warn header="Pas de `break/continue` à droite de '?'" Veuillez noter que les constructions de syntaxe qui ne sont pas des expressions ne peuvent pas être utilisées avec l'opérateur ternaire `?`. Tout particulièrement les directives telles que `break/continue` ne sont pas autorisées. Par exemple, si nous prenons ce code : ```js if (i > 5) { alert(i); } else { continue; } ``` … Et le réécrivons à l'aide d'un point d'interrogation : ```js no-beautify (i > 5) ? alert(i) : *!*continue*/!*; // continue n'est pas autorisé ici ``` … Ensuite cesse de fonctionner : il y a une erreur de syntaxe. C’est une autre raison pour ne pas utiliser l'opérateur point d’interrogation `?` au lieu de `if`. ```` ## Des labels pour break/continue Parfois, nous devons sortir de plusieurs boucles imbriquées en même temps. Par exemple, dans le code ci-dessous, nous bouclons sur `i` et `j` pour demander les coordonnées `(i, j)` de `(0,0)` à `(2,2)` : ```js run no-beautify for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let input = prompt(`Value at coords (${i},${j})`, ''); // Et si nous voulons sortir d'ici à Done (ci-dessous) ? } } alert('Done!'); ``` Nous avons besoin d'un moyen d'arrêter le processus si l'utilisateur annule la saisie. Le `break` ordinaire après `input` ne ferait que briser la boucle intérieure. Ce n’est pas suffisant -- les *labels* viennent à la rescousse. Une *label* est un identifiant avec deux points avant une boucle : ```js labelName: for (...) { ... } ``` L'instruction `break ` dans la boucle interrompt tout le bloc de code relatif au label. Comme ici : ```js run no-beautify *!*outer:*/!* for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let input = prompt(`Value at coords (${i},${j})`, ''); // si une chaîne est vide ou annulée, alors rompre les deux boucles if (!input) *!*break outer*/!*; // (*) // faire quelque chose avec la valeur … } } alert('Done!'); ``` Dans le code ci-dessus, `break outer` regarde vers le haut le label `outer` et sort de cette boucle. Donc, le contrôle va directement de `(*)` à `alert('Done!')`. Nous pouvons également déplacer le label sur une ligne séparée : ```js no-beautify outer: for (let i = 0; i < 3; i++) { ... } ``` La directive `continue` peut également être utilisée avec un label. Dans ce cas, l'exécution passe à l'itération suivante de la boucle labellisée. ````warn header="Les labels ne permettent pas de \"sauter\" n'importe où" Les labels ne nous permettent pas de sauter dans un endroit arbitraire du code. Par exemple, il est impossible de faire ceci : ```js break label; // saute au label ci-dessous (ne fonctionne pas) label: for (...) ``` Une directive `break` doit être à l'intérieur d'un bloc de code. Techniquement, tout bloc de code étiqueté fera l'affaire, par exemple : ```js label: { // ... break label; // works // ... } ``` ... Bien que 99,9% du temps les `break` utilisés sont à l'intérieur de boucles, comme nous l'avons vu dans les exemples ci-dessus. Un `continue` n'est possible que depuis l'intérieur d'une boucle. ```` ## Résumé Nous avons couvert 3 types de boucles : - `while` -- La condition est vérifiée avant chaque itération. - `do..while` -- La condition est vérifiée après chaque itération. - `for (;;)` -- La condition est vérifiée avant chaque itération, des paramètres supplémentaires sont disponibles. Pour créer une boucle "infinie", on utilise généralement la construction `while(true)`. Une telle boucle, comme toute autre, peut être stoppée avec la directive `break`. Si nous ne voulons rien faire avec l’itération actuelle et que nous souhaitons avancer jusqu'à la suivante, la directive `continue` nous permet de faire cela. `break/continue` accepte les labels précédents la boucle. Un label est le seul moyen de `break/continue` pour échapper à l'imbrication et accéder en dehors de la boucle. ================================================ FILE: 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md ================================================ Pour correspondre précisément à la fonctionnalité du `switch`, le `if` doit utiliser une comparaison stricte `'==='`. Cependant, pour des chaînes de caractères données, un simple `'=='` fonctionne également. ```js no-beautify if(browser == 'Edge') { alert("You've got the Edge!"); } else if (browser == 'Chrome' || browser == 'Firefox' || browser == 'Safari' || browser == 'Opera') { alert( 'Okay we support these browsers too' ); } else { alert( 'We hope that this page looks ok!' ); } ``` Remarque: la construction `browser == 'Chrome' || navigateur == 'Firefox'…` est divisée en plusieurs lignes pour une meilleure lisibilité. Mais la construction `switch` est toujours plus propre et plus descriptive. ================================================ FILE: 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md ================================================ importance: 5 --- # Réécrire le "switch" dans un "if" Écrivez le code en utilisant `if..else` qui correspondrait au `switch` suivant : ```js switch (browser) { case 'Edge': alert( "You've got the Edge!" ); break; case 'Chrome': case 'Firefox': case 'Safari': case 'Opera': alert( 'Okay we support these browsers too' ); break; default: alert( 'We hope that this page looks ok!' ); } ``` ================================================ FILE: 1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md ================================================ Les deux premiers contrôles se transforment en deux `case`. Le troisième contrôle est divisé en deux `case` : ```js run let a = +prompt('a?', ''); switch (a) { case 0: alert( 0 ); break; case 1: alert( 1 ); break; case 2: case 3: alert( '2,3' ); *!* break; */!* } ``` Remarque: le `break` en bas n'est pas requis. Mais nous le mettons pour rendre le code à l'épreuve du temps. Dans le futur, il est possible que nous voulions ajouter un `case` supplémentaire, par exemple le `case 4`. Et si nous oublions d’ajouter un `break` avant, à la fin du `case 3`, il y aura une erreur. C’est donc une sorte d’assurance. ================================================ FILE: 1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md ================================================ importance: 4 --- # Réécrire le "if" dans un "switch" Réécrivez le code ci-dessous en utilisant une seule instruction `switch` : ```js run let a = +prompt('a?', ''); if (a == 0) { alert( 0 ); } if (a == 1) { alert( 1 ); } if (a == 2 || a == 3) { alert( '2,3' ); } ``` ================================================ FILE: 1-js/02-first-steps/14-switch/article.md ================================================ # La déclaration "switch" Une instruction `switch` peut remplacer plusieurs vérification `if`. Cela donne un moyen plus descriptif de comparer une valeur avec plusieurs variantes. ## La syntaxe Le `switch` a un ou plusieurs blocs `case` (cas) et une valeur par défaut facultative. Cela ressemble à ceci : ```js no-beautify switch(x) { case 'value1': // si (x === 'value1') ... [break] case 'value2': // si (x === 'value2') ... [break] default: ... [break] } ``` - La valeur de `x` est vérifiée pour une égalité stricte avec la valeur du premier `case` (c'est-à-dire, `value1`), puis du second (`value2`) et ainsi de suite. - Si l'égalité est trouvée, `switch` commence à exécuter le code à partir du `case` correspondant, jusqu'au prochain `break` (ou jusqu'à la fin du switch). - Si aucun cas ne correspond, le code par défaut (`default`) est exécuté (s'il existe). ## Un exemple Un exemple de `switch` (le code exécuté est mis en évidence) : ```js run let a = 2 + 2; switch (a) { case 3: alert( 'Too small' ); break; *!* case 4: alert( 'Exactly!' ); break; */!* case 5: alert( 'Too big' ); break; default: alert( "I don't know such values" ); } ``` Ici, le `switch` commence à comparer `a` avec le premier `case` dont la valeur est `3`. La correspondance échoue. Ensuite `4`, c’est une correspondance. L’exécution commence donc à partir du `case 4` jusqu’au prochain `break`. **S'il n'y a pas de `break`, l'exécution continue avec le `case` suivant sans aucun contrôle.** Un exemple sans `break`: ```js run let a = 2 + 2; switch (a) { case 3: alert( 'Too small' ); *!* case 4: alert( 'Exactly!' ); case 5: alert( 'Too big' ); default: alert( "I don't know such values" ); */!* } ``` Dans l'exemple ci-dessus, nous verrons l'exécution séquentielle de trois `alert` : ```js alert( 'Exactly!' ); alert( 'Too big' ); alert( "I don't know such values" ); ``` ````smart header="Toute expression peut être un argument `switch/case`" `Switch` et `case` permettent des expressions arbitraires. Par exemple : ```js run let a = "1"; let b = 0; switch (+a) { *!* case b + 1: alert("this runs, because +a is 1, exactly equals b+1"); break; */!* default: alert("this doesn't run"); } ``` Ici `+a` donne `1`, qui est comparé à `b + 1` dans le `case`, et le code correspondant est exécuté. ```` ## Groupement de "case" Plusieurs variantes de `case` partageant le même code peuvent être regroupées. Par exemple, si nous voulons que le même code soit exécuté pour les `case 3` et `case 5` : ```js run no-beautify let a = 2 + 2; switch (a) { case 4: alert('Right!'); break; *!* case 3: // (*) grouped two cases case 5: alert('Wrong!'); alert("Why don't you take a math class?"); break; */!* default: alert('The result is strange. Really.'); } ``` Maintenant, les `3` et `5` affichent le même message. La possibilité de "grouper" les `case` est un effet secondaire de la façon dont le `switch/case` fonctionne sans `break`. Ici, l’exécution du `case 3` commence à partir de la ligne `(*)` et passe par le `case 5`, car il n’y a pas de `break`. ## Le type compte Soulignons que le contrôle d’égalité est toujours strict. Les valeurs doivent être du même type pour correspondre. Par exemple, considérons le code suivant : ```js run let arg = prompt("Enter a value?"); switch (arg) { case '0': case '1': alert( 'One or zero' ); break; case '2': alert( 'Two' ); break; case 3: alert( 'Never executes!' ); break; default: alert( 'An unknown value' ); } ``` 1. Pour `0`, `1`, la première `alert` est exécutée. 2. Pour `2`, la deuxième `alert` est exécutée. 3. Mais pour `3`, le résultat du prompt est une chaîne de caractères `"3"`, ce qui n’est pas strictement égal `===` au chiffre `3`. Nous avons donc un code mort dans le `case 3` ! La variante par défaut sera donc exécutée. ================================================ FILE: 1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md ================================================ Aucune différence. Dans les deux cas, `return confirm('Did parents allow you?')` s'exécute exactement lorsque la condition `if` est fausse. ================================================ FILE: 1-js/02-first-steps/15-function-basics/1-if-else-required/task.md ================================================ importance: 4 --- # Est-ce que "else" est requis ? La fonction suivante renvoie `true` si le paramètre `age` est supérieur à `18`. Sinon, il demande une confirmation et renvoie son résultat : ```js function checkAge(age) { if (age > 18) { return true; *!* } else { // ... return confirm('Did parents allow you?'); } */!* } ``` La fonction fonctionnera-t-elle différemment si `else` est supprimé ? ```js function checkAge(age) { if (age > 18) { return true; } *!* // ... return confirm('Did parents allow you?'); */!* } ``` Existe-t-il une différence dans le comportement de ces deux variantes ? ================================================ FILE: 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md ================================================ En utilisant un opérateur point d’interrogation `'?'` : ```js function checkAge(age) { return (age > 18) ? true : confirm('Did parents allow you?'); } ``` En utilisant OU `||` (la variante la plus courte) : ```js function checkAge(age) { return (age > 18) || confirm('Did parents allow you?'); } ``` Notez que les parenthèses autour de `age > 18` ne sont pas obligatoires ici. Elles existent pour une meilleure lisibilité. ================================================ FILE: 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md ================================================ importance: 4 --- # Réécrivez la fonction en utilisant '?' ou '||' La fonction suivante renvoie `true` si le paramètre `age` est supérieur à `18`. Sinon, il demande une confirmation et renvoie le résultat. ```js function checkAge(age) { if (age > 18) { return true; } else { return confirm('Did parents allow you?'); } } ``` Réécrivez-le, pour effectuer la même chose, mais sans `if`, et en une seule ligne. Faites deux variantes de `checkAge` : 1. En utilisant un opérateur point d'interrogation `?` 2. En utilisant OU `||` ================================================ FILE: 1-js/02-first-steps/15-function-basics/3-min/solution.md ================================================ Une solution utilisant `if` : ```js function min(a, b) { if (a < b) { return a; } else { return b; } } ``` Une solution utilisant l'opérateur point d'interrogation `'?'` : ```js function min(a, b) { return a < b ? a : b; } ``` P.S. Dans le cas d'une égalité `a == b`, peu importe ce qu'il faut retourner. ================================================ FILE: 1-js/02-first-steps/15-function-basics/3-min/task.md ================================================ importance: 1 --- # Fonction min(a, b) Ecrivez une fonction `min(a, b)` qui renvoie le plus petit des deux nombres `a` et `b`. Par exemple : ```js min(2, 5) == 2 min(3, -1) == -1 min(1, 1) == 1 ``` ================================================ FILE: 1-js/02-first-steps/15-function-basics/4-pow/solution.md ================================================ ```js run demo function pow(x, n) { let result = x; for (let i = 1; i < n; i++) { result *= x; } return result; } let x = prompt("x?", ''); let n = prompt("n?", ''); if (n < 1) { alert(`Power ${n} is not supported, use a positive integer`); } else { alert( pow(x, n) ); } ``` ================================================ FILE: 1-js/02-first-steps/15-function-basics/4-pow/task.md ================================================ importance: 4 --- # Fonction pow(x,n) Ecrivez une fonction `pow(x, n)` qui renvoie `x` à la puissance `n`. Ou, autrement dit, multiplie `x` par lui-même `n` fois et renvoie le résultat. ```js pow(3, 2) = 3 * 3 = 9 pow(3, 3) = 3 * 3 * 3 = 27 pow(1, 100) = 1 * 1 * ...* 1 = 1 ``` Créez une page Web qui demande (`prompt`)`x` et `n`, puis affiche le résultat de `pow(x, n)`. [demo] P.S. Dans cette tâche, la fonction ne doit prendre en charge que les valeurs naturelles de `n` : entiers supérieurs à `1`. ================================================ FILE: 1-js/02-first-steps/15-function-basics/article.md ================================================ # Fonctions Très souvent, nous devons effectuer une action similaire à plusieurs endroits du script. Par exemple, nous devons afficher un beau message lorsqu'un visiteur se connecte, se déconnecte et peut-être ailleurs. Les fonctions sont les principales "composantes" du programme. Ils permettent au code d'être appelé plusieurs fois sans répétition. Nous avons déjà vu des exemples de fonctions intégrées, telles que `alert(message)`, `prompt(message, default)` et `confirm(question)`. Mais nous pouvons aussi créer nos propres fonctions. ## Déclaration de fonction Pour créer une fonction, nous pouvons utiliser une *déclaration de fonction*. Cela ressemble à ceci : ```js function showMessage() { alert( 'Hello everyone!' ); } ``` Le mot-clé `function` commence en premier, puis le *nom de la fonction*, puis une liste de *paramètres* entre les parenthèses (séparés par des virgules, vides dans l'exemple ci-dessus, nous verrons des exemples plus tard) et enfin le code de la fonction, également appelé "le corps de la fonction", entre des accolades. ```js function name(parameter1, parameter2, ... parameterN) { // body } ``` Notre nouvelle fonction peut être appelée par son nom : `showMessage()`. Par exemple : ```js run function showMessage() { alert( 'Hello everyone!' ); } *!* showMessage(); showMessage(); */!* ``` L'appel `showMessage()` exécute le code de la fonction. Ici, nous verrons le message deux fois, parce qu'on l'appelle deux fois. Cet exemple illustre clairement l’un des principaux objectifs des fonctions: éviter la duplication de code. Si nous devons un jour modifier le message ou son affichage, il suffit de modifier le code à un endroit: la fonction qui le renvoie. ## Variables locales Une variable déclarée à l'intérieur d'une fonction n'est visible qu'à l'intérieur de cette fonction. Par exemple : ```js run function showMessage() { *!* let message = "Hello, I'm JavaScript!"; // variable locale */!* alert( message ); } showMessage(); // Hello, I'm JavaScript! alert( message ); // <-- Erreur! La variable est locale à la fonction ``` ## Variables externes Une fonction peut également accéder à une variable externe, par exemple : ```js run no-beautify let *!*userName*/!* = 'John'; function showMessage() { let message = 'Hello, ' + *!*userName*/!*; alert(message); } showMessage(); // Hello, John ``` La fonction a un accès complet à la variable externe. Cela peut aussi la modifier. Par exemple : ```js run let *!*userName*/!* = 'John'; function showMessage() { *!*userName*/!* = "Bob"; // (1) changé la variable externe let message = 'Hello, ' + *!*userName*/!*; alert(message); } alert( userName ); // *!*John*/!* avant l'appel de fonction showMessage(); alert( userName ); // *!*Bob*/!*, la valeur a été modifiée par la fonction ``` La variable externe n’est utilisée que s’il n’y a pas de variable locale. Si une variable du même nom est déclarée à l'intérieur de la fonction, elle *eclipsera* la variable externe. Par exemple, dans le code ci-dessous, la fonction utilise le nom `userName` local. L'externe est ignoré : ```js run let userName = 'John'; function showMessage() { *!* let userName = "Bob"; // déclarer une variable locale */!* let message = 'Hello, ' + userName; // *!*Bob*/!* alert(message); } // la fonction créera et utilisera son propre userName showMessage(); alert( userName ); // *!*John*/!*, inchangé, la fonction n'a pas accédé à la variable externe ``` ```smart header="Variables globales" Les variables déclarées en dehors de toute fonction, telle que `userName` externe dans le code ci-dessus, sont appelées *globales*. Les variables globales sont visibles depuis n'importe quelle fonction (sauf si elles sont masquées par les variables locales). C'est une bonne pratique de minimiser l'utilisation de variables globales. Le code moderne a peu ou pas de variable globales. La plupart des variables résident dans leurs fonctions. Parfois, cependant, ils peuvent être utiles pour stocker des données au niveau du projet. ``` ## Arguments Nous pouvons transmettre des données arbitraires à des fonctions à l'aide de paramètres. Dans l'exemple ci-dessous, la fonction a deux paramètres: `from` et `text`. ```js run function showMessage(*!*from, text*/!*) { // arguments : from, text alert(from + ': ' + text); } *!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*) *!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**) ``` Lorsque la fonction est appelée dans les lignes `(*)` et `(**)`, les valeurs données sont copiées dans les variables locales `from` et `text`. Ensuite, la fonction les utilise. Voici un autre exemple: nous avons une variable `from` et la transmettons à la fonction. Remarque : la fonction change `from`, mais le changement n'est pas visible à l'extérieur, car une fonction obtient toujours une copie de la valeur : ```js run function showMessage(from, text) { *!* from = '*' + from + '*'; // améliore l'apparence de "from" */!* alert( from + ': ' + text ); } let from = "Ann"; showMessage(from, "Hello"); // *Ann*: Hello // la valeur de "from" est la même, la fonction a modifié une copie locale alert( from ); // Ann ``` Lorsqu'une valeur est passée en tant que paramètre de fonction, elle est également appelée *argument*. En d'autres termes, pour mettre ces termes au clair : - Un paramètre est la variable répertoriée entre parenthèses dans la fonction déclaration (c'est un terme du temps de la déclaration). - Un argument est la valeur qui est transmise à la fonction lorsqu'elle est appelée (c'est un terme du temps de l'appel). Nous déclarons des fonctions en listant leurs paramètres, puis les appelons en passant des arguments. Dans l'exemple ci-dessus, on pourrait dire : "la fonction `showMessage` est déclarée avec deux paramètres, puis appelée avec deux arguments : `from` et `"Hello"`. ## Les valeurs par défaut Si une fonction est appelée, mais qu'aucun argument n'est fourni, alors la valeur correspondante devient `undefined`. Par exemple, la fonction `showMessage(from, text)` mentionnée précédemment peut être appelée avec un seul argument : ```js showMessage("Ann"); ``` Ce n'est pas une erreur. Un tel appel produirait `"*Ann*: undefined"`. Comme la valeur de `text` n'est pas transmise, elle devient `undefined`. Nous pouvons spécifier la valeur dite "par défaut" (à utiliser si omise) pour un paramètre dans la déclaration de fonction, en utilisant `=` : ```js run function showMessage(from, *!*text = "no text given"*/!*) { alert( from + ": " + text ); } showMessage("Ann"); // Ann: no text given ``` Maintenant, si le paramètre `text` n'est pas passé, il obtiendra la valeur `"no text given"`. La valeur par défaut saute également si le paramètre existe, mais est strictement égal à `undefined`, comme ceci : ```js showMessage("Ann", undefined); // Ann: no text given ``` Ici, `"no text given"` est une chaîne de caractères, mais il peut s'agir d'une expression plus complexe, qui n'est évaluée et affectée que si le paramètre est manquant. Donc, cela est également possible : ```js run function showMessage(from, text = anotherFunction()) { // anotherFunction() est exécuté uniquement si aucun texte n'est fourni // son résultat devient la valeur de text } ``` ```smart header="Évaluation des paramètres par défaut" En JavaScript, un paramètre par défaut est évalué chaque fois que la fonction est appelée sans le paramètre correspondant. Dans l'exemple ci-dessus, `anotherFunction()` n'est pas du tout appelé, si le paramètre `text` est fourni. D'un autre côté, il est appelé indépendamment à chaque fois que `text` est manquant. ``` ````smart header="Paramètres par défaut dans l'ancien code JavaScript" Il y a plusieurs années, JavaScript ne prenait pas en charge la syntaxe des paramètres par défaut. Les gens ont donc utilisé d'autres moyens pour les spécifier. De nos jours, on peut les croiser dans d'anciens scripts. Par exemple, une vérification explicite pour `undefined` : ```js function showMessage(from, text) { *!* if (text === undefined) { text = 'no text given'; } */!* alert( from + ": " + text ); } ``` ...Ou en utilisant l'opérateur `||` : ```js function showMessage(from, text) { // Si la valeur du texte est fausse, attribuez la valeur par défaut // cela suppose que text == "" est identique à pas de texte du tout text = text || 'no text given'; ... } ``` ```` ### Paramètres par défaut alternatifs Il est parfois judicieux de définir des valeurs par défaut pour les paramètres non pas dans la fonction déclaration, mais à un stade ultérieur, lors de son exécution. Nous pouvons vérifier si le paramètre est passé lors de l'exécution de la fonction, en le comparant avec `undefined` : ```js run function showMessage(text) { // ... *!* if (text === undefined) { // si le paramètre est manquant text = 'empty message'; } */!* alert(text); } showMessage(); // empty message ``` ...Ou nous pourrions utiliser l'opérateur `||` : ```js function showMessage(text) { // if text is undefined or otherwise falsy, set it to 'empty' text = text || 'empty'; ... } ``` Les moteurs JavaScript modernes prennent en charge [l'opérateur de coalescence des nuls](info:nullish-coalescing-operator) `??`, c'est mieux quand des valeurs fausses, telles que `0`, sont considérées comme "normales" : ```js run function showCount(count) { // if count is undefined or null, show "unknown" alert(count ?? "unknown"); } showCount(0); // 0 showCount(null); // unknown showCount(); // unknown ``` ## Renvoyer une valeur Une fonction peut renvoyer une valeur dans le code appelant en tant que résultat. L'exemple le plus simple serait une fonction qui additionne deux valeurs : ```js run no-beautify function sum(a, b) { *!*return*/!* a + b; } let result = sum(1, 2); alert( result ); // 3 ``` La directive `return` peut être n'importe où dans la fonction. Lorsque l'exécution le permet, la fonction s'arrête et la valeur est renvoyée au code appelant (affecté à `result` ci-dessus). Il peut y avoir plusieurs occurrences de `return` dans une seule fonction. Par exemple : ```js run function checkAge(age) { if (age >= 18) { *!* return true; */!* } else { *!* return confirm('Do you have permission from your parents?'); */!* } } let age = prompt('How old are you?', 18); if ( checkAge(age) ) { alert( 'Access granted' ); } else { alert( 'Access denied' ); } ``` Il est possible d'utiliser `return` sans valeur. Cela entraîne la sortie immédiate de la fonction. Par exemple : ```js function showMovie(age) { if ( !checkAge(age) ) { *!* return; */!* } alert( "Showing you the movie" ); // (*) // ... } ``` Dans le code ci-dessus, si `checkAge(age)` renvoie `false`, alors `ShowMovie` n’effectuera pas l’`alert`. ````smart header="Une fonction avec un `return` vide ou rien dedans retourne `undefined`" ```js run function doNothing() { /* vide */ } alert( doNothing() === undefined ); // true ``` Un `return` vide est également identique à un `return undefined` : ```js run function doNothing() { return; } alert( doNothing() === undefined ); // true ``` ```` ````warn header="N'ajoutez jamais de nouvelle ligne entre `return` et la valeur" Pour une longue expression dans `return`, il pourrait être tentant de la mettre sur une ligne séparée, comme ceci : ```js return (some + long + expression + or + whatever * f(a) + f(b)) ``` Cela ne fonctionne pas, car JavaScript suppose un point-virgule après le `return`. Cela fonctionnera comme : ```js return*!*;*/!* (some + long + expression + or + whatever * f(a) + f(b)) ``` Donc, cela devient effectivement un retour vide. Si nous voulons que l'expression renvoyée recouvre plusieurs lignes, nous devons la démarrer à la même ligne que `return`. Ou du moins mettre les parenthèses d'ouverture comme suit : ```js return ( some + long + expression + or + whatever * f(a) + f(b) ) ``` Et cela fonctionnera comme prévu. ```` ## Nommer une fonction [#function-naming] Les fonctions sont des actions. Donc, leur nom est généralement un verbe. Il convient de décrire brièvement, mais aussi précisément que possible, le rôle de la fonction. Pour qu'une personne qui lit le code reçoive le bon indice. C'est une pratique répandue de commencer une fonction avec un préfixe verbal qui décrit vaguement l'action. Il doit exister un accord au sein de l'équipe sur la signification des préfixes. Par exemple, les fonctions qui commencent par `"show"` affichent généralement quelque chose. Fonction commençant par… - `"get…"` -- retourne une valeur, - `"calc…"` -- calcule quelque chose, - `"create…"` -- créer quelque chose, - `"check…"` -- vérifie quelque chose et retourne un booléen, etc. Exemples de quelques noms : ```js no-beautify showMessage(..) // affiche un message getAge(..) // renvoie l'âge (l'obtient en quelque sorte) calcSum(..) // calcule une somme et renvoie le résultat createForm(..) // crée un formulaire (et le retourne généralement) checkPermission(..) // vérifie une permission, retourne vrai/faux ``` Avec les préfixes en place, un coup d'œil sur un nom de fonction permet de comprendre le type de travail effectué et le type de valeur renvoyé. ```smart header="Une fonction - une action" Une fonction doit faire exactement ce qui est suggéré par son nom, pas plus. Deux actions indépendantes méritent généralement deux fonctions, même si elles sont généralement appelées ensemble (dans ce cas, nous pouvons créer une troisième fonction qui appelle ces deux actions). Quelques exemples de violation de cette règle : - `getAge` -- serait mauvais si elle affichait une `alert` avec l'âge (devrait seulement obtenir). - `createForm` -- serait mauvais s’il modifiait le document en y ajoutant un formulaire (il ne devrait que le créer et le renvoyer). - `checkPermission` -- serait mauvais s'il affiche le message d'accès accordé/refusé (doit uniquement effectuer la vérification et renvoyer le résultat). Ces exemples supposent des significations communes de préfixes. Vous et votre équipe êtes libres de vous entendre sur d'autres sens, mais ils ne sont généralement pas très différents. Dans tous les cas, vous devez bien comprendre ce que signifie un préfixe, ce qu'une fonction préfixée peut et ne peut pas faire. Toutes les fonctions ayant le même préfixe doivent obéir aux règles. Et l'équipe devrait partager ces connaissances. ``` ```smart header="Noms de fonction ultra-courts" Les fonctions utilisées *très souvent* portent parfois des noms ultra-courts. Par exemple le framework [jQuery](https://jquery.com) définit une fonction avec `$`. La librairie [LoDash](https://lodash.com/) a nommé sa fonction principale `_`. Ce sont des exceptions. En règle générale, les noms de fonctions doivent être concis et descriptifs. ``` ## Fonctions == Commentaires Les fonctions doivent être courtes et faire exactement une seule chose. Si cette chose est conséquente, il vaut peut-être la peine de scinder la fonction en quelques fonctions plus petites. Parfois, suivre cette règle peut ne pas être aussi facile, mais c’est définitivement une bonne pratique. Une fonction distincte est non seulement plus facile à tester et à déboguer -- son existence même est un excellent commentaire! Par exemple, comparez les deux fonctions `showPrimes(n)` ci-dessous. Chacune extrait les [nombres premiers](https://fr.wikipedia.org/wiki/Nombre_premier) jusqu'à `n`. La première variante utilise un label : ```js function showPrimes(n) { nextPrime: for (let i = 2; i < n; i++) { for (let j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; } alert( i ); // un nombre premier } } ``` La deuxième variante utilise une fonction supplémentaire `isPrime(n)` pour tester la primalité : ```js function showPrimes(n) { for (let i = 2; i < n; i++) { *!*if (!isPrime(i)) continue;*/!* alert(i); // un nombre premier } } function isPrime(n) { for (let i = 2; i < n; i++) { if ( n % i == 0) return false; } return true; } ``` La deuxième variante est plus facile à comprendre, n’est-ce pas ? Au lieu du bloc de code, nous voyons le nom de l'action (`isPrime`). Parfois, les gens se réfèrent à ce code comme étant *auto-descriptif*. Des fonctions peuvent donc être créées même si nous n’avons pas l’intention de les réutiliser. Ils structurent le code et le rendent lisible. ## Résumé Une déclaration de fonction ressemble à ceci : ```js function name(parameters, delimited, by, comma) { /* code */ } ``` - Les valeurs transmises à une fonction en tant que paramètres sont copiées dans ses variables locales. - Une fonction peut accéder à des variables externes. Mais cela ne fonctionne que de l'intérieur. Le code en dehors de la fonction ne voit pas ses variables locales. - Une fonction peut renvoyer une valeur. Si ce n'est pas le cas, le résultat est `undefined`. Pour rendre le code propre et facile à comprendre, il est recommandé d’utiliser principalement des variables et des paramètres locaux dans la fonction, et non des variables externes. Il est toujours plus facile de comprendre une fonction qui possède des paramètres, fonctionne avec eux et renvoie un résultat, plutôt qu’une fonction qui ne comporte aucun paramètre, mais modifie des variables externes comme un effet secondaire. Nommage de fonction : - Un nom doit clairement décrire le rôle de la fonction. Lorsque nous voyons un appel de fonction dans le code, un bon nom nous donne instantanément une compréhension de ce qu’elle fait et de ce qu’elle retourne. - Une fonction est une action, les noms de fonctions sont donc généralement verbaux. - Il existe de nombreux préfixes de fonctions bien connus, tels que `create…`, `show…`, `get…`, `check…` et ainsi de suite. Utilisez-les pour indiquer ce que fait une fonction. Les fonctions sont les principaux éléments constitutifs des scripts. Maintenant que nous avons couvert les bases, nous pouvons donc commencer à les créer et les utiliser. Mais ce n’est que le début du chemin. Nous allons y revenir plusieurs fois, en approfondissant leurs fonctionnalités avancées. ================================================ FILE: 1-js/02-first-steps/16-function-expressions/article.md ================================================ # Fonctions Expressions En JavaScript, une fonction n'est pas une "structure de langage magique", mais un type de valeur particulier. La syntaxe utilisée précédemment s'appelle une *déclaration de fonction* : ```js function sayHi() { alert( "Hello" ); } ``` Il existe une autre syntaxe pour créer une fonction appelée *Expression de Fonction*. Cela nous permet de créer une nouvelle fonction au milieu de n'importe quelle expression. Par exemple : ```js let sayHi = function() { alert( "Hello" ); }; ``` Ici, nous pouvons voir une variable `sayHi` obtenir une valeur, la nouvelle fonction, créée en tant que `function() { alert("Hello"); }`. Comme la création de la fonction se produit dans le contexte de l'expression d'affectation (à droite de `=`), il s'agit d'une *Fonction Expression*. Veuillez noter qu'il n'y a pas de nom après le mot clé `function`. L'omission d'un nom est autorisée pour les fonctions expressions. Ici, nous l'assignons immédiatement à la variable, donc la signification de ces exemples de code est la même : "créer une fonction et la mettre dans la variable `sayHi`". Dans des situations plus avancées, que nous verrons plus tard, une fonction peut être créée et immédiatement appelée ou planifiée pour une exécution ultérieure, non stockée nulle part, restant ainsi anonyme. ## La fonction est une valeur Répétons-le : quelle que soit la manière dont la fonction est créée, une fonction est une valeur. Les deux exemples ci-dessus stockent une fonction dans la variable `sayHi`. La signification de ces exemples de code est la même : "créer une fonction et la placer dans la variable `sayHi`". Nous pouvons même afficher cette valeur en utilisant `alert` : ```js run function sayHi() { alert( "Hello" ); } *!* alert( sayHi ); // affiche le code de la fonction */!* ``` Veuillez noter que la dernière ligne n'exécute pas la fonction, car il n'y a pas de parenthèses après `sayHi`. Il y a des langages de programmation où toute mention d'un nom de fonction provoque son exécution, mais JavaScript n'est pas comme ça. En JavaScript, une fonction est une valeur, nous pouvons donc la traiter comme une valeur. Le code ci-dessus montre sa représentation sous forme de chaîne de caractères, qui est le code source. Certes, une fonction est une valeur spéciale, en ce sens que nous pouvons l'appeler comme cela `sayHi()`. Mais c’est toujours une valeur. Nous pouvons donc travailler avec comme avec d’autres types de valeurs. Nous pouvons copier une fonction dans une autre variable : ```js run no-beautify function sayHi() { // (1) créer alert( "Hello" ); } let func = sayHi; // (2) copier func(); // Hello // (3) exécuter la copie (ça fonctionne)! sayHi(); // Hello // cela fonctionne toujours aussi (pourquoi pas) ``` Voici ce qui se passe ci-dessus en détail : 1. La Déclaration de Fonction `(1)` crée la fonction et la place dans la variable nommée `sayHi`. 2. La ligne `(2)` la copie dans la variable `func`. Veuillez noter à nouveau : il n'y a pas de parenthèses après `sayHi`. S'il y en avait, alors `func = sayHi()` écrirait *le résultat de l'appel* `sayHi()` dans `func`, et non *la fonction* `sayHi` elle-même. 3. Maintenant, la fonction peut être appelée à la fois en tant que `sayHi()` et `func()`. Nous aurions aussi pu utiliser une Fonction Expression pour déclarer `sayHi`, à la première ligne : ```js let sayHi = function() { // (1) create alert( "Hello" ); }; let func = sayHi; // ... ``` Tout fonctionnerait de la même manière. ````smart header="Pourquoi y a-t-il un point-virgule à la fin ?" Il peut y avoir une question, pourquoi la Fonction Expression a un point-virgule `;` à la fin, et la Fonction Déclaration non : ```js function sayHi() { // ... } let sayHi = function() { // ... }*!*;*/!* ``` La réponse est simple : une expression de fonction est créée ici en tant que `function(…) {…}` à l'intérieur de l'instruction d'affectation : `let sayHi = …;`. Le point-virgule `;` est recommandé à la fin de l'instruction, il ne fait pas partie de la syntaxe de la fonction. Le point-virgule serait là pour une affectation plus simple, telle que `let sayHi = 5;`, et il est également là pour une affectation de fonction. ```` ## Fonctions callback (de rappel) Examinons plus d’exemples de fonctions passées en tant que valeurs et utilisant des expressions de fonction. Nous allons écrire une fonction `ask(question, yes, no)` avec trois paramètres : `question` : Texte de la question `yes` : Fonction à exécuter si la réponse est “Yes” `no` : Fonction à exécuter si la réponse est “No” La fonction doit poser la question et, en fonction de la réponse de l'utilisateur, appeler `yes()` ou `no()` : ```js run *!* function ask(question, yes, no) { if (confirm(question)) yes(); else no(); } */!* function showOk() { alert( "You agreed." ); } function showCancel() { alert( "You canceled the execution." ); } // utilisation: les fonctions showOk, showCancel sont transmises en tant qu'arguments à ask ask("Do you agree?", showOk, showCancel); ``` En pratique, ces fonctions sont très utiles. La principale différence entre une demande réelle (`ask`) et l'exemple ci-dessus est que les fonctions réelles utilisent des moyens d'interagir avec l'utilisateur plus complexes que la simple confirmation (`confirm`). Dans le navigateur, une telle fonction dessine généralement une belle fenêtre de questions. Mais c'est une autre histoire. **Les arguments `showOk` et `showCancel` de `ask` s'appellent des *fonctions callback* (fonctions de rappel) ou simplement des *callbacks* (rappels).** L'idée est que nous passions une fonction et attendions qu'elle soit "rappelée" plus tard si nécessaire. Dans notre cas, `showOk` devient le rappel pour la réponse "oui" et `showCancel` pour la réponse "non". Nous pouvons utiliser les Fonctions Expressions pour écrire la même fonction mais plus courte : ```js run no-beautify function ask(question, yes, no) { if (confirm(question)) yes(); else no(); } *!* ask( "Do you agree?", function() { alert("You agreed."); }, function() { alert("You canceled the execution."); } ); */!* ``` Ici, les fonctions sont déclarées directement dans l'appel `ask(...)`. Elles n'ont pas de nom et sont donc appelées *anonymes*. De telles fonctions ne sont pas accessibles en dehors de `ask` (car elles ne sont pas affectées à des variables), mais c’est exactement ce que nous voulons ici. Ce genre de code apparaît dans nos scripts très naturellement, c’est dans l’esprit de JavaScript. ```smart header="Une fonction est une valeur représentant une \"action\"" Des valeurs régulières telles que des chaînes de caractères ou des nombres représentent les *données*. Une fonction peut être perçue comme une *action*. Nous pouvons tout aussi bien la passer en tant que variable ou l'exécuter si nous le voulons. ``` ## Fonction Expression vs Fonction Déclaration Formulons les principales différences entre les déclarations de fonction et les expressions de fonctions. Tout d'abord, la syntaxe : comment les différencier dans le code. - *La Fonction Déclaration* une fonction déclarée séparément dans le flux de code principal. ```js // Function Declaration function sum(a, b) { return a + b; } ``` - *La Fonction Expression :* une fonction créée dans une expression ou dans une autre construction de syntaxe. Ici, la fonction est créée à droite de "l'affectation de l'expression" `=` : ```js // Function Expression let sum = function(a, b) { return a + b; }; ``` La différence la plus subtile est *quand* une fonction est créée par le moteur JavaScript. **Une Fonction Expression est créée lorsque l’exécution l’atteint.** Une fois que le flux d'exécution passe à droite de l'affectation, `let sum = function…` , la fonction est créée et peut désormais être utilisée (assignée, appelée, etc.). Les déclarations de fonction sont différentes. **Une Fonction Déclaration peut être appelée plus tôt que sa définition.** Par exemple, une fonction déclaration globale est visible dans tout le script, peu importe où elle se trouve. Cela est dû aux algorithmes internes. Lorsque JavaScript se prépare à exécuter le script, il recherche d'abord les fonctions déclarations globales et les crée. Nous pouvons considérer cela comme une "étape d'initialisation". Une fois que toutes les déclarations de fonctions ont été traitées, le reste du code est exécuté. Ainsi, il a accès à ces fonctions pour les appeler. Par exemple, cela fonctionne : ```js run refresh untrusted *!* sayHi("John"); // Hello, John */!* function sayHi(name) { alert( `Hello, ${name}` ); } ``` La déclaration de fonction `sayHi` est créée lorsque JavaScript est sur le point de démarrer le script et est visible partout dans celui-ci. … S’il s’agissait d’une Fonction Expression, cela ne fonctionnerait pas : ```js run refresh untrusted *!* sayHi("John"); // erreur! */!* let sayHi = function(name) { // (*) plus de magie alert( `Hello, ${name}` ); }; ``` Les expressions de fonction sont créées lorsque l'exécution les atteint. Cela ne se produirait que dans la ligne `(*)`. Trop tard. Une autre particularité des Fonctions Declaration est leur portée de bloc. **En mode strict, quand une Fonction Déclaration se trouve dans un bloc de code, elle est visible partout dans ce bloc. Mais pas en dehors.** Par exemple, imaginons que nous ayons besoin de déclarer une fonction `welcome()` en fonction de la variable d’`age` obtenue lors de l’exécution. Et ensuite, nous prévoyons de l'utiliser quelque temps plus tard. Si nous utilisons la fonction déclaration, cela ne fonctionnera pas comme prévu : ```js run let age = prompt("Quel est votre age ?", 18); // déclarer conditionnellement une fonction if (age < 18) { function welcome() { alert("Hello!"); } } else { function welcome() { alert("Greetings!"); } } // ...l'utiliser plus tard *!* welcome(); // Error: welcome is not defined */!* ``` C’est parce qu’une déclaration de fonction n’est visible que dans le bloc de code dans lequel elle réside. Voici un autre exemple : ```js run let age = 16; // prendre 16 comme exemple if (age < 18) { *!* welcome(); // \ (exécution) */!* // | function welcome() { // | alert("Hello!"); // | La déclaration de fonction est disponible } // | partout dans le bloc où elle est déclarée // | *!* welcome(); // / (exécution) */!* } else { function welcome() { alert("Greetings!"); } } // Ici, nous sommes en dehors des accolades, // nous ne pouvons donc pas voir les déclarations de fonction faites à l'intérieur de celles-ci. *!* welcome(); // Error: welcome is not defined */!* ``` Que pouvons-nous faire pour rendre `welcome` visible en dehors de `if` ? L'approche correcte consisterait à utiliser une expression de fonction et à attribuer `welcome` à la variable déclarée en dehors de `if` et offrant la visibilité appropriée. Ce code fonctionne comme prévu : ```js run let age = prompt("What is your age?", 18); let welcome; if (age < 18) { welcome = function() { alert("Hello!"); }; } else { welcome = function() { alert("Greetings!"); }; } *!* welcome(); // ok maintenant */!* ``` Ou nous pourrions simplifier encore davantage en utilisant un opérateur conditionnel ternaire `?` : ```js run let age = prompt("What is your age?", 18); let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); }; *!* welcome(); // ok maintenant */!* ``` ```smart header="Quand choisir la fonction déclaration par rapport à la fonction expression ?" En règle générale, lorsque nous devons déclarer une fonction, la première chose à prendre en compte est la syntaxe de la fonction déclaration. Cela donne plus de liberté dans l'organisation de notre code, car nous pouvons appeler de telles fonctions avant qu'elles ne soient déclarées. C’est également meilleur pour la lisibilité, car il est plus facile de rechercher la `fonction f(…) {…}` dans le code que `let f = function(…) {…};`. Les fonction déclarations sont plus "accrocheuses". … Mais si une déclaration de fonction ne nous convient pas pour une raison quelconque (nous en avons vu un exemple ci-dessus), alors il convient d'utiliser une Fonction Expression. ``` ## Résumé - Les fonctions sont des valeurs. Elles peuvent être attribuées, copiées ou déclarées à n’importe quel endroit du code. - Si la fonction est déclarée comme une instruction distincte dans le flux de code principal, cela s'appelle une "déclaration de fonction". - Si la fonction est créée dans le cadre d’une expression, elle est appelée "expression de fonction". - Les déclarations de fonctions sont traitées avant l'exécution du bloc de code. Elles sont visibles partout dans le bloc. - Les expressions de fonction sont créées lorsque le flux d’exécution les atteint. Dans la plupart des cas, lorsque nous devons déclarer une fonction, une fonction déclaration est préférable parce qu'elle est visible avant la déclaration elle-même. Cela nous donne plus de flexibilité dans l'organisation du code et il est généralement plus lisible. Nous devrions donc utiliser une fonction expression uniquement lorsqu'une fonction déclaration n'est pas adaptée à la tâche. Nous en avons vu quelques exemples dans ce chapitre et nous en verrons d'autres à l'avenir. ================================================ FILE: 1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md ================================================ ```js run function ask(question, yes, no) { if (confirm(question)) yes(); else no(); } ask( "Do you agree?", *!* () => alert("You agreed."), () => alert("You canceled the execution.") */!* ); ``` Ça a l'air court et propre, non ? ================================================ FILE: 1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md ================================================ # Réécrire avec les fonctions fléchées Remplacez les expressions de fonction par des fonctions fléchées dans le code ci-dessous : ```js run function ask(question, yes, no) { if (confirm(question)) yes(); else no(); } ask( "Do you agree?", function() { alert("You agreed."); }, function() { alert("You canceled the execution."); } ); ``` ================================================ FILE: 1-js/02-first-steps/17-arrow-functions-basics/article.md ================================================ # Fonctions fléchées, les bases Il existe une syntaxe plus simple et concise pour créer des fonctions, c'est souvent mieux que les Expressions de Fonction. Les "fonctions fléchées" sont appelées ainsi pour leur syntaxe : ```js let func = (arg1, arg2, ..., argN) => expression; ``` Cela va créer une function `func` qui accepte les arguments `arg1...argN`, puis évalue l'`expression` sur le côté droit et retourne le résultat. C'est donc la version raccourcie de : ```js let func = function(arg1, arg2, ..., argN) { return expression; }; ``` Voyons un exemple concret : ```js run let sum = (a, b) => a + b; /* Cette fonction fléchée est la forme raccourcie de : let sum = function(a, b) { return a + b; }; */ alert( sum(1, 2) ); // 3 ``` Comme vous pouvez le voir `(a, b) => a + b` représente une fonction qui accepte 2 arguments nommés `a` et `b`. Lors de l'exécution, elle évalue l'expression `a + b` et retourne le résultat. - Pour un argument unique, alors les parenthèses autour du paramètre peuvent être omises, rendant la fonction encore plus courte. Par exemple: ```js run *!* let double = n => n * 2; // Similaire à : let double = function(n) { return n * 2 } */!* alert( double(3) ); // 6 ``` - S’il n’y a pas d’arguments, les parenthèses seront alors vides, mais elles doivent êtres présentes : ```js run let sayHi = () => alert("Hello!"); sayHi(); ``` Les fonctions fléchées peuvent être utilisées de la même manière que les Expressions de Fonction. Par exemple pour créer une fonction dynamiquement : ```js run let age = prompt("What is your age?", 18); let welcome = (age < 18) ? () => alert("Hello!") : () => alert("Greetings!"); welcome(); // ok now ``` Les fonctions fléchées peuvent paraître étranges et peu lisibles au début, mais cela change rapidement avec les yeux s'habituant à cette structure. Elles sont très utiles pour des actions sur une ligne et que l'on est juste paresseux d'écrire d'autres mots. ## Les fonctions fléchées multiligne Les fonctions fléchées que nous avons vues jusqu'à présent étaient très simples. Elles ont pris des arguments à gauche de `=>`, les ont évalués et ont renvoyé l'expression de droite avec elles. Parfois nous avons besoin de plus de complexité, comme des expressions multiples ou des déclarations. Cela est possible avec des accolades les délimitant. Il faut ensuite utiliser un `return` à l'intérieur de celles-ci. Comme cela : ```js run let sum = (a, b) => { // Les accolades ouvre une fonction multiligne let result = a + b; *!* return result; // si nous utilisons des accolades, nous avons besoin d'un "return" explicite */!* }; alert( sum(1, 2) ); // 3 ``` ```smart header="Plus à venir" Nous nous arrêtons ici sur les fonctions fléchées pour leur syntaxe brève mais ce n'est pas tout ! Les fonctions fléchées ont d'autres particularités intéressantes. Pour les apprendre en profondeur, nous devons d'abord voir d'autres aspects de JavaScript, nous reviendrons donc aux fonctions fléchées plus tard dans le chapitre . Pour l'instant, nous pouvons les utiliser pour des actions sur une ligne ou des callbacks (rappels). ``` ## Résumé Les fonctions fléchées sont pratiques pour des actions simples, en particulier pour les one-liners. Ils se déclinent en deux variantes : 1. Sans accolades : `(...args) => expression` -- le côté droit est une expression : la fonction l'évalue et retourne le résultat. Les parenthèses peuvent être omises s'il n'y a qu'un seul argument, par ex. `n => n*2`. 2. Avec accolades : `(...args) => { body }` -- les accolades nous permet des déclarations multiples au sein de la fonction, mais nous devons ajouter un `return` explicite pour retourner quelque chose. ================================================ FILE: 1-js/02-first-steps/18-javascript-specials/article.md ================================================ # JavaScript specials Ce chapitre récapitule brièvement les fonctionnalités de JavaScript que nous avons apprises à ce jour, en accordant une attention particulière aux moments les plus subtils. ## Structure du code Les instructions sont délimitées par un point-virgule : ```js run no-beautify alert('Hello'); alert('World'); ``` Habituellement, un saut de ligne est également traité comme un séparateur, de sorte que cela fonctionnerait également : ```js run no-beautify alert('Hello') alert('World') ``` Cela s'appelle "insertion automatique de point-virgule". Parfois, cela ne fonctionne pas, par exemple : ```js run alert("Il y aura une erreur après ce message") [1, 2].forEach(alert) ``` La plupart des guides de style de code conviennent que nous devrions mettre un point-virgule après chaque déclaration. Les points-virgules ne sont pas nécessaires après les blocs de code `{...}` et les constructions de syntaxe les utilisant comme des boucles : ```js function f() { // aucun point-virgule nécessaire après la déclaration de la fonction } for(;;) { // pas de point-virgule nécessaire après la boucle } ``` … Mais même si nous pouvons mettre un point-virgule supplémentaire quelque part, ce n’est pas une erreur. Ce sera ignoré. Plus d'informations dans : . ## Mode strict Pour activer pleinement toutes les fonctionnalités de JavaScript moderne, nous devrions commencer les scripts avec `"use strict"`. ```js 'use strict'; ... ``` La directive doit être au sommet d'un script ou au début d'un corps de fonction. Sans `"use strict"`, tout fonctionne toujours, mais certaines fonctionnalités se comportent à l'ancienne, de manière "compatible". Nous préférons généralement le comportement moderne. Certaines fonctionnalités modernes du langage (telles que les classes que nous étudierons dans le futur) activent implicitement le mode strict. Plus d’informations dans : . ## Variables Peut être déclaré en utilisant : - `let` - `const` (constant, ne peut pas être changé) - `var` (à l'ancienne, nous le verrons plus tard) Un nom de variable peut inclure : - Lettres et chiffres, mais le premier caractère ne peut pas être un chiffre. - Les caractères `$` et `_` sont normaux, à égalité avec les lettres. - Les alphabets et les hiéroglyphes non latins sont également autorisés, mais ils ne sont généralement pas utilisés. Les variables sont typées dynamiquement. Elles peuvent stocker n'importe quelle valeur : ```js let x = 5; x = "John"; ``` Il y a 8 types de données : - `number` pour les nombres à virgule flottante et les nombres entiers, - `bigint` pour des nombres entiers de longueur arbitraire, - `string` pour les chaînes de caractères, - `boolean` pour les valeurs logiques : `true/false`, - `null` -- un type avec une seule valeur `null`, signifiant "vide" ou "n'existe pas", - `undefined` -- un type avec une seule valeur `undefined`, signifiant "non assigné", - `object` et `symbol` -- pour les structures de données complexes et les identifiants uniques, nous ne les avons pas encore appris. L'opérateur `typeof` renvoie le type d'une valeur, à deux exceptions près : ```js typeof null == "object" // erreur dans le langage typeof function(){} == "function" // les fonctions sont traitées spécialement ``` Plus d’informations dans : et . ## Interaction Nous utilisons un navigateur comme environnement de travail. Les fonctions de base de l'interface utilisateur sont les suivantes : [`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) : Posez une question et retournez soit ce que le visiteur a entré, soit `null` s'il clique sur "cancel". [`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) : Posez une `question` et suggérez de choisir entre Ok et Annuler. Le choix est retourné comme `true/false`. [`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) : Affiche un `message`. Toutes ces fonctions sont *modales*, elles suspendent l'exécution du code et empêchent le visiteur d'interagir avec la page tant qu'il n'a pas répondu. Par exemple : ```js run let userName = prompt("Your name?", "Alice"); let isTeaWanted = confirm("Do you want some tea?"); alert( "Visitor: " + userName ); // Alice alert( "Tea wanted: " + isTeaWanted ); // true ``` Plus d’informations dans : . ## Operateurs JavaScript prend en charge les opérateurs suivants : Arithmétique : Regulier : `* + - /`, aussi `%` pour le reste et `**` pour la puissance d'un nombre. Le binaire plus `+` concatène des chaînes de caractères. Et si l'un des opérandes est une chaîne de caractères, l'autre est également converti en chaîne de caractères : ```js run alert( '1' + 2 ); // '12', string alert( 1 + '2' ); // '12', string ``` Affectations : Il y a une assignation simple : `a = b` et des combinés comme `a *= 2`. Bitwise : Les opérateurs au niveau du bit fonctionnent avec des entiers 32 bits au niveau du bit le plus bas : voir la [doc](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) quand ils sont nécessaires. Conditionnel : Le seul opérateur avec trois paramètres : `cond ? resultA : resultB`. Si `cond` est vrai, retourne `resultA`, autrement `resultB`. Opérateurs logiques : ET logique `&&` et OU `||` effectuent une évaluation en circuit court puis renvoyent la valeur là où ils se sont arrêtés (pas nécessairement `true`/`false`). NOT logique `!` convertit l'opérande en type booléen et retourne la valeur inverse. L'opérateur de coalescence des nuls : L'opérateur `??` permet de choisir une valeur définie dans une liste de variables. Le résultat de `a ?? b` est `a` sauf s'il est `null`/`undefined`, alors `b`. Comparaisons : Le contrôle d’égalité `==` pour les valeurs de types différents les convertit en un nombre (sauf `null` et `undefined`, égales entre elles et rien d’autre), elles sont donc égales : ```js run alert( 0 == false ); // true alert( 0 == '' ); // true ``` D'autres comparaisons sont également converties en nombre. L’opérateur d’égalité stricte `===` ne fait pas la conversion : différents types signifient toujours différentes valeurs pour lui. Les valeurs `null` et `undefined` sont spéciales: elles sont égales `==` les unes aux autres et n’égalent rien d’autre. Les comparaisons supérieures/inférieures comparent des chaînes caractère par caractère, les autres types sont convertis en nombre. Autres opérateurs : Il y en a quelques autres, comme un opérateur de virgule. Plus d'informations dans : , , . ## Boucles - Nous avons couvert 3 types de boucles : ```js // 1 while (condition) { ... } // 2 do { ... } while (condition); // 3 for(let i = 0; i < 10; i++) { ... } ``` - La variable déclarée dans la boucle `for(let ...)` est visible uniquement à l'intérieur de la boucle. Mais nous pouvons aussi omettre `let` et réutiliser une variable existante. - Les directives `break/continue` permettent de sortir complètement de la boucle / de l'itération en cours. Utilisez des labels pour rompre les boucles imbriquées. Details dans : . Plus tard, nous étudierons plus de types de boucles pour traiter des objets. ## La construction "switch" La construction "switch" peut remplacer plusieurs vérifications `if`. Il utilise `===` (égalité stricte) pour les comparaisons. Par exemple : ```js run let age = prompt('Your age?', 18); switch (age) { case 18: alert("Won't work"); // le résultat de prompt est une chaîne de caractères, pas un nombre case "18": alert("This works!"); break; default: alert("Any value not equal to one above"); } ``` Details dans : . ## Fonctions Nous avons couvert trois manières de créer une fonction en JavaScript : 1. Déclaration de fonction: la fonction dans le flux de code principal ```js function sum(a, b) { let result = a + b; return result; } ``` 2. Expression de fonction : fonction dans le contexte d'une expression ```js let sum = function(a, b) { let result = a + b; return result; }; ``` 3. Fonctions fléchées : ```js // expression à droite let sum = (a, b) => a + b; // ou une syntaxe multiligne avec {...}, il faut return ici : let sum = (a, b) => { // ... return a + b; } // sans arguments let sayHi = () => alert("Hello"); // avec un seul argument let double = n => n * 2; ``` - Les fonctions peuvent avoir des variables locales: celles déclarées dans son corps ou sa liste de paramètres. Ces variables ne sont visibles qu'à l'intérieur de la fonction. - Les paramètres peuvent avoir des valeurs par défaut : `function sum(a = 1, b = 2) {...}`. - Les fonctions retournent toujours quelque chose. Si aucune instruction `return` n’est renvoyée, le résultat est `undefined`. Details : voir , . ## Plus à venir C’était une brève liste de fonctionnalités de JavaScript. Pour l’instant, nous n’avons étudié que les bases. Plus loin dans le tutoriel, vous trouverez plus de fonctionnalités spéciales et avancées de JavaScript. ================================================ FILE: 1-js/02-first-steps/index.md ================================================ # Fondamentaux JavaScript Apprenons les bases de la construction de scripts. ================================================ FILE: 1-js/03-code-quality/01-debugging-chrome/article.md ================================================ # Débogage dans le navigateur Avant d’écrire un code plus complexe, parlons de débogage. Le [Debugging](https://en.wikipedia.org/wiki/Debugging) est le processus de recherche et de correction des erreurs dans un script. Tous les navigateurs modernes et la plupart des autres environnements prennent en charge les outils de débogage - une interface utilisateur spéciale dans les outils de développement facilitant grandement le débogage. Cela permet également de tracer le code étape par étape pour voir ce qui se passe exactement. Nous allons utiliser Chrome ici, car il possède suffisamment de fonctionnalités, la plupart des autres navigateurs utilisent un processus similaire. ## Le volet "Sources" Votre version de Chrome peut sembler un peu différente, mais vous devez tout de même savoir ce qui est là. - Ouvrez la [page d'exemple](debugging/index.html) dans Chrome. - Activer les outils de développement avec `key:F12` (Mac: `key:Cmd+Opt+I`). - Séléctionner le volet `Sources`. Voici ce que vous devriez voir si vous le faites pour la première fois : ![](chrome-open-sources.svg) Le bouton ouvre le volet avec les fichiers. Cliquez dessus et sélectionnez `hello.js` dans l’arborescence. Voici ce qui devrait apparaître : ![](chrome-tabs.svg) Ici nous pouvons voir 3 parties : 1. Le volet **explorateur de fichiers** répertorie les fichiers HTML, JavaScript, CSS et autres fichiers, y compris les images jointes à la page. Des extensions Chrome peuvent également apparaître ici. 2. Le volet **Editeur de Code** affiche le code source. 3. Le volet **Débugueur JavaScript** est pour le débogage, nous l'explorerons bientôt. Maintenant, vous pouvez cliquer sur le même bouton à nouveau pour masquer la liste des ressources et laisser un peu d’espace au code. ## Console Si nous appuyons sur `key:Esc`, une console s'ouvre ci-dessous. Nous pouvons taper des commandes ici et appuyer sur `key:Entrée` pour les exécuter. Une fois une instruction exécutée, son résultat est présenté ci-dessous. Par exemple, ici `1+2` donne `3`, tandis que l'appel de fonction `hello("debugger")` ne renvoie rien, donc le résultat est `undefined` : ![](chrome-sources-console.svg) ## Breakpoints Examinons ce qui se passe dans le code de la [page d'exemple](debugging/index.html). Dans `hello.js`, cliquez sur le numéro de ligne `4`. Oui, sur le chiffre `4`, pas sur le code. Félicitations ! Vous avez défini un point d'arrêt. Veuillez également cliquer sur le numéro correspondant à la ligne `8`. Cela devrait ressembler à ceci (le bleu est l'endroit où vous devez cliquer) : ![](chrome-sources-breakpoint.svg) Un *breakpoint* est un point dans le code où le débogueur mettra automatiquement en pause l'exécution de JavaScript. Pendant que le code est en pause, nous pouvons examiner les variables actuelles, exécuter des commandes dans la console, etc. En d'autres termes, nous pouvons le déboguer. Nous pouvons toujours trouver une liste de points d'arrêt dans le volet de droite. C’est utile lorsque nous avons plusieurs points d’arrêt dans divers fichiers. Ça nous permet de : - Sauter rapidement au point d'arrêt dans le code (en cliquant dessus dans le volet de droite). - Désactiver temporairement le point d'arrêt en le décochant. - Supprimer le point d'arrêt en cliquant avec le bouton droit de la souris et en sélectionnant Supprimer. - … Et ainsi de suite. ```smart header="Points d'arrêt conditionnels" *Clic droit* sur le numéro de ligne permet de créer un point d'arrêt *conditionnel*. Il ne se déclenche que lorsque l'expression donnée, que vous devez fournir lors de sa création, est vraie. C’est pratique lorsque nous devons nous arrêter uniquement pour une certaine valeur de variable ou pour certains paramètres de fonction. ``` ## La commande "debugger" Nous pouvons également suspendre le code en utilisant la commande `debugger`, comme ceci : ```js function hello(name) { let phrase = `Hello, ${name}!`; *!* debugger; // <-- le débogueur s'arrête ici */!* say(phrase); } ``` Une telle commande ne fonctionne que lorsque les outils de développement sont ouverts, sinon le navigateur l'ignore. ## Pause et regarder autour Dans notre exemple, `hello()` est appelé lors du chargement de la page, donc le moyen le plus simple d'activer le débogueur (après avoir défini les points d'arrêt) est de recharger la page. Appuyez donc sur `key:F5` (Windows, Linux) ou sur `key:Cmd+R` (Mac). Lorsque le point d'arrêt est défini, l'exécution s'interrompt à la 4ème ligne : ![](chrome-sources-debugger-pause.svg) Veuillez ouvrir les menus déroulants d’information à droite (indiqués par des flèches). Ils vous permettent d'examiner l'état du code actuel : 1. **`Watch` -- affiche les valeurs actuelles pour toutes les expressions.** Vous pouvez cliquer sur le plus `+` et saisir une expression. Le débogueur affichera sa valeur, la recalculant automatiquement dans le processus d'exécution. 2. **`Call Stack` -- affiche la chaîne des appels imbriqués.** À ce moment précis, le débogueur se trouve dans l’appel `hello()`, appelé par un script dans `index.html` (aucune fonction n’est appelée, elle est donc appelée "anonyme"). Si vous cliquez sur un élément de la pile (ex: "anonymous"), le débogueur passe au code correspondant, et toutes ses variables peuvent également être examinées. 3. **`Scope` -- variables actuelles.** `Local` affiche les variables de fonction locales. Vous pouvez également voir leurs valeurs surlignées directement sur la source. `Global` a des variables globales (en dehors de toutes fonctions). Il y a aussi le mot-clé `this` que nous n’avons pas encore étudié, mais nous le ferons bientôt. ## Tracer l'exécution Il est maintenant temps de *tracer* le script. Il y a des boutons pour cela en haut du volet de droite. Actionnons-les. -- "Reprendre" : continue l'exécution, raccourci clavier `key:F8`. : Reprend l'exécution. S'il n'y a pas de points d'arrêt supplémentaires, l'exécution continue et le débogueur perd le contrôle. Voici ce que nous pouvons voir après un clic dessus : ![](chrome-sources-debugger-trace-1.svg) L'exécution a repris, atteint un autre point d'arrêt à l'intérieur de `say()` et s'y est arrêtée. Jetez un coup d’œil à "Call stack" à droite. Il a augmenté d'un appel supplémentaire. Nous sommes à l'intérieur `say()` maintenant. -- "Step": lance la commande suivante, raccourci clavier `key:F9`. : Exécute la prochaine déclaration. Si nous cliquons dessus maintenant, `alert` sera affiché. En cliquant dessus encore et encore, vous parcourrez toutes les instructions de script une par une. -- "Step over": lance la commande suivante, mais *n'entre pas dans une fonction*, raccourci clavier `key:F10`.  : Similaire à la commande "Step" précédente, mais se comporte différemment si l'instruction suivante est un appel de fonction (pas une fonction intégrée, comme `alert`, mais une fonction qui nous est propre). Si nous les comparons, la commande "Step" entre dans un appel de fonction imbriqué et interrompt l'exécution à sa première ligne, tandis que "Step over" exécute l'appel de fonction imbriqué de manière invisible pour nous, en sautant les fonctions internes. L'exécution est alors suspendue immédiatement après cette fonction. C'est bien si nous ne sommes pas intéressés à voir ce qui se passe dans l'appel de fonction. -- "Step into", raccourci clavier `key:F11`. : Cela ressemble à "Step", mais se comporte différemment dans le cas d'appels de fonctions asynchrones. Si vous commencez seulement à apprendre le JavaScript, vous pouvez alors ignorer la différence, car nous n'avons pas encore d'appels asynchrones. Pour le futur, il suffit de noter que la commande "Step" ignore les actions asynchrones, telles que `setTimeout` (appel de fonction planifiée), qui s'exécutent ultérieurement. Le "Pas à pas" entre dans leur code, les attend si nécessaire. Voir [DevTools manual](https://developers.google.com/web/updates/2018/01/devtools#async) pour plus de détails. -- "Step out": continuer l'exécution jusqu'à la fin de la fonction en cours, raccourci clavier `key:Shift+F11`. : Continue l'exécution et l'arrête à la toute dernière ligne de la fonction en cours. C'est pratique lorsque nous avons accidentellement entré un appel imbriqué en utilisant , mais cela ne nous intéresse pas et nous voulons continuer jusqu'au bout le plus tôt possible. -- active / désactive tous les points d'arrêt. : Ce bouton ne déplace pas l'exécution. Juste un ensemble de on/off pour les points d'arrêt. -- active/désactive la pause automatique en cas d'erreur. : Lorsqu'il est activé, si les outils de développement sont ouverts, une erreur lors de l'exécution du script le met automatiquement en pause. Ensuite, nous pouvons analyser les variables dans le débogueur pour voir ce qui n'a pas fonctionné. Donc, si notre script s’arrête avec une erreur, nous pouvons ouvrir le débogueur, activer cette option et recharger la page pour voir où il s’arrête et quel est le contexte à ce moment-là. ```smart header="Continue to here" Un clic droit sur une ligne de code ouvre le menu contextuel avec une excellente option appelée "Continue to here". C’est pratique lorsque nous voulons faire plusieurs pas en avant, mais nous sommes trop paresseux pour définir un point d’arrêt. ``` ## Logging Pour afficher quelque chose sur la console depuis notre code, utilisez la fonction `console.log`. Par exemple, cela affiche les valeurs de `0` à `4` sur la console : ```js run // ouvrir la console pour visualiser for (let i = 0; i < 5; i++) { console.log("value,", i); } ``` Les internautes ne voient pas cette sortie, elle se trouve dans la console. Pour la voir, ouvrez l'onglet Console des outils de développement ou appuyez sur `key:Esc` lorsque vous vous trouvez dans un autre onglet : la console en bas s'ouvre. Si nous avons assez de logging dans notre code, nous pouvons voir ce qui se passe dans les enregistrements, sans le débogueur. ## Résumé Comme nous pouvons le constater, il existe trois méthodes principales pour suspendre un script : 1. Les points d'arrêt (breakpoint). 2. Les instructions du `debugger`. 3. Une erreur (si les outils de développement sont ouverts et le bouton est activé) En pause, nous pouvons déboguer -- examiner les variables et suivre le code pour voir où l’exécution s’est mal passée. Il y a beaucoup plus d'options dans les outils de développement que celles couvertes ici. Le manuel complet est ici . Les informations de ce chapitre sont suffisantes pour commencer le débogage, mais plus tard, en particulier si vous utilisez beaucoup de fonctions de navigateur, allez-y et examinez les fonctionnalités plus avancées des outils de développement. Oh, et vous pouvez aussi cliquer sur différents endroits des outils de développement et voir ce qui s’affiche. C’est probablement la voie la plus rapide pour apprendre les outils de développement. N'oubliez pas le clic droit et les menus contextuels ! ================================================ FILE: 1-js/03-code-quality/01-debugging-chrome/debugging.view/hello.js ================================================ function hello(name) { let phrase = `Hello, ${name}!`; say(phrase); } function say(phrase) { alert(`** ${phrase} **`); } ================================================ FILE: 1-js/03-code-quality/01-debugging-chrome/debugging.view/index.html ================================================ An example for debugging. ================================================ FILE: 1-js/03-code-quality/01-debugging-chrome/head.html ================================================ ================================================ FILE: 1-js/03-code-quality/02-coding-style/1-style-errors/solution.md ================================================ Vous pouvez noter ce qui suit : ```js no-beautify function pow(x,n) // <- pas d'espace entre les arguments { // <- accolade sur une ligne séparée let result=1; // <- pas d'espaces des deux côtés de = for(let i=0;i Discutons maintenant des règles et de leurs raisons en détail. ```warn header="Il n'y a pas de règles \"vous devez\"" Rien n'est figé ici. Ce sont des préférences de style, pas des dogmes religieux. ``` ### Accolades Dans la plupart des projets JavaScript, les accolades sont écrites sur la même ligne que le mot clé correspondant, et non sur la nouvelle ligne, dans un style dit «égyptien». Il y a aussi un espace avant un crochet d’ouverture. Comme ceci : ```js if (condition) { // fait ceci // ...et cela // ...et cela } ``` Une construction sur une seule ligne, comme `if (condition) doSomething()`, est un cas important. Devrions-nous utiliser des accolades ? Voici les variantes annotées pour que vous puissiez juger de leur lisibilité : 1. 😠 Les débutants font parfois cela. C'est une mauvaise pratique ! Les accolades ne sont pas nécessaires : ```js if (n < 0) *!*{*/!*alert(`Power ${n} is not supported`);*!*}*/!* ``` 2. 😠 Lorsque vous n'utilisez pas d'accolades, évitez de passer pas à la ligne car il est facile de se tromper : ```js if (n < 0) alert(`Power ${n} is not supported`); ``` 3. 😏 Ne pas utiliser d'accolade sur une seule ligne, est acceptable tant que cela reste court : ```js if (n < 0) alert(`Power ${n} is not supported`); ``` 4. 😃 Voici une bonne manière de faire : ```js if (n < 0) { alert(`Power ${n} is not supported`); } ``` Pour un code tres court, une ligne est autorisée, par exemple `if (cond) return null`. Mais la variante numéro 4 est généralement plus lisible. ### Longueur de la ligne Personne n'aime lire une longue ligne horizontale de code. La meilleure pratique est de la scinder. Par exemple : ```js // les guillemets backtick ` permettent de scinder la chaîne de caractères en plusieurs lignes let str = ` ECMA International's TC39 is a group of JavaScript developers, implementers, academics, and more, collaborating with the community to maintain and evolve the definition of JavaScript. `; ``` Et pour les déclarations `if` : ```js if ( id === 123 && moonPhase === 'Waning Gibbous' && zodiacSign === 'Libra' ) { letTheSorceryBegin(); } ``` La longueur de ligne maximale est convenue au niveau de l'équipe. C’est généralement 80 ou 120 caractères. ### Indentations Il existe deux types d'indentations : - **Un retrait horizontal : 2 ou 4 espaces.** Une indentation horizontale est faite en utilisant 2 ou 4 espaces ou le symbole horizontal de tabulation (touche `key:Tab`). Lequel choisir est une vieille guerre sainte. Les espaces sont plus communs de nos jours. Un des avantages des espaces sur les tabulations est qu’ils permettent des configurations de retrait plus flexibles que le symbole tabulation. Par exemple, nous pouvons aligner les arguments avec la parenthèse d’ouverture, comme ceci : ```js no-beautify show(parameters, aligned, // 5 espaces à gauche one, after, another ) { // ... } ``` - **Un retrait vertical: lignes vides pour fractionner le code en blocs logiques.** Même une seule fonction peut souvent être divisée en blocs logiques. Dans l'exemple ci-dessous, l'initialisation des variables, la boucle principale et le retour du résultat sont fractionnés verticalement : ```js function pow(x, n) { let result = 1; // <-- for (let i = 0; i < n; i++) { result *= x; } // <-- return result; } ``` Insérez une nouvelle ligne où cela aide à rendre le code plus lisible. Il ne devrait pas y avoir plus de neuf lignes de code sans indentation verticale. ### Un point-virgule Un point-virgule doit être présent après chaque déclaration. Même si cela pourrait éventuellement être ignoré. Il y a des langages où le point-virgule est vraiment optionnel. Il est donc rarement utilisé. Mais dans JavaScript, il y a peu de cas où un saut de ligne n'est parfois pas interprété comme un point-virgule. Cela laisse place à des erreurs de programmation. Plus d'informations à ce sujet dans le chapitre . Si vous êtes un programmeur JavaScript expérimenté, vous pouvez choisir un style de code sans point-virgule comme [StandardJS](https://standardjs.com/). Autrement, il est préférable d’utiliser des points-virgules pour éviter les pièges possibles. La majorité des développeurs mettent des points-virgules. ### Niveaux d'imbrications Il ne devrait pas y avoir trop de niveaux d'imbrication. Par exemple, dans une boucle, c’est parfois une bonne idée d’utiliser la directive ["continue"](info:while-for#continue) pour éviter une imbrication supplémentaire. Par exemple, au lieu d’ajouter un `if` imbriqué conditionnel comme ceci : ```js for (let i = 0; i < 10; i++) { if (cond) { ... // <- un autre niveau d'imbrication } } ``` Nous pouvons écrire : ```js for (let i = 0; i < 10; i++) { if (!cond) *!*continue*/!*; ... // <- pas de niveau d'imbrication supplémentaire } ``` Une chose similaire peut être faite avec `if`/`else` et `return`. Par exemple, les deux constructions ci-dessous sont identiques. Le premier : ```js function pow(x, n) { if (n < 0) { alert("Negative 'n' not supported"); } else { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } } ``` Et ceci : ```js function pow(x, n) { if (n < 0) { alert("Negative 'n' not supported"); return; } let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } ``` Le second est plus lisible, parce que le "cas marginal" de `n < 0` est traité tôt. Une fois la vérification effectuée, nous pouvons passer au flux de code "principal" sans avoir besoin d'imbrication supplémentaire. ## Placement des fonctions Si vous écrivez plusieurs fonctions "helper" (auxiliaires) et le code pour les utiliser, il existe trois façons de les placer. 1. Déclarez les fonctions *au-dessus* du code qui les utilise : ```js // *!*fonctions declarations*/!* function createElement() { ... } function setHandler(elem) { ... } function walkAround() { ... } // *!*le code qui les utilise*/!* let elem = createElement(); setHandler(elem); walkAround(); ``` 2. Le code d'abord, puis les fonctions ```js // *!*le code qui utilise les fonctions*/!* let elem = createElement(); setHandler(elem); walkAround(); // --- *!*fonctions helper*/!* --- function createElement() { ... } function setHandler(elem) { ... } function walkAround() { ... } ``` 3. Mixte : une fonction est décrite là où elle a été utilisée pour la première fois. La plupart du temps, la deuxième variante est préférée. C’est parce qu’en lisant du code, nous voulons d’abord savoir ce qu’il fait. Si le code commence en premier, il devient clair dès le début. Ensuite, nous n’aurons peut-être pas besoin de lire les fonctions du tout, surtout si leur nom décrit ce qu’elles font réellement. ## Guides de style Un guide de style contient des règles générales sur "comment écrire" du code. Exemple : les quotes à utiliser, le nombre d'espaces pour indenter, la longueur de ligne maximale, etc. Beaucoup de petites choses. Lorsque tous les membres d'une équipe utilisent le même guide de style, le code est uniforme. Peu importe qui l’a écrit, c’est toujours le même style. Bien sûr, une équipe peut toujours écrire son propre guide de style, mais cela n’est généralement pas nécessaire. Il existe de nombreux guides existants à choisir. Par exemple : - [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) - [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) - [StandardJS](https://standardjs.com/) - (il y en a plus) Si vous êtes un développeur novice, commencez par le cheatsheet au début de ce chapitre. Ensuite, vous pouvez parcourir d'autres guides de style pour trouver plus d'idées et décider lequel vous préférez. ## Linters automatisés Les linters sont des outils permettant de vérifier automatiquement le style de votre code et de formuler des suggestions d'amélioration. Ce qui est génial avec eux, c'est que la vérification du style trouve également des bugs, comme une faute de frappe dans une variable ou un nom de fonction. En raison de cette fonctionnalité, l'utilisation d'un linter est recommandée même si vous ne souhaitez pas vous en tenir à un "style de code" particulier. Voici quelques linters bien connus : - [JSLint](http://www.jslint.com/) -- l'un des premiers linters. - [JSHint](http://www.jshint.com/) -- plus de paramètres que JSLint. - [ESLint](http://eslint.org/) -- probablement le plus récent. Tous peuvent faire le travail. L'auteur utilise [ESLint](http://eslint.org/). La plupart des linters sont intégrés aux éditeurs: il suffit d'activer le plug-in dans l'éditeur et de configurer le style. Par exemple, pour ESLint, vous devez procéder comme suit : 1. Installer [Node.js](https://nodejs.org/). 2. Installer ESLint avec la commande `npm install -g eslint` (npm est un gestionnaire de paquets JavaScript). 3. Créez un fichier de configuration nommé `.eslintrc` dans la racine de votre projet JavaScript (dans le dossier contenant tous vos fichiers). 4. Installez / activez le plug-in pour votre éditeur qui s'intègre à ESLint. La majorité des éditeurs en ont un. Voici un exemple de `.eslintrc`: ```js { "extends": "eslint:recommended", "env": { "browser": true, "node": true, "es6": true }, "rules": { "no-console": 0, "indent": 2 } } ``` Ici, la directive `"extends"` indique que nous nous basons sur l'ensemble de paramètres "eslint:recommended", puis nous spécifions les nôtres. Il est aussi possible de télécharger des ensembles de règles de style à partir du Web et de les étendre. Voir pour plus de détails sur l'installation. De plus, certains IDE prennent en charge le linting nativement, ce qui peut également être bien, mais pas aussi ajustables que ESLint. ## Résumé Toutes les règles de syntaxe de ce chapitre et les guides de style visent à améliorer la lisibilité, elles sont donc toutes discutables. Lorsque nous pensons à écrire du "meilleur" code, les questions que nous devrions nous poser sont les suivantes : "Qu'est-ce qui rend le code plus lisible et plus facile à comprendre ?" et "Qu'est-ce qui peut nous aider à éviter les erreurs ?". Telles sont les principales choses à garder à l'esprit lors du choix et du débat sur les styles de code. Lisez les guides de style pour connaître les dernières idées à ce sujet et suivez celles que vous trouvez les meilleures. ================================================ FILE: 1-js/03-code-quality/03-comments/article.md ================================================ # Commentaires Comme nous le savons du chapitre , les commentaires peuvent être simples : à partir de `//` et multiligne : `/* ... */`. Nous les utilisons normalement pour décrire comment et pourquoi le code fonctionne. De prime abord, les commentaires peuvent sembler évidents, mais les novices en programmation les utilisent souvent à tort. ## Mauvais commentaires Les novices ont tendance à utiliser des commentaires pour expliquer "ce qui se passe dans le code". Comme ceci : ```js // Ce code fera cette chose (...) et cette chose (...) // ...Et qui sait quoi d'autre... very; complex; code; ``` Mais en bon code, le nombre de ces commentaires "explicatifs" devrait être minime. Sérieusement, le code devrait être facile à comprendre sans eux. Il existe une excellente règle à ce sujet: "Si le code est si peu clair qu’il nécessite un commentaire, il devrait peut-être être réécrit". ### Recette: refactoriser les fonctions Parfois, il est avantageux de remplacer un code par une fonction, comme ici : ```js function showPrimes(n) { nextPrime: for (let i = 2; i < n; i++) { *!* // check if i is a prime number for (let j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; } */!* alert(i); } } ``` La meilleure variante, avec une fonction factorisée est `isPrime` : ```js function showPrimes(n) { for (let i = 2; i < n; i++) { *!*if (!isPrime(i)) continue;*/!* alert(i); } } function isPrime(n) { for (let i = 2; i < n; i++) { if (n % i == 0) return false; } return true; } ``` Maintenant, nous pouvons comprendre le code facilement. La fonction elle-même devient le commentaire. Un tel code est appelé *auto-descriptif*. ### Recette: créer des fonctions Et si nous avons une longue "feuille de code" comme celle-ci : ```js // ici on ajoute du whisky for(let i = 0; i < 10; i++) { let drop = getWhiskey(); smell(drop); add(drop, glass); } // ici on ajoute du jus for(let t = 0; t < 3; t++) { let tomato = getTomato(); examine(tomato); let juice = press(tomato); add(juice, glass); } // ... ``` Ce pourrait être une meilleure variante de le refactoriser dans des fonctions comme : ```js addWhiskey(glass); addJuice(glass); function addWhiskey(container) { for(let i = 0; i < 10; i++) { let drop = getWhiskey(); //... } } function addJuice(container) { for(let t = 0; t < 3; t++) { let tomato = getTomato(); //... } } ``` Une fois encore, les fonctions elles-mêmes racontent ce qui se passe. Il n’y a rien à commenter. Et aussi la structure du code est meilleure quand elle est divisée. C'est clair ce que chaque fonction fait, ce qu’elle nécessite et ce qu’elle renvoie. En réalité, nous ne pouvons pas totalement éviter les commentaires «explicatifs». Il existe des algorithmes complexes. Et il existe des "réglages" intelligents à des fins d'optimisation. Mais généralement, nous devrions essayer de garder le code simple et auto-descriptif. ## Bons commentaires Ainsi, les commentaires explicatifs sont généralement mauvais. Quels commentaires sont bons ? Décrivez l'architecture : Fournissez une vue d’ensemble des composants, de leurs interactions, de ce que sont les flux de contrôle dans diverses situations… En bref -- une vue plongeante du code. Il existe un langage spécial [UML](https://fr.wikipedia.org/wiki/UML_(informatique)) pour les diagrammes d'architecture de haut niveau. Ça vaut vraiment la peine de l'étudier. Documenter les paramètres de fonction et leur utilisation : Il y a une syntaxe spéciale [JSDoc](https://fr.wikipedia.org/wiki/JSDoc) pour documenter une fonction : utilisation, paramètres, valeur renvoyée. Par exemple : ```js /** * Renvoie x élevé à la n-ième puissance. * * @param {number} x Le nombre à augmenter. * @param {number} n L'exposant doit être un nombre naturel. * @return {number} x élevé à la n-ème puissance. */ function pow(x, n) { ... } ``` De tels commentaires nous permettent de comprendre le but de la fonction et de l’utiliser correctement sans regarder dans son code. À ce propos, de nombreux éditeurs comme [WebStorm](https://www.jetbrains.com/webstorm/) peut aussi les comprendre et les utiliser pour fournir une autocomplétion et une vérification automatique du code. En outre, il existe des outils comme [JSDoc 3](https://github.com/jsdoc/jsdoc) qui peut générer une documentation HTML à partir des commentaires. Vous pouvez lire plus d'informations sur JSDoc à l'adresse . Pourquoi la tâche est-elle résolue de cette façon ? : Ce qui est écrit est important. Mais ce qui *n’est pas* écrit peut être encore plus important pour comprendre ce qui se passe. Pourquoi la tâche est-elle résolue exactement de cette façon ? Le code ne donne pas de réponse. S'il y a plusieurs façons de résoudre la tâche, pourquoi celle-ci ? Surtout quand ce n’est pas la plus évidente. Sans ces commentaires, la situation suivante est possible : 1. Vous (ou votre collègue) ouvrez le code écrit il y a quelque temps et constatez qu'il n'est pas optimal. 2. Vous pensez: "À quel point j'étais bête à ce moment-là et à quel point je suis plus malin maintenant", puis réécrivez en utilisant la variante "plus évidente et correcte". 3. … L'envie de réécrire était bonne. Mais dans le processus, vous constatez que la solution "plus évidente" fait défaut. Vous vous rappelez même vaguement pourquoi, parce que vous l'avez déjà essayé il y a longtemps. Vous revenez à la bonne variante, mais le temps a été perdu. Les commentaires qui expliquent la solution sont très importants. Ils aident à continuer le développement de la bonne façon. Les caractéristiques subtiles du code ? Où sont-elles utilisés ? : Si le code a quelque chose de subtil et de contre-intuitif, cela vaut vraiment la peine de le commenter. ## Résumé Les commentaires sont une caractéristique importante du bon développeur : leur présence et même leur absence. Les bons commentaires nous permettent de bien maintenir le code, d'y revenir après un délai et de l'utiliser plus efficacement. **Commentez ceci :** - Architecture globale, vue de haut niveau. - Utilisation de la fonction. - Les solutions importantes, surtout lorsqu'elles ne sont pas immédiatement évidentes. **Évitez les commentaires :** - Qui disent "comment fonctionne le code" et "ce qu'il fait". - Ne les mettez que s’il est impossible de rendre le code aussi simple et auto-descriptif qu’il n’en nécessite pas. Les commentaires sont également utilisés pour les outils de documentation automatique tels que JSDoc3. Ils les lisent et génèrent des documents HTML (ou des documents dans un autre format). ================================================ FILE: 1-js/03-code-quality/04-ninja-code/article.md ================================================ # Ninja code ```quote author="Confucius (Entretiens)" Apprendre sans réfléchir est vain. Réfléchir sans apprendre est dangereux. ``` Les programmeurs ninjas du passé ont utilisé ces astuces pour aiguiser l'esprit des mainteneurs de code. Les gourous de la révision de code les recherchent dans les tâches de test. Les développeurs novices les utilisent parfois encore mieux que les programmeurs ninjas. Lisez-les attentivement et découvrez qui vous êtes: un ninja, un novice ou peut-être un critique de code ? ```warn header="Ironie detectée" Beaucoup essaient de suivre les chemins des ninjas. Peu réussissent. ``` ## La concision est l'âme de l'esprit Faites le code aussi court que possible. Montrez à quel point vous êtes intelligent. Laissez les fonctionnalités du langage subtiles vous guider. Par exemple, jetez un oeil à cet opérateur ternaire `'?'` : ```js // tiré d'une bibliothèque javascript bien connue i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ``` Cool, non ? Si vous écrivez comme ça, le développeur qui arrive à cette ligne et essaie de comprendre quelle est la valeur de `i` va passer un bon moment. Ensuite vient votre tour, cherchant une réponse. Dites-leur que le plus court est toujours mieux. Initiez-les dans les chemins du ninja. ## Variables à une lettre ```quote author="Laozi (Tao Te Ching)" Le Dao se cache sans mots. Seul le Dao est bien commencé et bien terminé. ``` Une autre façon de coder plus rapidement consiste à utiliser des noms de variable d'une seule lettre partout. Comme `a`, `b` ou `c`. Une petite variable disparaît dans le code comme un vrai ninja dans la forêt. Personne ne pourra la trouver en utilisant la "recherche" de l'éditeur. Et même si quelqu'un le fait, il ne pourra pas "déchiffrer" la signification du nom `a` ou `b`. … Mais il y a une exception. Un vrai ninja n'utilisera jamais `i` comme compteur dans une boucle `"for"`. N'importe où, mais pas ici. Regardez autour de vous, il y a beaucoup plus de lettres exotiques. Par exemple, `x` ou `y`. Une variable exotique en tant que compteur de boucle est particulièrement intéressante si le corps de la boucle nécessite 1 à 2 pages (rallongez-la si vous le pouvez). Ensuite, si quelqu'un regarde au fond de la boucle, il ne sera pas en mesure de comprendre rapidement que la variable nommée `x` est le compteur de boucles. ## Utiliser des abréviations Si les règles de l'équipe interdisent l'utilisation de noms d'une seule lettre et de noms vagues, abrégez-les, faites des abréviations. Comme ceci : - `list` -> `lst`. - `userAgent` -> `ua`. - `browser` -> `brsr`. - ...etc Seul celui qui a vraiment une bonne intuition sera capable de comprendre de tels noms. Essayez de tout raccourcir. Seule une personne digne de ce nom devrait être capable de soutenir le développement de votre code. ## Prenez de la hauteur. Soyez abstrait. ```quote author="Laozi (Tao Te Ching)" The great square is cornerless
The great vessel is last complete,
The great note is rarified sound,
The great image has no form. ``` En choisissant un nom, essayez d’utiliser le mot le plus abstrait. Comme `obj`, `data`, `value`, `item`, `elem` etc. - **Le nom idéal pour une variable est `data`.** Utilisez-le partout où vous le pouvez. En effet, chaque variable contient des données, non ? … Mais que faire si `data` est déjà pris ? Essayez `value`, elle est aussi universelle. Après tout, une variable obtient finalement une *valeur*. - **Nommez une variable par son type : `str`, `num`...** Accordez-leur une chance. Un jeune initié peut se demander : de tels noms sont-ils vraiment utiles à un ninja ? En effet, ils le sont ! Bien sûr, le nom de la variable signifie toujours quelque chose. Il indique ce qui est à l’intérieur de la variable: une chaîne de caractères, un nombre ou autre chose. Mais quand une personne essaiera de comprendre le code, elle sera surprise de constater qu’il n’y a en réalité aucune information ! Et finalement, elle ne pourra pas modifier votre code bien pensé. Le type de valeur est facile à déterminer par le débogage. Mais quel est le sens de la variable ? Quelle chaîne de caractères/nombre est-il stocké ? Il n’est pas possible de comprendre sans une bonne méditation ! - **… Mais s'il n'y a plus de tels noms disponibles ?** Il suffit d'ajouter un numéro : `data1, item2, elem5`... ## Test d'attention Seul un programmeur vraiment attentif devrait être capable de comprendre votre code. Mais comment vérifier ça ? **Une des façons - utilisez des noms de variables similaires, comme `date` et `data`.** Mélangez-les où vous pouvez. Une lecture rapide de ce code devient impossible. Et quand il ya une faute de frappe… Humm… Nous sommes coincés longtemps, le temps de boire du thé. ## Des synonymes intelligents ```quote author="Confucius" L'une des chose les plus difficiles est de trouver un chat noir dans une pièce sombre, surtout s’il n’y a pas de chat. ``` Utiliser des noms *similaires* pour les *mêmes* choses rend la vie plus intéressante et montre votre créativité au public. Par exemple, considérons les préfixes de fonction. Si une fonction affiche un message à l'écran, lancez-la avec `display…`, comme `displayMessage`. Et puis, si une autre fonction affiche à l'écran quelque chose d'autre, comme un nom d'utilisateur, lancez-le avec `show…` (comme `showName`). Insinuez qu’il existe une différence subtile entre ces fonctions, alors qu’il n’en existe aucune. Faites un pacte avec les autres ninjas de l'équipe: si John commence à "afficher" des fonctions avec `display` ... dans son code, Peter pourra utiliser `render` ..., et Ann - `paint` ... Notez à quel point le code est devenu plus intéressant et diversifié. … Et maintenant le tour de magie ! Pour deux fonctions présentant des différences importantes, utilisez le même préfixe ! Par exemple, la fonction `printPage(page)` utilisera une imprimante. Et la fonction `printText(text)` mettra le texte à l'écran. Laissez un lecteur inconnu réfléchir à la fonction `printMessage`, qui porte le même nom: "Où place-t-il le message ? Pour une imprimante ou à l'écran ?". Pour le rendre vraiment brillant, `printMessage(message)` devrait l'extraire dans la nouvelle fenêtre! ## Réutiliser des noms ```quote author="Laozi (Tao Te Ching)" Une fois que le tout est divisé, les parties
ont besoin de noms.
Il y a déjà assez de noms.
Il faut savoir quand s'arrêter. ``` Ajoutez une nouvelle variable uniquement lorsque cela est absolument nécessaire. Au lieu de cela, réutilisez les noms existants. Il suffit d'écrire de nouvelles valeurs en eux. Dans une fonction, n'utilisez que des variables passées en paramètres. Cela va rendre vraiment difficile d’identifier ce qui est exactement dans la variable maintenant. Et aussi d'où ça vient. Le but est de développer l’intuition et la mémoire de la personne qui lit le code. Une personne ayant une faible intuition devrait analyser le code ligne par ligne et suivre les modifications dans chaque branche de code. **Une variante avancée de l'approche consiste à remplacer secrètement (!) La valeur par quelque chose de similaire au milieu d'une boucle ou d'une fonction.** Par exemple : ```js function ninjaFunction(elem) { // 20 lignes de code fonctionnant avec elem elem = clone(elem); // 20 lignes supplémentaires, fonctionnant maintenant avec le clone de elem ! } ``` Un collègue programmeur qui veut travailler avec `elem` dans la seconde moitié de la fonction sera surpris… Seulement lors du débogage, après avoir examiné le code, ils découvrira qu’il travaille avec un clone ! MVu dans du code régulièrement. Mortellement efficace même contre un ninja expérimenté. ## Underscores for fun Placez les underscores `_` et `__` avant les noms de variables. Comme `_name` ou `__value`. Ce serait génial si seulement vous connaissiez leur signification. Ou, mieux, ajoutez-les juste pour le plaisir, sans signification particulière. Ou différentes significations dans différents endroits. Vous faites d'une pierre deux coups. Premièrement, le code devient plus long et moins lisible, et deuxièmement, un autre développeur peut passer beaucoup de temps à essayer de comprendre ce que signifient les soulignements. Un ninja intelligent place les traits de soulignement à un endroit du code et les évite à d’autres endroits. Cela rend le code encore plus fragile et augmente la probabilité d'erreurs futures. ## Montrez votre amour Laissez tout le monde voir à quel point vos entités sont magnifiques! Des noms comme `superElement`, `megaFrame` et `niceItem` illumineront définitivement le lecteur. En effet, d’une part, quelque chose s’écrit: `super ..`, `mega ..`, `nice ..`. Mais de l’autre -- cela n’apporte aucun détail. Un lecteur peut décider de chercher un sens caché et de méditer pendant une heure ou deux de leur temps de travail rémunéré. ## Chevaucher des variables externes ```quote author="Guan Yin Zi" Lorsqu'on est dans la lumière, on ne peut rien voir dans l’obscurité.
Lorsqu'on est dans l'obscurité, on peut tout voir dans la lumière. ``` Utilisez les mêmes noms pour les variables à l'intérieur et à l'extérieur d'une fonction. Aussi simple que cela. Pas besoin de faire des efforts pour inventer de nouveaux noms. ```js let *!*user*/!* = authenticateUser(); function render() { let *!*user*/!* = anotherValue(); ... ...beaucoup de lignes... ... ... // <-- un programmeur veut travailler avec l'utilisateur ici et … ... } ``` Un programmeur qui saute dans le `render` ne remarquera probablement pas qu’il ya un `user` local qui masque celui de l’extérieur. Ensuite, il essaiera de travailler avec l’`user` en supposant que c’est la variable externe, le résultat de `authenticateUser()`… Le piège est déclenché ! Bonjour debugger… ## Effets secondaires partout ! Certaines fonctions donnent l’impression de ne rien changer. Comme `isReady()`, `checkPermission()`, `findTags()`… Elles sont supposés effectuer des calculs, trouver et renvoyer les données, sans rien changer en dehors d'eux. En d'autres termes, sans "effets secondaires". **Une très belle astuce consiste à leur ajouter une action "utile", en plus de la tâche principale.** L’expression de surprise hébétée sur le visage de vos collègues quand ils voient une fonction nommée `is..`, `check..` ou `find...` changer quelque chose -- va certainement élargir vos limites de la raison. **Une autre façon de surprendre est de renvoyer un résultat non standard.** Montrez votre pensée originale ! Laissez l'appel de `checkPermission` renvoyer non pas `true/false`, mais un objet complexe avec les résultats de la vérification. Les développeurs qui essaient d’écrire `if(checkPermission(..))` se demanderont pourquoi cela ne fonctionne pas. Dites-leur : "Lisez la documentation!". Et donnez cet article. ## Fonctions puissantes ! ```quote author="Laozi (Tao Te Ching)" The great Tao flows everywhere,
both to the left and to the right. ``` Ne limitez pas la fonction à ce qui est écrit dans son nom. Soyez plus large. Par exemple, une fonction `validateEmail(email)` pourrait (en plus de vérifier l'exactitude de l'email) afficher un message d'erreur et demander à ressaisir l'email. Les actions supplémentaires ne doivent pas être évidentes à partir du nom de la fonction. Un vrai codeur ninja ne les rendra pas évidents à partir du code non plus. **La jonction de plusieurs actions en une seule protège votre code de la réutilisation.** Imaginez, un autre développeur souhaitant uniquement vérifier le courrier électronique et ne pas générer de message. Votre fonction `validateEmail(email)` qui fait les deux ne leur conviendra pas. Donc, ils ne briseront pas votre méditation en posant des questions à ce sujet. ## Résumé Tous les "conseils" ci-dessus sont tirés de code réel … Parfois écrits par des développeurs expérimentés. Peut-être même plus expérimenté que vous ;) - Suivez certains d'entre eux et votre code deviendra plein de surprises. - Suivez beaucoup d'entre eux, et votre code deviendra vraiment le vôtre, personne ne voudra le changer. - Suivez tout et votre code deviendra une leçon précieuse pour les jeunes développeurs à la recherche d'illumination. ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/solution.md ================================================ Le test illustre l'une des tentations qu'un développeur rencontre lorsqu'il écrit des tests. Ce que nous avons ici est en fait 3 tests, mais présentés comme une seule fonction avec 3 affirmations. Parfois, il est plus facile d’écrire de cette façon, mais si une erreur se produit, ce qui a mal tourné est beaucoup moins évident. Si une erreur survient au beau milieu d'un flux d'exécution complexe, alors nous devrons bien comprendre les données à ce stade. Nous devrons en fait *déboguer le test*. Il serait bien préférable de diviser le test en plusieurs blocs `it` avec des entrées et des sorties clairement écrites. Comme ceci : ```js describe("Raises x to power n", function() { it("5 in the power of 1 equals 5", function() { assert.equal(pow(5, 1), 5); }); it("5 in the power of 2 equals 25", function() { assert.equal(pow(5, 2), 25); }); it("5 in the power of 3 equals 125", function() { assert.equal(pow(5, 3), 125); }); }); ``` Nous avons remplacé l'`it` unique par un `describe` et un groupe d'`it`. Si quelque chose échouait, nous verrions clairement quelles étaient les données erronées. Nous pouvons également isoler un seul test et l'exécuter en mode autonome en l'écrivant `it.only` à la place de `it` : ```js describe("Raises x to power n", function() { it("5 in the power of 1 equals 5", function() { assert.equal(pow(5, 1), 5); }); *!* // Mocha ne va exécuter que ce code it.only("5 in the power of 2 equals 25", function() { assert.equal(pow(5, 2), 25); }); */!* it("5 in the power of 3 equals 125", function() { assert.equal(pow(5, 3), 125); }); }); ``` ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/task.md ================================================ importance: 5 --- # Quel est le problème dans le test ? Qu'est-ce qui ne va pas dans le test de `pow` ci-dessous ? ```js it("Raises x to the power n", function() { let x = 5; let result = x; assert.equal(pow(x, 1), result); result *= x; assert.equal(pow(x, 2), result); result *= x; assert.equal(pow(x, 3), result); }); ``` P.S. Syntaxiquement, le test est correct et réussi. ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/article.md ================================================ # Testing automatisé avec Mocha Les tests automatisés seront utilisés dans d'autres tâches. Ils sont également largement utilisés dans des projets réels. ## Pourquoi avons-nous besoin de tests ? Lorsque nous écrivons une fonction, nous pouvons généralement imaginer ce qu’elle doit faire : quels paramètres donnent quels résultats. Au cours du développement, nous pouvons vérifier la fonction en l'exécutant et en comparant le résultat obtenu. Par exemple, nous pouvons le faire dans la console. Si quelque chose ne va pas -- alors nous corrigeons le code, exécutons à nouveau, vérifions le résultat -- et ainsi de suite jusqu'à ce que cela fonctionne. Mais de telles "ré-exécutions" manuelles sont imparfaites. **Lors du test manuel d’un code, il est facile de rater quelque chose.** Par exemple, nous créons une fonction `f`. On écrit du code, on teste : `f(1)` fonctionne, mais `f(2)` ne fonctionne pas. Nous corrigeons le code et maintenant `f(2)` fonctionne. Cela semble complet ? Mais nous avons oublié de re-tester `f(1).` Cela peut conduire à une erreur. C’est très typique. Lorsque nous développons quelque chose, nous gardons à l’esprit beaucoup de cas d’utilisation possibles. Mais il est difficile de s’attendre à ce qu’un programmeur les vérifie manuellement après chaque modification. Il devient donc facile de réparer une chose et d'en casser une autre. **Le test automatisé signifie que les tests sont écrits séparément, en plus du code. Ils exécutent nos fonctions de différentes manières et comparent les résultats avec les attentes.** ## Behavior Driven Development (BDD) Commençons par une technique nommée [Behavior Driven Development](https://fr.wikipedia.org/wiki/Behavior-driven_development) ou, en bref, BDD. **BDD, c'est trois choses en une : les tests ET la documentation ET les exemples.** Pour comprendre BDD, examinons un cas pratique de développement. ## Développement de "pow": la spec Imaginons que nous voulions créer une fonction `pow(x, n)` qui élève `x` à la puissance d'un entier `n`. Nous supposons que `n≥0`. Cette tâche n’est qu’un exemple : il existe l’opérateur `**` en JavaScript qui peut le faire, mais nous nous concentrons ici sur le flux de développement pouvant également s’appliquer à des tâches plus complexes. Avant de créer le code de `pow`, nous pouvons imaginer ce que la fonction devrait faire et la décrire. Cette description s'appelle une *spécification* ou, en bref, une **spec**, et contient des descriptions de cas d'utilisation ainsi que des tests pour ceux-ci, comme ceci : ```js describe("pow", function() { it("raises to n-th power", function() { assert.equal(pow(2, 3), 8); }); }); ``` Une spécification a trois blocs de construction principaux que vous pouvez voir ci-dessus : `describe("title", function() { ... })` : Quelle fonctionnalité nous décrivons. Dans notre cas, nous décrivons la fonction `pow`, utilisée pour grouper les "workers" -- le bloc `it`. `it("use case description", function() { ... })` : Dans le titre de `it`, nous décrivons d'une *manière lisible par l'homme* le cas particulier d'utilisation, et le deuxième argument est une fonction qui le teste. `assert.equal(value1, value2)` : Le code à l'intérieur du bloc `it`, si l'implémentation est correcte, doit s'exécuter sans erreur. Les fonctions `assert.*` sont utilisées pour vérifier si `pow` fonctionne comme prévu. Ici, nous utilisons l’un d’eux - `assert.equal`, qui compare les arguments et génère une erreur s’ils ne sont pas égaux. Ici, il vérifie que le résultat de `pow(2, 3)` est égal à `8`. Nous ajouterons plus tard d'autres types de comparaisons et de contrôles. La spécification peut être exécutée et le test spécifié dans le bloc `it` sera exécuté. Nous verrons cela plus tard. ## Le flux de développement Le flux de développement ressemble généralement à ceci : 1. Une spécification initiale est écrite, avec des tests pour les fonctionnalités les plus élémentaires. 2. Une implémentation initiale est créée. 3. Pour vérifier si cela fonctionne, nous exécutons le framework de test [Mocha](https://mochajs.org/) (plus de détails bientôt) qui exécute la spécification. Tant que la fonctionnalité n'est pas complète, des erreurs sont affichées. Nous apportons des corrections jusqu'à ce que tout fonctionne. 4. Nous avons maintenant une implémentation initiale de travail avec des tests. 5. Nous ajoutons d'autres cas d'utilisation à la spécification, probablement pas encore pris en charge par les implémentations. Les tests commencent à échouer. 6. Passez à l'étape 3, mettez à jour l'implémentation jusqu'à ce que les tests ne génèrent aucune erreur. 7. Répétez les étapes 3 à 6 jusqu'à ce que la fonctionnalité soit prête. Donc, le développement est *itératif*. Nous écrivons la spécification, la mettons en œuvre, nous nous assurons que les tests réussissent, puis rédigeons d'autres tests, nous nous assurons qu'ils fonctionnent, etc. À la fin, nous avons une implémentation qui fonctionne et des tests. Voyons ce flux de développement dans notre cas pratique. La première étape est déjà terminée : nous avons une spécification initiale pour `pow`. Maintenant, avant de procéder à l’implémentation, utilisons quelques bibliothèques JavaScript pour exécuter les tests, histoire de voir qu’elles fonctionnent (elles échoueront toutes). ## La spec en action Dans ce tutoriel, nous utiliserons les bibliothèques JavaScript suivantes pour les tests : - [Mocha](https://mochajs.org/) -- le framework central : il fournit des fonctions de test communes, y compris `describe`, et `it` ainsi que la fonction principale qui exécute les tests. - [Chai](https://chaijs.com) -- la bibliothèque avec de nombreuses affirmations. Elle permet d’utiliser beaucoup d’affirmations différentes, pour le moment nous n’avons besoin que de `assert.equal`. - [Sinon](https://sinonjs.org/) -- une bibliothèque pour espionner des fonctions, émuler des fonctions intégrées et plus encore, nous en aurons besoin beaucoup plus tard. Ces bibliothèques conviennent aux tests sur le navigateur et sur le serveur. Ici, nous allons considérer la variante du navigateur. La page HTML complète avec ces frameworks et `pow` spec : ```html src="index.html" ``` La page peut être divisée en quatre parties : 1. Le `` -- ajouter des bibliothèques et des styles tiers pour les tests. 2. Le `
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js ================================================ describe("test", function() { // Mocha usually waits for the tests for 2 seconds before considering them wrong this.timeout(200000); // With this code we increase this - in this case to 200,000 milliseconds // This is because of the "alert" function, because if you delay pressing the "OK" button the tests will not pass! before(() => alert("Testing started – before all tests")); after(() => alert("Testing finished – after all tests")); beforeEach(() => alert("Before a test – enter a test")); afterEach(() => alert("After a test – exit a test")); it('test 1', () => alert(1)); it('test 2', () => alert(2)); }); ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-1.view/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js ================================================ describe("pow", function() { it("raises to n-th power", function() { assert.equal(pow(2, 3), 8); }); }); ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-2.view/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js ================================================ describe("pow", function() { it("2 raised to power 3 is 8", function() { assert.equal(pow(2, 3), 8); }); it("3 raised to power 4 is 81", function() { assert.equal(pow(3, 4), 81); }); }); ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-3.view/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js ================================================ describe("pow", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-4.view/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js ================================================ describe("pow", function() { describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); // ... more tests to follow here, both describe and it can be added }); ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-full.view/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js ================================================ describe("pow", function() { describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); it("if n is negative, the result is NaN", function() { assert.isNaN(pow(2, -1)); }); it("if n is not integer, the result is NaN", function() { assert.isNaN(pow(2, 1.5)); }); }); ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-min.view/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js ================================================ describe("pow", function() { it("raises to n-th power", function() { assert.equal(pow(2, 3), 8); }); }); ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-nan.view/index.html ================================================
================================================ FILE: 1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js ================================================ describe("pow", function() { describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); it("if n is negative, the result is NaN", function() { assert.isNaN(pow(2, -1)); }); it("if n is not integer, the result is NaN", function() { assert.isNaN(pow(2, 1.5)); }); }); ================================================ FILE: 1-js/03-code-quality/06-polyfills/article.md ================================================ # Polyfills et transpilers Le langage JavaScript évolue régulièrement. De nouvelles propositions pour le langage apparaissent régulièrement, elles sont analysées et, si elles sont jugées utiles, elles sont ajoutées à la liste dans et ensuite progressent vers la [specification officielle](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). Les équipes derrière les moteurs JavaScript ont leurs propres idées sur ce qu'il faut d'abord mettre en œuvre. Elles peuvent décider de mettre en œuvre des propositions qui sont en projet et reporter des éléments qui figurent déjà dans les spécifications, car ils sont moins intéressants ou tout simplement plus difficiles à faire. Il est donc assez courant pour un moteur de ne mettre en œuvre qu'une partie du standard. Une bonne page pour voir l’état actuel de la prise en charge des fonctionnalités du langage est (c’est énorme, nous avons encore beaucoup à étudier). En tant que programmeurs, nous aimerions utiliser les fonctionnalités les plus récentes. Plus il y a de bonnes choses, mieux c'est ! D'un autre côté, comment faire fonctionner le code moderne sur des moteurs plus anciens qui ne comprennent pas encore les fonctionnalités récentes ? Il existe deux outils pour cela : 1. Les transpilers. 2. Les polyfills. Ici, dans ce chapitre, notre objectif est de comprendre l'essentiel de leur fonctionnement et de leur place dans le développement Web. ## Les transpilers Un [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) est un logiciel spécial qui traduit le code source en un autre code source. Il peut analyser ("lire et comprendre") du code moderne et le réécrire en utilisant des constructions syntaxiques plus anciennes, de sorte qu'il fonctionnera également dans des moteurs obsolètes. Par exemple, JavaScript avant l'année 2020 n'avait pas "l'opérateur de coalescence des nuls" `??`. Ainsi, si un visiteur utilise un navigateur obsolète, il peut ne pas comprendre le code tel que `height = height ?? 100`. Un transpiler analyserait notre code et réécrirait `height ?? 100` en `(height !== undefined && height !== null) ? height : 100`. ```js // avant d'exécuter le transpiler height = height ?? 100; // après avoir exécuté le transpiler height = (height !== undefined && height !== null) ? height : 100; ``` Désormais, le code réécrit convient aux anciens moteurs JavaScript. Habituellement, un développeur exécute le transpiler sur son propre ordinateur, puis déploie le code transpilé sur le serveur. En parlant de noms, [Babel](https://babeljs.io) est l'un des transpileurs les plus connus. Les systèmes de construction de projets modernes, tels que [webpack](http://webpack.js.org/), fournissent des moyens pour exécuter un transpileur automatiquement à chaque changement de code, il est donc très facile à intégrer dans le processus de développement. ## Les polyfills Les nouvelles fonctionnalités du langage peuvent inclure non seulement des constructions de syntaxe et des opérateurs, mais également des fonctions intégrées. Par exemple, `Math.trunc(n)` est une fonction qui "coupe" la partie décimale d'un nombre, par exemple `Math.trunc(1.23)` retourne `1`. Dans certains moteurs JavaScript (très obsolètes), il n'y a pas de `Math.trunc`, donc un tel code échouera. Comme nous parlons de nouvelles fonctions, pas de changements de syntaxe, il n'est pas nécessaire de transpiler quoi que ce soit ici. Nous avons juste besoin de déclarer la fonction manquante. Un script qui met à jour/ajoute de nouvelles fonctions est appelé "polyfill". Il "comble" le vide et ajoute les implémentations manquantes. Pour ce cas particulier, le polyfill pour `Math.trunc` est un script qui l'implémente, comme ceci : ```js if (!Math.trunc) { // si une telle fonction n'existe pas // l'implémenter Math.trunc = function(number) { // Math.ceil et Math.floor existe même dans les anciens moteurs JavaScript // ils sont traités plus tard dans le tutoriel return number < 0 ? Math.ceil(number) : Math.floor(number); }; } ``` JavaScript est un langage très dynamique, les scripts peuvent ajouter/modifier toutes les fonctions, y compris celles intégrées. Deux librairies intéressantes de polyfills sont : - [core js](https://github.com/zloirock/core-js) qui prend en charge beaucoup de choses et permet d'inclure uniquement les fonctionnalités nécessaires. - [polyfill.io](https://polyfill.io) est un service qui fournit un script avec des polyfills, en fonction des fonctionnalités et du navigateur de l'utilisateur. ## Résumé Dans ce chapitre, nous aimerions vous motiver à étudier les fonctionnalités du langage modernes et même "de pointe", même si elles ne sont pas encore bien prises en charge par les moteurs JavaScript. N'oubliez pas d'utiliser un transpiler (si vous utilisez une syntaxe ou des opérateurs modernes) et des polyfills (pour ajouter des fonctions qui peuvent manquer). Ils veilleront à ce que le code fonctionne. Par exemple, plus tard, lorsque vous serez familiarisé avec JavaScript, vous pourrez configurer un système de création de code basé sur [webpack](http://webpack.js.org/) avec le plugin [babel-loader](https://github.com/babel/babel-loader). De bonnes ressources qui montrent l'état actuel de la prise en charge de diverses fonctionnalités : - - pour du pur JavaScript. - - pour les fonctions liées au navigateur. P.S. Google Chrome est généralement le plus à jour avec les fonctionnalités du langage, essayez-le si une démonstration d'un tutoriel échoue. La plupart des démos de didacticiels fonctionnent avec n'importe quel navigateur moderne. ================================================ FILE: 1-js/03-code-quality/index.md ================================================ # Qualité du code Ce chapitre explique les pratiques de codage que nous utiliserons plus loin dans le développement. ================================================ FILE: 1-js/04-object-basics/01-object/2-hello-object/solution.md ================================================ ```js let user = {}; user.name = "John"; user.surname = "Smith"; user.name = "Pete"; delete user.name; ``` ================================================ FILE: 1-js/04-object-basics/01-object/2-hello-object/task.md ================================================ importance: 5 --- # Bonjour objet Écrivez le code, une ligne pour chaque action : 1. Créer un objet vide `user`. 2. Ajoutez la propriété `name` avec la valeur `John`. 3. Ajoutez la propriété `surname` avec la valeur `Smith`. 4. Changer la valeur de `name` pour `Pete`. 5. Supprimez la propriété `name` de l'objet. ================================================ FILE: 1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js ================================================ function isEmpty(obj) { for (let key in obj) { // si la boucle a commencé, il y a une propriété return false; } return true; } ================================================ FILE: 1-js/04-object-basics/01-object/3-is-empty/_js.view/test.js ================================================ describe("isEmpty", function() { it("returns true for an empty object", function() { assert.isTrue(isEmpty({})); }); it("returns false if a property exists", function() { assert.isFalse(isEmpty({ anything: false })); }); }); ================================================ FILE: 1-js/04-object-basics/01-object/3-is-empty/solution.md ================================================ Passez simplement une boucle sur l’objet et `return false` immédiatement s’il existe au moins une propriété. ================================================ FILE: 1-js/04-object-basics/01-object/3-is-empty/task.md ================================================ importance: 5 --- # Vérifier le vide Ecrivez la fonction `isEmpty(obj)` qui renvoie `true` si l'objet n'a pas de propriétés, sinon `false`. Devrait fonctionner comme ça : ```js let schedule = {}; alert( isEmpty(schedule) ); // true schedule["8:30"] = "get up"; alert( isEmpty(schedule) ); // false ``` ================================================ FILE: 1-js/04-object-basics/01-object/4-const-object/solution.md ================================================ Bien sûr, ça fonctionne, pas de problème. Le `const` ne protège que la variable elle-même du changement. En d'autres termes, `user` stocke une référence à l'objet. Et cela ne peut pas être changé. Mais le contenu de l'objet peut. ```js run const user = { name: "John" }; *!* // fonctionne user.name = "Pete"; */!* // erreur user = 123; ``` ================================================ FILE: 1-js/04-object-basics/01-object/4-const-object/task.md ================================================ importance: 5 --- # Objets constants ? Est-il possible de changer un objet déclaré avec `const`, comment ? ```js const user = { name: "John" }; *!* // est-ce que ça fonctionne ? user.name = "Pete"; */!* ``` ================================================ FILE: 1-js/04-object-basics/01-object/5-sum-object/solution.md ================================================ ```js run let salaries = { John: 100, Ann: 160, Pete: 130 }; let sum = 0; for (let key in salaries) { sum += salaries[key]; } alert(sum); // 390 ``` ================================================ FILE: 1-js/04-object-basics/01-object/5-sum-object/task.md ================================================ importance: 5 --- # Somme des propriétés de l'objet Nous avons un objet stockant les salaires de notre équipe : ```js let salaries = { John: 100, Ann: 160, Pete: 130 } ``` Écrivez le code pour additionner tous les salaires et les enregistrer dans la variable `sum`. Devrait être égale à `390` dans l'exemple ci-dessus. Si `salaries` est vide, le résultat doit être `0`. ================================================ FILE: 1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/solution.js ================================================ function multiplyNumeric(obj) { for (let key in obj) { if (typeof obj[key] == 'number') { obj[key] *= 2; } } } ================================================ FILE: 1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js ================================================ let menu = { width: 200, height: 300, title: "My menu" }; function multiplyNumeric(obj) { /* your code */ } multiplyNumeric(menu); alert( "menu width=" + menu.width + " height=" + menu.height + " title=" + menu.title ); ================================================ FILE: 1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/test.js ================================================ describe("multiplyNumeric", function() { it("multiplies all numeric properties by 2", function() { let menu = { width: 200, height: 300, title: "My menu" }; let result = multiplyNumeric(menu); assert.equal(menu.width, 400); assert.equal(menu.height, 600); assert.equal(menu.title, "My menu"); }); it("returns nothing", function() { assert.isUndefined( multiplyNumeric({}) ); }); }); ================================================ FILE: 1-js/04-object-basics/01-object/8-multiply-numeric/solution.md ================================================ ================================================ FILE: 1-js/04-object-basics/01-object/8-multiply-numeric/task.md ================================================ importance: 3 --- # Multipliez les valeurs de propriétés numériques par 2 Créez une fonction `multiplyNumeric(obj)` qui multiplie toutes les valeurs de propriétés numériques de `obj` par `2`. Par exemple : ```js // before the call let menu = { width: 200, height: 300, title: "My menu" }; multiplyNumeric(menu); // after the call menu = { width: 400, height: 600, title: "My menu" }; ``` Veuillez noter que `multiplyNumeric` n’a pas besoin de retourner quoi que ce soit. Il devrait modifier l'objet en place. P.S. Utilisez `typeof` pour rechercher un `number` ici. ================================================ FILE: 1-js/04-object-basics/01-object/article.md ================================================ # Objets Comme nous le savons du chapitre , il existe huit types de données dans le langage JavaScript. Sept d'entre elles sont appelées "primitives", car leurs valeurs ne contiennent qu'une seule chose (que ce soit une chaîne, un nombre ou autre). En revanche, les objets sont utilisés pour stocker des collections de données variées et d’entités plus complexes. En JavaScript, les objets pénètrent dans presque tous les aspects du langage. Nous devons donc d'abord les comprendre avant d'aller plus loin. Un objet peut être créé avec des accolades `{…}`, avec une liste optionnelle de *propriétés*. Une propriété est une paire "clé: valeur", dans laquelle la clé (`key`) est une chaîne de caractères (également appelée "nom de la propriété"), et la valeur (`value`) peut être n'importe quoi. Nous pouvons imaginer un objet comme une armoire avec des fichiers signés. Chaque donnée est stockée dans son fichier par la clé. Il est facile de trouver un fichier par son nom ou d’ajouter/supprimer un fichier. ![](object.svg) Un objet vide ("armoire vide") peut être créé en utilisant l'une des deux syntaxes suivantes : ```js let user = new Object(); // syntaxe "constructeur d'objet" let user = {}; // syntaxe "littéral objet" ``` ![](object-user-empty.svg) Habituellement, les accolades `{...}` sont utilisées. Cette déclaration s'appelle un littéral objet (*object literal*). ## Littéraux et propriétés Nous pouvons immédiatement inclure certaines propriétés dans `{...}` sous forme de paires "clé: valeur" : ```js let user = { // un objet name: "John", // par clé "nom" valeur de stockage "John" age: 30 // par clé "age" valeur de stockage 30 }; ``` Une propriété a une clé (également appelée "nom" ou "identifiant") avant les deux points `":"` et une valeur à sa droite. Dans l'objet `user`, il y a deux propriétés : 1. La première propriété porte le nom `"name"` et la valeur `"John"`. 2. La seconde a le nom `"age"` et la valeur `30`. L'objet `user` résultant peut être imaginé comme une armoire avec deux fichiers signés intitulés "nom" et "âge". ![user object](object-user.svg) Nous pouvons ajouter, supprimer et lire des fichiers à tout moment. Les valeurs de propriété sont accessibles à l'aide de la notation par points : ```js // récupère les valeurs de propriété de l'objet : alert( user.name ); // John alert( user.age ); // 30 ``` La valeur peut être de tout type. Ajoutons un booléen : ```js user.isAdmin = true; ``` ![user object 2](object-user-isadmin.svg) Pour supprimer une propriété, nous pouvons utiliser l'opérateur `delete` : ```js delete user.age; ``` ![user object 3](object-user-delete.svg) Nous pouvons également utiliser des noms de propriété multi-mots, mais ils doivent ensuite être entourés de quotes : ```js let user = { name: "John", age: 30, "likes birds": true // le nom de la propriété multi-mots doit être entourée de quotes }; ``` ![](object-user-props.svg) La dernière propriété de la liste peut se terminer par une virgule : ```js let user = { name: "John", age: 30*!*,*/!* } ``` Cela s'appelle une virgule "trailing" ou "hanging". Elle facilite l'ajout/suppression/déplacement des propriétés, car toutes les lignes se ressemblent. ## Crochets Pour les propriétés multi-mots, l’accès par points ne fonctionne pas : ```js run // cela donnerait une erreur de syntaxe user.likes birds = true ``` JavaScript ne comprend pas cela. Il pense que nous adressons `user.likes`, ensuite il donne une erreur de syntaxe lorsqu'il rencontre des `birds` inattendus. Le point nécessite que la clé soit un identificateur de variable valide. Cela implique qu'elle ne contient aucun espace, ne commence pas par un chiffre et n'inclut pas de caractères spéciaux (`$` et `_` sont autorisés). Il existe une autre “notation entre crochets” qui fonctionne avec n’importe quelle chaîne : ```js run let user = {}; // set user["likes birds"] = true; // get alert(user["likes birds"]); // true // delete delete user["likes birds"]; ``` Maintenant tout va bien. Veuillez noter que la chaîne de caractères entre crochets est correctement entourée de quotes (tout type de guillemets fera l'affaire). Les crochets fournissent également un moyen d'obtenir le nom de la propriété comme résultat de toute expression (par opposition à une chaîne de caractères littérale), semblable à une variable, comme ceci : ```js let key = "likes birds"; // pareil que user["likes birds"] = true; user[key] = true; ``` Ici, la variable `key` peut être calculée au moment de l'exécution ou dépendre de la saisie de l'utilisateur. Et ensuite, nous l'utilisons pour accéder à la propriété. Cela nous donne beaucoup de flexibilité. Par exemple : ```js run let user = { name: "John", age: 30 }; let key = prompt("What do you want to know about the user?", "name"); // accès par variable alert( user[key] ); // John (si entré "name") ``` La notation par points ne peut pas être utilisée de la même manière : ```js run let user = { name: "John", age: 30 }; let key = "name"; alert( user.key ) // undefined ``` ### Propriétés calculées Nous pouvons utiliser des crochets dans un objet littéral, lorsqu'on crée un objet. Cela s'appelle des propriétés calculées (*computed propertie*). Par exemple : ```js run let fruit = prompt("Which fruit to buy?", "apple"); let bag = { *!* [fruit]: 5, // le nom de la propriété est tiré de la variable fruit */!* }; alert( bag.apple ); // 5 si fruit="apple" ``` La signification d'une propriété calculée est simple: `[fruit]` signifie que le nom de la propriété doit être extrait de `fruit`. Ainsi, si un visiteur entre `"apple"`, `bag` deviendra `{apple: 5}`. Essentiellement, cela fonctionne de la même façon que : ```js run let fruit = prompt("Which fruit to buy?", "apple"); let bag = {}; // prendre le nom de la propriété de la variable fruit bag[fruit] = 5; ``` … Mais a une meilleure apparence. Nous pouvons utiliser des expressions plus complexes entre crochets : ```js let fruit = 'apple'; let bag = { [fruit + 'Computers']: 5 // bag.appleComputers = 5 }; ``` Les crochets sont beaucoup plus puissants que la notation par points. Ils autorisent tous les noms de propriété et variables. Mais ils sont aussi plus lourds à écrire. Ainsi, la plupart du temps, lorsque les noms de propriété sont connus et simples, le point est utilisé. Et si nous avons besoin de quelque chose de plus complexe, nous passons aux crochets. ## Valeur de propriété abrégée (Property value shorthand) Dans du code réel, nous utilisons souvent des variables existantes en tant que valeurs pour les noms de propriétés. Par exemple : ```js run function makeUser(name, age) { return { name: name, age: age, // ...autres propriétés }; } let user = makeUser("John", 30); alert(user.name); // John ``` Dans l'exemple ci-dessus, les propriétés portent les mêmes noms que les variables. Le cas d’utilisation de la création d’une propriété à partir d’une variable est si courant qu’il existe une valeur spéciale de propriété abrégée (*property value shorthand*) pour la rendre plus courte. Au lieu de `name:name`, nous pouvons simplement écrire `name`, comme ceci : ```js function makeUser(name, age) { *!* return { name, // pareil que name: name age, // pareil que age: age // ... }; */!* } ``` Nous pouvons utiliser à la fois des propriétés normales et des raccourcis dans le même objet : ```js let user = { name, // pareil que name:name age: 30 }; ``` ## Limitations des noms de propriété Comme nous le savons déjà, une variable ne peut pas avoir un nom égal à l'un des mots réservés au langage comme "for", "let", "return" etc. Mais pour une propriété d'objet, il n'y a pas de telle restriction : ```js run // ces propriétés sont toutes correctes let obj = { for: 1, let: 2, return: 3 }; alert( obj.for + obj.let + obj.return ); // 6 ``` En bref, il n'y a aucune limitation sur les noms de propriété. Il peut s'agir de n'importe quelle chaîne de caractères ou symbole (un type spécial pour les identifiants, qui sera traité plus tard). Les autres types sont automatiquement convertis en chaînes de caractères. Par exemple, un nombre `0` devient une chaîne `"0"` lorsqu'il est utilisé comme clé de propriété : ```js run let obj = { 0: "test" // identique à "0": "test" }; // les 2 alertes accèdent à la même propriété (le chiffre 0 est converti en string "0") alert( obj["0"] ); // test alert( obj[0] ); // test (same property) ``` Il y a un problème mineur avec une propriété spéciale nommée `__proto__`. Nous ne pouvons pas le définir sur une valeur non-objet : ```js run let obj = {}; obj.__proto__ = 5; // assignation d'un nombre alert(obj.__proto__); // [object Object] - la valeur est un objet, n'a pas fonctionné comme prévu ``` Comme nous le voyons dans le code, l'affectation à une primitive `5` est ignorée. Nous couvrirons la nature particulière de `__proto__` dans les [chapitres suivants](info:prototype-inheritance), et nous suggèrerons une [façon de corriger](info:prototype-methods) ce genre de comportement. ## Test d'existence de propriété, opérateur "in" Une caractéristique notable des objets en JavaScript, par rapport à de nombreux autres langages, est qu'il est possible d'accéder à n'importe quelle propriété. Il n'y aura pas d'erreur si la propriété n'existe pas ! La lecture d'une propriété non existante renvoie simplement `undefined`. Nous pouvons donc facilement tester si la propriété existe : ```js run let user = {}; alert( user.noSuchProperty === undefined ); // true signifie "pas une telle propriété" ``` Il existe également un opérateur spécial `"in"` pour cela. La syntaxe est : ```js "key" in object ``` Par exemple : ```js run let user = { name: "John", age: 30 }; alert( "age" in user ); // true, user.age existe alert( "blabla" in user ); // false, user.blabla n'existe pas ``` Veuillez noter que sur le côté gauche de `in`, il doit y avoir un *nom de propriété*. C’est généralement une chaîne de caractères entre guillemets. Si nous omettons les guillemets, cela signifie qu'une variable doit contenir le nom réel à tester. Par exemple : ```js run let user = { age: 30 }; let key = "age"; alert( *!*key*/!* in user ); // true, la propriété "age" existe ``` Pourquoi l'opérateur `in` existe-t-il ? N'est-ce pas suffisant de comparer avec `undefined` ? Eh bien, la plupart du temps, la comparaison avec `undefined` fonctionne bien. Mais il y a un cas particulier quand il échoue, mais `in` fonctionne correctement. C’est lorsque une propriété d’objet existe, mais qu'elle stocke undefined : ```js run let obj = { test: undefined }; alert( obj.test ); // c'est indéfini, donc - pas une telle propriété ? alert( "test" in obj ); // true, la propriété existe ! ``` Dans le code ci-dessus, la propriété `obj.test` existe techniquement. Donc, l'opérateur `in` fonctionne bien. Des situations comme celle-ci se produisent très rarement, parce que `undefined` n'est généralement pas attribué. Nous utilisons principalement `null` pour les valeurs "inconnues" ou "vides". Ainsi, l'opérateur `in` est un invité exotique dans le code. ## La boucle "for..in" [#forin] Pour parcourir toutes les clés d'un objet, il existe une forme spéciale de boucle : `for..in`. C'est une chose complètement différente de la construction `for(;;)` que nous avons étudiée auparavant. La syntaxe : ```js for (key in object) { // exécute le corps pour chaque clé parmi les propriétés de l'objet } ``` Par exemple, affichons toutes les propriétés de `user` : ```js run let user = { name: "John", age: 30, isAdmin: true }; for (let key in user) { // keys alert( key ); // name, age, isAdmin // valeurs pour les clés alert( user[key] ); // John, 30, true } ``` Notez que toutes les constructions "for" nous permettent de déclarer la variable en boucle à l'intérieur de la boucle, comme `let key` ici. En outre, nous pourrions utiliser un autre nom de variable ici au lieu de `key`. Par exemple, `for(let prop in obj)` est également largement utilisé. ### Ordonné comme un objet Les objets sont-ils ordonnés ? En d'autres termes, si nous parcourons un objet en boucle, obtenons-nous toutes les propriétés dans le même ordre où elles ont été ajoutées ? Pouvons-nous compter sur cela ? La réponse courte est : "ordonné de manière spéciale" : les propriétés des entiers sont triées, les autres apparaissent dans l'ordre de création. Nous allons voir cela en détails. Par exemple, considérons un objet avec les indicatifs de téléphone par pays : ```js run let codes = { "49": "Germany", "41": "Switzerland", "44": "Great Britain", // .., "1": "USA" }; *!* for(let code in codes) { alert(code); // 1, 41, 44, 49 } */!* ``` L'objet peut être utilisé pour suggérer une liste d'options à l'utilisateur. Si nous créons un site principalement pour le public allemand, nous voulons probablement que `49` soit le premier. Mais si nous exécutons ce code, nous voyons une image totalement différente : - USA (1) passe en premier - puis Switzerland (41) et ainsi de suite. Les indicatifs de téléphone sont classés par ordre croissant, car ce sont des entiers. Donc on voit `1, 41, 44, 49`. ````smart header="Propriétés entier (integer properties) ? Qu'est-ce que c'est ?" Le terme "propriété entier" (integer properties) désigne ici une chaîne de caractères qui peut être convertie en un nombre entier ou inversement sans changement. Ainsi, `"49"` est un nom de propriété entier, parce que lorsqu'il est transformé en nombre entier et inversement, il reste identique. Mais `"+49"` et `"1.2"` ne le sont pas : ```js run // Number(...) convertit explicitement en nombre // Math.trunc est une fonction intégrée qui supprime la partie décimale alert( String(Math.trunc(Number("49"))) ); // "49", identique, propriété entière alert( String(Math.trunc(Number("+49"))) ); // "49", non identique "+49" ⇒ propriété non entière alert( String(Math.trunc(Number("1.2"))) ); // "1", non identique "1.2" ⇒ propriété non entière ``` ```` … Par contre, si les clés ne sont pas des entiers, elles sont listées dans l'ordre de création, par exemple : ```js run let user = { name: "John", surname: "Smith" }; user.age = 25; // Ajouter une clé de plus *!* // les propriétés non-entiers sont listées dans l'ordre de création */!* for (let prop in user) { alert( prop ); // name, surname, age } ``` Donc, pour résoudre le problème avec les indicatifs de téléphone, nous pouvons "tricher" en rendant ces indicatifs non entiers. Ajouter un signe plus `"+"` avant chaque indicatif suffit. Comme ceci : ```js run let codes = { "+49": "Germany", "+41": "Switzerland", "+44": "Great Britain", // .., "+1": "USA" }; for(let code in codes) { alert( +code ); // 49, 41, 44, 1 } ``` Maintenant, cela fonctionne comme prévu. ## Résumé Les objets sont des tableaux associatifs dotés de plusieurs fonctionnalités spéciales. Ils stockent des propriétés (paires clé-valeur), où : - Les clés de propriété doivent être des chaînes de caractères ou des symboles (généralement des chaînes de caractères). - Les valeurs peuvent être de tout type. Pour accéder à une propriété, nous pouvons utiliser : - La notation par points : `obj.property`. - Notation entre crochets `obj["property"]`. Les crochets permettent de prendre la clé à partir d’une variable, comme `obj[varWithKey]`. Opérateurs supplémentaires : - Pour supprimer une propriété : `delete obj.prop`. - Pour vérifier si une propriété avec la clé donnée existe : `"key" in obj`. - Pour parcourir un objet : la boucle `for (let key in obj)`. Ce que nous avons étudié dans ce chapitre s’appelle un "objet simple" (plain object) ou juste `Object`. Il existe de nombreux autres types d'objets en JavaScript : - `Array` pour stocker des collections de données ordonnées, - `Date` pour stocker des informations sur la date et l'heure, - `Error` pour stocker des informations sur une erreur. - Etc. Ils ont leurs particularités que nous étudierons plus tard. Parfois, les gens disent quelque chose comme "type Tableau" ou "type Date", mais ils ne sont pas formellement propres, mais appartiennent à un seul type de données "objet". Et ils l'étendent de différentes manières. Les objets en JavaScript sont très puissants. Nous venons de gratter la surface d’un sujet vraiment énorme. Nous allons travailler étroitement avec les objets et en apprendre davantage à leur sujet dans d’autres parties du tutoriel. ================================================ FILE: 1-js/04-object-basics/02-object-copy/article.md ================================================ # Les références d'objet et leur copie Une des différences fondamentale des objets avec les primitives est que ceux-ci sont stockés et copiés "par référence", en opposition des valeurs primitives : strings, numbers, booleans, etc. -- qui sont toujours copiés comme "valeur entière". On comprendra plus facilement en regardant "sous le capot" ce qui se passe lorsque nous copions une valeure. Commençons avec une primitive, comme une chaîne de caractères. Ici nous assignons une copie de `message` dans `phrase` : ```js let message = "Hello!"; let phrase = message; ``` Il en résulte deux variables indépendantes, chacune stockant la chaîne de caractères `"Hello!"`. ![](variable-copy-value.svg) Un résultat plutôt évident n'est-ce pas ? Les objets ne fonctionnent pas comme cela. **Une variable assignée à un objet ne stocke pas l'objet lui-même, mais son "adresse en mémoire", en d'autres termes "une référence" à celui-ci.** Prenons un exemple d'une telle variable : ```js let user = { name: "John" }; ``` Et ici comment elle est stockée en mémoire : ![](variable-contains-reference.svg) L'objet est stocké quelque part dans la mémoire (du coté droit de l'image), tandis que la variable `user` (du coté gauche) a une référence à celui-ci. On peut imaginer la variable d'objet, ici `user`, comme une feuille de papier avec l'adresse de l'objet écrit dessus. Lorque l'on réalise une action avec l'objet, par exemple récupérer la propriété `user.name`, le moteur de JavaScript regarde à l'adresse et réalise l'opération sur l'objet actuel. Et voilà pourquoi cela est important. **Lorsqu'une variable d'objet est copiée -- la référence est copiée, l'objet lui-même n'est pas dupliqué.** Par exemple: ```js no-beautify let user = { name: "John" }; let admin = user; // copie la référence ``` Maintenant nous avons deux variables, chacune avec la référence vers le même objet : ![](variable-copy-reference.svg) Comme vous pouvez le voir, il n'y a toujours qu'un seul objet, mais maintenant avec deux variables qui le référence. On peut utiliser n'importe quelle variable pour accéder à l'objet et modifier son contenu : ```js run let user = { name: 'John' }; let admin = user; *!* admin.name = 'Pete'; // changé par la référence "admin" */!* alert(*!*user.name*/!*); // 'Pete', les changements sont visibles sur la référence "user" ``` C'est comme si nous avions une armoire avec deux clés et que nous en utilisions une (`admin`) pour y entrer et y apporter des modifications. Ensuite, si nous utilisons plus tard une autre clé (`user`), nous ouvrons toujours la même armoire et pouvons accéder au contenu modifié. ## Comparaison par référence Deux objets sont égaux seulement s'ils sont le même objet. Par exemple, ici `a` et `b` référencent le même objet, aussi sont-ils similaires : ```js run let a = {}; let b = a; // copie la référence alert( a == b ); // true, les deux variables référencent le même objet alert( a === b ); // true ``` Et ici deux objets indépendants ne sont pas égaux, même s'ils se ressemblent (les deux sont vides) : ```js run let a = {}; let b = {}; // 2 objets indépendants alert( a == b ); // false ``` Pour des comparaisons comme `obj1 > obj2` ou des comparaisons avec une primitive `obj == 5`, les objets sont convertis en primitives. Nous étudierons comment les conversions d'objets fonctionnent très bientôt, mais pour dire la vérité, de telles comparaisons sont rarement nécessaires, en général elles sont le résultat d'une erreur de programmation. ````smart header="Les objets const peuvent être modifiés" Un effet secondaire important du stockage des objets en tant que références est qu'un objet déclaré comme `const` *peut* être modifié. Par exemple : ```js run const user = { name: "John" }; *!* user.name = "Pete"; // (*) */!* alert(user.name); // Pete ``` Il peut sembler que la ligne `(*)` causerait une erreur, mais ce n'est pas le cas. La valeur de `user` est constante, elle doit toujours référencer le même objet, mais les propriétés de cet objet sont libres de changer. En d'autres termes, `const user` donne une erreur uniquement si nous essayons de définir `user=...` dans son ensemble. Cela dit, si nous avons vraiment besoin de créer des propriétés d'objet constantes, c'est également possible, mais en utilisant des méthodes totalement différentes. Nous le mentionnerons dans le chapitre . ```` ## Clonage et fusion, Object.assign [#cloning-and-merging-object-assign] Copier une variable object crée une référence en plus vers le même objet. Mais que se passe-t-il si nous devons dupliquer un objet ? Nous pouvons créer un nouvel objet et reproduire la structure de l'existant, en itérant sur ses propriétés et en les copiant au niveau primitif. Comme cela : ```js run let user = { name: "John", age: 30 }; *!* let clone = {}; // le nouvel object vide // on copie toutes les propritétés de user for (let key in user) { clone[key] = user[key]; } */!* // maintenant clone est un objet complètement indépendant avec le même contenu clone.name = "Pete"; // On change les données de celui-ci alert( user.name ); // c'est toujour John dans l'objet copié ``` On peut aussi utiliser la méthode [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) pour cela. La syntaxe est : ```js Object.assign(dest, ...sources) ``` - Le premier argument `dest` est l'objet cible - Les autres arguments sont une liste d'objets source. Il copie les propriétés de tous les objets sources dans la cible `dest`, puis les renvoie comme résultat. Par exemple, nous avons l'objet `user`, ajoutons-lui quelques autorisations : ```js run let user = { name: "John" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; *!* // copie toutes les propriétés de permissions1 et 2 dans user Object.assign(user, permissions1, permissions2); */!* // now user = { name: "John", canView: true, canEdit: true } alert(user.name); // John alert(user.canView); // true alert(user.canEdit); // true ``` Si la propriété copiée existe déja, elle est écrasée. ```js run let user = { name: "John" }; Object.assign(user, { name: "Pete" }); alert(user.name); // on a user = { name: "Pete" } ``` Nous pouvons également utiliser `Object.assign` pour effectuer un simple clonage d'objet : ```js run let user = { name: "John", age: 30 }; *!* let clone = Object.assign({}, user); */!* alert(clone.name); // John alert(clone.age); // 30 ``` Ici cela copie toutes les propriétés de `user` dans l'objet vide et le retourne. Il existe également d'autres méthodes de clonage d'un objet, par ex. en utilisant la [syntaxe spread](info:rest-parameters-spread) `clone = {...user}`, abordé plus loin dans le tutoriel. ## Clonage imbriqué Jusqu'à maintenant on suppose que toutes les propriétés de `user` sont des primitives. Mais les propriétés peuvent être des références vers d'autres objets. Comment gérer ces cas-là ? Comme ceci : ```js run let user = { name: "John", sizes: { height: 182, width: 50 } }; alert( user.sizes.height ); // 182 ``` Ce n'est plus suffisant de copier `clone.sizes = user.sizes`, car `user.sizes` est un objet, il sera copié par référence. Donc `clone` et `user` partageront le même objet `sizes` : ```js run let user = { name: "John", sizes: { height: 182, width: 50 } }; let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // true, c'est le même objet // user et clone partage l'objet sizes user.sizes.width = 60; // changer une propriété d'un endroit alert(clone.sizes.width); // 60, obtenir le résultat de l'autre ``` Pour résoudre ce problème et faire en sorte que `user` et `clone` soient des objets véritablement séparés, nous devrions utiliser une boucle de clonage qui examine chaque valeur de `user[key]` et, s'il s'agit d'un objet, répliquer également sa structure. C'est ce qu'on appelle un « clonage profond » ou « clonage structuré ». Il existe une méthode [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) qui implémente le clonage en profondeur. ### structuredClone L'appel `structuredClone(object)` clone l'`object` avec toutes les propriétés imbriquées. Voici comment nous pouvons l'utiliser dans notre exemple : ```js run let user = { name: "John", sizes: { height: 182, width: 50 } }; *!* let clone = structuredClone(user); */!* alert( user.sizes === clone.sizes ); // false, c'est un objet différent // user et clone n'ont plus aucun lien entre eux user.sizes.width = 60; // changer une propriété d'un endroit alert(clone.sizes.width); // 50, sans lien ``` La méthode `structuredClone` peut cloner la plupart des types de données, tels que des objets, des tableaux, des valeurs primitives. Il prend également en charge les références circulaires, lorsqu'une propriété d'objet fait référence à l'objet lui-même (directement ou via une chaîne ou des références). Par exemple : ```js run let user = {}; // créons une référence circulaire : // user.me fait référence à l'utilisateur lui-même user.me = user; let clone = structuredClone(user); alert(clone.me === clone); // true ``` Comme vous pouvez le voir, `clone.me` fait référence au `clone`, pas à `user` ! Ainsi, la référence circulaire a également été clonée correctement. Cependant, il existe des cas où `structuredClone` échoue. Par exemple, lorsqu'un objet a une propriété de fonction : ```js run // error structuredClone({ f: function() {} }); ``` Les propriétés de fonction ne sont pas prises en charge. Pour gérer des cas aussi complexes, nous devrons peut-être utiliser une combinaison de méthodes de clonage, écrire du code personnalisé ou, pour ne pas réinventer la roue, prendre une implémentation existante, par exemple [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) de la bibliothèque JavaScript [lodash](https://lodash.com). ## Résumé Les objets sont assignés et copiés par référence. En d'autres termes, une variable ne stocke pas la "valeur de l'objet" mais la "référence" (l'adresse en mémoire) de la valeur. Donc copier cette variable, ou la passer en argument d'une fonction, copie la référence, pas l'objet lui-même. Toutes les opérations faites par une copie de la référence (comme ajouter/supprimer une propriété) sont faites sur le même objet. Pour faire une "copie réelle" (un clone), nous pouvons utiliser `Object.assign` pour la soi-disant "copie superficielle" (les objets imbriqués sont copiés par référence) ou une fonction de "clonage en profondeur" `structuredClone` ou utiliser une implementation personnalisée de clonage, telle que [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). ================================================ FILE: 1-js/04-object-basics/03-garbage-collection/article.md ================================================ # Ramasse-miettes (garbage collection) La gestion de la mémoire en JavaScript est effectuée automatiquement et de manière invisible pour nous. Nous créons des primitives, des objets, des fonctions… Tout cela prend de la mémoire. Que se passe-t-il quand quelque chose n'est plus nécessaire ? Comment le moteur JavaScript le découvre et le nettoie ? ## Accessibilité Le concept principal de la gestion de la mémoire en JavaScript est l’*accessibilité*. En termes simples, les valeurs "accessibles" sont celles qui sont accessibles ou utilisables d’une manière ou d’une autre. Elles sont garanties d'être stockés en mémoire. 1. Il existe un ensemble de base de valeurs intrinsèquement accessibles, qui ne peuvent pas être supprimées pour des raisons évidentes. Par exemple : - Variables locales et paramètres de la fonction en cours d’exécution. - Variables et paramètres pour d'autres fonctions sur la chaîne d'appels imbriqués en cours. - Variables globales. - (il y en a d'autres, internes aussi) Ces valeurs s'appellent des racines (*roots*). 2. Toute autre valeur est considérée comme accessible si elle est accessible depuis une racine par une référence ou par une chaîne de références. Par exemple, s’il existe un objet dans une variable globale et que cet objet a une propriété référençant un autre objet, *cet* objet est considéré comme accessible. Et ceux auxquels il fait référence sont également accessibles. Des exemples détaillés à suivre. Il existe un processus d’arrière-plan dans le moteur JavaScript appelé [Ramasse-miettes (Garbage Collector)](https://fr.wikipedia.org/wiki/Ramasse-miettes_(informatique)). Il surveille tous les objets et supprime ceux qui sont devenus inaccessibles. ## Un exemple simple Voici l'exemple le plus simple : ```js // user a une référence à l'objet let user = { name: "John" }; ``` ![](memory-user-john.svg) Ici, la flèche représente une référence d'objet. La variable globale `"user"` fait référence à l’objet `{name: "John"}` (nous l’appellerons John par souci de brièveté). La propriété `"name"` de John stocke une primitive, elle est donc stockée à l'intérieur de l'objet. Si la valeur de `user` est écrasée, la référence est perdue : ```js user = null; ``` ![](memory-user-john-lost.svg) Maintenant, John devient inaccessible. Il n’y a aucun moyen d’y accéder, pas de référence. Le ramasse-miettes (garbage collector) détruit les données et libère la mémoire. ## Deux références Imaginons maintenant que nous ayons copié la référence de `user` à `admin` : ```js // user a une référence à l'objet let user = { name: "John" }; *!* let admin = user; */!* ``` ![](memory-user-john-admin.svg) Maintenant si nous faisons la même chose : ```js user = null; ``` … Ensuite, l’objet est toujours accessible via la variable globale `admin`, il est donc encore en mémoire. Si nous écrasons également `admin`, alors il sera supprimé. ## Objets liés Maintenant, un exemple plus complexe. La famille : ```js function marry(man, woman) { woman.husband = man; man.wife = woman; return { father: man, mother: woman } } let family = marry({ name: "John" }, { name: "Ann" }); ``` La fonction `marry` "marie" deux objets en leur donnant des références et renvoie un nouvel objet les contenant tous les deux. Le résultat de la structure de mémoire : ![](family.svg) À partir de maintenant, tous les objets sont accessibles. Supprimons maintenant deux références : ```js delete family.father; delete family.mother.husband; ``` ![](family-delete-refs.svg) Il ne suffit pas de supprimer une seule de ces deux références, car tous les objets seraient toujours accessibles. Mais si nous supprimons les deux, alors nous pouvons voir que John n’a plus de référence entrante : ![](family-no-father.svg) Les références sortantes importent peu. Seuls les objets entrants peuvent rendre un objet accessible. Ainsi, John est maintenant inaccessible et sera supprimé de la mémoire avec toutes ses données qui sont également devenues inaccessibles. Après le passage du ramasse-miettes (garbage collector) : ![](family-no-father-2.svg) ## Île inaccessible Il est possible que toute l'île d'objets liés entre eux devienne inaccessible et soit supprimée de la mémoire. L'objet source est le même que ci-dessus. Ensuite : ```js family = null; ``` L'image en mémoire devient : ![](family-no-family.svg) Cet exemple montre à quel point le concept d'accessibilité est important. Il est évident que John et Ann sont toujours liés, les deux ont des références entrantes. Mais cela ne suffit pas. L’ancien objet `"family"` a été dissocié de la racine, elle n’y fait plus référence, toute l’île devient inaccessible et sera donc supprimée. ## Algorithmes internes L'algorithme de base de la récupération de place (garbage collection) s'appelle "mark-and-sweep". Les étapes suivantes du "ramasse-miettes" (garbage collection) sont régulièrement effectuées : - Le ramasse-miettes prend les racines et les "marque" (se souvient). - Ensuite, il visite et "marque" toutes les références. - Ensuite, il visite les objets marqués et marque *leurs* références. Tous les objets visités sont mémorisés afin de ne pas visiter le même objet deux fois dans le futur. - … Et ainsi de suite tant qu'il y a des références non consultées (accessibles depuis les racines). - Tous les objets sont supprimés sauf ceux qui sont marqués. Par exemple, imaginons notre structure d'objet ressembler à ceci : ![](garbage-collection-1.svg) Nous pouvons clairement voir une "île inaccessible" sur le côté droit. Voyons maintenant comment le garbage collector "mark-and-sweep" le gère. La première étape marque les racines : ![](garbage-collection-2.svg) Ensuite, nous suivons leurs références et marquons les objets référencés : ![](garbage-collection-3.svg) ...Et continuons à suivre d'autres références, dans la mesure du possible : ![](garbage-collection-4.svg) Désormais, les objets qui n'ont pas pu être visités sont considérés comme inaccessibles et seront supprimés : ![](garbage-collection-5.svg) Nous pouvons également imaginer que le processus consiste à renverser un énorme seau de peinture à la racine, qui traverse toutes les références et marque tous les objets accessibles. Les non marqués sont ensuite supprimés. C'est le concept de la façon dont la garbage collection fonctionne. Les moteurs JavaScript appliquent de nombreuses optimisations pour accélérer l’exécution et ne pas affecter l’exécution. Certaines des optimisations : - **Collecte générationnelle** -- les objets sont divisés en deux ensembles : "nouveaux" et "anciens". Dans un code typique, de nombreux objets ont une courte durée de vie : ils apparaissent, font leur travail et meurent rapidement, il est donc logique de suivre les nouveaux objets et d'en effacer la mémoire si c'est le cas. Ceux qui survivent assez longtemps deviennent "vieux" et sont examinés moins souvent. - **Collecte incrémentielle** -- s'il y a beaucoup d'objets et que nous essayons de parcourir et de marquer l'ensemble d'objets en une seule fois, cela peut prendre un certain temps et introduire des retards visibles dans l'exécution. Ainsi, le moteur divise l'ensemble des objets existants en plusieurs parties. Et puis nettoie ces parties les unes après les autres. Il existe de nombreux petits garbage collections au lieu d'un total. Cela nécessite une comptabilité supplémentaire entre eux pour suivre les changements, mais nous obtenons de nombreux petits retards au lieu d'un gros. - **Collecte en cas d'inactivité** -- le garbage collector essaie de s'exécuter uniquement lorsque le processeur est inactif, afin de réduire l'effet possible sur l'exécution. Il existe d'autres optimisations et variantes d'algorithmes de récupération de place. Même si je souhaite les décrire ici, je dois m'abstenir, car différents moteurs implémentent différentes techniques et ajustements. Et, ce qui est encore plus important, les choses changent à mesure que les moteurs se développent. Donc aller plus loin de manière plus poussée, sans réel besoin, n’en vaut probablement pas la peine. À moins, bien sûr, que ce soit une question qui vous intéresse vraiment, vous trouverez quelques liens pour vous ci-dessous. ## Résumé Les principales choses à savoir : - La garbage collection est effectuée automatiquement. Nous ne pouvons ni forcer ni empêcher cela. - Les objets sont conservés en mémoire tant qu'ils sont accessibles. - Être référencé n'est pas la même chose qu'être accessible (depuis une racine) : un groupe d'objets liés entre eux peut devenir inaccessible dans son ensemble. Les moteurs modernes implémentent des algorithmes avancés de récupération de place. Un livre général intitulé "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al.) En parle. Si vous êtes familiarisé avec la programmation de bas niveau, les informations plus détaillées sur le garbage collector V8 se trouvent dans l'article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). Le [blog V8](https://v8.dev) publie également des articles sur les modifications de la gestion de la mémoire de temps à autre. Naturellement, pour apprendre la récupération de place, vous feriez mieux de vous préparer en vous renseignant sur les éléments internes de V8 en général et en lisant le blog de [Vyacheslav Egorov](https://mrale.ph) qui a travaillé comme l'un des ingénieurs V8. Je dis: «V8», car c'est le plus couvert d'articles sur Internet. Pour d'autres moteurs, de nombreuses approches sont similaires, mais la récupération de place diffère à de nombreux égards. Une connaissance approfondie des moteurs est utile lorsque vous avez besoin d'optimisations de bas niveau. Il serait sage de planifier cela comme prochaine étape après la connaissance du langage. ================================================ FILE: 1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md ================================================ **Réponse : une erreur.** Essayez le : ```js run function makeUser() { return { name: "John", ref: this }; } let user = makeUser(); alert( user.ref.name ); // Erreur: Impossible de lire la propriété 'nom' de undefined ``` C'est parce que les règles qui définissent `this` ne prennent pas en compte la définition d'objet. Seul le moment de l'appel compte. Ici, la valeur de `this` à l'intérieur de `makeUser()` est `undefined`, car elle est appelée en tant que fonction et non en tant que méthode avec la syntaxe au "point". La valeur de `this` est la même pour toute la fonction, les blocs de code et les littéraux d'objet ne l'affectent pas. Donc `ref: this` prend actuellement le `this` courant de la fonction. Nous pouvons réécrire la fonction et renvoyer le même `this` avec la valeur `undefined` : ```js run function makeUser(){ return this; // cette fois il n'y a pas d'objet littéral } alert( makeUser().name ); // Error: Cannot read property 'name' of undefined ``` Comme vous pouvez le constater, le résultat de `alert( makeUser().name )` est identique à celui de `alert( user.ref.name )` de l'exemple précédent. Voici le cas contraire : ```js run function makeUser() { return { name: "John", *!* ref() { return this; } */!* }; } let user = makeUser(); alert( user.ref().name ); // John ``` Maintenant cela fonctionne parce que `user.ref()` est une méthode. Et la valeur de `this` est définie pour l'objet avant le point `.`. ================================================ FILE: 1-js/04-object-basics/04-object-methods/4-object-property-this/task.md ================================================ importance: 5 --- # Utilisation de "this" dans le littéral d'objet Ici, la fonction `makeUser` renvoie un objet. Quel est le résultat de l'accès à sa `ref` ? Pourquoi ? ```js function makeUser() { return { name: "John", ref: this }; } let user = makeUser(); alert( user.ref.name ); // Quel est le résultat ? ``` ================================================ FILE: 1-js/04-object-basics/04-object-methods/7-calculator/_js.view/solution.js ================================================ let calculator = { sum() { return this.a + this.b; }, mul() { return this.a * this.b; }, read() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; ================================================ FILE: 1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js ================================================ describe("calculator", function() { context("when 2 and 3 entered", function() { beforeEach(function() { sinon.stub(window, "prompt"); prompt.onCall(0).returns("2"); prompt.onCall(1).returns("3"); calculator.read(); }); afterEach(function() { prompt.restore(); }); it('the read get two values and saves them as object properties', function () { assert.equal(calculator.a, 2); assert.equal(calculator.b, 3); }); it("the sum is 5", function() { assert.equal(calculator.sum(), 5); }); it("the multiplication product is 6", function() { assert.equal(calculator.mul(), 6); }); }); }); ================================================ FILE: 1-js/04-object-basics/04-object-methods/7-calculator/solution.md ================================================ ```js run demo solution let calculator = { sum() { return this.a + this.b; }, mul() { return this.a * this.b; }, read() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() ); ``` ================================================ FILE: 1-js/04-object-basics/04-object-methods/7-calculator/task.md ================================================ importance: 5 --- # Créer une calculatrice Créez un objet `calculator` avec trois méthodes : - `read()` demande deux valeurs et les enregistre en tant que propriétés d'objet avec les noms `a` et `b` respectivement. - `sum()` renvoie la somme des valeurs sauvegardées. - `mul()` multiplie les valeurs sauvegardées et renvoie le résultat. ```js let calculator = { // ... votre code ... }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() ); ``` [demo] ================================================ FILE: 1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js ================================================ let ladder = { step: 0, up: function() { this.step++; return this; }, down: function() { this.step--; return this; }, showStep: function() { alert(this.step); return this; } }; ================================================ FILE: 1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js ================================================ describe('Ladder', function() { before(function() { window.alert = sinon.stub(window, "alert"); }); beforeEach(function() { ladder.step = 0; }); it('up() should return this', function() { assert.equal(ladder.up(), ladder); }); it('down() should return this', function() { assert.equal(ladder.down(), ladder); }); it('showStep() should call alert', function() { ladder.showStep(); assert(alert.called); }); it('up() should increase step', function() { assert.equal(ladder.up().up().step, 2); }); it('down() should decrease step', function() { assert.equal(ladder.down().step, -1); }); it('down().up().up().up() ', function() { assert.equal(ladder.down().up().up().up().step, 2); }); it('showStep() should return this', function() { assert.equal(ladder.showStep(), ladder); }); it('up().up().down().showStep().down().showStep()', function () { assert.equal(ladder.up().up().down().showStep().down().showStep().step, 0) }); after(function() { ladder.step = 0; alert.restore(); }); }); ================================================ FILE: 1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md ================================================ La solution consiste à renvoyer l'objet lui-même à partir de chaque appel. ```js run let ladder = { step: 0, up() { this.step++; *!* return this; */!* }, down() { this.step--; *!* return this; */!* }, showStep() { alert( this.step ); *!* return this; */!* } }; ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` Nous pouvons également écrire un seul appel par ligne. Pour les longues chaînes, c'est plus lisible : ```js ladder .up() .up() .down() .showStep() // 1 .down() .showStep(); // 0 ``` ================================================ FILE: 1-js/04-object-basics/04-object-methods/8-chain-calls/task.md ================================================ importance: 2 --- # Chaining Il y a un objet `ladder` qui permet de monter et descendre : ```js let ladder = { step: 0, up() { this.step++; }, down() { this.step--; }, showStep: function() { // affiche l'étape en cours alert( this.step ); } }; ``` Maintenant, si nous devons faire plusieurs appels en séquence, nous pouvons le faire comme ceci : ```js ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 ladder.down(); ladder.showStep(); // 0 ``` Modifiez le code de `up` et `down` pour rendre les appels chaînables, comme ceci : ```js ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` Cette approche est largement utilisée dans les bibliothèques JavaScript. ================================================ FILE: 1-js/04-object-basics/04-object-methods/article.md ================================================ # Méthodes d'objet, "this" Les objets sont généralement créés pour représenter des entités du monde réel, comme des utilisateurs, des commandes, etc. : ```js let user = { name: "John", age: 30 }; ``` Et, dans le monde réel, un utilisateur peut agir : sélectionner un élément du panier, se connecter, se déconnecter, etc. Les actions sont représentées en JavaScript par des fonctions dans les propriétés. ## Exemples de méthodes Pour commencer, apprenons à `user` à dire bonjour : ```js run let user = { name: "John", age: 30 }; *!* user.sayHi = function() { alert("Hello!"); }; */!* user.sayHi(); // Hello! ``` Ici, nous venons d'utiliser une fonction expression pour créer la fonction et l'affecter à la propriété `user.sayHi` de l'objet. Ensuite, nous pouvons l'appeler comme `user.sayHi()`. L'utilisateur peut maintenant parler! Une fonction qui est la propriété d'un objet s'appelle sa *méthode*. Nous avons donc ici une méthode `sayHi` de l’objet `user`. Bien sûr, nous pourrions utiliser une fonction pré-déclarée comme méthode, comme ceci : ```js run let user = { // ... }; *!* // d'abord, déclarer function sayHi() { alert("Hello!"); } // puis ajouter comme une méthode user.sayHi = sayHi; */!* user.sayHi(); // Hello! ``` ```smart header="Programmation orientée objet" Lorsque nous écrivons notre code en utilisant des objets pour représenter des entités, cela s'appelle une [programmation orientée objet](https://fr.wikipedia.org/wiki/Programmation_orient%C3%A9e_objet), en bref : "POO". La programmation orientée objet est un élément important, une science intéressante en soi. Comment choisir les bonnes entités ? Comment organiser l'interaction entre elles ? C’est une architecture, et il existe d’excellents livres sur ce sujet, tels que "Design Patterns: Elements of Reusable Object-Oriented Software" de E. Gamma, R. Helm, R. Johnson, J. Vissides ou "Object-Oriented Analysis and Design with Applications" de G. Booch, et plus. ``` ### Méthode abrégée Il existe une syntaxe plus courte pour les méthodes dans un littéral d'objet : ```js // ces objets font la même chose user = { sayHi: function() { alert("Hello"); } }; // la méthode abrégée semble mieux, non ? user = { *!* sayHi() { // identique à "sayHi: function(){...}" */!* alert("Hello"); } }; ``` Comme démontré, nous pouvons omettre `"function"` et simplement écrire `sayHi()`. A vrai dire, les notations ne sont pas totalement identiques. Il existe des différences subtiles liées à l'héritage d'objet (à couvrir plus tard), mais pour le moment, elles importent peu. Dans presque tous les cas, la syntaxe la plus courte est préférable. ## "this" dans les méthodes Il est courant qu'une méthode d'objet ait besoin d'accéder aux informations stockées dans l'objet pour effectuer son travail. Par exemple, le code à l'intérieur de `user.sayHi()` peut nécessiter le nom de `user`. **Pour accéder à l'objet, une méthode peut utiliser le mot-clé `this`.** La valeur de `this` est l'objet "avant le point", celui utilisé pour appeler la méthode. Par exemple : ```js run let user = { name: "John", age: 30, sayHi() { *!* // "this" est "l'objet actuel" alert(this.name); */!* } }; user.sayHi(); // John ``` Ici, lors de l'exécution de `user.sayHi()`, la valeur de `this` sera `user`. Techniquement, il est également possible d’accéder à l’objet sans `this`, en le référençant via la variable externe : ```js let user = { name: "John", age: 30, sayHi() { *!* alert(user.name); // "user" au lieu de "this" */!* } }; ``` … Mais un tel code n'est pas fiable. Si nous décidons de copier `user` dans une autre variable, par exemple `admin = user` et écraser `user` avec quelque chose d'autre, il accédera au mauvais objet. Cela est démontré ci-dessous : ```js run let user = { name: "John", age: 30, sayHi() { *!* alert( user.name ); // conduit à une erreur */!* } }; let admin = user; user = null; // écraser pour rendre les choses évidentes *!* admin.sayHi(); // TypeError: Cannot read property 'name' of null */!* ``` Si nous utilisions `this.name` au lieu de `user.name` dans l'`alert`, le code fonctionnerait. ## "this" n'est pas lié En JavaScript, le mot clé `this` se comporte différemment de la plupart des autres langages de programmation. Il peut être utilisé dans n'importe quelle fonction, même si ce n'est pas une méthode d'un objet. Il n’y a pas d’erreur de syntaxe dans le code suivant : ```js function sayHi() { alert( *!*this*/!*.name ); } ``` La valeur de `this` est évaluée pendant l'exécution, en fonction du contexte. Par exemple, ici la même fonction est assignée à deux objets différents et a un "this" différent dans les appels : ```js run let user = { name: "John" }; let admin = { name: "Admin" }; function sayHi() { alert( this.name ); } *!* // utiliser la même fonction dans deux objets user.f = sayHi; admin.f = sayHi; */!* // ces appels ont un this différent // "this" à l'intérieur de la fonction est l'objet "avant le point" user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin (le point ou les crochets accèdent à la méthode - peu importe) ``` La règle est simple : si `obj.f()` est appelé, alors `this` est `obj` pendant l'appel de `f`. C'est donc l'`user` ou l'`admin` dans l'exemple ci-dessus. ````smart header="Appel sans objet : `this` == undefined" Nous pouvons même appeler la fonction sans objet du tout : ```js run function sayHi() { alert(this); } sayHi(); // undefined ``` Dans ce cas, `this` est `undefined` en mode strict. Si nous essayons d'accéder à `this.name`, il y aura une erreur. En mode non strict (si on oublie `use strict`), la valeur de `this` dans ce cas sera l’*objet global* (la fenêtre d’un navigateur, nous y reviendrons plus tard). Ceci est un comportement historique que le mode strict corrige. Ce genre d'appel est généralement une erreur de programmation. Si il y a un `this` dans une fonction, il s'attend à être appelée dans un contexte d'objet. ```` ```smart header="Les conséquences d'un `this` non lié" Si vous venez d'un autre langage de programmation, vous êtes probablement habitué à l'idée d'un "`this` lié", où les méthodes définies dans un objet ont toujours `this` en référence à cet objet. En JavaScript, `this` est "libre", sa valeur est évaluée au moment de l'appel et ne dépend pas de l'endroit où la méthode a été déclarée, mais plutôt de l'objet "avant le point". Le concept de temps d'exécution évalué de `this` présente à la fois des avantages et des inconvénients. D'une part, une fonction peut être réutilisée pour différents objets. D'autre part, une plus grande flexibilité ouvre la place à des erreurs. Ici, notre position n'est pas de juger si cette décision de conception linguistique est bonne ou mauvaise. Nous comprendrons comment travailler avec elle, comment obtenir des avantages et éviter les problèmes. ``` ## Les fonctions fléchées n'ont pas de "this" Les fonctions fléchées sont spéciales : elles n’ont pas leur "propre" `this`. Si nous faisons référence à `this` à partir d’une telle fonction, cela provient de la fonction externe "normale". Par exemple, ici `arrow()` utilise `this` depuis la méthode externe `user.sayHi()` : ```js run let user = { firstName: "Ilya", sayHi() { let arrow = () => alert(this.firstName); arrow(); } }; user.sayHi(); // Ilya ``` C’est une particularité des fonctions fléchées. C’est utile lorsque nous ne voulons pas réellement avoir un this distinct, mais plutôt le prendre à partir du contexte extérieur. Plus tard dans le chapitre nous allons approfondir les fonctions fléchées. ## Résumé - Les fonctions stockées dans les propriétés de l'objet s'appellent des "méthodes". - Les méthodes permettent aux objets d’agir comme `object.doSomething()`. - Les méthodes peuvent référencer l'objet comme `this`. La valeur de `this` est définie au moment de l'exécution. - Lorsqu'une fonction est déclarée, elle peut utiliser `this`, mais ce `this` n'a aucune valeur jusqu'à ce que la fonction soit appelée. - Une fonction peut être copiée entre des objets. - Lorsqu'une fonction est appelée dans la syntaxe "méthode" : `object.method()`, la valeur de `this` lors de l'appel est `objet`. Veuillez noter que les fonctions fléchées sont spéciales : elles n'ont pas `this`. Lorsque `this` est accédé dans une fonction fléchée, il est pris de l'extérieur. ================================================ FILE: 1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md ================================================ Oui c'est possible. Si une fonction retourne un objet alors `new` le retourne au lieu de `this`. Ainsi, ils peuvent, par exemple, renvoyer le même objet défini en externe `obj` : ```js run no-beautify let obj = {}; function A() { return obj; } function B() { return obj; } alert( new A() == new B() ); // true ``` ================================================ FILE: 1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md ================================================ importance: 2 --- # Deux fonctions - un objet Est-il possible de créer des fonctions `A` et `B` tel que `new A() == new B()` ? ```js no-beautify function A() { ... } function B() { ... } let a = new A(); let b = new B(); alert( a == b ); // true ``` Si c'est le cas, donnez un exemple de leur code. ================================================ FILE: 1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/solution.js ================================================ function Calculator() { this.read = function() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); }; this.sum = function() { return this.a + this.b; }; this.mul = function() { return this.a * this.b; }; } ================================================ FILE: 1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js ================================================ describe("calculator", function() { let calculator; before(function() { sinon.stub(window, "prompt") prompt.onCall(0).returns("2"); prompt.onCall(1).returns("3"); calculator = new Calculator(); calculator.read(); }); it("the read method asks for two values using prompt and remembers them in object properties", function() { assert.equal(calculator.a, 2); assert.equal(calculator.b, 3); }); it("when 2 and 3 are entered, the sum is 5", function() { assert.equal(calculator.sum(), 5); }); it("when 2 and 3 are entered, the product is 6", function() { assert.equal(calculator.mul(), 6); }); after(function() { prompt.restore(); }); }); ================================================ FILE: 1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md ================================================ ```js run demo function Calculator() { this.read = function() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); }; this.sum = function() { return this.a + this.b; }; this.mul = function() { return this.a * this.b; }; } let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() ); ``` ================================================ FILE: 1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md ================================================ importance: 5 --- # Créer une nouvelle calculatrice Créer une fonction constructeur `Calculator` qui crée des objets avec 3 méthodes : - `read()` demande deux valeurs et les enregistre en tant que propriétés d'objet avec les noms `a` et `b` respectivement. - `sum()` renvoie la somme de ces propriétés. - `mul()` renvoie le produit de la multiplication de ces propriétés. Par exemple : ```js let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() ); ``` [demo] ================================================ FILE: 1-js/04-object-basics/06-constructor-new/3-accumulator/_js.view/solution.js ================================================ function Accumulator(startingValue) { this.value = startingValue; this.read = function() { this.value += +prompt('How much to add?', 0); }; } ================================================ FILE: 1-js/04-object-basics/06-constructor-new/3-accumulator/_js.view/test.js ================================================ describe("Accumulator", function() { beforeEach(function() { sinon.stub(window, "prompt") }); afterEach(function() { prompt.restore(); }); it("initial value is the argument of the constructor", function() { let accumulator = new Accumulator(1); assert.equal(accumulator.value, 1); }); it("after reading 0, the value is 1", function() { let accumulator = new Accumulator(1); prompt.returns("0"); accumulator.read(); assert.equal(accumulator.value, 1); }); it("after reading 1, the value is 2", function() { let accumulator = new Accumulator(1); prompt.returns("1"); accumulator.read(); assert.equal(accumulator.value, 2); }); }); ================================================ FILE: 1-js/04-object-basics/06-constructor-new/3-accumulator/solution.md ================================================ ```js run demo function Accumulator(startingValue) { this.value = startingValue; this.read = function() { this.value += +prompt('How much to add?', 0); }; } let accumulator = new Accumulator(1); accumulator.read(); accumulator.read(); alert(accumulator.value); ``` ================================================ FILE: 1-js/04-object-basics/06-constructor-new/3-accumulator/task.md ================================================ importance: 5 --- # Créer un nouvel accumulateur Créer une fonction constructeur `Accumulator(startingValue)`. L'objet qu'il crée devrait : - Stocker la "valeur actuelle" dans la propriété `value`. La valeur de départ est définie sur l'argument du constructeur `startingValue`. - La méthode `read()` devrait utiliser `prompt` pour lire un nouveau numéro et l'ajouter à `value`. En d'autres termes, la propriété `value` est la somme de toutes les valeurs entrées par l'utilisateur avec la valeur initiale `startingValue`. Voici la démo du code : ```js let accumulator = new Accumulator(1); // valeur initiale 1 accumulator.read(); // ajoute la valeur entrée par l'utilisateur accumulator.read(); // ajoute la valeur entrée par l'utilisateur alert(accumulator.value); // affiche la somme de ces valeurs ``` [demo] ================================================ FILE: 1-js/04-object-basics/06-constructor-new/article.md ================================================ # Le constructeur, l'opérateur "new" La syntaxe normale `{...}` permet de créer un seul objet. Mais souvent, nous devons créer de nombreux objets similaires, tels que plusieurs utilisateurs ou éléments de menu, etc. Cela peut être fait en utilisant les fonctions constructeur et l'opérateur `"new"`. ## La function constructeur Les fonctions constructeur sont techniquement des fonctions habituelles. Il existe cependant deux conventions : 1. Elles sont nommées avec une lettre majuscule en premier. 2. Elles ne devraient être executées qu'avec l'opérateur `"new"`. Par exemple : ```js run function User(name) { this.name = name; this.isAdmin = false; } *!* let user = new User("Jack"); */!* alert(user.name); // Jack alert(user.isAdmin); // false ``` Quand une fonction est exécutée avec `new`, elle effectue les étapes suivantes : 1. Un nouvel objet vide est créé et affecté à `this`. 2. Le corps de la fonction est exécuté. Habituellement, il modifie `this`, y ajoutant de nouvelles propriétés. 3. La valeur de `this` est retournée. En d'autres termes, `new User(...)` fait quelque chose comme : ```js function User(name) { *!* // this = {}; (implicitement) */!* // ajoute des propriétés à this this.name = name; this.isAdmin = false; *!* // return this; (implicitement) */!* } ``` Donc `let user = new User("Jack")` donne le même résultat que : ```js let user = { name: "Jack", isAdmin: false }; ``` Maintenant, si nous voulons créer d'autres utilisateurs, nous pouvons appeler `new User("Ann")`, `new User("Alice")` etc. Beaucoup plus court que d'écrire littéralement à chaque fois, et aussi facile à lire. C’est l’objectif principal des constructeurs -- implémenter du code de création d’objet réutilisable. Notons encore une fois -- techniquement, n'importe quelle fonction (à l'exception des fonctions fléchées, car elles n'ont pas de `this`) peut être utilisée comme constructeur. Elle peut être exécutée avec `new`, et elle exécutera l'algorithme ci-dessus. La "première lettre majuscule" est une convention, pour indiquer clairement qu'une fonction doit être exécutée avec `new`. ````smart header="new function() { ... }" Si nous avons beaucoup de lignes de code concernant la création d'un seul objet complexe, nous pouvons les envelopper dans une fonction constructeur, comme ceci : ```js // create a function and immediately call it with new let user = new function() { this.name = "John"; this.isAdmin = false; // ...autre code pour la création d'utilisateur // peut-être une logique complexe et des déclarations // de variables locales etc. }; ``` Ce constructeur ne peut pas être appelé à nouveau, car il n'est enregistré nulle part, juste créé et appelé. Cette astuce vise donc à encapsuler le code qui construit l'objet unique, sans réutilisation future. ```` ## Constructeur mode test : new.target ```smart header="Trucs avancés" La syntaxe de cette section est rarement utilisée, sautez-la à moins de vouloir tout savoir. ``` Dans une fonction, nous pouvons vérifier si elle a été appelée avec `new` ou sans, en utilisant la propriété spéciale `new.target`. Elle n'est pas définie pour les appels réguliers et équivaut à la fonction si elle est appelée avec `new` : ```js run function User() { alert(new.target); } // sans "new": *!* User(); // undefined */!* // avec "new": *!* new User(); // function User { ... } */!* ``` Cela peut être utilisé dans la fonction pour savoir si elle a été appelée avec `new`, "en mode constructeur", ou sans "en mode normal". Nous pouvons également faire des appels `new` et réguliers pour faire la même chose, comme ceci : ```js run function User(name) { if (!new.target) { // si vous m'executer sans new return new User(name); // ...j'ajouterai un new pour vous } this.name = name; } let john = User("John"); // redirige l'appel vers un new User alert(john.name); // John ``` Cette approche est parfois utilisée dans les librairies pour rendre la syntaxe plus flexible. Pour que les gens puissent appeler la fonction avec ou sans `new`, et que cela fonctionne toujours. Ce n’est probablement pas une bonne chose à utiliser partout cependant, car l’omission de `new` rend un peu moins évident ce qui se passe. Avec `new`, nous savons tous que le nouvel objet est en cours de création. ## Retour des constructeurs Généralement, les constructeurs n'ont pas d'instruction `return`. Leur tâche consiste à écrire tous les éléments nécessaires dans `this`, qui devient automatiquement le résultat. Mais s'il y a une déclaration `return`, alors la règle est simple : - Si `return` est appelé avec un object, alors il est renvoyé à la place de `this`. - Si `return` est appelé avec une primitive, elle est ignorée. En d'autres termes, `return` avec un objet renvoie cet objet, dans tous les autres cas, `this` est renvoyé. Par exemple, ici `return` remplace `this` en retournant un objet : ```js run function BigUser() { this.name = "John"; return { name: "Godzilla" }; // <-- retourne cet objet } alert( new BigUser().name ); // Godzilla, obtenu cet objet ``` Et voici un exemple avec un `return` vide (ou nous pourrions placer une primitive après, peu importe) : ```js run function SmallUser() { this.name = "John"; return; // renvoie this } alert( new SmallUser().name ); // John ``` Généralement, les constructeurs n’ont pas d’instruction `return`. Nous mentionnons ici le comportement spécial avec les objets renvoyés principalement dans un souci de complétude. ````smart header="Omettre les parenthèses" À propos, on peut omettre les parenthèses après `new` : ```js let user = new User; // <-- pas de parenthèses // identique à let user = new User(); ``` L'omission de parenthèses ici n'est pas considérée comme un "bon style", mais la syntaxe est autorisée par la spécification. ```` ## Les méthodes dans les constructeurs L'utilisation de fonctions de constructeur pour créer des objets offre une grande flexibilité. La fonction constructeur peut avoir des paramètres qui définissent comment construire l'objet et ce qu'il doit y mettre. Bien sûr, nous pouvons ajouter à `this` non seulement des propriétés, mais également des méthodes. Par exemple, `new User(name)` ci-dessous crée un objet avec le `name` donné et la méthode `sayHi` : ```js run function User(name) { this.name = name; this.sayHi = function() { alert( "My name is: " + this.name ); }; } *!* let john = new User("John"); john.sayHi(); // My name is: John */!* /* john = { name: "John", sayHi: function() { ... } } */ ``` Pour créer des objets complexes, il existe une syntaxe plus avancée, les [classes](info:classes), que nous allons couvrir plus tard. ## Résumé - Les fonctions constructeur ou, plus brièvement, les constructeurs, sont des fonctions normales, mais il est généralement convenu de les nommer avec une première lettre en majuscule. - Les fonctions constructeur ne doivent être appelées qu'avec `new`. Un tel appel implique la création d'un objet `this` vide au début de la fonction et le renvoi de l'objet complété à la fin. Nous pouvons utiliser des fonctions constructeurs pour créer plusieurs objets similaires. JavaScript fournit des fonctions constructeur pour de nombreux objets intégrés du langage : comme `Date` pour les dates, `Set` pour les ensembles et d'autres que nous prévoyons d’étudier. ```smart header="Objets, nous reviendrons !" Dans ce chapitre, nous ne couvrons que les bases sur les objets et les constructeurs. Elles sont essentielles pour en savoir plus sur les types de données et les fonctions dans les chapitres suivants. Après avoir appris cela, nous reviendrons aux objets et les couvrirons en profondeur dans les chapitres et . ``` ================================================ FILE: 1-js/04-object-basics/07-optional-chaining/article.md ================================================ # Chaînage optionnel '?.' [recent browser="new"] Le chaînage optionnel `?.` est un moyen sécurisé d'accéder aux propriétés d'objet imbriquées, même si une propriété intermédiaire n'existe pas. ## Le problème de la "propriété non existante" Si vous venez de commencer à lire le tutoriel et à apprendre JavaScript, peut-être que le problème ne vous a pas encore touché, mais c'est assez courant. À titre d'exemple, disons que nous avons des objets `user` qui contiennent les informations sur nos utilisateurs. La plupart de nos utilisateurs ont des adresses dans la propriété `user.address`, avec la rue `user.address.street`, mais certains ne les ont pas fournies. Dans ce cas, lorsque nous essayons d'obtenir `user.address.street`, et que l'utilisateur se trouve sans adresse, nous obtenons une erreur : ```js run let user = {}; // un utilisateur sans propriété "address" alert(user.address.street); // Error! ``` C'est le résultat attendu. JavaScript fonctionne comme cela. Comme `user.address` est `undefined`, une tentative d'obtention de `user.address.street` échoue avec une erreur. Dans de nombreux cas pratiques, nous préférerions obtenir `undefined` au lieu d'une erreur ici (signifiant "pas de rue"). ... Et un autre exemple. Dans le développement Web, nous pouvons obtenir un objet qui correspond à un élément de page Web à l'aide d'un appel de méthode spécial, tel que `document.querySelector('.elem')`, et il renvoie `null` lorsqu'il n'y a pas ce type d'élément. ```js run // document.querySelector('.elem') est nul s'il n'y a pas d'élément let html = document.querySelector('.elem').innerHTML; // error if it's null ``` Encore une fois, si l'élément n'existe pas, nous obtiendrons une erreur lors de l'accès à la propriété `.innerHTML` de `null`. Et dans certains cas, lorsque l'absence de l'élément est normale, nous aimerions éviter l'erreur et accepter simplement `html = null` comme résultat. Comment peut-on le faire ? La solution évidente serait de vérifier la valeur en utilisant `if` ou l'opérateur conditionnel `?`, avant d'accéder à sa propriété, comme ceci : ```js let user = {}; // l'utilisateur n'a pas d'adresse alert(user.address ? user.address.street : undefined); ``` Cela fonctionne, il n'y a pas d'erreur... Mais c'est assez inélégant. Comme vous pouvez le voir, `"user.address"` apparaît deux fois dans le code. Voici à quoi ressemblerait la même chose pour `document.querySelector` : ```js run let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null; ``` Nous pouvons voir que l'élément de recherche `document.querySelector('.elem')` est en fait appelé deux fois ici. Pas bon. Pour les propriétés plus profondément imbriquées, cela devient encore plus laid, car davantage de répétitions sont nécessaires. Par exemple. récupérons `user.address.street.name` de la même manière. ```js let user = {}; // l'utilisateur n'a pas d'adresse alert(user.address ? user.address.street ? user.address.street.name : null : null); ``` C'est juste horrible, on peut même avoir des problèmes pour comprendre un tel code. Il existe une meilleure façon de l'écrire, en utilisant l'opérateur `&&` : ```js run let user = {}; // l'utilisateur n'a pas d'adresse alert( user.address && user.address.street && user.address.street.name ); // undefined (no error) ``` Et le chemin complet vers la propriété garantit que tous les composants existent (sinon, l'évaluation s'arrête), mais n'est pas non plus idéal. Comme vous pouvez le voir, les noms de propriétés sont toujours dupliqués dans le code. Par exemple, dans le code ci-dessus, `user.address` apparaît trois fois. C'est pourquoi le chaînage facultatif `?.` a été ajouté au langage. Pour résoudre ce problème une fois pour toutes ! ## Chaînage optionnel Le chaînage optionnel `?.` arrête l'évaluation si la valeur avant `?.` est `undefined` ou `null` et renvoie `undefined`. **Plus loin dans cet article, par souci de brièveté, nous dirons que quelque chose "existe" si ce n'est pas "null" et non "undefined".** En d'autres termes, `value?.prop` : - est identique à `value.prop` si `value` existe, - sinon (lorsque `value` est `undefined`/`null`), il renvoie `undefined`. Voici le moyen sûr d'accéder à `user.address.street` en utilisant `?.` : ```js run let user = {}; // l'utilisateur n'a pas d'adresse alert( user?.address?.street ); // undefined (pas d'erreur) ``` Le code est court et propre, il n'y a aucune duplication. Voici un exemple avec `document.querySelector` : ```js run let html = document.querySelector('.elem')?.innerHTML; // sera undefined s'il n'y a pas d'élément ``` La lecture de l'adresse avec `user?.address` fonctionne même si l'objet `user` n'existe pas : ```js run let user = null; alert( user?.address ); // undefined alert( user?.address.street ); // undefined ``` Remarque : la syntaxe `?.` rend facultative la valeur qui la précède, mais pas plus. Par exemple, dans `user?.address.street.name` le `?.` permet à `user` d'être en toute sécurité `null`/`undefined` (et renvoie `undefined` dans ce cas), mais ce n'est que pour `user`. D'autres propriétés sont accessibles de manière régulière. Si nous voulons que certaines d'entre elles soient optionnelles, alors nous devrons remplacer plus de `.` par `?.`. ```warn header="N'abusez pas du chaînage optionnel" Nous ne devrions utiliser `?.` que là où il est normal que quelque chose n'existe pas. Par exemple, si selon notre logique de codage, l'objet `user` doit exister, mais que `address` est facultatif, alors nous devrions écrire `user.address?.street`, mais pas `user?.address?.street`. Ensuite, si `user` n'est pas défini, nous verrons une erreur de programmation à ce sujet et nous la corrigerons. Sinon, si nous abusons de `?.`, les erreurs de codage peuvent être réduites au silence là où cela n'est pas approprié et devenir plus difficiles à déboguer. ``` ````warn header="La variable avant `?.` doit être déclarée" S'il n'y a pas du tout de variable `user`, alors `user?.anything` déclenche une erreur : ```js run // ReferenceError: user is not defined user?.address; ``` La variable doit être déclarée (par exemple `let/const/var user` ou en tant que paramètre de fonction). Le chaînage facultatif ne fonctionne que pour les variables déclarées. ```` ## Court-circuit Comme il a été dit précédemment, le `?.` arrête immédiatement ("court-circuite") l'évaluation si la partie gauche n'existe pas. Ainsi, s'il y a d'autres appels de fonction ou opérations à droite de `?.`, elles ne seront pas effectuées. Par exemple : ```js run let user = null; let x = 0; user?.sayHi(x++); // pas de "user", donc l'exécution n'atteint pas l'appel sayHi et x++ alert(x); // 0, la valeur n'est pas incrémenté ``` ## Autres variantes : ?.(), ?.[] `?.` n'est pas un opérateur, mais une construction syntaxique particulière qui fonctionne aussi avec les appels de fonction et les crochets. Par exemple, `?.()` est utilisé pour exécuter une fonction seulement si elle existe. Ainsi dans cet exemple la méthode `admin` n'existe pas pour tout le monde : ```js run let userAdmin = { admin() { alert("I am admin"); } }; let userGuest = {}; *!* userAdmin.admin?.(); // I am admin */!* *!* userGuest.admin?.(); // rien ne se passe (aucune méthode de ce nom) */!* ``` Ici, dans les deux lignes, nous utilisons d'abord le point (`userAdmin.admin`) pour obtenir la propriété `admin`, car nous supposons que l'objet `user` existe, il peut donc être lu en toute sécurité. Puis `?.()` vérifie la partie gauche : si la fonction `admin` existe, alors elle s'exécute (c'est le cas pour `userAdmin`). Sinon (pour `userGuest`) l'évaluation s'arrête sans erreur. La syntaxe `?.[]` fonctionne également, si nous voulons utiliser des crochets `[]` pour accéder aux propriétés au lieu du point `.`. Similaire aux cas précédents, il permet de lire en toute sécurité une propriété à partir d'un objet qui peut ne pas exister. ```js run let key = "firstName"; let user1 = { firstName: "John" }; let user2 = null; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined ``` Nous pouvons également utiliser `?.` avec `delete` : ```js run delete user?.name; // supprime user.name si user existe ``` ```warn header="Nous pouvons utiliser `?.` pour lire et supprimer en toute sécurité, mais pas pour écrire" Le chaînage optionnel `?.` n'a aucune utilité sur le côté gauche d'une affectation : Par exemple : ```js run let user = null; user?.name = "John"; // Erreur, ne fonctionne pas // car il évalue : undefined = "John" ``` Ce n'est tout simplement pas si intelligent. ## Résumé Le chaînage optionnel '?.' a trois formes : 1. `obj?.prop` -- retourne `obj.prop` si `obj` existe, sinon `undefined`. 2. `obj?.[prop]` -- retourne `obj[prop]` si `obj` existe, sinon `undefined`. 3. `obj.method?.()` -- appel `obj.method()` si `obj.method` existe, sinon retourne `undefined`. Comme nous pouvons le voir, tous sont simples et simples à utiliser. Le `?.` vérifie la partie gauche pour `null`/`undefined` et permet à l'évaluation de se poursuivre si ce n'est pas le cas. Une chaîne de `?.` permet d'accéder en toute sécurité aux propriétés imbriquées. Néanmoins, nous devons appliquer `?.` avec précaution, uniquement là où il est acceptable, selon la logique de notre code, que la partie gauche n'existe pas. Pour qu'il ne nous cache pas les erreurs de programmation, si elles se produisent. ================================================ FILE: 1-js/04-object-basics/08-symbol/article.md ================================================ # Type symbole Par spécification, seuls deux types primitifs peuvent servir de clés de propriété d'objet : - type string, ou - type symbol. Sinon, si l'on utilise un autre type, tel que nombre, il est automatiquement converti en chaîne de caractères. Ainsi, `obj[1]` est identique à `obj["1"]`, et `obj[true]` est identique à `obj["true"]`. Jusqu'à présent, nous n'utilisions que des chaînes de caractères. Explorons maintenant les symboles, voyons ce qu'ils peuvent faire pour nous. ## Symboles Un “Symbol” représente un identifiant unique. Une valeur de ce type peut être créée en utilisant `Symbol()` : ```js let id = Symbol(); ``` Lors de la création, nous pouvons donner au symbole une description (également appelée nom de symbole), particulièrement utile pour le débogage : ```js // id est un symbole avec la description "id" let id = Symbol("id"); ``` Les symboles sont garantis d'être uniques. Même si nous créons beaucoup de symboles avec la même description, ce sont des valeurs différentes. La description est juste une étiquette qui n’affecte rien. Par exemple, voici deux symboles avec la même description -- ils ne sont pas égaux : ```js run let id1 = Symbol("id"); let id2 = Symbol("id"); *!* alert(id1 == id2); // false */!* ``` Si vous connaissez Ruby ou un autre langage qui comporte également une sorte de "symboles", attention à ne pas vous tromper. Les symboles JavaScript sont différents. Donc, pour résumer, un symbole est une "valeur unique primitive" avec une description facultative. Voyons où nous pouvons les utiliser. ````warn header="Les symboles ne se convertissent pas automatiquement en chaîne de caractères" La plupart des valeurs de JavaScript prennent en charge la conversion implicite en chaîne de caractères. Par exemple, nous pouvons `alert` presque toutes les valeurs et cela fonctionnera. Les symboles sont spéciaux. Ils ne se convertissent pas automatiquement. Par exemple, cette `alert` affichera une erreur : ```js run let id = Symbol("id"); *!* alert(id); // TypeError: Impossible de convertir une valeur de symbole en chaîne de caractères */!* ``` C'est un "gardien du langage" contre les erreurs, parce que les chaînes de caractères et les symboles sont fondamentalement différents et ne doivent accidentellement pas être convertis les uns en les autres. Si nous voulons vraiment afficher un symbole, nous devons appeler `.toString()` dessus, comme ici : ```js run let id = Symbol("id"); *!* alert(id.toString()); // Symbol(id), maintenant ça marche */!* ``` Ou récupérer la propriété `symbol.description` pour afficher la description uniquement : ```js run let id = Symbol("id"); *!* alert(id.description); // id */!* ``` ```` ## Propriétés "cachées" Les symboles nous permettent de créer des propriétés "cachées" d'un objet, qu'aucune autre partie du code ne peut accéder accidentellement ou écraser. Par exemple, si nous travaillons avec des objets `user` qui appartiennent à un code tiers, nous aimerions leur ajouter des identificateurs. Utilisons une clé symbole pour cela : ```js run let user = { // belongs to another code name: "John" }; let id = Symbol("id"); user[id] = 1; alert( user[id] ); // nous pouvons accéder aux données en utilisant le symbole comme clé ``` Quel est l’avantage de l’utilisation de `Symbol("id")` sur une chaîne de caractères `"id"` ? Poussons un peu plus loin l’exemple pour voir cela. Comme les objets `user` appartiennent à une autre base de code, il n'est pas sûr de leur ajouter des champs, car nous pourrions affecter le comportement prédéfini dans cette autre base de code. Cependant, les symboles ne peuvent pas être accédés accidentellement. Le code tiers ne sera pas conscient des symboles nouvellement définis, il est donc prudent d'ajouter des symboles aux objets `user`. Imaginez qu'un autre script veuille avoir son propre identifiant à l'intérieur de `user`, pour sa propre utilisation. Ensuite, ce script peut créer son propre `symbol("id")`, comme ceci : ```js // ... let id = Symbol("id"); user[id] = "Their id value"; ``` Il n'y aura pas de conflit entre nos identificateurs et les leurs, car les symboles sont toujours différents, même s'ils portent le même nom. Notez que si nous utilisions une chaîne de caractère `"id"` au lieu d'un symbole dans le même but, il y *aurait* un conflit : ```js let user = { name: "John" }; // Notre script utilise la propriété "id" user.id = "Our id value"; // ...Un autre script veut aussi "id" pour ses besoins … user.id = "Their id value" // Boom! écrasé par un autre script! ``` ### Symboles dans un objet littéral Si nous voulons utiliser un symbole dans un objet littéral `{...}`, nous avons besoin de crochets. Comme ceci : ```js let id = Symbol("id"); let user = { name: "John", *!* [id]: 123 // pas "id": 123 */!* }; ``` C’est parce que nous avons besoin de la valeur de la variable `id` comme clé, pas de la chaîne de caractères "id". ### Les symboles sont ignorés par for…in Les propriétés symboliques ne participent pas à la boucle `for..in`. Par exemple : ```js run let id = Symbol("id"); let user = { name: "John", age: 30, [id]: 123 }; *!* for (let key in user) alert(key); // name, age (pas de symboles) */!* // l'accès direct par le symbole fonctionne alert( "Direct: " + user[id] ); // Direct: 123 ``` [Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) les ignore également. Cela fait partie du principe général du "dissimulation des propriétés symboliques". Si un autre script ou une bibliothèque parcourt notre objet, il n’accédera pas de manière inattendue à une propriété symbolique. En revanche, [Object.assign](mdn:js/Object/assign) copie les propriétés de chaîne de caractères et de symbole : ```js run let id = Symbol("id"); let user = { [id]: 123 }; let clone = Object.assign({}, user); alert( clone[id] ); // 123 ``` Il n’y a pas de paradoxe ici. C'est par conception. L'idée est que lorsque nous clonons un objet ou que nous fusionnons des objets, nous souhaitons généralement que *toutes* les propriétés soient copiées (y compris les symboles tels que `id`). ## Symboles globaux Comme nous l’avons vu, habituellement tous les symboles sont différents, même s’ils portent les mêmes noms. Mais parfois, nous voulons que les symboles portant le même nom soient les mêmes entités. Par exemple, différentes parties de notre application veulent accéder au symbole `"id"` qui signifie exactement la même propriété. Pour cela, il existe un *registre de symboles global*. Nous pouvons créer des symboles et y accéder ultérieurement, ce qui garantit que les accès répétés portant le même nom renvoient exactement le même symbole. Pour lire (créer en cas d'absence) un symbole du registre, utilisez `Symbol.for(key)`. Cet appel vérifie le registre global et, s’il existe un symbole décrit comme `key`, le renvoie, sinon il crée un nouveau symbole `Symbol(key)` et le stocke dans le registre avec la `key` donnée. Par exemple : ```js run // lit le registre global let id = Symbol.for("id"); // si le symbole n'existait pas, il est créé // relit le registre (peut-être à partir d'une autre partie du code) let idAgain = Symbol.for("id"); // le même symbole alert( id === idAgain ); // true ``` Les symboles à l'intérieur de ce registre sont appelés *symboles globaux*. Si nous voulons un symbole à l’échelle de l’application, accessible partout dans le code, c’est ce moyen que nous allons utiliser. ```smart header="Cela ressemble à Ruby" Dans certains langages de programmation, comme Ruby, il existe un seul symbole par nom. Comme nous pouvons le constater, en JavaScript, c’est vrai pour les symboles globaux. ``` ### Symbol.keyFor Nous avons vu que pour les symboles globaux, `Symbol.for(key)` renvoie un symbole par son nom. Pour faire le contraire -- retourner un nom par symbole global -- nous pouvons utiliser : `Symbol.keyFor(sym)` : Par exemple : ```js run // get symbol by name let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // obtenir le nom par symbole alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` `Symbol.keyFor` utilise en interne le registre de symboles global pour rechercher la clé du symbole. Donc, cela ne fonctionne pas pour les symboles non globaux. Si le symbole n’est pas global, il ne pourra pas le trouver et retournera `undefined`. Cela dit, tous les symboles ont la propriété `description`. Par exemple : ```js run let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); alert( Symbol.keyFor(globalSymbol) ); // name, global symbol alert( Symbol.keyFor(localSymbol) ); // undefined, not global alert( localSymbol.description ); // name ``` ## System symbols Il existe de nombreux "systèmes" symboles que JavaScript utilise en interne et que nous pouvons utiliser pour affiner divers aspects de nos objets. Ils sont listés dans la documentation [Well-known symbols](https://tc39.github.io/ecma262/#sec-well-known-symbols) : - `Symbol.hasInstance` - `Symbol.isConcatSpreadable` - `Symbol.iterator` - `Symbol.toPrimitive` - Etc. Par exemple, `Symbol.toPrimitive` nous permet de décrire une conversion d’objet en primitive. Nous verrons son utilisation très bientôt. Nous nous familiariserons également avec d’autres symboles lorsque nous étudierons les caractéristiques du langage correspondantes. ## Résumé `Symbol` est un type primitif pour les identificateurs uniques. Les symboles sont créés avec l'appel `Symbol()` ainsi qu'une description facultative. Les symboles sont toujours de valeurs différentes, même s'ils portent le même nom. Si nous voulons que les symboles portant le même nom soient égaux, nous devons utiliser le registre global : `Symbol.for(key)` renvoie (crée si nécessaire) un symbole global avec `key` comme nom. Les multiples appels de `Symbol.for` avec la même `key` renvoient exactement le même symbole. Les symboles ont deux principaux cas d'utilisation : 1. Propriétés d'objet "masquées". Si nous voulons ajouter une propriété à un objet qui "appartient" à un autre script ou à une librairie, nous pouvons créer un symbole et l'utiliser comme clé de propriété. Une propriété symbolique n’apparait pas dans `for..in`, elle ne sera donc pas traitée accidentellement avec d'autres propriétés. De plus, elle ne sera pas accessible directement, car un autre script n’a pas notre symbole. Ainsi, la propriété sera protégée contre une utilisation accidentelle ou un écrasement. Ainsi, nous pouvons "dissimuler" quelque chose dans des objets dont nous avons besoin, mais que les autres ne devraient pas voir, en utilisant des propriétés symboliques. 2. De nombreux symboles système utilisés par JavaScript sont accessibles en tant que `Symbol.*`. Nous pouvons les utiliser pour modifier certains comportements internes. Par exemple, plus tard dans le tutoriel, nous utiliserons `Symbol.iterator` pour [iterables](info:iterable), `Symbol.toPrimitive`, etc. Techniquement, les symboles ne sont pas cachés à 100%. Il y a une méthode intégrée [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) qui nous permet d’obtenir tous les symboles. Il y a aussi une méthode nommée [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) qui renvoie *toutes* les clés d'un objet, y compris celles symboliques. Donc, ils ne sont pas vraiment cachés. Mais la plupart des bibliothèques, fonctions intégrées et structures de syntaxe n'utilisent pas ces méthodes. ================================================ FILE: 1-js/04-object-basics/09-object-toprimitive/article.md ================================================ # Conversion d'objet en primitive Que se passe-t-il lorsque des objets sont ajoutés `obj1 + obj2`, soustraits `obj1 - obj2` ou affichés à l'aide de `alert(obj)` ? JavaScript ne permet pas de personnaliser le fonctionnement des opérateurs sur les objets. Contrairement à certains autres langages de programmation, tels que Ruby ou C++, nous ne pouvons pas implémenter une méthode objet spéciale pour gérer un ajout (ou d'autres opérateurs). Dans le cas de telles opérations, les objets sont auto-convertis en primitives, puis l'opération est effectuée sur ces primitives et aboutit à une valeur primitive. C'est une limitation importante, car le résultat de `obj1 + obj2` (ou toute autre opération mathématique) ne peut pas être un autre objet ! Par exemple nous ne pouvons pas créer d'objets représentant des vecteurs ou des matrices (ou des réalisations ou autre), les ajouter et s'attendre à un objet "sommé" comme résultat. De telles prouesses architecturales sont automatiquement "hors jeu". Donc, parce que nous ne pouvons pas faire grand-chose ici, il n'y a pas de maths avec des objets dans de vrais projets. Lorsque cela se produit, c'est généralement à cause d'une erreur de programmation. Dans ce chapitre, nous verrons comment un objet se convertit en primitive et comment le personnaliser. Nous avons deux objectifs : 1. Cela nous permettra de comprendre ce qui se passe en cas d'erreur de programmation, lorsqu'une telle opération s'est produite accidentellement. 2. Il existe des exceptions, où de telles opérations sont possibles et semblent bonnes. Par exemple, soustraire ou comparer des dates (objets `Date`). Nous les verrons plus tard. ## Règles de conversion Dans le chapitre nous avons vu les règles de conversion des types primitifs numériques, chaînes de caractères et booléens. Mais nous avions mis de côté les objets. Maintenant que nous connaissons les méthodes et les symboles, il devient possible de l'aborder. 1. Il n'y a pas de conversion en booléen. Tous les objets sont "true" dans un contexte booléen, aussi simple que cela. Il n'existe que des conversions numériques et de chaînes de caractères. 2. La conversion numérique se produit lorsque nous soustrayons des objets ou appliquons des fonctions mathématiques. Par exemple, les objets `Date` (à traiter dans le chapitre ) peut être soustrait et le résultat de `date1 - date2` est la différence de temps entre deux dates. 3. En ce qui concerne la conversion de chaîne de caractères, cela se produit généralement lorsque nous affichons un objet tel que `alert(obj)` et dans des contextes similaires. Nous pouvons implémenter nous-mêmes la conversion de chaînes de caractères et de chiffres, en utilisant des méthodes d'objet spéciales. Passons maintenant aux détails techniques, car c'est le seul moyen d'aborder le sujet en profondeur. ## Hints Comment JavaScript décide-t-il quelle conversion appliquer ? Il existe trois variantes de conversion de type, qui se produisent dans diverses situations. Ils sont appelés "hints" (indices), comme décrit dans la [spécification](https://tc39.github.io/ecma262/#sec-toprimitive) : Il existe trois variantes de conversion de type, appelées "hints", décrites dans la [specification](https://tc39.github.io/ecma262/#sec-toprimitive) : **`"string"`** Pour une conversion d'un objet vers une chaîne de caractères, lorsque nous effectuons une opération sur un objet qui attend une chaîne, comme `alert` : ```js // output alert(obj); // utiliser un objet comme clé de propriété anotherObj[obj] = 123; ``` **`"number"`** Pour une conversion d'objet en nombre, comme lorsque nous faisons des calculs : ```js // conversion explicite let num = Number(obj); // maths (except binary plus) let n = +obj; // unary plus let delta = date1 - date2; // comparaison supérieur/inférieur let greater = user1 > user2; ``` La plupart des fonctions mathématiques intégrées comprennent également ce type de conversion. **`"default"`** Se produit dans de rares cas où l'opérateur n'est "pas sûr" du type auquel il doit s'attendre. Par exemple, le plus binaire `+` peut fonctionner à la fois avec des chaînes de caractères (les concaténer) et des nombres (les ajouter). Donc, si le plus binaire obtient un objet sous forme d'argument, il utilise le hint `"default"` pour le convertir. En outre, si un objet est comparé à l'aide de `==` avec une chaîne de caractères, un nombre ou un symbole, il est également difficile de savoir quelle conversion doit être effectuée, par conséquent l'indicateur `"default"` est utilisé. ```js // binary plus uses the "default" hint let total = obj1 + obj2; // obj == number uses the "default" hint if (user == 1) { ... }; ``` Les opérateurs de comparaison supérieurs et inférieurs, tels que `<` et `>`, peuvent également fonctionner avec des chaînes de caractères et des nombres. Néanmoins, ils utilisent l'indicateur `"number"`, pas `default`. C'est pour des raisons historiques. En pratique cependant, les choses sont un peu plus simples. Tous les objets intégrés à l'exception d'un cas (objet `Date`, nous l'apprendrons plus tard) implémentent la conversion `"default"` de la même manière que `"number"`. Et nous devrions probablement faire de même. Pourtant, il est important de connaître les 3 hints, nous verrons bientôt pourquoi. **Pour effectuer la conversion, JavaScript essaie de trouver et d'appeler trois méthodes d'objet :** 1. Appeler la méthode `obj[Symbol.toPrimitive](hint)` avec la clé symbolique `Symbol.toPrimitive` (symbole système), si une telle méthode existe. 2. Sinon, si l'indice est `"string"`, essaie d'appeler `obj.toString()` puis `obj.valueOf()`, selon ce qui existe. 3. Sinon, si l'indice est `"number"` ou `"default"`, essaie d'appeler `obj.valueOf()` puis `obj.toString()`, selon ce qui existe. ## Symbol.toPrimitive Commençons par la première méthode. Il existe un symbole intégré appelé `Symbol.toPrimitive` qui devrait être utilisé pour nommer la méthode de conversion, comme ceci : ```js obj[Symbol.toPrimitive] = function(hint) { // voici le code pour convertir cet objet en une primitive // il doit retourner une valeur primitive // hint = "string" ou "number" ou "default" }; ``` Si la méthode `Symbol.toPrimitive` existe, elle est utilisée pour tous les hints et aucune autre méthode n'est nécessaire. Par exemple, ici l'objet `user` l'implémente : ```js run let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // conversions demo: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` Comme on peut le voir d'après le code, `user` devient une chaîne de caractères auto-descriptive ou un montant d'argent en fonction de la conversion. La méthode unique `user[Symbol.toPrimitive]` gère tous les cas de conversion. ## toString / valueOf S'il n'y a pas de `Symbol.toPrimitive` alors JavaScript essaie de trouver les méthodes `toString` et `valueOf` : - Pour l'indice `"string"` : appel à la méthode `toString`, et si elle n'existe pas ou si elle renvoie un objet au lieu d'une valeur primitive, alors appel à `valueOf` (donc `toString` a la priorité pour la conversion de chaînes de caractères). - Pour les autres hints : appel à `valueOf`, et s'il n'existe pas ou s'il renvoie un objet au lieu d'une valeur primitive, alors appel à `toString` (donc `valueOf` a la priorité pour les mathématiques). Les méthodes `toString` et `valueOf` viennent des temps anciens. Ce ne sont pas des symboles (les symboles n'existaient pas il y a si longtemps), mais plutôt des méthodes nommées par des chaînes de caractères "normales". Ils fournissent une alternative "à l'ancienne" pour implémenter la conversion. Ces méthodes doivent renvoyer une valeur primitive. Si `toString` ou `valueOf` renvoie un objet, il est ignoré (comme s'il n'y avait pas de méthode). Par défaut, un objet brut a les méthodes `toString` et `valueOf` suivantes : - La méthode `toString` renvoie une chaîne de caractères `"[object Object]"`. - La méthode `valueOf` renvoie l'objet en question. Voici la démo : ```js run let user = {name: "John"}; alert(user); // [object Object] alert(user.valueOf() === user); // true ``` Donc, si nous essayons d'utiliser un objet en tant que chaîne de caractères, comme dans un `alert` ou autre chose, nous voyons par défaut `[object Object]`. Et la valeur par défaut `valueOf` n'est mentionnée ici que par souci d'exhaustivité, afin d'éviter toute confusion. Comme vous pouvez le constater, l'objet lui-même est renvoyé et est donc ignoré. Ne me demandez pas pourquoi, c'est pour des raisons historiques. Nous pouvons donc supposer que cela n'existe pas. Implémentons ces méthodes pour personnaliser la conversion. Par exemple, ici, `user` fait la même chose que ci-dessus en combinant `toString` et `valueOf` au lieu de `Symbol.toPrimitive` : ```js run let user = { name: "John", money: 1000, // for hint="string" toString() { return `{name: "${this.name}"}`; }, // pour hint="number" ou "default" valueOf() { return this.money; } }; alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500 ``` Comme on peut le constater, le comportement est identique à celui de l'exemple précédent avec `Symbol.toPrimitive`. Nous voulons souvent un seul endroit "fourre-tout" pour gérer toutes les conversions primitives. Dans ce cas, nous pouvons implémenter `toString` uniquement, comme ceci : ```js run let user = { name: "John", toString() { return this.name; } }; alert(user); // toString -> John alert(user + 500); // toString -> John500 ``` En l'absence de `Symbol.toPrimitive` et de `valueOf`, `toString` gérera toutes les conversions primitives. ### Une conversion peut renvoyer n'importe quel type primitif La chose importante à savoir sur toutes les méthodes de conversion de primitives est qu'elles ne renvoient pas nécessairement la primitive "hinted". Il n'y a pas de control pour vérifier si `toString` renvoie exactement une chaîne de caractères ou si la méthode `Symbol.toPrimitive` renvoie un nombre pour le hint `"number"`. **La seule chose obligatoire : ces méthodes doivent renvoyer une primitive, pas un objet.** ```smart header="Notes historiques" Pour des raisons historiques, si `toString` ou `valueOf` renvoie un objet, il n’y a pas d’erreur, mais cette valeur est ignorée (comme si la méthode n’existait pas). C’est parce que jadis, il n’existait pas de bon concept "d’erreur" en JavaScript. En revanche, `Symbol.toPrimitive` est plus strict, il *doit* retourner une primitive, sinon il y aura une erreur. ``` ## Autres conversions Comme nous le savons déjà, de nombreux opérateurs et fonctions effectuent des conversions de types, par exemple la multiplication `*` convertit les opérandes en nombres. Si nous passons un objet en argument, il y a deux étapes de calcul : 1. L'objet est converti en primitive (en utilisant les règles décrites ci-dessus). 2. Si cela est nécessaire pour d'autres calculs, la primitive résultante est également convertie. Par exemple : ```js run let obj = { // toString gère toutes les conversions en l'absence d'autres méthodes toString() { return "2"; } }; alert(obj * 2); // 4, objet converti en primitive "2", puis la multiplication le transforme en un nombre ``` 1. La multiplication `obj * 2` convertit d'abord l'objet en primitive (cela devient une chaîne de caractère `"2"`). 2. Ensuite `"2" * 2` devient `2 * 2` (la chaîne de caractères est convertie en nombre). Le plus binaire `+` va concaténer des chaînes de caractères dans la même situation, car il accepte volontiers une chaîne de caractères : ```js run let obj = { toString() { return "2"; } }; alert(obj + 2); // 22 ("2" + 2), la conversion en primitive a renvoyé une chaîne de caractères => concaténation ``` ## Résumé La conversion objet à primitive est appelée automatiquement par de nombreuses fonctions intégrées et opérateurs qui attendent une primitive en tant que valeur. Il en existe 3 types (hints) : - `"string"` (pour `alert` et d'autres opérations qui nécessitent une chaîne de caractères) - `"number"` (pour des maths) - `"default"` (peu d'opérateurs, généralement les objets l'implémentent de la même manière que `"number"`) La spécification décrit explicitement quel opérateur utilise quel hint. L'algorithme de conversion est : 1. Appeler `obj[Symbol.toPrimitive](hint)` si la méthode existe, 2. Sinon, si l'indice est `"string"`, essaie `obj.toString()` puis `obj.valueOf()`, selon ce qui existe. 3. Sinon, si l'indice est `"number"` ou `"default"`, essaie `obj.valueOf()` puis `obj.toString()`, selon ce qui existe. Toutes ces méthodes doivent renvoyer une primitive pour fonctionner (si elle est définie). En pratique, il suffit souvent d'implémenter uniquement `obj.toString()` comme méthode "fourre-tout" pour les conversions de chaînes de caractères qui devraient renvoyer une représentation "lisible par l'homme" d'un objet, à des fins de journalisation ou de débogage. ================================================ FILE: 1-js/04-object-basics/index.md ================================================ # Objets: les bases ================================================ FILE: 1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md ================================================ Essayez de lancer : ```js run let str = "Hello"; str.test = 5; // (*) alert(str.test); ``` Selon que vous utilisiez `use strict` ou non, le résultat peut être : 1. `undefined` (pas de mode strict) 2. une erreur (mode strict) Pourquoi ? Répétons ce qui se pase à la ligne `(*)`: 1. Lorsqu'on accède à une propiété de `str`, un "wrapper d'objet" (conteneur) est créé. 2. En mode strict, l'écriture à l'intérieur est une erreur. 3. Sinon, l'opération avec la propriété est poursuivie, l'objet obtient la propriété `test`. Mais après cela, "l'objet wrapper" disparaît, de sorte que dans la dernière ligne, `str` n'a aucune trace de la propriété `test`. **Cet exemple montre clairement que les primitives ne sont pas des objets.** Ils ne peuvent pas stocker de données supplémentaires. ================================================ FILE: 1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md ================================================ importance: 5 --- # Pouvons-nous ajouter une propiété à une primitive ? Considérons le code suivant: ```js let str = "Hello"; str.test = 5; alert(str.test); ``` Pensez-vous que ça va fonctionner ? Qu'est-ce qui sera affiché ? ================================================ FILE: 1-js/05-data-types/01-primitives-methods/article.md ================================================ # Méthodes des primitives JavaScript nous permet de travailler avec des primitives (chaînes de caractères, nombres, etc.) comme s'il s'agissait d'objets. Ils prévoient également des méthodes pour les appeler en tant que tel. Nous étudierons cela très bientôt, mais nous verrons d'abord comment cela fonctionne car, bien entendu, les primitives ne sont pas des objets (et nous allons rendre cela plus clair). Examinons les principales différences entre primitives et objets. Une primitive - Est une valeur de type primitif. - Il existe 7 types primitifs : `string`, `number`, `bigint`, `boolean`, `symbol`, `null` et `undefined`. Un objet - Est capable de stocker plusieurs valeurs en tant que propriétés. - Peut être créé avec `{}`, par exemple : `{name: "John", age: 30}`. Il existe d'autres types d'objets en JavaScript. Les fonctions, par exemple, sont des objets. L'une des meilleurs choses à propos des objets est que nous pouvons stocker une fonction en tant que l'une de ses propriétés. ```js run let john = { name: "John", sayHi: function() { alert("Hi buddy!"); } }; john.sayHi(); // Hi buddy! ``` Nous avons donc crée un objet `john` avec la méthode `sayHI`. De nombreux objets intégrés existent déjà, tels que ceux qui fonctionnent avec des dates, des erreurs, des éléments HTML, etc. Ils ont des propriétés et des méthodes différente. Mais, ces fonctionnalités ont un coût ! Les objets sont "plus lourds" que les primitives. Ils ont besoin de ressources supplémentaires pour soutenir le mécanisme interne. ## Une primitive en tant qu'objet Voici le paradoxe auquel est confronté le créateur de JavaScript : - Il y a beaucoup de choses que l'on voudrait faire avec une primitive telle qu'une chaîne de caractères ou un nombre. Ce serait génial d'y avoir accès avec des méthodes. - Les primitives doivent être aussi rapides et légères que possible. La solution semble peu commode, mais la voici : 1. Les primitives sont toujours primitives. Une seule valeur, au choix. 2. Le langage permet d'accéder aux méthodes et aux propriétés des chaînes de caractères, des nombres, des booléens et des symboles. 3. Pour que cela fonctionne, un "wrapper d'objet" (conteneur) spécial est crée pour fournir la fonctionnalité supplémentaire, puis il est détruit. Les "wrapper d'objets" (conteneurs) sont différents pour chaque type de primitive et sont appelés `String`, `Number`, `Boolean` et `Symbol`. Ainsi, ils fournissent différents ensembles de méthodes. Par exemple, il existe une méthode de string [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) qui renvoie une chaîne de caractères `str` en majuscule. Voici comment ça fonctionne: ```js run let str = "Hello"; alert( str.toUpperCase() ); // HELLO ``` Simple, non? Voici ce qui se passe réellement dans `str.toUpperCase()`: 1. La chaîne de caractères `str` est une primitive. Ainsi, au moment d'accéder à sa propriété, un objet spécial est crée, qui connaît la valeur de la chaîne de caractères et possède des méthodes utiles, comme `toUpperCase()`. 2. Cette méthode s'exécute et retourne une nouvelle chaîne de caractères (indiquée par `alert`). 3. L'objet spécial est détruit, laissant le primitif `str` seul. Les primitives peuvent donc fournir des méthodes, mais elles restent légères. Le moteur JavaScript optimise fortement ce processus. Il peut même ignorer la création de l'objet supplémentaire. Mais il doit toujours adhérer à la spécification et se comporter comme s'il en crée un. Un nombre a ses propres méthodes, par exemple, [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) arrondit le nombre à la précision indiquée : ```js run let n = 1.23456; alert( n.toFixed(2) ); // 1.23 ``` Nous verrons des méthodes plus spécifiques dans les chapitres [Nombres](https://javascript.info/number) et [Chaînes de caractères](https://javascript.info/string). ````warn header="Les constructeurs `String`, `Number` et `Boolean` sont réservés à un usage interne." Certains langages comme Java nous permettent de créer des "wrapper d'objet" (conteneur) pour les primitives en utilisant une syntaxe telle que `new Number(1)` ou `new Boolean(false)`. En JavaScript, cela est également possible pour des raisons historique, mais fortement **déconseillé**. Cela peut très vite se compliquer à plusieurs endroits. Par exemple : ```js run alert( typeof 0 ); // "number" alert( typeof new Number(0) ); // "object"! ``` Les objets sont toujours vrais dans les `if`, alors l'alerte apparaîtra ici : ```js run let zero = new Number(0); if (zero) { // zéro est vrai, parce que c'est un objet alert( "zero is truthy!?!" ); } ``` Par ailleurs, utiliser les mêmes fonctions `String`, `Number` et `Boolean` sans `new` est une chose totalement valide et même recommandée. Ils convertissent une valeur dans le type correspondant : une chaîne de caractères, un nombre ou un booléen (primitive). Par exemple, ceci est entièrement valide : ```js let num = Number("123"); // convertir une chaîne de caractères en nombre ``` ```` ````warn header="`null` / `undefined` n'ont pas de méthode" Les primitives spéciales `null` et `undefined` sont des exceptions. Elles n'ont pas de "wrapper d'objet" (conteneur) correspondants et ne fournissent aucune méthode. En un sens, elles sont "les plus primitives". Une tentative d'accès à une propriété d'une telle valeur donnerait l'erreur suivante: ```js run alert(null.test); // error ``` ```` ## Résumé - Les primitives sauf `null` et `undefined` fournissent de nombreuses méthodes utiles. Nous étudierons cela dans les prochains chapitres. - Officiellement, ces méthodes fonctionnent via des objets temporaires, mais les moteurs JavaScript sont bien ajustés pour optimiser cela en interne, elles ne sont donc pas coûteuses à appeler. ================================================ FILE: 1-js/05-data-types/02-number/1-sum-interface/solution.md ================================================ ```js run demo let a = +prompt("Le premier numéro?", ""); let b = +prompt("Le second numéro?", ""); alert( a + b ); ``` Notez le plus unaire `+` avant le `prompt`. Il convertit immédiatement la valeur en nombre. Sinon, `a` et `b` seraient des `string` et leur somme serait leur concaténation, c'est à dire: `"1" + "2" = "12"`. ================================================ FILE: 1-js/05-data-types/02-number/1-sum-interface/task.md ================================================ importance: 5 --- # Somme des nombres du visiteur Créez un script qui invite le visiteur à entrer deux nombres, puis affiche leur somme. [Lancer la démo] P.S. Il y a un piège avec les types. ================================================ FILE: 1-js/05-data-types/02-number/2-why-rounded-down/solution.md ================================================ En interne, la fraction décimale `6.35` est un nombre binaire sans fin. Comme toujours dans de tels cas, il est stocké avec une perte de précision. Voyons cela : ```js run alert( 6.35.toFixed(20) ); // 6.34999999999999964473 ``` La perte de précision peut causer à la fois une augmentation et une diminution d'un nombre. Dans ce cas particulier, le nombre diminue un peu, c'est pourquoi il a été arrondi à 6.3. Et quand est-il de `1.35` ? ```js run alert( 1.35.toFixed(20) ); // 1.35000000000000008882 ``` Ici, la perte de précision rend le nombre un peu plus grand, c'est pourquoi il a été arrondi à 1.4. **Comment pouvons-nous résoudre le problème avec `6.35` si nous voulons qu'il soit arrondi correctement ?** Nous devons le rapprocher d'un nombre entier avant d'arrondir : ```js run alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000 ``` Notez que `63.5` n'a aucune perte de précision. C'est parce que la partie décimale `0.5` est en réalité `1/2`. Les fractions divisées par les puissances de `2` sont représentées sans perte de précision dans le système binaire, on peut maintenant les arrondir : ```js run alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(arrondi) -> 6.4 ``` ================================================ FILE: 1-js/05-data-types/02-number/2-why-rounded-down/task.md ================================================ importance: 4 --- # Pourquoi 6.35.toFixed(1) == 6.3 ? Selon la documentation, `Math.round` et `toFixed` arrondissent tous les deux au nombre le plus proche : `0..4` arrondi par défaut (à la baisse) tantdis que `5..9` arrondi par excès (à la hausse). Par exemple : ```js run alert( 1.35.toFixed(1) ); // 1.4 ``` Dans l'exemple similaire ci-dessous, pourquoi est-ce que `6.35` est arrondi à `6.3` et non `6.4` ? ```js run alert( 6.35.toFixed(1) ); // 6.3 ``` Comment arrondir `6.35` de la bonne manière ? ================================================ FILE: 1-js/05-data-types/02-number/3-repeat-until-number/_js.view/solution.js ================================================ function readNumber() { let num; do { num = prompt("Enter a number please?", 0); } while ( !isFinite(num) ); if (num === null || num === '') return null; return +num; } ================================================ FILE: 1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js ================================================ beforeEach(function() { sinon.stub(window, "prompt"); }); afterEach(function() { prompt.restore(); }); describe("readNumber", function() { it("if a number, returns it", function() { prompt.returns("123"); assert.strictEqual(readNumber(), 123); }); it("if 0, returns it", function() { prompt.returns("0"); assert.strictEqual(readNumber(), 0); }); it("continues the loop until meets a number", function() { prompt.onCall(0).returns("not a number"); prompt.onCall(1).returns("not a number again"); prompt.onCall(2).returns("1"); assert.strictEqual(readNumber(), 1); }); it("if an empty line, returns null", function() { prompt.returns(""); assert.isNull(readNumber()); }); it("if cancel, returns null", function() { prompt.returns(null); assert.isNull(readNumber()); }); }); ================================================ FILE: 1-js/05-data-types/02-number/3-repeat-until-number/solution.md ================================================ ```js run demo function readNumber() { let num; do { num = prompt("Entrez un nombre s'il vous plaît", 0); } while ( !isFinite(num) ); if (num === null || num === '') return null; return +num; } alert(`Read: ${readNumber()}`); ``` La solution est un peu plus complexe qu'elle n'y paraît car nous devons gérer des lignes `null` / vides. Nous répétons donc la demande jusqu'à ce qu'il s'agisse d'un "nombre régulier". Les lignes `null` (cancel) et vide répondent également à cette condition car, sous forme numérique, elles valent `0`. Après que nous nous sommes arrêtés, nous devons traiter spécialement les lignes `null` et vides (retourner `null`), car les convertir en nombre renverrait `0`. ================================================ FILE: 1-js/05-data-types/02-number/3-repeat-until-number/task.md ================================================ importance: 5 --- # Répéter jusqu'à ce que l'entrée soit un nombre Créez une fonction `readNumber` qui invite à entrer un nombre jusqu'à ce que le visiteur saisisse une valeur numérique valide. La valeur résultante doit être renvoyée sous forme de nombre. Le visiteur peut également arrêter le processus en entrant une ligne vide ou en appuyant sur "CANCEL". Dans ce cas, la fonction doit renvoyer `null`. [Lancer la démo] ================================================ FILE: 1-js/05-data-types/02-number/4-endless-loop-error/solution.md ================================================ C'est parce que `i` ne sera jamais exactement égal à 10. Exécutez-le pour voir les valeurs *réelles* de i : ```js run let i = 0; while (i < 11) { i += 0.2; if (i > 9.8 && i < 10.2) alert( i ); } ``` Aucun d'entre eux n'est exactement `10`. De telles choses se produisent à cause des pertes de précision lors de l'ajout des fractions comme `0.2`. Conclusion : évitez les contrôles d'égalité lorsque vous travaillez avec des fractions décimales. ================================================ FILE: 1-js/05-data-types/02-number/4-endless-loop-error/task.md ================================================ importance: 4 --- # Une boucle infinie Cette boucle est infinie. Ça ne finit jamais. Pourquoi ? ```js let i = 0; while (i != 10) { i += 0.2; } ``` ================================================ FILE: 1-js/05-data-types/02-number/8-random-min-max/solution.md ================================================ Nous devons "mapper" toutes les valeurs de l'intervalle 0...1 en valeurs de `min` à `max`. Cela peut être fait en deux étapes : 1. Si nous multiplions un nombre aléatoire de 0...1 par `max-min`, l'intervalle des valeurs possible augmente de `0..1` à `0..max-min`. 2. Maintenant, si nous ajoutons `min`, l'intervalle possible devient de `min` à `max`. La fonction : ```js run function random(min, max) { return min + Math.random() * (max - min); } alert( random(1, 5) ); alert( random(1, 5) ); alert( random(1, 5) ); ``` ================================================ FILE: 1-js/05-data-types/02-number/8-random-min-max/task.md ================================================ importance: 2 --- # Un nombre aléatoire de min à max La fonction intégrée `Math.random()` crée une valeur aléatoire de 0 à 1 (1 non compris). Ecrivez la fonction `random(min, max)` pour générer un nombre aléatoire compris entre `min` et `max` (max non compris). Quelques exemples : ```js alert( random(1, 5) ); // 1.2345623452 alert( random(1, 5) ); // 3.7894332423 alert( random(1, 5) ); // 4.3435234525 ``` ================================================ FILE: 1-js/05-data-types/02-number/9-random-int-min-max/solution.md ================================================ # La solution simple mais fausse La solution la plus simple mais fausse serait de générer une valeur de `min` à `max` et de l'arrondir : ```js run function randomInteger(min, max) { let rand = min + Math.random() * (max - min); return Math.round(rand); } alert( randomInteger(1, 3) ); ``` La fonction marche, mais elle est incorrecte. La probabilité d'obtenir les valeurs `min` et `max` est deux fois inférieure à toute autre. Si vous exécutez l'exemple ci-dessous plusieurs fois, vous verrez facilement que `2` apparaît le plus souvent. Cela se produit car `Math.round()` obtient des nombres aléatoires à partir de l'intervalle `1..3` et les arrondit comme ici : ```js no-beautify values from 1 ... to 1.4999999999 devient 1 values from 1.5 ... to 2.4999999999 devient 2 values from 2.5 ... to 2.9999999999 devient 3 ``` Maintenant, nous pouvons clairement voir que `1` obtient deux fois moins de valeurs que `2`. Et la même chose avec `3`. # La bonne solution Il existe de nombreuses solutions correctes à la tâche. L'une d'elles consiste à ajuster les limites d'intervalle. Pour garantir les mêmes intervalles, nous pouvons générer des valeurs comprises entre `0.5` et `3.5`, ajoutant ainsi les probabilités requises : ```js run *!* function randomInteger(min, max) { // maintenant rand est entre (min-0.5) et (max+0.5) let rand = min - 0.5 + Math.random() * (max - min + 1); return Math.round(rand); } */!* alert( randomInteger(1, 3) ); ``` Une autre solution pourrait être d'utiliser `Math.floor` pour un nombre aléatoie compris entre `min` et `max+1`. ```js run *!* function randomInteger(min, max) { // ici rand est de min à (max+1) let rand = min + Math.random() * (max + 1 - min); return Math.floor(rand); } */!* alert( randomInteger(1, 3) ); ``` Maintenant, tous les intervalles sont mappés de cette façon : ```js no-beautify values from 1 ... to 1.9999999999 devient 1 values from 2 ... to 2.9999999999 devient 2 values from 3 ... to 3.9999999999 devient 3 ``` Tous les intervalles ont la même longueur, rendant la distribution finale uniforme. ================================================ FILE: 1-js/05-data-types/02-number/9-random-int-min-max/task.md ================================================ importance: 2 --- # Un entier aléatoire de min à max Créez une fonction `randomInteger(min, max)` qui génère un nombre entier aléatoire compris entre `min` et `max` (`min` et `max` inclut). Tout nombre compris dans l'intervalle `min..max` doit apparaître avec la même probabilité. Quelques exemples : ```js alert( randomInteger(1, 5) ); // 1 alert( randomInteger(1, 5) ); // 3 alert( randomInteger(1, 5) ); // 5 ``` Vous pouvez utiliser la solution de la [tâche précédente](info:task/random-min-max) comme base. ================================================ FILE: 1-js/05-data-types/02-number/article.md ================================================ # Nombres En JavaScript moderne, il existe deux types de nombres : 1. Les nombres standards en JavaScript sont stockés au format 64 bits [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), également connu sous le nom de "nombres à virgule flottante double précision". Ce sont des chiffres que nous utilisons le plus souvent, et nous en parlerons dans ce chapitre. 2. Les nombres BigInt pour représenter des entiers de longueur arbitraire. Ils sont parfois nécessaires, car un nombre régulier ne peut pas dépasser de manière précise 253 ou être inférieur à -253. Comme les bigints sont utilisés dans quelques zones spéciales, nous leur consacrons un chapitre spécial . Nous allons donc parler ici des nombres réguliers. Développons nos connaissances à leur sujet. ## Plus de façons d'écrire un nombre Imaginez que nous ayons besoin d'écrire 1 milliard. Le moyen évident est : ```js let milliard = 1000000000; ``` Nous pouvons également utiliser l’underscore `_` comme séparateur : ```js let billion = 1_000_000_000; ``` Ici, l'underscore `_` joue le rôle de "[sucre syntaxique](https://en.wikipedia.org/wiki/Syntactic_sugar)", il rend le nombre plus lisible. Le moteur JavaScript ignore simplement `_` entre les chiffres, donc c'est exactement le même milliard que ci-dessus. Dans la vraie vie cependant, nous essayons d'éviter d'écrire de longues séquences de zéros. Nous sommes trop paresseux pour ça. Nous essaierons d'écrire quelque chose comme "1 milliard" pour un milliard ou "7,3 milliards" pour 7 milliards 300 millions. La même chose est vraie pour la plupart des grands nombres. En JavaScript, nous pouvons raccourcir un nombre en y ajoutant la lettre `"e"` et en spécifiant le nombre de zéros : ```js run let milliard = 1e9; // 1 milliard, littéralement : 1 suivi de 9 zéros alert( 7.3e9 ); // 7.3 milliards (pareil que 7300000000 ou 7_300_000_000) ``` En d'autres termes, `e` multiplie le nombre par `1` suivi du nombre de zéros spécifié. ```js 1e3 === 1 * 1000 // e3 signifie *1000 1.23e6 === 1.23 * 1000000 // e6 signifie *1000000 ``` Maintenant, écrivons quelque chose de très petit. Disons, 1 microseconde (un millionième de seconde) : ```js let us = 0.000001; ``` Comme avant, l'utilisation de `"e"` peut nous aider. Si nous voulons éviter d'écrire les zéros explicitement, nous pourrions dire la même chose comme : ```js let us = 1e-6; // cinq zéros à gauche de 1 ``` Si nous comptons les zéros dans `0.000001`, il y en a 6. Donc logiquement, c'est `1e-6`. En d'autres termes, un nombre négatif après `"e"` signifie une division par 1 suivi du nombre spécifié de zéros : ```js // -3 divise par 1 avec 3 zéros 1e-3 === 1 / 1000; // 0.001 // -6 divise par 1 avec 6 zéros 1.23e-6 === 1.23 / 1000000; // 0.00000123 // an example with a bigger number 1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times ``` ### Nombres hexadécimaux, binaires et octaux Les nombres [Hexadécimaux](https://fr.wikipedia.org/wiki/Syst%C3%A8me_hexad%C3%A9cimal) sont souvent utilisés en JavaScript pour représenter des couleurs, encoder des caractères et pour beaucoup d'autres choses. Alors, naturellement, il existe un moyen plus court de les écrire : `0x` puis le nombre. Par exemple : ```js run alert( 0xff ); // 255 alert( 0xFF ); // 255 (même résultat car la casse n'a pas d'importance) ``` Les systèmes numériques binaires et octaux sont rarement utilisés, mais sont également supportés avec les préfixes `0b` et `0o` : ```js run let a = 0b11111111; // forme binaire de 255 let b = 0o377; // forme octale de 255 alert( a == b ); // true, car a et b valent le même nombre, soit 255 ``` Cependant ça ne fonctionne qu'avec ces 3 systèmes de numération. Pour les autres systèmes numérique, nous devrions utiliser la fonction `parseInt` (que nous verrons plus loin dans ce chapitre). ## La méthode toString(base) La méthode `num.toString(base)` retourne une chaîne de caractères représentant `num` dans le système numérique de la `base` donnée. Par exemple : ```js run let num = 255; alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` La `base` peut varier de `2` à `36`. Par défaut, il s'agit de `10`. Les cas d'utilisation courants sont : - **base=16** est utilisé pour les couleurs hexadécimales, les encodages de caractères, etc. Les chiffres peuvent être `0..9` ou `A..F`. - **base=2** est principalement utilisé pour le débogage d'opérations binaires, les chiffres pouvant être `0` ou `1`. - **base=36** est le maximum, les chiffres peuvent être `0..9` ou `A..Z`. L'alphabet latin entier est utilisé pour représenter un nombre. Un cas amusant mais utile pour la base `36` consiste à transformer un identifiant numérique long en quelque chose de plus court, par exemple pour créer une URL courte. On peut simplement le représenter dans le système numérique avec base `36` : ```js run alert( 123456..toString(36) ); // 2n9c ``` ```warn header="Deux points pour appeler une méthode" Veuillez noter que deux points sur `123456..toString(36)` n'est pas une faute de frappe. Si nous voulons appeler une méthode directement sur un nombre, comme `toString` dans l'exemple ci-dessus, nous devons placer deux points `..` avant la méthode. Si nous plaçions un seul point : `123456.toString(36)`, il y aurait une erreur, car la syntaxe JavaScript applique la partie décimale après le premier point et il lirait `toString(36)` comme étant la partie décimale de `123456` ce qui n'est pas le cas. Si nous plaçons un point de plus, alors JavaScript sait que la partie décimale est vide et peut maintenant appliquer la méthode. Il est aussi possible d'écrire `(123456).toString(36)`. ``` ## Arrondir Arrondir est l'une des opérations les plus utilisées pour travailler avec des nombres. Il existe plusieurs fonctions intégrées pour arrondir : `Math.floor` : Arrondis vers le bas : `3.1` devient `3`, et `-1.1` devient `-2`. `Math.ceil` : Arrondis ver le haut : `3.1` devient `4`, et `-1.1` devient `-1`. `Math.round` : Arrondit à l'entier le plus proche : `3,1` devient `3`, `3,6` devient `4` et pour le cas du milieu, `3,5` est également arrondit à `4`. `Math.trunc` (non pris en charge par Internet Explorer) : Supprime tout ce qui suit le point décimal : `3.1` devient `3`, `-1.1` devient `-1`. Voici le tableau pour résumer les différences entre eux : | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |------|--------------|-------------|--------------|--------------| | `3.1`| `3` | `4` | `3` | `3` | | `3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | Ces fonctions couvrent toutes les manières possibles de traiter la partie décimale d'un nombre. Mais que se passe-t-il si nous voulons arrondir le nombre à un certain chiffre après la virgule ? Par exemple, nous avons `1.2345` et voulons l'arrondir à 2 chiffres, pour obtenir seulement `1.23`. Il y a deux façons de le faire: 1. Multiplier et diviser. Par exemple, pour arrondir le nombre au 2ème chiffre après la décimale, nous pouvons multiplier le nombre par "100", appeler la fonction d'arrondi puis le diviser. ```js run let num = 1.23456; alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 ``` 2. La méthode [toFixed(n)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/toFixed) arrondit le nombre à `n` chiffres après le point et renvoie une chaîne de caractères du résultat. ```js run let num = 12.34; alert( num.toFixed(1) ); // "12.3" ``` Ceci arrondit à la valeur la plus proche, similaire à `Math.round` : ```js run let num = 12.36; alert( num.toFixed(1) ); // "12.4" ``` Veuillez noter que le résultat de `toFixed` est une chaîne de caractères. Si la partie décimale est plus courte qu'indiquée, des zéros sont ajoutés à la fin : ```js run let num = 12.34; alert( num.toFixed(5) ); // "12.34000", ajout de zéros pour faire exactement 5 chiffres ``` Nous pouvons le convertir en un nombre en utilisant le plus unaire `+` ou un appel `Number()` : `+num.toFixed(5)`. ## Calculs imprécis En interne, un nombre est représenté au format 64 bits [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), il y a donc exactement 64 bits pour stocker un nombre : 52 d'entre eux sont utilisés pour stocker les chiffres, 11 d'entre eux stockent la position du point décimal (ils sont zéro pour les nombres entiers), et 1 bit est pour le signe. Si un nombre est vraiment énorme, il peut déborder du stockage 64 bits et devenir une valeur numérique spéciale `Infinity` : ```js run alert( 1e500 ); // infini ``` Ce qui est peut-être un peu moins évident, mais qui arrive souvent, est la perte de précision. Considérez ce (faux !) test d'égalité : ```js run alert( 0.1 + 0.2 == 0.3 ); // *!*faux*/!* ``` Si on vérifie si la somme de `0.1` et `0.2` est égale à `0.3` on obtient `faux`. Étrange ! Qu'est-ce que c'est alors si ce n'est pas `0.3`? ```js run alert( 0.1 + 0.2 ); // 0.30000000000000004 ``` Aie ! Imaginez que vous créiez un site d'e-commerce et que le visiteur mette des produits à `0,10 €` et `0,20 €` dans son panier. Le total de la commande sera de `0,30000000000000004 €`. Cela surprendrait n'importe qui. Mais pourquoi cela se produit-il ? Un nombre est stocké en mémoire sous sa forme binaire, une séquence de uns et de zéros. Mais les fractions telles que `0.1`, `0.2`, qui semblent simples dans le système numérique décimal, sont en réalité des fractions sans fin dans leur forme binaire. En d'autres termes, qu'est-ce que `0.1` ? C'est un divisé par dix `1/10`, un dixième. Dans le système numérique décimal, ces nombres sont facilement représentables. Comparez-le à un tiers : `1/3`. Cela devient une fraction sans fin `0.33333(3)`. Ainsi, la division par puissances `10` est garantie de bien fonctionner dans le système décimal, mais la division par `3` ne l'est pas. Pour la même raison, dans le système de numération binaire, la division par des puissances de `2` est garantie, mais `1/10` devient une fraction binaire sans fin. Il n'existe aucun moyen de stocker **exactement 0.1** ou **exactement 0.2** à l'aide du système binaire, tout comme il n'existe aucun moyen de stocker un tiers sous forme de fraction décimale. Le format numérique IEEE-754 résout ce problème en arrondissant au nombre le plus proche possible. Ces règles d'arrondissement ne nous permettent normalement pas de voir cette "petite perte de précision", mais elle existe. Nous pouvons voir cela en action : ```js run alert( 0.1.toFixed(20) ); // 0.10000000000000000555 ``` Et quand on additionne deux nombres, leurs "pertes de précision" s'additionnent. C'est pourquoi `0.1` + `0.2` n'est pas exactement `0.3`. ```smart header="Pas seulement JavaScript" Le même problème existe dans de nombreux autres langages de programmation. PHP, Java, C, Perl, Ruby donnent exactement le même résultat, car ils sont basés sur le même format numérique. ``` Pouvons-nous contourner le problème ? Bien sûr, la méthode la plus fiable est d'arrondir le résultat à l'aide d'une méthode [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): ```js run let sum = 0.1 + 0.2; alert( sum.toFixed(2) ); // "0.30" ``` Veuillez noter que `toFixed` renvoie toujours une chaîne de caractères. Il s'assure qu'il a 2 chiffres après le point décimal. C'est pratique si nous avons un magasin en ligne et devons montrer `0.30$`. Pour les autres cas, nous pouvons utiliser le plus unaire `+` pour le contraindre en un nombre : ```js run let sum = 0.1 + 0.2; alert( +sum.toFixed(2) ); // 0.3 ``` Nous pouvons également multiplier temporairement les nombres par 100 (ou un nombre plus grand) pour les transformer en nombres entiers, faire le calcul, puis rediviser. Ensuite, comme nous faisons des calculs avec des nombres entiers, l'erreur diminue quelque peu, mais nous l'obtenons toujours sur la division : ```js run alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` Ainsi, l'approche multiplier/diviser réduit l'erreur, mais ne la supprime pas totalement. Parfois, nous pourrions essayer d'éviter les fractions complètement. Par exemple, si nous traitons avec un magasin, nous pouvons stocker les prix en cents au lieu de dollars. Mais que se passe-t-il si nous appliquons une réduction de 30 % ? En pratique, il est rarement possible d'éviter totalement les fractions. Il suffit de les arrondir pour couper les "queues" si nécessaire. ````smart header="La chose amusante" Essayez de lancer ceci : ```js run // Bonjour! Je suis un nombre auto-croissant! alert( 9999999999999999 ); // affiche 10000000000000000 ``` La cause est encore une fois le manque de précision. Le nombre comporte 64 bits, dont 52 peuvent être utilisés pour stocker des chiffres, mais cela ne suffit pas. Alors, les chiffres les moins significatifs disparaissent. JavaScript ne déclenche pas d'erreur dans de tels événements. il fait de son mieux pour adapter le nombre au format souhaité, mais malheureusement, ce format n'est pas assez grand. ```` ```smart header="Deux zéros" Une autre conséquence amusante de la représentation interne des nombres est l'existence de deux zéros : `0` et `-0`. C'est parce que le signe est représenté par un bit, il peut donc être défini ou non pour n’importe quel nombre, y compris le zéro. Dans le plupart des cas, la distinction est imperceptible, car les opérateurs peuvent les traiter de la même manière. ``` ## Tests : isFinite et isNaN Vous rappelez-vous de ces deux valeurs numériques spéciales ? - `Infinite` (et `-Infinite`) sont des valeurs numériques spéciales qui sont supérieures (inférieure) à tout. - `NaN` représente une erreur. Ils appartiennent au type `number`, mais ne sont pas des numéros "normaux". Il existe donc des fonctions spéciales pour les vérifier : - `isNaN(valeur)` convertit son argument en un nombre et teste ensuite s'il est `NaN` : ```js run alert( isNaN(NaN) ); // true alert( isNaN("str") ); // true ``` Mais avons-nous besoin de cette fonction ? Ne pouvons-nous pas simplement utiliser la comparaison `=== NaN` ? Malheureusement non. La valeur `NaN` est unique en ce sens qu'elle ne vaut rien, y compris elle-même : ```js run alert( NaN === NaN ); // false ``` - `isFinite(valeur)` convertit son argument en un nombre et renvoie true s'il s'agit d'un nombre régulier, pas de `NaN` / `Infinity` / `-Infinity` : ```js run alert( isFinite("15") ); // true alert( isFinite("str") ); // false, car c'est une valeur non régulière: NaN alert( isFinite(Infinity) ); // false, car c'est aussi une valeur non régulière: Infinity ``` Parfois, `isFinite` est utilisé pour valider si une valeur de chaîne de caractères est un nombre régulier : ```js run let num = +prompt("Entrez un nombre", ''); // sera vrai, sauf si vous entrez Infinity, -Infinity ou NaN alert( isFinite(num) ); ``` Veuillez noter qu'une chaîne de caractères vide ou une chaîne de caractères contenant seulement des espaces est traitée comme `0` dans toutes les fonctions numérique, y compris `isFinite`. ````smart header="`Number.isNaN` et `Number.isFinite`" Les méthodes [Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) et [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) sont des versions plus "strictes" des fonctions `isNaN` et `isFinite`. Elles ne convertissent pas automatiquement leur argument en nombre, mais vérifient s'il appartient au type `number`. - `Number.isNaN(value)` retourne `true` si l'argument appartient au type `number` et s'il vaut `NaN`. Dans tous les autres cas, elle retourne `false`. ```js run alert( Number.isNaN(NaN) ); // true alert( Number.isNaN("str" / 2) ); // true // Notez la différence : alert( Number.isNaN("str") ); // false, car "str" est de type "string" alert( isNaN("str") ); // true, car isNaN convertit la string "str" en un nombre et obtient NaN comme résultatde la conversion ``` - `Number.isFinite(value)` retourne `true` si l'argument appartient au type `number` et s'il vaut ni `NaN`, ni `Infinity`, ni `-Infinity`. Dans tous les autres cas, elle retourne `false`. ```js run alert( Number.isFinite(123) ); // true alert( Number.isFinite(Infinity) ); // false alert( Number.isFinite(2 / 0) ); // false // Notez la différence : alert( Number.isFinite("123") ); // false, car "123" est de type "string" alert( isFinite("123") ); // true, car isFinite convertit la string "123" en un nombre 123 ``` En quelque sorte, les fonction `Number.isNaN` et `Number.isFinite` sont plus simples et plus directes que les fonctions `isNaN` et `isFinite`. Cependant, en pratique `isNaN` et `isFinite` sont plus souvent utilisées car elles sont plus courtes à écrire. ```` ```smart header="Comparer avec Object.is" Il existe une méthode intégrée spéciale [Object.is](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/is) qui compare des valeurs telles que `===`, mais qui est plus fiable pour deux cas extrêmes : 1. Cela fonctionne avec `NaN` : `Object.is(NaN, NaN) === true`, c'est une bonne chose. 2. Les valeurs `0` et `-0` sont différentes : `Object.is(0, -0) === false`, techniquement c’est vrai, car le numéro a en interne un bit de signe qui peut être différent même si tous les autres bits sont à zéro. Dans tous les autres cas, `Object.is(a, b)` est identique à `a === b`. Nous mentionnons `Object.is` ici, car il est souvent utilisé dans les spécifications JavaScript. Lorsqu'un algorithme interne doit comparer deux valeurs pour être exactement identiques, il utilise `Object.is` (appelé en interne [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). ``` ## parseInt et parseFloat La conversion numérique à l'aide d'un plus `+` ou `Number()` est strict. Si une valeur n'est pas exactement un nombre, elle échoue : ```js run alert( +"100px" ); // NaN ``` La seule exception concerne les espaces au début ou à la fin de la chaîne de caractères, car ils sont ignorés. Mais dans la vraie vie, nous avons souvent des valeurs en unités, comme `"100px"` ou `"12pt"` en CSS. En outre, dans de nombreux pays, le symbole monétaire se situe après le montant. Nous avons donc `"19€"` et souhaitons en extraire une valeur numérique. C'est à quoi servent `parseInt` et `parseFloat`. Ils "lisent" un nombre d'une chaîne jusqu'à ce qu'ils ne puissent plus. En cas d'erreur, le numéro rassemblé est renvoyé. La fonction `parseInt` renvoie un entier, tandis que `parseFloat` renvoie un nombre à virgule : ```js run alert( parseInt('100px') ); // 100 alert( parseFloat('12.5em') ); // 12.5 alert( parseInt('12.3') ); // 12, seule la partie entière est renvoyée alert( parseFloat('12.3.4') ); // 12.3, le deuxième point arrête la lecture ``` Il y a des situations où `parseInt` / `parseFloat` retournera `NaN`. Cela arrive quand on ne peut lire aucun chiffre : ```js run alert( parseInt('a123') ); // NaN, le premier symbole arrête le processus ``` ````smart header="Le second argument de `parseInt(str, radix)`" La fonction `parseInt()` a un second paramètre optionnel. Il spécifie la base du système numérique, ce qui permet à `parseInt` d'analyser également les chaînes de nombres hexadécimaux, binaires, etc. : ```js run alert( parseInt('0xff', 16) ); // 255 alert( parseInt('ff', 16) ); // 255, sans 0x cela fonctionne également alert( parseInt('2n9c', 36) ); // 123456 ``` ```` ## Autres fonctions mathématiques JavaScript a un objet [Math](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Math) intégré qui contient une petite bibliothèque de fonctions et de constantes mathématiques. Quelques exemples : `Math.random()` : Retourne un nombre aléatoire de 0 à 1 (1 non compris). ```js run alert( Math.random() ); // 0.1234567894322 alert( Math.random() ); // 0.5435252343232 alert( Math.random() ); // ... (tout nombre aléatoire) ``` `Math.max(a, b, c...)` et `Math.min(a, b, c...)` : Renvoie le plus grand et le plus petit d'un nombre arbitraire d'arguments. ```js run alert( Math.max(3, 5, -10, 0, 1) ); // 5 alert( Math.min(1, 2) ); // 1 ``` `Math.pow(n, power)` : Renvoie `n` élevé à la puissance `power` donnée. ```js run alert( Math.pow(2, 10) ); // 2 puissance 10 = 1024 ``` Il y a plus de fonctions et de constantes dans l'objet Math, y compris la trigonométrie, que vous pouvez trouver dans la [documentation de l'objet Math](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Math). ## Résumé Pour écrire des nombres avec beaucoup de zéros : - Ajoutez `"e"` avec le nombre de zéros au nombre. Comme `123e6` est `123` avec 6 zéros soit `123000000`. - Un nombre négatif après le `"e"` entraîne la division du nombre par 1 suivi par le nombre de zéros donnés. Comme `123-e6` est `123` divisé par 1 suivi de 6 zéros, soit `0.000123` (`123` millionièmes). Pour différents systèmes de numération : - Il est possible d'écrire des nombres directement dans les systèmes hex (`0x`), octal (`0o`) et binaire (`0b`). - `parseInt(str, base)` passe la chaîne de caractères `str` vers un système numérique avec une `base` donnée : `2 ≤ base ≤ 36`. - `num.toString(base)` convertit un nombre en chaîne de caractères dans le système numérique de la `base` donnée. Pour les tests de nombres réguliers : - `isNaN(value)` convertit son argument en nombre puis vérifie s'il s'agit de `NaN`. - `Number.isNaN(value)` vérifie si son argument appartient au type `number` et, si c'est le cas, vérifie s'il s'agit de `NaN`. - `isFinite(value)` convertit son argument en nombre puis vérifie s'il ne s'agit pas de `NaN` / `Infinity` / `-Infinity`. - `Number.isFinite(value)` vérifie si son argument appartient au type `number` et, si c'est le cas, vérifie s'il ne s'agit pas de `NaN` / `Infinity` / `-Infinity`. Pour convertir des valeurs telles que `12pt` et `100px` en nombres : - Utiliser `parseInt` / `parseFloat` pour la conversion "soft", qui lit un nombre à partir d'une chaîne de caractères, puis renvoie la valeur qu'il pouvait lire avant de rencontrer une erreur. Pour les fractions : - Arrondissez en utilisant `Math.floor`, `Math.ceil`, `Math.trunc`, `Math.round` ou `num.toFixed(précision)`. - Assurez-vous de ne pas perdre de précision lorsque vous travaillez avec des fractions. Plus de fonctions mathématiques : - Voir l'objet [Math](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Math) quand vous en avez besoin. La bibliothèque est très petite, mais peut couvrir les besoins de base. ================================================ FILE: 1-js/05-data-types/03-string/1-ucfirst/_js.view/solution.js ================================================ function ucFirst(str) { if (!str) return str; return str[0].toUpperCase() + str.slice(1); } ================================================ FILE: 1-js/05-data-types/03-string/1-ucfirst/_js.view/test.js ================================================ describe("ucFirst", function() { it('Uppercases the first symbol', function() { assert.strictEqual(ucFirst("john"), "John"); }); it("Doesn't die on an empty string", function() { assert.strictEqual(ucFirst(""), ""); }); }); ================================================ FILE: 1-js/05-data-types/03-string/1-ucfirst/solution.md ================================================ Nous ne pouvons pas "remplacer" le premier caractère, car les chaînes de caractères en JavaScript sont immuables. Mais nous pouvons créer une nouvelle chaîne de caractères basée sur celle existante, avec le premier caractère majuscule : ```js let newStr = str[0].toUpperCase() + str.slice(1); ``` Il y a un petit problème cependant. Si `str` est vide, alors `str[0]` est `undefined`, et comme `undefined` n’a pas la méthode `toUpperCase()`, nous aurons une erreur. La solution la plus simple consiste à ajouter un test pour une chaîne vide, comme ceci : ```js run demo function ucFirst(str) { if (!str) return str; return str[0].toUpperCase() + str.slice(1); } alert( ucFirst("john") ); // John ``` ================================================ FILE: 1-js/05-data-types/03-string/1-ucfirst/task.md ================================================ importance: 5 --- # Mettre en majuscule le premier caractère Écrire une fonction `ucFirst(str)` qui retourne la chaîne de caractères `str` avec le premier caractère majuscule, par exemple : ```js ucFirst("john") == "John"; ``` ================================================ FILE: 1-js/05-data-types/03-string/2-check-spam/_js.view/solution.js ================================================ function checkSpam(str) { let lowerStr = str.toLowerCase(); return lowerStr.includes('viagra') || lowerStr.includes('xxx'); } ================================================ FILE: 1-js/05-data-types/03-string/2-check-spam/_js.view/test.js ================================================ describe("checkSpam", function() { it('finds spam in "buy ViAgRA now"', function() { assert.isTrue(checkSpam('buy ViAgRA now')); }); it('finds spam in "free xxxxx"', function() { assert.isTrue(checkSpam('free xxxxx')); }); it('no spam in "innocent rabbit"', function() { assert.isFalse(checkSpam('innocent rabbit')); }); }); ================================================ FILE: 1-js/05-data-types/03-string/2-check-spam/solution.md ================================================ Pour rendre la recherche insensible à la casse, transformons la chaîne de caractères en minuscule, puis recherchons : ```js run demo function checkSpam(str) { let lowerStr = str.toLowerCase(); return lowerStr.includes('viagra') || lowerStr.includes('xxx'); } alert( checkSpam('buy ViAgRA now') ); alert( checkSpam('free xxxxx') ); alert( checkSpam("innocent rabbit") ); ``` ================================================ FILE: 1-js/05-data-types/03-string/2-check-spam/task.md ================================================ importance: 5 --- # Vérifier le spam Écrire une fonction `checkSpam(str)` qui retourne `true` si `str` contient 'viagra' ou 'XXX', sinon `false`. La fonction doit être sensible à la casse : ```js checkSpam('buy ViAgRA now') == true checkSpam('free xxxxx') == true checkSpam("innocent rabbit") == false ``` ================================================ FILE: 1-js/05-data-types/03-string/3-truncate/_js.view/solution.js ================================================ function truncate(str, maxlength) { return (str.length > maxlength) ? str.slice(0, maxlength - 1) + '…' : str; } ================================================ FILE: 1-js/05-data-types/03-string/3-truncate/_js.view/test.js ================================================ describe("truncate", function() { it("truncate the long string to the given length (including the ellipsis)", function() { assert.equal( truncate("What I'd like to tell on this topic is:", 20), "What I'd like to te…" ); }); it("doesn't change short strings", function() { assert.equal( truncate("Hi everyone!", 20), "Hi everyone!" ); }); }); ================================================ FILE: 1-js/05-data-types/03-string/3-truncate/solution.md ================================================ La longueur maximale doit être `maxlength`, il faut donc la couper un peu plus courte pour laisser de la place aux ellipses. Notez qu'il existe en réalité un seul caractère Unicode pour une ellipse. Ce n’est pas trois points. ```js run function truncate(str, maxlength) { return (str.length > maxlength) ? str.slice(0, maxlength - 1) + '…' : str; } ``` ================================================ FILE: 1-js/05-data-types/03-string/3-truncate/task.md ================================================ importance: 5 --- # Tronquer le texte Créer une fonction `truncate(str, maxlength)` qui vérifie la longueur de `str` et, si elle dépasse `maxlength`, remplace la fin de `str` avec le caractère des ellipses `"…"` pour rendre sa longueur égale à `maxlength`. Le résultat de la fonction doit être la chaîne de caractères tronquée (si nécessaire). Par exemple : ```js truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" truncate("Hi everyone!", 20) = "Hi everyone!" ``` ================================================ FILE: 1-js/05-data-types/03-string/4-extract-currency/_js.view/solution.js ================================================ function extractCurrencyValue(str) { return +str.slice(1); } ================================================ FILE: 1-js/05-data-types/03-string/4-extract-currency/_js.view/test.js ================================================ describe("extractCurrencyValue", function() { it("for the string $120 returns the number 120", function() { assert.strictEqual(extractCurrencyValue('$120'), 120); }); }); ================================================ FILE: 1-js/05-data-types/03-string/4-extract-currency/solution.md ================================================ ================================================ FILE: 1-js/05-data-types/03-string/4-extract-currency/task.md ================================================ importance: 4 --- # Extraire l'argent Nous avons un coût sous la forme `"$120"`. C'est-à-dire que le signe dollar commence, puis le nombre. Créer une fonction `extractCurrencyValue(str)` qui extrait la valeur numérique d'une telle chaîne de caractères et la renvoit. L'exemple : ```js alert( extractCurrencyValue('$120') === 120 ); // true ``` ================================================ FILE: 1-js/05-data-types/03-string/article.md ================================================ # Strings En JavaScript, les données de type texte sont stockées sous forme de chaînes de caractères. Il n'y a pas de type séparé pour un seul caractère. Le format interne des chaînes de caractères est toujours [UTF-16](https://en.wikipedia.org/wiki/UTF-16), il n'est pas lié au codage de la page. ## Quotes Rappelons les types de quotes. Les chaînes de caractères peuvent être placées entre guillemets simples, doubles ou backticks : ```js let single = 'single-quoted'; let double = "double-quoted"; let backticks = `backticks`; ``` Les guillemets simples et doubles sont essentiellement les mêmes. Les backticks nous permettent toutefois d’incorporer n’importe quelle expression dans la chaîne de caractères, en l'enveloppant dans `${…}` : ```js run function sum(a, b) { return a + b; } alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3. ``` L’utilisation des backticks présente également l’avantage de permettre à une chaîne de caractères de couvrir plusieurs lignes : ```js run let guestList = `Guests: * John * Pete * Mary `; alert(guestList); // une liste d'invités sur plusieurs lignes ``` Ça a l'air naturel, non? Mais les guillemets simples ou doubles ne fonctionnent pas de cette façon. Si nous les utilisons et essayons d'utiliser plusieurs lignes, il y aura une erreur : ```js run let guestList = "Guests: // Error: Unexpected token ILLEGAL * John"; ``` Les guillemets simples et doubles proviennent d'anciens temps de la création linguistique lorsque la nécessité de chaînes multilignes n'était pas prise en compte. Les backticks sont apparus beaucoup plus tard et sont donc plus polyvalents. Les backticks nous permettent également de spécifier un "modèle de fonction" avant le premier backtick. La syntaxe est la suivante : func`string`. La fonction `func` est appelée automatiquement, elle reçoit la chaîne de caractères et les expressions incorporées et peut les traiter. Cette fonctionnalité est appelée "tagged templates", elle est rarement vue, mais vous pouvez en savoir plus à ce sujet dans la doc MDN : [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). ## Caractères spéciaux Il est encore possible de créer des chaînes de caractères multilignes avec des guillemets simples et doubles en utilisant un "caractère de nouvelle ligne", écrit comme ceci `\n`, qui spécifie un saut de ligne : ```js run let guestList = "Guests:\n * John\n * Pete\n * Mary"; alert(guestList); // une liste d'invités multiligne, pareil qu'au dessus ``` Comme exemple plus simple, ces deux lignes sont égales, juste écrites différemment : ```js run let str1 = "Hello\nWorld"; // deux lignes utilisant un "symbole de nouvelle ligne" // deux lignes utilisant une nouvelle ligne normale et des backticks let str2 = `Hello World`; alert(str1 == str2); // true ``` Il existe d'autres caractères "spéciaux" moins courants. Voici la liste complète : | Caractère | Description | |----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `\n` | Nouvelle ligne | | `\r` | Dans les fichiers texte Windows, une combinaison de deux caractères `\r\n` représente une nouvelle pause, tandis que sur un système d'exploitation non Windows, il s'agit simplement de `\n`. C'est pour des raisons historiques, la plupart des logiciels Windows comprennent également `\n`. | | `\'`, `\"` | Quotes | | `\\` | Backslash | | `\t` | Tab | | `\b`, `\f`, `\v` | Backspace, Form Feed, Vertical Tab -- conservés pour compatibilité, non utilisés de nos jours. | Comme vous pouvez le voir, tous les caractères spéciaux commencent par un backslash (barre oblique inversée) `\`. On l'appelle aussi "caractère d'échappement". Parce que c'est si spécial, si nous devons afficher une véritable barre oblique inverse `\` dans la chaîne, nous devons la doubler : ```js run alert( `The backslash: \\` ); // The backslash: \ ``` Les guillemets dits "échappés" `\'`, `\"`, \\` sont utilisés pour insérer un guillemet dans la même chaîne entre guillemets. Par exemple : ```js run alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! ``` Comme vous pouvez le constater, nous devons précéder le simple quote intérieure du backslash `\'`, sinon, cela indiquerait la fin de la chaîne de caractères. Bien sûr, il ne faut échapper que les guillemets identiques à ceux qui les entourent. Donc, comme solution plus élégante, nous pourrions passer aux guillemets doubles ou aux backticks : ```js run alert( "I'm the Walrus!" ); // I'm the Walrus! ``` Outre ces caractères spéciaux, il existe également une notation spéciale pour les codes Unicode `\u…`, elle est rarement utilisée et est couverte dans le chapitre facultatif sur [Unicode](info:unicode). ## Longueur de chaîne de caractères La propriété `length` indique la longueur de la chaîne de caractères : ```js run alert( `My\n`.length ); // 3 ``` Notez que `\n` est un seul caractère "spécial", la longueur est donc bien `3`. ```warn header="`length` est une propriété" Les personnes ayant des connaissances dans d'autres langages peuvent parfois commettre des erreurs en l'appelant `str.length()` au lieu de `str.length`. Cela ne fonctionne pas. Veuillez noter que `str.length` est une propriété numérique et non une fonction. Il n'est pas nécessaire d'ajouter des parenthèses après. Pas `.length()`, mais `.length`. ``` ## Accéder aux caractères Pour obtenir un caractère à la position `pos`, utilisez des crochets `[pos]` ou appelez la méthode [str.at(pos)](mdn:js/String/at). Le premier caractère commence à la position zéro : ```js run let str = `Hello`; // le premier caractère alert( str[0] ); // H alert( str.at(0) ); // H // le dernier caractère alert( str[str.length - 1] ); // o alert( str.at(-1) ); // o ``` Comme vous pouvez le voir, la méthode `.at(pos)` a l'avantage de permettre une position négative. Si `pos` est négatif, alors il est compté à partir de la fin de la chaîne de caractères. Donc `.at(-1)` signifie le dernier caractère, et `.at(-2)` est celui qui le précède, etc. Les crochets renvoient toujours `undefined` pour les index négatifs, par exemple : ```js run let str = `Hello`; alert( str[-2] ); // undefined alert( str.at(-2) ); // l ``` Nous pouvons également parcourir les caractères en utilisant un `for..of` : ```js run for (let char of "Hello") { alert(char); // H,e,l,l,o (char devient "H", ensuite "e", ensuite "l", etc.) } ``` ## Les chaînes de caractères sont immuables Les chaînes de caractères ne peuvent pas être changées en JavaScript. Il est impossible de modifier un caractère. Essayons de démontrer que cela ne fonctionne pas : ```js run let str = 'Hi'; str[0] = 'h'; // error alert( str[0] ); // ne fonctionne pas ``` La solution habituelle consiste à créer une nouvelle chaîne et à l’affecter à `str` au lieu de l’ancienne. Par exemple : ```js run let str = 'Hi'; str = 'h' + str[1]; // remplace la haine de caractères alert( str ); // hi ``` Nous verrons plus d'exemples dans les sections suivantes. ## Modifier la casse Les méthodes [toLowerCase()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/toLowerCase) et [toUpperCase()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/toUpperCase) modifient la casse : ```js run alert( 'Interface'.toUpperCase() ); // INTERFACE alert( 'Interface'.toLowerCase() ); // interface ``` Ou, si nous voulons un seul caractère minuscule : ```js run alert( 'Interface'[0].toLowerCase() ); // 'i' ``` ## Rechercher un substring (partie de la chaîne de caractères) Il existe plusieurs façons de rechercher une partie d'une chaîne de caractères. ### str.indexOf La première méthode est [str.indexOf(substr, pos)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/indexOf). Il cherche le `substr` dans `str`, en partant de la position donnée `pos`, et retourne la position où la correspondance a été trouvée ou `-1` si rien ne peut être trouvé. Par exemple : ```js run let str = 'Widget with id'; alert( str.indexOf('Widget') ); // 0, parce que 'Widget' est trouvé au début alert( str.indexOf('widget') ); // -1, pas trouvé, la recherche est sensible à la casse alert( str.indexOf("id") ); // 1, "id" est trouvé à la position 1 (..idget avec id) ``` Le second paramètre optionnel nous permet de rechercher à partir de la position donnée. Par exemple, la première occurrence de `"id"` est à la position `1`. Pour rechercher l’occurrence suivante, commençons la recherche à partir de la position `2` : ```js run let str = 'Widget with id'; alert( str.indexOf('id', 2) ) // 12 ``` Si toutes les occurrences nous intéressent, nous pouvons exécuter `indexOf` dans une boucle. Chaque nouvel appel est passé avec la position après le match précédent : ```js run let str = 'As sly as a fox, as strong as an ox'; let target = 'as'; // cherchons le let pos = 0; while (true) { let foundPos = str.indexOf(target, pos); if (foundPos == -1) break; alert( `Found at ${foundPos}` ); pos = foundPos + 1; // continue la recherche à partir de la position suivante } ``` Le même algorithme peut être raccourci : ```js run let str = "As sly as a fox, as strong as an ox"; let target = "as"; *!* let pos = -1; while ((pos = str.indexOf(target, pos + 1)) != -1) { alert( pos ); } */!* ``` ```smart header="`str.lastIndexOf(pos)`" Il y a aussi une méthode similaire [str.lastIndexOf(pos)](mdn:js/String/lastIndexOf) qui cherche de la fin d'une chaîne de caractères à son début. Elle liste les occurrences dans l'ordre inverse. ``` Il y a un léger inconvénient avec `indexOf` dans le test `if`. On ne peut pas le mettre dans le `if` comme ceci : ```js run let str = "Widget with id"; if (str.indexOf("Widget")) { alert("We found it"); // ne fonctionne pas ! } ``` L’`alert` dans l’exemple ci-dessus ne s’affiche pas parce que `str.indexOf("Widget")` retourne `0` (ce qui signifie qu'il a trouvé la correspondance à la position de départ). Oui, mais `if` considère que `0` est `false`. Nous devrions donc effectuer la vérification avec `-1`, comme ceci : ```js run let str = "Widget with id"; *!* if (str.indexOf("Widget") != -1) { */!* alert("We found it"); // fonctionne maintenant ! } ``` ### includes, startsWith, endsWith La méthode plus moderne [str.includes(substr, pos)](mdn:js/String/includes) retourne `true`/`false` en fonction de si `str` contient `substr`. C’est le bon choix si nous devons tester la présence, mais n’avons pas besoin de sa position : ```js run alert( "Widget with id".includes("Widget") ); // true alert( "Hello".includes("Bye") ); // false ``` Le deuxième argument optionnel de `str.includes` est la position de départ de la recherche : ```js run alert( "Widget".includes("id") ); // true alert( "Widget".includes("id", 3) ); // false, à partir de la position 3, il n'y a pas de "id" ``` Les méthodes [str.startsWith](mdn:js/String/startsWith) et [str.endsWith](mdn:js/String/endsWith) font exactement ce qu'elle disent : ```js run alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" démarre avec "Wid" alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" fini avec "get" ``` ## Obtenir un substring (sous-chaîne de caractères) Il existe 3 méthodes en JavaScript pour obtenir un substring : `substring`, `substr` et `slice`. `str.slice(start [, end])` : Renvoie la partie de la chaîne de caractères de `start` jusqu'à (sans l'inclure) `end`. Par exemple : ```js run let str = "stringify"; alert( str.slice(0, 5) ); // 'strin', le substring de 0 à 5 (sans inclure 5) alert( str.slice(0, 1) ); // 's', de 0 à 1, mais sans inclure 1, donc uniquement le caractère à l'index 0 ``` S'il n'y a pas de second argument, `slice` va jusqu'à la fin de la chaîne de caractères : ```js run let str = "st*!*ringify*/!*"; alert( str.slice(2) ); // 'ringify', à partir de la 2e position jusqu'à la fin ``` Des valeurs négatives pour `start`/`end` sont également possibles. Elles veulent dire que la position est comptée à partir de la fin de la chaîne de caractères : ```js run let str = "strin*!*gif*/!*y"; // commence à la 4ème position à partir de la droite, se termine au 1er à partir de la droite alert( str.slice(-4, -1) ); // 'gif' ``` `str.substring(start [, end])` : Renvoie la partie de la chaîne de caractères *entre* `start` et `end` (`end` non inclus). C'est presque la même chose que `slice`, mais cela permet à `start` d'être supérieur à `end` (dans ce cas, il échange simplement les valeurs `start` et `end`). Par exemple : ```js run let str = "st*!*ring*/!*ify"; // ce sont les mêmes pour substring alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" // ...mais pas pour slice : alert( str.slice(2, 6) ); // "ring" (le même résultat) alert( str.slice(6, 2) ); // "" (une chaîne de caractères vide) ``` Les arguments négatifs ne sont pas supportés (contrairement à slice), ils sont traités comme `0`. `str.substr(start [, length])` : Renvoie la partie de la chaîne de caractères à partir de `start`, avec le `length` (longueur) donné. Contrairement aux méthodes précédentes, celle-ci nous permet de spécifier la longueur `length` au lieu de la position finale : ```js run let str = "st*!*ring*/!*ify"; alert( str.substr(2, 4) ); // 'ring', à partir de la 2ème position on obtient 4 caractères ``` Le premier argument peut être négatif, pour compter à partir de la fin : ```js run let str = "strin*!*gi*/!*fy"; alert( str.substr(-4, 2) ); // 'gi', à partir de la 4ème position on obtient 2 caractères ``` Cette méthode réside dans l'[Annexe B](https://tc39.es/ecma262/#sec-string.prototype.substr) de la spécification du langage. Cela signifie que seuls les moteurs JavaScript hébergés par un navigateur doivent le prendre en charge et qu'il n'est pas recommandé de l'utiliser. En pratique, il est supporté partout. Récapitulons ces méthodes pour éviter toute confusion : | méthodes | séléction ... | valeurs negatives | |-------------------------|-----------------------------------------|--------------------------------------| | `slice(start, end)` | de `start` à `end` (n'inclue pas `end`) | permet les négatifs | | `substring(start, end)` | entre `start` et `end` | les valeurs négatives signifient `0` | | `substr(start, length)` | de `start` obtient `length` caractères | permet un `start` negatif | ```smart header="Lequel choisir ?" Tous peuvent faire le travail. Formellement, `substr` présente un inconvénient mineur : il n’est pas décrit dans la spécification JavaScript principale, mais dans l’Annexe B, qui couvre les fonctionnalités réservées au navigateur qui existent principalement pour des raisons historiques. Ainsi, les environnements autres que les navigateurs peuvent ne pas le prendre en charge. Mais dans la pratique, cela fonctionne partout. Parmi les deux autres variantes, `slice` est un peu plus flexible, il permet des arguments négatifs et une écriture plus courte. Donc, pour une utilisation pratique, il suffit de ne retenir que "slice". ``` ## Comparer les strings Comme nous le savons du chapitre , les strings sont comparées caractère par caractère dans l'ordre alphabétique. Bien que, il y a quelques bizarreries. 1. Une lettre minuscule est toujours plus grande qu'une majuscule : ```js run alert( 'a' > 'Z' ); // true ``` 2. Les lettres avec des signes diacritiques sont "hors d'usage" : ```js run alert( 'Österreich' > 'Zealand' ); // true ``` Cela peut conduire à des résultats étranges si nous trions ces noms de pays. Habituellement, les gens s'attendent à trouver `Zealand` après `Österreich` dans la liste. Pour comprendre ce qui se passe, nous devons être conscients que les chaînes de caractères en JavaScript sont encodées en utilisant [UTF-16](https://en.wikipedia.org/wiki/UTF-16). C'est-à-dire que chaque caractère a un code numérique correspondant. Il existe des méthodes spéciales qui permettent d'obtenir le caractère pour le code et inversement : `str.codePointAt(pos)` : Renvoie un nombre décimal représentant le code du caractère à la position `pos` : ```js run // différentes lettres majuscules ont des codes différents alert( "Z".codePointAt(0) ); // 90 alert( "z".codePointAt(0) ); // 122 alert( "z".codePointAt(0).toString(16) ); // 7a (si nous avons besoin d'une valeur hexadécimale) ``` `String.fromCodePoint(code)` : Crée un caractère par son `code` chiffre ```js run alert( String.fromCodePoint(90) ); // Z alert( String.fromCodePoint(0x5a) ); // Z (nous pouvons également utiliser une valeur hexadécimale comme argument) ``` Voyons maintenant les caractères avec les codes `65..220` (l’alphabet latin et un peu plus) en créant une chaîne de caractères de ceux-ci : ```js run let str = ''; for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); // Output: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` Vous voyez ? Les caractères majuscules sont les premiers, puis quelques spéciaux, puis les minuscules, et `Ö` vers la fin de la sortie. Maintenant, cela devient évident pourquoi `a > Z`. Les caractères sont comparés par leur code numérique. Le plus grand code signifie que le caractère est plus grand. Le code pour `a` (97) est supérieur au code pour `Z` (90). - Toutes les lettres minuscules vont après les lettres majuscules car leurs codes sont plus grands. - Certaines lettres comme `Ö` se distinguent de l'alphabet principal. Ici, le code est supérieur à tout ce qui va de `a` à `z`. ### Les comparaisons correctes [#comparaisons-correctes] L'algorithme "approprié" pour effectuer des comparaisons de chaînes est plus complexe qu'il n'y paraît, car les alphabets diffèrent d'une langue à l'autre. Le navigateur doit donc connaître la langue à comparer. Heureusement, les navigateurs modernes prennent en charge la norme d'internationalisation [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). Elle fournit une méthode spéciale pour comparer des chaînes de caractères dans différentes langues, en respectant leurs règles. L'appel [str.localeCompare(str2)](mdn:js/String/localeCompare) renvoie un entier indiquant si `str` est inférieur, égal ou supérieur à `str2` selon les règles du langage : - Renvoie un nombre négatif si `str` est inférieur à `str2` - Renvoie un nombre positif si `str` est supérieur à `str2` - Renvoie `0` s'ils sont équivalents. Par exemple : ```js run alert( 'Österreich'.localeCompare('Zealand') ); // -1 ``` Cette méthode a en fait deux arguments supplémentaires spécifiés dans [la documentation](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/localeCompare), ce qui lui permet de spécifier la langue (par défaut, pris dans l'environnement, l'ordre des lettres dépend de la langue) et de définir des règles supplémentaires telles que la sensibilité à la casse ou doit-on traiter `"a"` et `"á"` de la même manière, etc. ## Résumé - Il existe 3 types de quotes. Les backticks permettent à une chaîne de caractères de s'étendre sur plusieurs lignes et d'intégrer des expressions `${…}`. - Nous pouvons utiliser des caractères spéciaux, comme un saut de ligne `\n`. - Pour obtenir un caractère d'une string, utilisez `[]` ou la méthode `at`. - Pour obtenir un substring, utilisez `slice` ou `substring`. - Pour mettre une chaîne de caractères en minuscule ou en majuscule, utilisez `toLowerCase` ou `toUpperCase`. - Pour rechercher un substring, utilisez `indexOf`, ou `includes` / `startsWith` / `endsWith` pour de simple vérifications. - Pour comparer les chaînes de caractères en fonction de la langue, utilisez la méthode `localeCompare`, si non ils sont comparés par les codes de caractères. Il existe plusieurs autres méthodes utiles dans les strings : - `str.trim()` -- retire les espaces ("trims") du début et de la fin de la chaîne de caractères. - `str.repeat(n)` -- répète la chaîne de caractères `n` fois. - … et plus. Voir le [manuel](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String) pour plus de détails. Les strings ont aussi des méthodes pour rechercher / remplacer avec des expressions régulières. Mais c’est un sujet important, il est donc expliqué dans une autre section de ce tutoriel . De plus, à partir de maintenant, il est important de savoir que les chaînes de caractères sont basées sur l'encodage Unicode, et donc il y a des problèmes avec les comparaisons. Il y a plus d'informations sur Unicode dans le chapitre . ================================================ FILE: 1-js/05-data-types/04-array/1-item-value/solution.md ================================================ Le résultat est `4` : ```js run let fruits = ["Apples", "Pear", "Orange"]; let shoppingCart = fruits; shoppingCart.push("Banana"); *!* alert( fruits.length ); // 4 */!* ``` C'est parce que les tableaux sont des objets. Donc, shoppingCart et fruits sont les références du même tableau. ================================================ FILE: 1-js/05-data-types/04-array/1-item-value/task.md ================================================ importance: 3 --- # Le tableau est-il copié ? Qu'est-ce que ce code va montrer ? ```js let fruits = ["Apples", "Pear", "Orange"]; // pousser une nouvelle valeur dans la "copie" let shoppingCart = fruits; shoppingCart.push("Banana"); // Qu'y a-t-il dans fruits ? alert( fruits.length ); // ? ``` ================================================ FILE: 1-js/05-data-types/04-array/10-maximal-subarray/_js.view/solution.js ================================================ function getMaxSubSum(arr) { let maxSum = 0; let partialSum = 0; for (let item of arr) { partialSum += item; maxSum = Math.max(maxSum, partialSum); if (partialSum < 0) partialSum = 0; } return maxSum; } ================================================ FILE: 1-js/05-data-types/04-array/10-maximal-subarray/_js.view/test.js ================================================ describe("getMaxSubSum", function() { it("maximal subsum of [1, 2, 3] equals 6", function() { assert.equal(getMaxSubSum([1, 2, 3]), 6); }); it("maximal subsum of [-1, 2, 3, -9] equals 5", function() { assert.equal(getMaxSubSum([-1, 2, 3, -9]), 5); }); it("maximal subsum of [-1, 2, 3, -9, 11] equals 11", function() { assert.equal(getMaxSubSum([-1, 2, 3, -9, 11]), 11); }); it("maximal subsum of [-2, -1, 1, 2] equals 3", function() { assert.equal(getMaxSubSum([-2, -1, 1, 2]), 3); }); it("maximal subsum of [100, -9, 2, -3, 5] equals 100", function() { assert.equal(getMaxSubSum([100, -9, 2, -3, 5]), 100); }); it("maximal subsum of [] equals 0", function() { assert.equal(getMaxSubSum([]), 0); }); it("maximal subsum of [-1] equals 0", function() { assert.equal(getMaxSubSum([-1]), 0); }); it("maximal subsum of [-1, -2] equals 0", function() { assert.equal(getMaxSubSum([-1, -2]), 0); }); it("maximal subsum of [2, -8, 5, -1, 2, -3, 2] equals 6", function() { assert.equal(getMaxSubSum([2, -8, 5, -1, 2, -3, 2]), 6); }); }); ================================================ FILE: 1-js/05-data-types/04-array/10-maximal-subarray/solution.md ================================================ # Solution lente Nous pouvons calculer tous les subsums possibles. Le moyen le plus simple consiste à prendre chaque élément et à calculer les sommes de tous les sous-tableaux à partir de celui-ci. Par exemple, pour `[-1, 2, 3, -9, 11]` : ```js no-beautify // Commence à -1 : -1 -1 + 2 -1 + 2 + 3 -1 + 2 + 3 + (-9) -1 + 2 + 3 + (-9) + 11 // Commence à 2 : 2 2 + 3 2 + 3 + (-9) 2 + 3 + (-9) + 11 // Commence à 3 : 3 3 + (-9) 3 + (-9) + 11 // Commence à -9 : -9 -9 + 11 // Commence à 11 : 11 ``` Le code est en réalité une boucle imbriquée : la boucle externe recouvrant les éléments du tableau, et l'interne compte les sous-sommes commençant par l'élément en cours. ```js run function getMaxSubSum(arr) { let maxSum = 0; // si on ne prend aucun élément, zéro sera retourné for (let i = 0; i < arr.length; i++) { let sumFixedStart = 0; for (let j = i; j < arr.length; j++) { sumFixedStart += arr[j]; maxSum = Math.max(maxSum, sumFixedStart); } } return maxSum; } alert( getMaxSubSum([-1, 2, 3, -9]) ); // 5 alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11 alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3 alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 ``` La solution a une complexité temporelle de [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). En d'autres termes, si nous augmentons la taille du tableau 2 fois, l'algorithme fonctionnera 4 fois plus longtemps. Pour les grands tableaux (1'000, 10'000 éléments ou plus), de tels algorithmes peuvent conduire à une grande lenteur. # Solution rapide Parcourons le tableau et conservons la somme partielle actuelle des éléments dans la variable `s`. Si `s` devient négatif à un moment donné, assignez `s=0`. Le maximum de tous ces `s` sera la réponse. Si la description est trop vague, veuillez voir le code, il est assez court : ```js run demo function getMaxSubSum(arr) { let maxSum = 0; let partialSum = 0; for (let item of arr) { // pour chaque élément d'arr partialSum += item; // l'ajouter à partialSum maxSum = Math.max(maxSum, partialSum); // mémorise le maximum if (partialSum < 0) partialSum = 0; // zéro si négatif } return maxSum; } alert( getMaxSubSum([-1, 2, 3, -9]) ); // 5 alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11 alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([-1, -2, -3]) ); // 0 ``` L'algorithme nécessite exactement 1 passage de tableau, la complexité temporelle est donc O(n). Vous pouvez trouver plus d'informations détaillées sur l'algorithme ici : [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). Si la raison de ce fonctionnement n’est pas encore évidente, tracez l’algorithme à partir des exemples ci-dessus et voyez comment il fonctionne. ================================================ FILE: 1-js/05-data-types/04-array/10-maximal-subarray/task.md ================================================ importance: 2 --- # Un sous-tableau maximal L'entrée est un tableau de nombres, par exemple `arr = [1, -2, 3, 4, -9, 6]`. La tâche est la suivante : trouver le sous-tableau contigu de `arr` avec la somme maximale des items. Écrire la fonction `getMaxSubSum(arr)` qui retournera cette somme. Par exemple : ```js getMaxSubSum([-1, *!*2, 3*/!*, -9]) == 5 (la somme des éléments en surbrillance) getMaxSubSum([*!*2, -1, 2, 3*/!*, -9]) == 6 getMaxSubSum([-1, 2, 3, -9, *!*11*/!*]) == 11 getMaxSubSum([-2, -1, *!*1, 2*/!*]) == 3 getMaxSubSum([*!*100*/!*, -9, 2, -3, 5]) == 100 getMaxSubSum([*!*1, 2, 3*/!*]) == 6 (prend tout) ``` Si tous les éléments sont négatifs, cela signifie que nous n'en prenons aucun (le sous-tableau est vide), la somme est donc zéro : ```js getMaxSubSum([-1, -2, -3]) = 0 ``` S'il vous plaît essayez de penser à une solution rapide : [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation) ou même à O(n) si vous le pouvez. ================================================ FILE: 1-js/05-data-types/04-array/2-create-array/solution.md ================================================ ```js run let styles = ["Jazz", "Blues"]; styles.push("Rock-n-Roll"); styles[Math.floor((styles.length - 1) / 2)] = "Classics"; alert( styles.shift() ); styles.unshift("Rap", "Reggae"); ``` ================================================ FILE: 1-js/05-data-types/04-array/2-create-array/task.md ================================================ importance: 5 --- # Opérations de tableaux Essayons 5 opérations de tableau. 1. Créez un tableau `styles` avec les éléments "Jazz" et "Blues". 2. Ajoutez "Rock-n-Roll" à la fin. 3. Remplacez la valeur au milieu par "Classiques". Votre code pour trouver la valeur moyenne devrait fonctionner pour tous les tableaux de longueur impaire. 4. Extrayez la première valeur du tableau et affichez-la. 5. Ajoutez `Rap` et `Reggae` au tableau. Le processus du tableau : ```js no-beautify Jazz, Blues Jazz, Blues, Rock-n-Roll Jazz, Classics, Rock-n-Roll Classics, Rock-n-Roll Rap, Reggae, Classics, Rock-n-Roll ``` ================================================ FILE: 1-js/05-data-types/04-array/3-call-array-this/solution.md ================================================ L'appel de `arr[2]()` est syntaxiquement le bon vieux `obj[method]()`, dans le rôle de `obj` on a `arr`, et dans le rôle de `method` on a `2`. Nous avons donc un appel de la fonction `arr[2]` comme méthode d'objet. Naturellement, il reçoit `this` en référençant l'objet `arr` et sort le tableau : ```js run let arr = ["a", "b"]; arr.push(function() { alert( this ); }) arr[2](); // a,b,function(){...} ``` Le tableau a 3 valeurs. Il en avait initialement deux, plus la fonction. ================================================ FILE: 1-js/05-data-types/04-array/3-call-array-this/task.md ================================================ importance: 5 --- # Appel dans un contexte de tableau Quel est le résultat ? Et pourquoi ? ```js let arr = ["a", "b"]; arr.push(function() { alert( this ); }); arr[2](); // ? ``` ================================================ FILE: 1-js/05-data-types/04-array/5-array-input-sum/solution.md ================================================ Veuillez noter le détail subtile mais important de la solution. Nous ne convertissons pas instantanément `value` en nombre après le `prompt`, parce qu'après `value = +value` nous ne pourrions pas distinguer une chaîne vide (signe d’arrêt) du zéro (nombre valide). Nous le faisons plus tard à la place. ```js run demo function sumInput() { let numbers = []; while (true) { let value = prompt("A number please?", 0); // devrions-nous annuler ? if (value === "" || value === null || !isFinite(value)) break; numbers.push(+value); } let sum = 0; for (let number of numbers) { sum += number; } return sum; } alert( sumInput() ); ``` ================================================ FILE: 1-js/05-data-types/04-array/5-array-input-sum/task.md ================================================ importance: 4 --- # Somme des nombres saisis Écrivez la fonction `sumInput()` qui : - Demande à l'utilisateur des valeurs utilisant `prompt` et stocke les valeurs dans le tableau. - Finit de demander lorsque l'utilisateur entre une valeur non numérique, une chaîne vide ou appuie sur "Annuler". - Calcule et retourne la somme des éléments du tableau. P.S. Un zéro `0` est un nombre valide, donc s'il vous plaît n'arrêtez pas l'entrée sur zéro. [demo] ================================================ FILE: 1-js/05-data-types/04-array/article.md ================================================ # Arrays Les objets vous permettent de stocker des collections de valeurs à clé. C'est très bien. Mais assez souvent, nous trouvons qu'il nous faut une *collection ordonnée*, où nous avons un 1er, un 2ème, un 3ème élément, etc. Par exemple, nous avons besoin de cela pour stocker une liste de quelque chose : utilisateurs, trucs, éléments HTML, etc. Il n'est pas pratique d'utiliser un objet ici, car il ne fournit aucune méthode pour gérer l'ordre des éléments. Nous ne pouvons pas insérer une nouvelle propriété "entre" celles existantes. Les objets ne sont tout simplement pas destinés à un tel usage. Il existe une structure de données spéciale appelée `Array` (tableau), pour stocker les collections ordonnées. ## Déclaration Il existe deux syntaxes pour créer un tableau vide : ```js let arr = new Array(); let arr = []; ``` La plupart du temps c'est la deuxième syntaxe qui est utilisée. Nous pouvons fournir des éléments initiaux entre parenthèses : ```js let fruits = ["Apple", "Orange", "Plum"]; ``` Les éléments de tableau sont numérotés en commençant par zéro. On peut obtenir un élément par son numéro grace aux crochets : ```js run let fruits = ["Apple", "Orange", "Plum"]; alert( fruits[0] ); // Apple alert( fruits[1] ); // Orange alert( fruits[2] ); // Plum ``` Nous pouvons remplacer un élément : ```js fruits[2] = 'Pear'; // maintenant ["Apple", "Orange", "Pear"] ``` ...Ou en ajouter un nouveau au tableau : ```js fruits[3] = 'Lemon'; // maintenant ["Apple", "Orange", "Pear", "Lemon"] ``` Le nombre total d'éléments dans le tableau est sa `length` (longueur) : ```js run let fruits = ["Apple", "Orange", "Plum"]; alert( fruits.length ); // 3 ``` Nous pouvons également utiliser un `alert` pour afficher l'ensemble du tableau : ```js run let fruits = ["Apple", "Orange", "Plum"]; alert( fruits ); // Apple,Orange,Plum ``` Un tableau peut stocker des éléments de tout type. Par exemple : ```js run no-beautify // mélange de valeurs let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ]; // récupère l'objet à l'index 1 et montre ensuite son nom alert( arr[1].name ); // John // affiche la fonction à l'index 3 et l'exécute la arr[3](); // hello ``` ````smart header="Trailing comma (virgule de fin)" Un tableau, comme pour un objet, peut se terminer par une virgule : ```js let fruits = [ "Apple", "Orange", "Plum"*!*,*/!* ]; ``` Le style "virgule de fin" facilite l'insertion et la suppression d'éléments, car toutes les lignes se ressemblent. ```` ## Récupérer les derniers éléments avec "at" [recent browser="new"] Disons que nous voulons le dernier élément du tableau. Certains langages de programmation permettent l'utilisation d'index négatifs pour ça, comme `fruits[-1]`. Tandis qu'en JavaScript ça ne fonctionnera pas. Le résultat sera `undefined`, parce que l'index dans les crochets est traité littéralement. Nous pouvons calculer explicitement l'index du dernier élément et donc y accéder: `fruits[fruits.length - 1]`. ```js run let fruits = ["Apple", "Orange", "Plum"]; alert( fruits[fruits.length-1] ); // Plum ``` Un peu lourd, n'est-ce pas ? Nous devons écrire le même nom de variable deux fois. Heureusement, il y a une syntaxe plus courte : `fruits.at(-1)` : ```js run let fruits = ["Apple", "Orange", "Plum"]; // Identique à fruits[fruits.length-1] alert( fruits.at(-1) ); // Plum ``` En d'autres termes, `arr.at(i)`: - est exactement identique à `arr[i]`, si `i >= 0`. - pour les valeurs négatives de `i`, ça recule depuis la fin du tableau. ## Les méthodes pop/push, shift/unshift Une [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) (file d'attente) est l'une des utilisations les plus courantes pour les tableaux. En informatique, cela signifie une collection ordonnée d’éléments qui supporte deux opérations : - `push` ajoute un élément à la fin. - `shift` enlève un élément depuis le début, en faisant avancer la file d'attente, de sorte que le deuxième élément devienne le premier. ![](queue.svg) Les tableaux prennent en charge les deux opérations. En pratique, nous en avons besoin très souvent. Par exemple, une file d'attente de messages devant être affichés à l'écran. Il y a un autre cas d'utilisation pour les tableaux -- la structure de données nommée [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)). Il supporte deux opérations : - `push` ajoute un élément à la fin. - `pop` enlève un élément de la fin. Ainsi, de nouveaux éléments sont ajoutés ou enlevés toujours à partir de la "fin". Un stack (pile) est généralement illustrée par un jeu de cartes. De nouvelles cartes sont ajoutées ou enlevées par le haut : ![](stack.svg) Pour les stacks (piles), le dernier élément envoyé est reçu en premier, c'est le principe LIFO (Last-In-First-Out, dernier entré, premier sorti). Pour les files d'attente, nous avons FIFO (First-In-First-Out, premier entré, premier sorti). Les tableaux en JavaScript peuvent fonctionner à la fois en queue et en stack. Ils vous permettent d'ajouter ou supprimer des éléments à la fois par le début ou par la fin. En informatique, la structure de données qui permet cela s'appelle [deque](https://en.wikipedia.org/wiki/Double-ended_queue). **Méthodes qui fonctionnent avec la fin du tableau :** `pop` : Extrait le dernier élément du tableau et le renvoie : ```js run let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.pop() ); // supprime "Pear" et l'alerte alert( fruits ); // Apple, Orange ``` Les deux méthodes `fruits.pop()` et `fruits.at(-1)` renvoient le dernier élément du tableau, mais `fruits.pop()` modifie également le tableau en supprimant l'élément. `push` : Ajoute l'élément à la fin du tableau : ```js run let fruits = ["Apple", "Orange"]; fruits.push("Pear"); alert( fruits ); // Apple, Orange, Pear ``` L'appel de `fruits.push(...)` est égal à `fruits[fruits.length] = ...`. **Méthodes qui fonctionnent avec le début du tableau :** `shift` : Extrait le premier élément du tableau et le renvoie : ```js run let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.shift() ); // supprime "Apple" et l'alerte alert( fruits ); // Orange, Pear ``` `unshift` : Ajoute l'élément au début du tableau : ```js run let fruits = ["Orange", "Pear"]; fruits.unshift("Apple"); alert( fruits ); // Apple, Orange, Pear ``` Les méthodes `push` et `unshift` peuvent ajouter plusieurs éléments à la fois : ```js run let fruits = ["Apple"]; fruits.push("Orange", "Peach"); fruits.unshift("Pineapple", "Lemon"); // ["Pineapple", "Lemon", "Apple", "Orange", "Peach"] alert( fruits ); ``` ## Les internes Un tableau est un type d'objet spécial. Les crochets utilisés pour accéder à la propriété `arr[0]` proviennent en fait de la syntaxe de l'objet. C'est essentiellement la même chose que `obj[key]`, où `arr` est l'objet, tandis que les nombres sont utilisés comme clés. Ils étendent les objets en fournissant des méthodes spéciales pour travailler avec des collections ordonnées de données ainsi que la propriété `length`. Mais au fond c'est toujours un objet. N'oubliez pas qu'il n'y a que huit types de base en JavaScript (voir le chapitre [Les types de données](info:types) pour plus d'infos). `Array` est un objet et se comporte donc comme un objet. Par exemple, il est copié par référence : ```js run let fruits = ["Banana"] let arr = fruits; // copier par référence (deux variables font référence au même tableau) alert( arr === fruits ); // true arr.push("Pear"); // modifie le tableau par référence alert( fruits ); // Banana, Pear - 2 items maintenant ``` ...Mais ce qui rend les tableaux vraiment spéciaux, c'est leur représentation interne. Le moteur tente de stocker ses éléments dans une zone de mémoire contiguë, l'un après l'autre, exactement comme le montrent les illustrations de ce chapitre. Il existe également d'autres optimisations permettant de faire fonctionner les tableaux très rapidement. Mais ils se cassent tous si nous arrêtons de travailler avec un tableau comme avec une "collection ordonnée" et commençons à le travailler comme s'il s'agissait d'un objet normal. Par exemple, techniquement, nous pouvons faire ceci : ```js let fruits = []; // créer un tableau fruits[99999] = 5; // assigne une propriété avec un index beaucoup plus grand que sa longueur fruits.age = 25; // créer une propriété avec un nom arbitraire ``` C'est possible, car les tableaux sont des objets à leur base. Nous pouvons leur ajouter des propriétés. Mais le moteur verra que nous travaillons avec le tableau comme avec un objet normal. Les optimisations spécifiques à un tableau ne sont pas adaptées à ce type de situation et seront désactivées. Leurs avantages disparaissent. Les moyens de casser un tableau : - Ajouter une propriété non numérique comme `arr.test = 5`. - Faire des trous, comme ajouter `arr[0]` et ensuite `arr[1000]` (et rien entre eux). - Remplire le tableau dans l'ordre inverse, comme `arr[1000]`, `arr[999]` etc. Veuillez considérer les tableaux comme des structures spéciales pour travailler avec les *données ordonées*. Ils fournissent des méthodes spéciales pour cela. Les tableaux sont soigneusement réglés dans les moteurs JavaScript pour fonctionner avec des données ordonnées contiguës, veuillez les utiliser de cette manière. Et si vous avez besoin de clés arbitraires, il y a de fortes chances pour que vous ayez réellement besoin d'un objet régulier `{}`. ## Performance Les méthodes `push`/`pop` vont vite, alors que `shift`/`unshift` sont lentes. ![](array-speed.svg) Pourquoi est-il plus rapide de travailler avec la fin d'un tableau qu'avec son début ? Voyons ce qui se passe pendant l'exécution : ```js fruits.shift(); // prends 1 élément du début ``` Il ne suffit pas de prendre l'élément avec le nombre `0`. D'autres éléments doivent également être renumérotés. L'opération `shift` doit faire 3 choses : 1. Supprimer l'élément avec l'index `0`. 2. Déplacer tous les éléments à gauche, les renuméroter de l'index `1` à `0`, de`2` à `1`, etc. 3. Mettre à jour la propriété `length`. ![](array-shift.svg) **Plus il y a d'éléments dans le tableau, plus il y faut de temps pour les déplacer, plus il y a d'opérations en mémoire.** La même chose se produit avec `unshift`. Pour ajouter un élément au début du tableau, nous devons d’abord déplacer les éléments existants vers la droite, en augmentant leur index. Et qu’en est-il avec `push`/`pop` ? Ils n'ont pas besoin de déplacer quoi que ce soit. Pour extraire un élément de la fin, la méthode `pop` nettoie l'index et raccourcit `length`. Les actions pour l'opération `pop` : ```js fruits.pop(); // enleve 1 élément de la fin ``` ![](array-pop.svg) **La méthode `pop` n'a pas besoin de déplacer quoi que ce soit, car les autres éléments conservent leurs index. C'est pourquoi c'est extrêmement rapide.** La même chose avec la méthode `push`. ## Boucles L'une des méthodes les plus anciennes pour cycler des éléments de tableau est la boucle `for` sur les index : ```js run let arr = ["Apple", "Orange", "Pear"]; *!* for (let i = 0; i < arr.length; i++) { */!* alert( arr[i] ); } ``` Mais pour les tableaux, il existe une autre forme de boucle, `for..of` : ```js run let fruits = ["Apple", "Orange", "Plum"]; // itère sur des éléments de tableau for (let fruit of fruits) { alert( fruit ); } ``` Le `for..of` ne donne pas accès au numéro de l'élément actuel, mais à sa valeur, mais dans la plupart des cas, cela suffit. Et c'est plus court. Techniquement, comme les tableaux sont des objets, il est également possible d'utiliser `for..in` : ```js run let arr = ["Apple", "Orange", "Pear"]; *!* for (let key in arr) { */!* alert( arr[key] ); // Apple, Orange, Pear } ``` Mais c'est en fait une mauvaise idée. Il y a des problèmes potentiels avec cela : 1. La boucle `for..in` itère sur *toutes les propriétés*, pas seulement les propriétés numériques. Il existe des objets dits "array-like" dans le navigateur et dans d'autres environnements, qui *ressemblent à des tableaux*. C'est-à-dire qu'ils ont les propriétés `length` et index, mais ils peuvent également avoir d'autres propriétés et méthodes non numériques, dont nous n'avons généralement pas besoin. La boucle `for..in` les listera cependant. Donc, si nous devons travailler avec des objets de type tableau, ces propriétés "supplémentaires" peuvent devenir un problème. 2. La boucle `for..in` est optimisée pour les objets génériques, pas pour les tableaux, elle est 10-100 fois plus lente. Bien sûr, c'est encore très rapide. L'accélération peut n'importer que dans les goulots d'étranglement ou sembler hors de propos. Mais il faut quand même être conscient de la différence. En règle générale, nous ne devrions pas utiliser `for..in` pour les tableaux. ## Un mot à propos de "length" La propriété `length` est automatiquement mise à jour lorsque nous modifions le tableau. Pour être précis, il ne s'agit pas du nombre de valeurs du tableau, mais du plus grand index numérique plus un. Par exemple, un seul élément avec un grand index donne une grande longueur : ```js run let fruits = []; fruits[123] = "Apple"; alert( fruits.length ); // 124 ``` Notez que nous n'utilisons généralement pas de tableaux de ce type. Une autre chose intéressante à propos de la propriété `length` est qu'elle est accessible en écriture. Si nous l'augmentons manuellement, rien d'intéressant ne se produit. Mais si nous le diminuons, le tableau est tronqué. Le processus est irréversible, voici l'exemple : ```js run let arr = [1, 2, 3, 4, 5]; arr.length = 2; // tronque à 2 éléments alert( arr ); // [1, 2] arr.length = 5; // retourne la length d'origine alert( arr[3] ); // undefined: les valeurs ne reviennent pas ``` Ainsi, le moyen le plus simple pour effacer le tableau est `arr.length = 0;`. ## new Array() [#new-array] Il y a une syntaxe supplémentaire pour créer un tableau : ```js let arr = *!*new Array*/!*("Apple", "Pear", "etc"); ``` Il est rarement utilisé, car les crochets `[]` sont plus courts. En outre, il comporte une caractéristique délicate. Si `new Array` est appelé avec un seul argument qui est un nombre, il crée un tableau *sans éléments, mais avec la longueur donnée*. Voyons comment on peut se tirer une balle dans le pied : ```js run let arr = new Array(2); // va-t-il créer un tableau de [2] ? alert( arr[0] ); // undefined! pas d'éléments. alert( arr.length ); // length 2 ``` Pour éviter de telles surprises, nous utilisons généralement des crochets, sauf si nous savons vraiment ce que nous faisons. ## Tableaux multidimensionnels Les tableaux peuvent avoir des éléments qui sont aussi des tableaux. On peut l'utiliser pour des tableaux multidimensionnels, pour stocker des matrices : ```js run let matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; alert( matrix[1][1] ); // 5, l'élément central ``` ## toString Les tableaux ont leur propre implémentation de la méthode `toString` qui renvoie une liste d'éléments séparés par des virgules. Par exemple : ```js run let arr = [1, 2, 3]; alert( arr ); // 1,2,3 alert( String(arr) === '1,2,3' ); // true ``` Aussi, essayons ceci : ```js run alert( [] + 1 ); // "1" alert( [1] + 1 ); // "11" alert( [1,2] + 1 ); // "1,21" ``` Les tableaux n'ont pas de `Symbol.toPrimitive`, ni de `valueOf` viable, ils implémentent uniquement la conversion `toString`, donc ici `[]` devient une chaîne vide, `[1]` devient `"1"` et `[1,2]` devient `"1,2"`. Lorsque l'opérateur binaire plus `+` ajoute quelque chose à une chaîne, il la convertit également en chaîne, de sorte que l'étape suivante se présente comme suit : ```js run alert( "" + 1 ); // "1" alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` ## Ne comparez pas les tableaux avec == Les tableaux en JavaScript, contrairement à certains autres langages de programmation, ne doivent pas être comparés avec l'opérateur `==`. Cet opérateur n'a pas de traitement spécial pour les tableaux, il fonctionne avec eux comme avec n'importe quel objet. Rappelons les règles : - Deux objets sont égaux `==` uniquement s'ils font référence au même objet. - Si l'un des arguments de `==` est un objet, et l'autre est une primitive, alors l'objet est converti en primitif, comme expliqué dans le chapitre . - ...À l'exception de `null` et `undefined` qui s'égalent `==` l'un l'autre et rien d'autre. La comparaison stricte `===` est encore plus simple, car elle ne convertit pas les types. Donc, si nous comparons des tableaux avec `==`, ils ne sont jamais les mêmes, sauf si nous comparons deux variables qui référencent exactement le même tableau. Par exemple : ```js run alert( [] == [] ); // false alert( [0] == [0] ); // false ``` Ces tableaux sont des objets techniquement différents. Donc, ils ne sont pas égaux. L'opérateur `==` ne fait pas de comparaison élément par élément. La comparaison avec les primitives peut également donner des résultats apparemment étranges : ```js run alert( 0 == [] ); // true alert('0' == [] ); // false ``` Ici, dans les deux cas, nous comparons une primitive à un objet tableau. Ainsi, le tableau `[]` est converti en primitive à des fins de comparaison et devient une chaîne vide `''`. Ensuite, le processus de comparaison se poursuit avec les primitives, comme décrit dans le chapitre : ```js run // après que [] soit converti vers '' alert( 0 == '' ); // true, car '' est converti en nombre 0 alert('0' == '' ); // false, pas de conversion de type, différentes chaînes de caractères ``` Alors, comment comparer des tableaux ? C'est simple, n'utilisez pas l'opérateur `==`. Au lieu de cela, comparez-les élément par élément dans une boucle ou en utilisant les méthodes d'itération expliquées dans le chapitre suivant. ## Résumé Array est un type d’objet spécial, adapté au stockage et à la gestion des éléments de données ordonnées. - La déclaration : ```js // crochets (habituel) let arr = [item1, item2...]; // new Array (exceptionnellement rare) let arr = new Array(item1, item2...); ``` L'appel de `new Array(number)` crée un tableau de longueur donnée, mais sans éléments. - La propriété `length` est la longueur du tableau ou, plus précisément, son dernier index numérique plus un. Il est auto-ajusté par les méthodes de tableau. - Si nous raccourcissons `length` manuellement, le tableau est tronqué. Obtenir les éléments : - nous pouvons obtenir un élément par son index, comme `arr[0]` - nous pouvons également utiliser la méthode `at(i)` qui autorise les index négatifs. Pour les valeurs négatives de `i`, il recule à partir de la fin du tableau. Si `i >= 0`, cela fonctionne comme `arr[i]`. Nous pouvons utiliser un tableau comme deque avec les opérations suivantes : - `push(...items)` ajoute `items` à la fin. - `pop()` supprime l'élément de la fin et le renvoie. - `shift()` supprime l'élément du début et le renvoie. - `unshift(... items)` ajoute des `items` au début. Pour boucler sur les éléments du tableau : - `for (let i = 0; i `, `<` et autres), car ils n'ont pas de traitement spécial pour les tableaux. Ils les traitent comme n'importe quel objet, et ce n'est pas ce que nous voulons habituellement. A la place, vous pouvez utiliser la boucle `for..of` pour comparer les tableaux élément par élément. Nous continuerons avec les tableaux et étudierons d'autres méthodes pour ajouter, supprimer, extraire des éléments et trier des tableaux dans le prochain chapitre . ================================================ FILE: 1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js ================================================ function camelize(str) { return str .split('-') // divise 'my-long-word' en tableau ['my', 'long', 'word'] .map( // capitalise les premières lettres de tous les éléments du tableau sauf le premier // convertit ['my', 'long', 'word'] en ['my', 'Long', 'Word'] (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1) ) .join(''); // rejoint ['my', 'Long', 'Word'] en -> myLongWord } ================================================ FILE: 1-js/05-data-types/05-array-methods/1-camelcase/_js.view/test.js ================================================ describe("camelize", function() { it("leaves an empty line as is", function() { assert.equal(camelize(""), ""); }); it("turns background-color into backgroundColor", function() { assert.equal(camelize("background-color"), "backgroundColor"); }); it("turns list-style-image into listStyleImage", function() { assert.equal(camelize("list-style-image"), "listStyleImage"); }); it("turns -webkit-transition into WebkitTransition", function() { assert.equal(camelize("-webkit-transition"), "WebkitTransition"); }); }); ================================================ FILE: 1-js/05-data-types/05-array-methods/1-camelcase/solution.md ================================================ ================================================ FILE: 1-js/05-data-types/05-array-methods/1-camelcase/task.md ================================================ importance: 5 --- # Traduit border-left-width en borderLeftWidth Ecrivez la fonction `camelize(str)` qui change les mots séparés par des tirets comme "my-short-string" en camel-cased "myShortString". La fonction doit donc supprimer tous les tirets et mettre en majuscule la première lettre de chaque mot à partir du deuxième mot. Exemples : ```js camelize("background-color") == 'backgroundColor'; camelize("list-style-image") == 'listStyleImage'; camelize("-webkit-transition") == 'WebkitTransition'; ``` P.S. Astuce : utilisez `split` pour scinder la chaîne dans un tableau, transformer la et ensuite utilisez `join`. ================================================ FILE: 1-js/05-data-types/05-array-methods/10-average-age/solution.md ================================================ ```js run function getAverageAge(users) { return users.reduce((prev, user) => prev + user.age, 0) / users.length; } let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 29 }; let arr = [ john, pete, mary ]; alert( getAverageAge(arr) ); // 28 ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/10-average-age/task.md ================================================ importance: 4 --- # Obtenir l'âge moyen Ecrivez la fonction `getAverageAge(users)` qui obtient un tableau d'objets avec la propriété `age` et qui ensuite retourne l'age moyen. La formule pour la moyenne est `(age1 + age2 + ... + ageN) / N`. Par exemple: ```js no-beautify let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 29 }; let arr = [ john, pete, mary ]; alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28 ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/11-array-unique/_js.view/solution.js ================================================ function unique(arr) { let result = []; for (let str of arr) { if (!result.includes(str)) { result.push(str); } } return result; } ================================================ FILE: 1-js/05-data-types/05-array-methods/11-array-unique/_js.view/test.js ================================================ describe("unique", function() { it("removes non-unique elements", function() { let strings = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; assert.deepEqual(unique(strings), ["Hare", "Krishna", ":-O"]); }); it("does not change the source array", function() { let strings = ["Krishna", "Krishna", "Hare", "Hare"]; unique(strings); assert.deepEqual(strings, ["Krishna", "Krishna", "Hare", "Hare"]); }); }); ================================================ FILE: 1-js/05-data-types/05-array-methods/11-array-unique/solution.md ================================================ Parcourons les éléments du tableau: - Pour chaque élément, nous vérifierons si le tableau résultant contient déjà cet élément. - S'il en est ainsi, alors ignorez-le, sinon ajoutez aux résultats. ```js run function unique(arr) { let result = []; for (let str of arr) { if (!result.includes(str)) { result.push(str); } } return result; } let strings = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; alert( unique(strings) ); // Hare, Krishna, :-O ``` Le code fonctionne, mais il comporte un problème de performances potentiel. La méthode `result.includes(str)` parcourt en interne le tableau `result` et compare chaque élément à `str` pour trouver la correspondance. Donc, s'il y a `100` éléments dans `result` et que personne ne correspond à `str`, alors il parcourra tout le `result` et fera exactement les `100` comparaisons. Et si `result` est grand, exemple `10000`, alors il y aura des `10000` comparaisons . Ce n'est pas un problème en soi, parce que les moteurs JavaScript sont très rapides, alors parcourir un tableau de `10000` éléments est une question de microsecondes. Mais nous faisons ce test pour chaque élément de `arr`, dans la boucle `for`. Donc, si `arr.length` vaut `10000`, nous aurons quelque chose comme `10000*10000` = 100 millions de comparaisons. C'est beaucoup. La solution n’est donc valable que pour les petits tableaux. Plus loin dans le chapitre , nous verrons comment l'optimiser. ================================================ FILE: 1-js/05-data-types/05-array-methods/11-array-unique/task.md ================================================ importance: 4 --- # Filtrer les membres uniques du tableau `arr` est un tableau. Créez une fonction `unique(arr)` qui devrait renvoyer un tableau avec des éléments uniques de `arr`. Par exemple: ```js function unique(arr) { /* votre code */ } let strings = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; alert( unique(strings) ); // Hare, Krishna, :-O ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js ================================================ function groupById(array) { return array.reduce((obj, value) => { obj[value.id] = value; return obj; }, {}) } ================================================ FILE: 1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js ================================================ describe("groupById", function() { it("creates an object grouped by id", function() { let users = [ {id: 'john', name: "John Smith", age: 20}, {id: 'ann', name: "Ann Smith", age: 24}, {id: 'pete', name: "Pete Peterson", age: 31}, ]; assert.deepEqual(groupById(users), { john: {id: 'john', name: "John Smith", age: 20}, ann: {id: 'ann', name: "Ann Smith", age: 24}, pete: {id: 'pete', name: "Pete Peterson", age: 31}, }); }); it("works with an empty array", function() { users = []; assert.deepEqual(groupById(users), {}); }); }); ================================================ FILE: 1-js/05-data-types/05-array-methods/12-reduce-object/solution.md ================================================ ================================================ FILE: 1-js/05-data-types/05-array-methods/12-reduce-object/task.md ================================================ importance: 4 --- # Create keyed object from array Let's say we received an array of users in the form `{id:..., name:..., age:... }`. Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values. For example: ```js let users = [ {id: 'john', name: "John Smith", age: 20}, {id: 'ann', name: "Ann Smith", age: 24}, {id: 'pete', name: "Pete Peterson", age: 31}, ]; let usersById = groupById(users); /* // after the call we should have: usersById = { john: {id: 'john', name: "John Smith", age: 20}, ann: {id: 'ann', name: "Ann Smith", age: 24}, pete: {id: 'pete', name: "Pete Peterson", age: 31}, } */ ``` Such function is really handy when working with server data. In this task we assume that `id` is unique. There may be no two array items with the same `id`. Please use array `.reduce` method in the solution. ================================================ FILE: 1-js/05-data-types/05-array-methods/2-filter-range/_js.view/solution.js ================================================ function filterRange(arr, a, b) { // added brackets around the expression for better readability return arr.filter(item => (a <= item && item <= b)); } ================================================ FILE: 1-js/05-data-types/05-array-methods/2-filter-range/_js.view/test.js ================================================ describe("filterRange", function() { it("returns the filtered values", function() { let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); assert.deepEqual(filtered, [3, 1]); }); it("doesn't change the array", function() { let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); assert.deepEqual(arr, [5,3,8,1]); }); }); ================================================ FILE: 1-js/05-data-types/05-array-methods/2-filter-range/solution.md ================================================ ```js run demo function filterRange(arr, a, b) { // ajout de crochets autour de l'expression pour une meilleure lisibilité return arr.filter(item => (a <= item && item <= b)); } let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); alert( filtered ); // 3,1 (valeur correspondate) alert( arr ); // 5,3,8,1 (non modifié) ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/2-filter-range/task.md ================================================ importance: 4 --- # Filter range Ecrivez une fonction `filterRange(arr, a, b)` qui obtient un tableau `arr`, recherche les éléments avec des valeurs supérieures ou égales à `a` et inférieures ou égales à `b` et retourne un résultat sous forme de tableau. La fonction ne doit pas modifier le tableau. Elle doit juste retourner le nouveau tableau. Par exemple : ```js let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); alert( filtered ); // 3,1 (valeurs correspondantes) alert( arr ); // 5,3,8,1 (non modifié) ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js ================================================ function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { let val = arr[i]; // enleve si en dehors de l'intervalle if (val < a || val > b) { arr.splice(i, 1); i--; } } } ================================================ FILE: 1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js ================================================ describe("filterRangeInPlace", function() { it("returns the filtered values", function() { let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 2, 5); assert.deepEqual(arr, [5, 3]); }); it("doesn't return anything", function() { assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4)); }); }); ================================================ FILE: 1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md ================================================ ```js run demo function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { let val = arr[i]; // enleve si en dehors de l'intervalle if (val < a || val > b) { arr.splice(i, 1); i--; } } } let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // supprime les nombres sauf de 1 à 4 alert( arr ); // [3, 1] ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md ================================================ importance: 4 --- # Filter range "in place" Ecrivez une fonction `filterRangeInPlace(arr, a, b)` qui obtient un tableau `arr` et en supprime toutes les valeurs, sauf celles comprises entre `a` et `b`. Le test est : `a ≤ arr[i] ≤ b`. La fonction doit juste modifier que le tableau. Elle ne doit rien retourner. Par exemple : ```js let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // supprime les nombres qui ne sont pas entre 1 et 4 alert( arr ); // [3, 1] ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/4-sort-back/solution.md ================================================ ```js run let arr = [5, 2, 1, -10, 8]; arr.sort((a, b) => b - a); alert( arr ); ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/4-sort-back/task.md ================================================ importance: 4 --- # Trier par ordre décroissant ```js let arr = [5, 2, 1, -10, 8]; // ... votre code pour le trier par ordre décroissant alert( arr ); // 8, 5, 2, 1, -10 ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/5-copy-sort-array/solution.md ================================================ Nous pouvons utiliser `slice()` pour faire une copie et exécuter le tri sur celle-ci : ```js run function copySorted(arr) { return arr.slice().sort(); } let arr = ["HTML", "JavaScript", "CSS"]; *!* let sorted = copySorted(arr); */!* alert( sorted ); alert( arr ); ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md ================================================ importance: 5 --- # Copier et trier le tableau Nous avons un tableau de chaînes `arr`. Nous aimerions en avoir une copie triée, mais sans modifier `arr`. Créez une fonction `copySorted(arr)` qui renvoie une copie triée. ```js let arr = ["HTML", "JavaScript", "CSS"]; let sorted = copySorted(arr); alert( sorted ); // CSS, HTML, JavaScript alert( arr ); // HTML, JavaScript, CSS (aucune modification) ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/6-array-get-names/solution.md ================================================ ```js run let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let users = [ john, pete, mary ]; let names = users.map(item => item.name); alert( names ); // John, Pete, Mary ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/6-array-get-names/task.md ================================================ importance: 5 --- # Map en noms Vous avez un tableau d'objets `user`, chacun ayant `user.name`. Écrivez le code qui le convertit en un tableau de noms. Par exemple : ```js no-beautify let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let users = [ john, petemary ]; let names = /* ... votre code */ alert( names ); // John, Pete, Mary ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js ================================================ function Calculator() { this.methods = { "-": (a, b) => a - b, "+": (a, b) => a + b }; this.calculate = function(str) { let split = str.split(' '), a = +split[0], op = split[1], b = +split[2]; if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } return this.methods[op](a, b); }; this.addMethod = function(name, func) { this.methods[name] = func; }; } ================================================ FILE: 1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/test.js ================================================ describe("Calculator", function() { let calculator; before(function() { calculator = new Calculator; }); it("calculate(12 + 34) = 46", function() { assert.equal(calculator.calculate("12 + 34"), 46); }); it("calculate(34 - 12) = 22", function() { assert.equal(calculator.calculate("34 - 12"), 22); }); it("add multiplication: calculate(2 * 3) = 6", function() { calculator.addMethod("*", (a, b) => a * b); assert.equal(calculator.calculate("2 * 3"), 6); }); it("add power: calculate(2 ** 3) = 8", function() { calculator.addMethod("**", (a, b) => a ** b); assert.equal(calculator.calculate("2 ** 3"), 8); }); }); ================================================ FILE: 1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md ================================================ - Please note how methods are stored. They are simply added to `this.methods` property. - All tests and numeric conversions are done in the `calculate` method. In future it may be extended to support more complex expressions. ================================================ FILE: 1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md ================================================ importance: 5 --- # Create an extendable calculator Create a constructor function `Calculator` that creates "extendable" calculator objects. The task consists of two parts. 1. First, implement the method `calculate(str)` that takes a string like `"1 + 2"` in the format "NUMBER operator NUMBER" (space-delimited) and returns the result. Should understand plus `+` and minus `-`. Usage example: ```js let calc = new Calculator; alert( calc.calculate("3 + 7") ); // 10 ``` 2. Then add the method `addMethod(name, func)` that teaches the calculator a new operation. It takes the operator `name` and the two-argument function `func(a,b)` that implements it. For instance, let's add the multiplication `*`, division `/` and power `**`: ```js let powerCalc = new Calculator; powerCalc.addMethod("*", (a, b) => a * b); powerCalc.addMethod("/", (a, b) => a / b); powerCalc.addMethod("**", (a, b) => a ** b); let result = powerCalc.calculate("2 ** 3"); alert( result ); // 8 ``` - No parentheses or complex expressions in this task. - The numbers and the operator are delimited with exactly one space. - There may be error handling if you'd like to add it. ================================================ FILE: 1-js/05-data-types/05-array-methods/7-map-objects/solution.md ================================================ ```js run no-beautify let john = { name: "John", surname: "Smith", id: 1 }; let pete = { name: "Pete", surname: "Hunt", id: 2 }; let mary = { name: "Mary", surname: "Key", id: 3 }; let users = [ john, pete, mary ]; *!* let usersMapped = users.map(user => ({ fullName: `${user.name} ${user.surname}`, id: user.id })); */!* /* usersMapped = [ { fullName: "John Smith", id: 1 }, { fullName: "Pete Hunt", id: 2 }, { fullName: "Mary Key", id: 3 } ] */ alert( usersMapped[0].id ); // 1 alert( usersMapped[0].fullName ); // John Smith ``` Please note that in the arrow functions we need to use additional brackets. We can't write like this: ```js let usersMapped = users.map(user => *!*{*/!* fullName: `${user.name} ${user.surname}`, id: user.id }); ``` As we remember, there are two arrow functions: without body `value => expr` and with body `value => {...}`. Here JavaScript would treat `{` as the start of function body, not the start of the object. The workaround is to wrap them in the "normal" brackets: ```js let usersMapped = users.map(user => *!*({*/!* fullName: `${user.name} ${user.surname}`, id: user.id })); ``` Now fine. ================================================ FILE: 1-js/05-data-types/05-array-methods/7-map-objects/task.md ================================================ importance: 5 --- # Map en objets Vous avez un tableau d'objets `user`, chacun ayant `name`, `surname` et `id`. Ecrivez le code pour créer un autre tableau à partir de celui-ci, avec les objets `id` et `fullName`, où `fullName` est généré à partir de `name` et `surname`. Par exemple: ```js no-beautify let john = { name: "John", surname: "Smith", id: 1 }; let pete = { name: "Pete", surname: "Hunt", id: 2 }; let mary = { name: "Mary", surname: "Key", id: 3 }; let users = [ john, pete, mary ]; *!* let usersMapped = /* ... votre code ... */ */!* /* usersMapped = [ { fullName: "John Smith", id: 1 }, { fullName: "Pete Hunt", id: 2 }, { fullName: "Mary Key", id: 3 } ] */ alert( usersMapped[0].id ) // 1 alert( usersMapped[0].fullName ) // John Smith ``` Donc, en réalité, vous devez mapper un tableau d'objets sur un autre. Essayez d'utiliser `=>` ici. Il y a une petite prise. ================================================ FILE: 1-js/05-data-types/05-array-methods/8-sort-objects/solution.md ================================================ ```js run no-beautify function sortByAge(arr) { arr.sort((a, b) => a.age - b.age); } let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let arr = [ john, pete, mary ]; sortByName(arr); // maitenant trié il est: [john, mary, pete] alert(arr[0].name); // John alert(arr[1].name); // Mary alert(arr[2].name); // Pete ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/8-sort-objects/task.md ================================================ importance: 5 --- # Trier les objets Ecrivez la fonction `sortByName(users)` qui obtient un tableau d'objets avec la propriété `name` et le trie. Par exemple: ```js no-beautify let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let arr = [ john, pete, mary ]; sortByName(arr); // maintenant: [john, mary, pete] alert(arr[0].name); // John alert(arr[1].name); // Mary alert(arr[2].name); // Pete ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/9-shuffle/solution.md ================================================ The simple solution could be: ```js run *!* function shuffle(array) { array.sort(() => Math.random() - 0.5); } */!* let arr = [1, 2, 3]; shuffle(arr); alert(arr); ``` That somewhat works, because `Math.random() - 0.5` is a random number that may be positive or negative, so the sorting function reorders elements randomly. But because the sorting function is not meant to be used this way, not all permutations have the same probability. For instance, consider the code below. It runs `shuffle` 1000000 times and counts appearances of all possible results: ```js run function shuffle(array) { array.sort(() => Math.random() - 0.5); } // counts of appearances for all possible permutations let count = { '123': 0, '132': 0, '213': 0, '231': 0, '321': 0, '312': 0 }; for (let i = 0; i < 1000000; i++) { let array = [1, 2, 3]; shuffle(array); count[array.join('')]++; } // show counts of all possible permutations for (let key in count) { alert(`${key}: ${count[key]}`); } ``` An example result (depends on JS engine): ```js 123: 250706 132: 124425 213: 249618 231: 124880 312: 125148 321: 125223 ``` We can see the bias clearly: `123` and `213` appear much more often than others. The result of the code may vary between JavaScript engines, but we can already see that the approach is unreliable. Why it doesn't work? Generally speaking, `sort` is a "black box": we throw an array and a comparison function into it and expect the array to be sorted. But due to the utter randomness of the comparison the black box goes mad, and how exactly it goes mad depends on the concrete implementation that differs between engines. There are other good ways to do the task. For instance, there's a great algorithm called [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle). The idea is to walk the array in the reverse order and swap each element with a random one before it: ```js function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i // swap elements array[i] and array[j] // we use "destructuring assignment" syntax to achieve that // you'll find more details about that syntax in later chapters // same can be written as: // let t = array[i]; array[i] = array[j]; array[j] = t [array[i], array[j]] = [array[j], array[i]]; } } ``` Let's test it the same way: ```js run function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } // counts of appearances for all possible permutations let count = { '123': 0, '132': 0, '213': 0, '231': 0, '321': 0, '312': 0 }; for (let i = 0; i < 1000000; i++) { let array = [1, 2, 3]; shuffle(array); count[array.join('')]++; } // show counts of all possible permutations for (let key in count) { alert(`${key}: ${count[key]}`); } ``` The example output: ```js 123: 166693 132: 166647 213: 166628 231: 167517 312: 166199 321: 166316 ``` Looks good now: all permutations appear with the same probability. Also, performance-wise the Fisher-Yates algorithm is much better, there's no "sorting" overhead. ================================================ FILE: 1-js/05-data-types/05-array-methods/9-shuffle/task.md ================================================ importance: 3 --- # Mélanger un tableau Ecrivez la fonction `shuffle(array)` qui mélange les éléments (de manière aléatoire) du tableau. Les exécutions multiples de `shuffle` peuvent conduire à différents ordres d'éléments. Par exemple: ```js let arr = [1, 2, 3]; shuffle(arr); // arr = [3, 2, 1] shuffle(arr); // arr = [2, 1, 3] shuffle(arr); // arr = [3, 1, 2] // ... ``` Tous les ordres d'éléments doivent avoir une probabilité égale. Par exemple, `[1,2,3]` peut être réorganisé comme `[1,2,3]` ou `[1,3,2]` ou `[3,1,2]` etc., avec une probabilité égale de chaque cas. ================================================ FILE: 1-js/05-data-types/05-array-methods/article.md ================================================ # Méthodes de tableau Les tableaux viennent avec beaucoup de méthodes. Pour faciliter les choses, dans ce chapitre, ils ont été divisés en groupes. ## Ajouter/Supprimer des éléments Nous connaissons déjà des méthodes qui ajoutent et suppriment des éléments au début ou à la fin : - `arr.push(...items)` -- ajoute des éléments à la fin, - `arr.pop()` -- supprime un élément à la fin, - `arr.shift()` -- supprime un élément au début, - `arr.unshift(...items)` -- ajouter des éléments au début. En voici quelques autres. ### splice Comment supprimer un élément du tableau ? Les tableaux sont des objets, nous pouvons donc utiliser `delete` : ```js run let arr = ["I", "go", "home"]; delete arr[1]; // supprime "go" alert( arr[1] ); // undefined // maintenant arr = ["I", , "home"]; alert( arr.length ); // 3 ``` L'élément a été supprimé, mais le tableau a toujours 3 éléments, on peut voir que `arr.length == 3` C'est normal, car `delete obj.key` supprime une valeur par la `clé`. C'est tout ce que ça fait. C'est donc parfait pour les objets. Mais pour les tableaux, nous souhaitons généralement que le reste des éléments se déplace et occupe la place libérée. Nous nous attendons à avoir un tableau plus court maintenant. Des méthodes spéciales doivent donc être utilisées. La méthode [arr.splice](mdn:js/Array/splice) est un couteau suisse pour les tableaux. Elle peut tout faire : ajouter, supprimer et remplacer des éléments. La syntaxe est la suivante : ```js arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` Il a modifié `arr` à partir de l'index `start` : supprime les éléments `deleteCount` puis insère `elem1, ..., elemN` à leur place. Renvoie le tableau des éléments supprimés. Cette méthode est facile à comprendre avec des exemples. Commençons par la suppression : ```js run let arr = ["I", "study", "JavaScript"]; *!* arr.splice(1, 1); // À partir de l'index 1 supprime 1 élément */!* alert( arr ); // ["I", "JavaScript"] ``` Facile, non ? À partir de l'index 1, il a supprimé 1 élément. Dans l'exemple suivant, nous supprimons 3 éléments et les remplaçons par les deux autres : ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; // supprime les 3 premiers éléments et les remplace par d'autre arr.splice(0, 3, "Let's", "dance"); alert( arr ) // maintenant [*!*"Let's", "dance"*/!*, "right", "now"] ``` Nous pouvons voir ici que `splice` renvoie le tableau des éléments supprimés : ```js run let arr = [*!*"I", "study",*/!* "JavaScript", "right", "now"]; // supprime les 2 premiers éléments let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- tableau des éléments supprimés ``` La méthode `splice` est également capable d'insérer les éléments sans aucune suppression. Pour cela, nous devons définir `nombreDeSuppression` sur 0 : ```js run let arr = ["I", "study", "JavaScript"]; // de l'index 2 // supprime 0 // et ajoute "complex" et "language" arr.splice(2, 0, "complex", "language"); alert( arr ); // "I", "study", "complex", "language", "JavaScript" ``` ````smart header="Index négatifs autorisés" Ici et dans d'autres méthodes de tableau, les index négatifs sont autorisés. Ils spécifient la position à partir de la fin du tableau, comme ici : ```js run let arr = [1, 2, 5]; // de l'index -1 (un déplacement à partir de la fin) // supprime 0 éléments, // puis insère 3 et 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5 ``` ```` ### slice La méthode [arr.slice](mdn:js/Array/slice) est beaucoup plus simple et est similaire à la méthode `arr.splice`. La syntaxe est la suivante : ```js arr.slice([start], [end]) ``` Il retourne un nouveau tableau dans lequel il copie tous les éléments index qui commencent de `start` à `end` (sans compter `end`). Les deux `start` et `end` peuvent être négatifs, dans ce cas, la position depuis la fin du tableau est supposée. Cela ressemble à une méthode string `str.slice`, mais au lieu de sous-chaînes de caractères, cela crée des sous-tableaux. Par exemple : ```js run let arr = ["t", "e", "s", "t"]; alert( arr.slice(1, 3) ); // e,s (copie de 1 à 3, 3 non compris) alert( arr.slice(-2) ); // s,t (copie de -2 jusqu'à la fin) ``` Nous pouvons aussi l'appeler sans arguments : `arr.slice()` pour créer une copie de `arr`. Cela est souvent utilisé pour obtenir une copie pour d'autres transformations qui ne devraient pas affecter le tableau d'origine. ### concat La méthode [arr.concat](mdn:js/Array/concat) crée un nouveau tableau qui inclut les valeurs d'autres tableaux et des éléments supplémentaires. La syntaxe est la suivante : ```js arr.concat(arg1, arg2...) ``` Il accepte n'importe quel nombre d'arguments -- des tableaux ou des valeurs. Le résultat est un nouveau tableau contenant les éléments `arr`, puis `arg1`, `arg2`, etc. Si un argument `argN` est un tableau, alors tous ses éléments sont copiés. Sinon, l'argument lui-même est copié. Par exemple : ```js run let arr = [1, 2]; // créer un tableau à partir de arr et [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4 // créer un tableau à partir de arr et [3,4] et [5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // créer un tableau à partir de arr et [3,4], puis ajoute les valeurs 5 et 6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` Normalement, il ne copie que les éléments des tableaux. Les autres objets, même s'ils ressemblent à des tableaux, sont ajoutés dans leur ensemble : ```js run let arr = [1, 2]; let arrayLike = { 0: "something", length: 1 }; alert( arr.concat(arrayLike) ); // 1,2,[object Object] ``` … Mais si un objet de type tableau (array-like) a une propriété spéciale `Symbol.isConcatSpreadable`, alors il est traité comme un tableau par `concat` : ses éléments sont ajoutés à la place : ```js run let arr = [1, 2]; let arrayLike = { 0: "something", 1: "else", *!* [Symbol.isConcatSpreadable]: true, */!* length: 2 }; alert( arr.concat(arrayLike) ); // 1,2,something,else ``` ## Itérer: forEach (pourChaque) La méthode [arr.forEach](mdn:js/Array/forEach) permet d’exécuter une fonction pour chaque élément du tableau. La syntaxe : ```js arr.forEach(function(item, index, array) { // ... fait quelques chose avec l'élément }); ``` Par exemple, cela montre chaque élément du tableau : ```js run // pour chaque élément appel l'alerte ["Bilbo", "Gandalf", "Nazgul"].forEach(alert); ``` Et ce code est plus élaboré sur leurs positions dans le tableau cible : ```js run ["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { alert(`${item} est à l'index ${index} dans ${array}`); }); ``` Le résultat de la fonction (s'il en renvoie) est jeté et ignoré. ## Recherche dans le tableau Voyons maintenant les méthodes de recherche dans un tableau. ### indexOf/lastIndexOf et includes Les méthodes [arr.indexOf](mdn:js/Array/indexOf), et [arr.includes](mdn:js/Array/includes) ont la même syntaxe et utilisent essentiellement la même chose que leurs équivalents de chaîne, mais fonctionnent sur des éléments au lieu de caractères : - `arr.indexOf(item, from)` recherche l'élément `item` à partir de l'index `from`, et retourne l'index où il a été trouvé, sinon il retourne `-1`. - `arr.includes(item, from)` -- recherche l'élément `item` en commençant par l'index `from`, retourne `true` si il est trouvé. Habituellement, ces méthodes sont utilisées avec un seul argument : l'élément à rechercher. Par défaut, la recherche s'effectue depuis le début. Par exemple : ```js run let arr = [1, 0, false]; alert( arr.indexOf(0) ); // 1 alert( arr.indexOf(false) ); // 2 alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` Veuillez noter que `indexOf` utilise l'égalité stricte `===` pour la comparaison. Donc, si nous cherchons "faux", il trouve exactement "faux" et non le zéro. Si nous voulons vérifier si `item` existe dans le tableau et n'avons pas besoin de l'index, alors `arr.includes` est préféré. La méthode [arr.lastIndexOf](mdn:js/Array/lastIndexOf) est la même que `indexOf`, mais recherche de droite à gauche. ```js run let fruits = ['Apple', 'Orange', 'Apple'] alert( fruits.indexOf('Apple') ); // 0 (first Apple) alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) ``` ````smart header="La méthode `includes` gère `NaN` correctement" Une caractéristique mineure mais remarquable de `includes` est qu'il gère correctement `NaN`, contrairement à `indexOf` : ```js run const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (faux, devrait être 0) alert( arr.includes(NaN) ); // true (correct) ``` C'est parce que `includes` a été ajouté à JavaScript beaucoup plus tard et utilise l'algorithme de comparaison le plus à jour en interne. ```` ### find et findIndex/findLastIndex Imaginez que nous ayons un tableau d'objets. Comment pouvons-nous trouver un objet avec une condition spécifique ? Ici la méthode [arr.find(fn)](mdn:js/Array/find) se révèle vraiment pratique. La syntaxe est la suivante : ```js let result = arr.find(function(item, index, array) { // devrait retourner true si l'élément correspond à ce que nous recherchons // pour le scénario de falsy (fausseté), renvoie undefined }); ``` La fonction est appelée pour chaque élément du tableau, l'un après l'autre : - `item` est l'élément. - `index` est sont index. - `array` est le tableau lui même. S'il renvoie `true`, la recherche est arrêtée, l'`item` est renvoyé. Si rien n'est trouvé, `undefined` est renvoyé. Par exemple, nous avons un tableau d’utilisateurs, chacun avec les champs `id` et `name`. Trouvons le premier avec l'`id == 1` : ```js run let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; let user = users.find(item => item.id == 1); alert(user.name); // John ``` Dans la vie réelle, les tableaux d'objets sont une chose courante, la méthode `find` est donc très utile. Notez que dans l'exemple, nous fournissons à `find` la fonction `item => item.id == 1` avec un argument. C'est typique, les autres arguments de cette fonction sont rarement utilisés. La méthode [arr.findIndex](mdn:js/Array/findIndex) est essentiellement la même, mais elle retourne l'index où l'élément a été trouvé à la place de l'élément lui-même. La valeur de `-1` est retournée si rien n'est trouvé. La méthode [arr.findLastIndex](mdn:js/Array/findLastIndex) est comme `findIndex`, mais recherche de droite à gauche, similaire à `lastIndexOf`. Voici un exemple : ```js run let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"}, {id: 4, name: "John"} ]; // Trouver l'index du premier John alert(users.findIndex(user => user.name == 'John')); // 0 // Trouver l'index du dernier John alert(users.findLastIndex(user => user.name == 'John')); // 3 ``` ### filter La méthode `find` recherche un seul (le premier) élément qui rend la fonction true. S'il y en a plusieurs, nous pouvons utiliser [arr.filter(fn)](mdn:js/Array/filter). La syntaxe est à peu près identique à celle de `find`, mais `filter` renvoie un tableau d'éléments correspondants : ```js let results = arr.filter(function(item, index, array) { // si true, l'item est poussé vers résultats et l'itération continue // retourne un tableau vide si rien n'est trouvé }); ``` Par exemple : ```js run let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; // retourne les tableaux des deux premiers users let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2 ``` ## Transformer un tableau Passons aux méthodes qui transforment et réorganisent un tableau. ### map La méthode [arr.map](mdn:js/Array/map) est l’une des plus utiles et des plus utilisées. Elle appelle la fonction pour chaque élément du tableau et renvoie le tableau de résultats. La syntaxe est : ```js let result = arr.map(function(item, index, array) { // renvoie la nouvelle valeur au lieu de l'item }); ``` Par exemple, ici nous transformons chaque élément en sa longueur : ```js run let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length) alert(lengths); // 5,7,6 ``` ### sort(fn) La méthode [arr.sort](mdn:js/Array/sort) trie le tableau *en place*, en changeant son ordre d'élément. Elle renvoie également le tableau trié, mais la valeur renvoyée est généralement ignorée, comme `arr` est lui-même modifié. Par exemple : ```js run let arr = [ 1, 2, 15 ]; // la méthode réordonne le contenu de arr arr.sort(); alert( arr ); // *!*1, 15, 2*/!* ``` Avez-vous remarqué quelque chose d'étrange dans le résultat ? L'ordre est devenu `1, 15, 2`. C'est incorrect. Mais pourquoi ? **Les éléments sont triés en tant que chaînes par défaut.** Littéralement, tous les éléments sont convertis en chaînes de caractères pour comparaisons. Pour les chaînes de caractères, l’ordre lexicographique est appliqué et donc `"2" > "15"`. Pour utiliser notre propre ordre de tri, nous devons fournir une fonction comme argument de `arr.sort()`. La fonction doit comparer deux valeurs arbitraires et renvoyer le résultat : ```js function compare(a, b) { if (a > b) return 1; // if the first value is greater than the second if (a == b) return 0; // if values are equal if (a < b) return -1; // if the first value is less than the second } ``` Par exemple, pour trier sous forme de nombres : ```js run function compareNumeric(a, b) { if (a > b) return 1; if (a == b) return 0; if (a < b) return -1; } let arr = [ 1, 2, 15 ]; *!* arr.sort(compareNumeric); */!* alert(arr); // *!*1, 2, 15*/!* ``` Maintenant, ça fonctionne comme nous l'avons prévu. Mettons cela de côté et regardons ce qui se passe. L'`arr` peut être un tableau de n'importe quoi, non ? Il peut contenir des nombres, des chaînes de caractères, des objets ou autre. Nous avons donc un ensemble de *quelques items*. Pour le trier, nous avons besoin d’une *fonction de classement* qui sache comment comparer ses éléments. La valeur par défaut est un ordre de chaîne de caractères. La méthode `arr.sort(fn)` intégre l'implémentation d'un algorithme générique de tri. Nous n'avons pas besoin de nous préoccuper de son fonctionnement interne (c'est un [tri rapide optimisé](https://fr.wikipedia.org/wiki/Tri_rapide) la plupart du temps). Il va parcourir le tableau, comparer ses éléments à l'aide de la fonction fournie et les réorganiser. Tout ce dont nous avons besoin est de fournir la `fn` qui effectue la comparaison. À propos, si nous voulons savoir quels éléments sont comparés, rien ne nous empêche de les alerter : ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); return a - b; }); ``` L'algorithme peut comparer un élément à plusieurs autres dans le processus, mais il essaie de faire le moins de comparaisons possible. ````smart header="Une fonction de comparaison peut renvoyer n'importe quel nombre" En réalité, une fonction de comparaison est requise uniquement pour renvoyer un nombre positif pour dire "plus grand" et un nombre négatif pour dire "plus petit". Cela permet d'écrire des fonctions plus courtes : ```js run let arr = [ 1, 2, 15 ]; arr.sort(function(a, b) { return a - b; }); alert(arr); // *!*1, 2, 15*/!* ``` ```` ````smart header="Fonction fléchée pour le meilleur" Souvenez-vous des [fonctions fléchées](info:arrow-functions-basics) ? Nous pouvons les utiliser ici pour un tri plus net : ```js arr.sort( (a, b) => a - b ); ``` Cela fonctionne exactement comme la version longue ci-dessus. ```` ````smart header="Utiliser `localeCompare` pour les chaînes de caractères" Souvenez-vous de l'algorithme de comparaison [des chaînes de caractères](info:string#correct-comparisons) ? Il compare les lettres par leurs codes par défaut. Pour de nombreux alphabets, il est préférable d'utiliser la méthode `str.localeCompare` pour trier correctement les lettres, comme `Ö`. Par exemple, trions quelques pays en allemand : ```js run let countries = ['Österreich', 'Andorra', 'Vietnam']; alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong) alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!) ``` ```` ### reverse La méthode [arr.reverse](mdn:js/Array/reverse) inverse l'ordre des éléments dans l'`arr`. Par exemple : ```js run let arr = [1, 2, 3, 4, 5]; arr.reverse(); alert( arr ); // 5,4,3,2,1 ``` Il retourne également le tableau `arr` après l'inversion. ### split et join Voici une situation réele. Nous écrivons une application de messagerie et la personne entre dans la liste des destinataires délimités par des virgules : `John, Pete, Mary`. Mais pour nous, un tableau de noms serait beaucoup plus confortable qu'une simple chaîne de caractères. Alors, comment l'obtenir ? La méthode [str.split(separator)](mdn:js/String/split) fait exactement cela. Elle divise la chaîne en un tableau selon le délimiteur `separator` donné. Dans l'exemple ci-dessous, nous les séparons par une virgule suivie d'un espace : ```js run let names = 'Bilbo, Gandalf, Nazgul'; let arr = names.split(', '); for (let name of arr) { alert( `Un message à ${name}.` ); // Un message à Bilbo (ainsi que les autres noms) } ``` La méthode `split` a un deuxième argument numérique facultatif -- une limite sur la longueur du tableau. S'il est fourni, les éléments supplémentaires sont ignorés. En pratique, il est rarement utilisé cependant : ```js run let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); alert(arr); // Bilbo, Gandalf ``` ````smart header="Divisé en lettres" L'appel de `split(s)` avec un `s` vide diviserait la chaîne en un tableau de lettres : ```js run let str = "test"; alert( str.split('') ); // t,e,s,t ``` ```` L'appel de [arr.join(separator)](mdn:js/Array/join) fait l'inverse de `split`. Elle crée une chaîne de caractères avec les éléments de `arr` joints entre eux par `separator`. Par exemple : ```js run let arr = ['Bilbo', 'Gandalf', 'Nazgul']; let str = arr.join(';'); // joint les éléments du tableau en une string en utilisant le caractère ";" alert( str ); // Bilbo;Gandalf;Nazgul ``` ### reduce/reduceRight Lorsque nous devons parcourir un tableau, nous pouvons utiliser `forEach`, `for` ou `for..of`. Lorsque nous devons itérer et renvoyer les données pour chaque élément, nous pouvons utiliser `map`. Les méthodes [arr.reduce](mdn:js/Array/reduce) et [arr.reduceRight](mdn:js/Array/reduceRight) appartiennent également à cette famille, mais sont un peu plus complexes. Ces méthodes sont utilisées pour calculer une valeur unique basée sur un tableau. La syntaxe est la suivante : ```js let value = arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]); ``` La fonction est appliquée à tous les éléments du tableau les uns après les autres et "reporte" son résultat à l'appel suivant. Les arguments : - `accumulator` -- est le résultat de l'appel de fonction précédent, égal à `initial` la première fois (si `initial` est fourni). - `item` -- est l'élément actuel du tableau. - `index` -- est sa position. - `array` -- est le tableau. Lorsque la fonction est appliquée, le résultat de l'appel de fonction précédent est transmis au suivant en tant que premier argument. Ainsi, le premier argument est l'accumulateur qui stocke le résultat combiné de toutes les exécutions précédentes. À la fin, il devient le résultat de la fonction `reduce`. Cela semble compliqué ? Le moyen le plus simple pour comprendre c'est avec un exemple. Ici nous obtenons la somme d'un tableau sur une ligne : ```js run let arr = [1, 2, 3, 4, 5]; let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 ``` La fonction passée à `reduce` utilise seulement 2 arguments, c’est généralement suffisant. Voyons en détails ce qu'il se passe. 1. Lors du premier passage, `sum` prend la valeur de `initial` (le dernier argument de `reduce`), égale à `0`, et `current` correspond au premier élément du tableau, égal à `1`. Donc le résultat de la fonction est `1`. 2. Lors du deuxième passage, `sum = 1`, nous y ajoutons le deuxième élément du tableau (`2`) et `sum` est retourné. 3. Au troisième passage, `sum = 3` et nous y ajoutons un élément supplémentaire, et ainsi de suite... Le flux de calcul : ![](reduce.svg) Ou sous la forme d'un tableau, où chaque ligne représente un appel de fonction sur l'élément de tableau suivant : | |`sum`|`current`|result| |---|-----|---------|------| |premier appel|`0`|`1`|`1`| |deuxième appel|`1`|`2`|`3`| |troisième appel|`3`|`3`|`6`| |quatrième appel|`6`|`4`|`10`| |cinquième appel|`10`|`5`|`15`| Ici, nous pouvons clairement voir comment le résultat de l'appel précédent devient le premier argument du suivant. Nous pouvons également omettre la valeur initiale : ```js run let arr = [1, 2, 3, 4, 5]; // Suppression de la valeur initiale de reduce (pas de 0) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15 ``` Le résultat est le même. En effet, s'il n'y a pas de valeur initiale, alors `reduce` prend le premier élément du tableau comme valeur initiale et lance l'itération à partir du deuxième élément. Le tableau de calcul est le même que celui ci-dessus, sans la première ligne. Mais une telle utilisation nécessite une extrême prudence. Si le tableau est vide, alors `reduce` appelé sans valeur initiale génèrera une erreur. Voici un exemple : ```js run let arr = []; // Erreur : Réduction du tableau vide sans valeur initiale // si la valeur initiale existait, reduce la renverrait pour l'arr vide. arr.reduce((sum, current) => sum + current); ``` Il est donc conseillé de toujours spécifier la valeur initiale. La méthode [arr.reduceRight](mdn:js/Array/reduceRight) fait la même chose, mais va de droite à gauche. ## Array.isArray Les tableaux ne forment pas un type distinct du langage. Ils sont basés sur des objets. Donc son `typeof` ne permet pas de distinguer un objet brut d'un tableau : ```js run alert(typeof {}); // object alert(typeof []); // object (pareil) ``` ...Mais les tableaux sont utilisés si souvent qu'il existe une méthode spéciale pour cela : [Array.isArray(value)](mdn:js/Array/isArray). Il renvoie `true` si la `value` est un tableau, sinon il renvoie `false`. ```js run alert(Array.isArray({})); // false alert(Array.isArray([])); // true ``` ## La plupart des méthodes supportent "thisArg" Presque toutes les méthodes de tableau qui appellent des fonctions -- comme `find`, `filter`, `map`, à l'exception de `sort`, acceptent un paramètre supplémentaire facultatif `thisArg`. Ce paramètre n'est pas expliqué dans les sections ci-dessus, car il est rarement utilisé. Mais pour être complet, nous devons quand même le voir. Voici la syntaxe complète de ces méthodes : ```js arr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg); // ... // thisArg est le dernier argument optionnel ``` La valeur du paramètre `thisArg` devient `this` pour `func`. Par exemple, nous utilisons ici une méthode de l'objet `army` en tant que filtre et `thisArg` passe le contexte : ```js run let army = { minAge: 18, maxAge: 27, canJoin(user) { return user.age >= this.minAge && user.age < this.maxAge; } }; let users = [ {age: 16}, {age: 20}, {age: 23}, {age: 30} ]; *!* // trouve les utilisateurs pour qui army.canJoin retourne true let soldiers = users.filter(army.canJoin, army); */!* alert(soldiers.length); // 2 alert(soldiers[0].age); // 20 alert(soldiers[1].age); // 23 ``` Si, dans l'exemple ci-dessus, nous utilisions `users.filter(army.canJoin)`, alors `army.canJoin` serait appelée en tant que fonction autonome, avec `this = undefined`, ce qui entraînerait une erreur instantanée. Un appel à `users.filter(army.canJoin, army)` peut être remplacé par `users.filter(user => army.canJoin(user))`, qui fait la même chose. Le premier est utilisé plus souvent, car il est un peu plus facile à comprendre pour la plupart des gens. ## Résumé Un cheat sheet des méthodes de tableau : - Pour ajouter / supprimer des éléments : - `push(...items)` -- ajoute des éléments à la fin, - `pop()` -- extrait un élément en partant de la fin, - `shift()` -- extrait un élément depuis le début, - `unshift(...items)` -- ajoute des éléments au début. - `splice(pos, deleteCount, ...items)` -- à l'index `pos` supprime `deleteCount` éléments et insert les éléments `items`. - `slice(start, end)` -- crée un nouveau tableau, y copie les éléments de `start` jusqu'à `end` (non inclus). - `concat(...items)` -- retourne un nouveau tableau : copie tous les membres du groupe actuel et lui ajoute des éléments. Si un des `items` est un tableau, ses éléments sont pris. - Pour rechercher parmi des éléments : - `indexOf/lastIndexOf(item, pos)` -- cherche l'`item` à partir de la position `pos`, retourne l'index `-1` s'il n'est pas trouvé. - `includes(value)` -- retourne `true` si le tableau contient une `value`, sinon `false`. - `find/filter(func)` -- filtre les éléments à travers la fonction, retourne la première / toutes les valeurs qui retournent `true`. - `findIndex` est similaire à `find`, mais renvoie l'index au lieu d'une valeur. - Pour parcourir les éléments : - `forEach(func)` -- appelle `func` pour chaque élément, ne retourne rien. - Pour transformer le tableau : - `map(func)` -- crée un nouveau tableau à partir des résultats de `func` pour chaque élément. - `sort(func)` -- trie le tableau courant, puis le renvoie. - `reverse()` -- inverse le tableau courant, puis le renvoie. - `split/join` -- convertit une chaîne en tableau et inversement. - `reduce(func, initial)` -- calcule une valeur unique sur le tableau en appelant `func` pour chaque élément et en transmettant un résultat intermédiaire entre les appels. - Aditionellement : - `Array.isArray(value)` vérifie que `value` est un tableau, si c'est le cas, renvoie `true`, sinon `false`. Veuillez noter que les méthodes `sort`, `reverse` et `splice` modifient le tableau lui-même. Ces méthodes sont les plus utilisées, elles couvrent 99% des cas d'utilisation. Mais il y en a encore d'autres : - [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) vérifie le tableau. La fonction `fn` est appelée sur chaque élément du tableau comme pour `map`. Si n'importe quel / tous les résultats sont `true`, il retourne `true`, sinon il retourne `false`. La fonction `fn` est appelée sur chaque élément du tableau similaire à `map`. Si un/tous les résultats sont `true`, renvoie `true`, sinon `false`. Ces méthodes se comportent en quelque sorte comme les opérateurs `||` et `&&` : si `fn` renvoie une valeur vraie, `arr.some()` renvoie immédiatement `true` et arrête de parcourir les autres éléments ; si `fn` renvoie une valeur fausse, `arr.every()`retourne immédiatement `false` et arrête également d'itérer sur les autres éléments. On peut utiliser `every` pour comparer les tableaux : ```js run function arraysEqual(arr1, arr2) { return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); } alert( arraysEqual([1, 2], [1, 2])); // true ``` - [arr.fill(value, start, end)](mdn:js/Array/fill) -- remplit le tableau avec une répétition de `value` de l'index `start` à `end`. - [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- copie ses éléments en lui-même de la position `start` jusqu'à la position `end`, à la position `target` (écrase les éléments éxistants). - [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) créer un nouveau tableau plat à partir d'un tableau multidimensionnel. Pour la liste complète, consultez le [manuel](mdn:js/Array). À première vue, vous pouvez penser qu’il existe de nombreuses méthodes difficiles à retenir. Mais en réalité, c'est beaucoup plus facile qu'il n'y paraît. Parcourez le cheat sheet et essayer de vous en souvenir. Ensuite, faites les exercices de ce chapitre afin de vous familiariser avec les méthodes de tableau. Ensuite, chaque fois que vous avez besoin de faire quelque chose avec un tableau, et que vous ne savez plus comment - revenez ici, regardez le cheatsheet et trouvez la bonne méthode. Des exemples vous aideront à l'écrire correctement. Bientôt, à force de pratiquer, vous vous souviendrez automatiquement des méthodes, sans efforts particuliers. ================================================ FILE: 1-js/05-data-types/06-iterable/article.md ================================================ # Iterables Les objets *Iterable* sont une généralisation des tableaux. C'est un concept qui permet de rendre n'importe quel objet utilisable dans une boucle `for..of`. Bien sûr, les tableaux sont itérables. Mais il existe de nombreux autres objets intégrés, qui sont également itérables. Par exemple, les chaînes de caractères sont également itérables. Si un objet n'est pas techniquement un tableau, mais représente une collection (liste, set) de quelque chose, alors `for..of` est une excellente syntaxe pour boucler dessus, voyons comment le faire fonctionner. ## Symbol.iterator Nous pouvons facilement saisir le concept des itérables en faisant le nôtre. Par exemple, nous avons un objet qui n'est pas un tableau, mais qui semble convenir pour une boucle `for..of`. Comme un objet `range` qui représente un intervalle de nombres : ```js let range = { from: 1, to: 5 }; // Nous voulons que le for..of fonctionne : // for (let num of range) ... num=1,2,3,4,5 ``` Pour rendre la `range` itérable (et donc laisser `for..of` faire son travail), nous devons ajouter une méthode à l'objet nommé `Symbol.iterator` (un symbole intégré spécial que pour cela). 1. Lorsque `for..of` démarre, il appelle cette méthode une fois (ou des erreurs si il n'est pas trouvé). La méthode doit retourner un *iterator* -- un objet avec la méthode `next`. 2. À partir de là, `for..of` ne fonctionne *qu'avec cet objet retourné*. 3. Quand `for..of` veut la valeur suivante, il appelle `next()` sur cet objet. 4. Le résultat de `next()` doit avoir la forme `{done: Boolean, value: any}`, où `done = true` signifie que l'itération est terminée, sinon `value` doit être la nouvelle valeur. Voici l'implémentation complète de `range` avec les remarques : ```js run let range = { from: 1, to: 5 }; // 1. l'appel d'un for..of appelle initialement ceci range[Symbol.iterator] = function() { // ...il retourne l'objet itérateur : // 2. À partir de là, for..of fonctionne uniquement avec cet itérateur, lui demandant les valeurs suivantes return { current: this.from, last: this.to, // 3. next() est appelée à chaque itération par la boucle for..of next() { // 4. il devrait renvoyer la valeur sous forme d'objet {done: .., valeur: ...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; }; // maintenant ça marche ! for (let num of range) { alert(num); // 1, ensuite 2, 3, 4, 5 } ``` Veuillez noter la fonctionnalité principale des iterables : separation of concerns (séparation des préoccupations). - Le `range` lui-même n'a pas la méthode `next()`. - Au lieu de cela, un autre objet, appelé "iterator", est créé par l'appel à `range[Symbol.iterator]()`, et sa méthode `next()` génère des valeurs pour l'itération. Ainsi, l'objet itérateur est séparé de l'objet sur lequel il est itéré. Techniquement, nous pouvons les fusionner et utiliser `range` lui-même comme itérateur pour simplifier le code. Comme ça : ```js run let range = { from: 1, to: 5, [Symbol.iterator]() { this.current = this.from; return this; }, next() { if (this.current <= this.to) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; for (let num of range) { alert(num); // 1, ensuite 2, 3, 4, 5 } ``` Maintenant, `range[Symbol.iterator]()` renvoie l'objet `range` lui-même : il dispose de la méthode `next()` et se souvient de la progression de l'itération en cours dans `this.current`. C'est plus court? Oui. Et parfois c'est aussi bien. L'inconvénient est qu'il est maintenant impossible d'avoir deux boucles `for..of` s'exécutant simultanément sur l'objet: elles partageront l'état d'itération, car il n'y a qu'un seul itérateur -- l'objet lui-même. Cependant, il est rare de disposer de deux for-of parallèles, faisables avec certains scénarios asynchrones. ```smart header="Itérateurs infinis" Des itérateurs infinis sont également possibles. Par exemple, `range` devient infini pour `range.to = Infinity`. Ou nous pouvons créer un objet itérable qui génère une suite infinie de nombres pseudo-aléatoires. Il peut être aussi utile. Il n'y a pas de limitation sur `next`, il peut renvoyer de plus en plus de valeurs, c'est normal. Bien sûr, la boucle `for..of` sur une telle itération serait sans fin. Mais on peut toujours l'arrêter en utilisant `break`. ``` ## String est iterable Les tableaux et les chaînes de caractères sont les iterables intégrés les plus largement utilisés. Pour une chaîne de caractères, `for..of` boucle sur ses caractères : ```js run for (let char of "test") { // se déclenche 4 fois: une fois pour chaque caractère alert( char ); // t, ensuite e, ensuite s, ensuite t } ``` Et cela fonctionne correctement avec les paires de substitution ! ```js run let str = '𝒳😂'; for (let char of str) { alert( char ); // 𝒳, et ensuite 😂 } ``` ## Appeler explicitement un itérateur Pour une compréhension plus approfondie, voyons comment utiliser explicitement un itérateur. Nous allons parcourir une chaîne de caractères de la même manière que `for..of`, mais avec des appels directs. Ce code crée un itérateur de chaîne de caractères et en récupère la valeur "manuellement" : ```js run let str = "Hello"; // fait la même chose que // for (let char of str) alert(char); *!* let iterator = str[Symbol.iterator](); */!* while (true) { let result = iterator.next(); if (result.done) break; alert(result.value); // affiche les caractères un par un } ``` Cela est rarement nécessaire, mais nous donne plus de contrôle sur le processus que `for..of`. Par exemple, nous pouvons scinder le processus d'itération : itérer un peu, puis arrêter, faire autre chose, puis reprendre plus tard. ## Iterables et array-likes [#array-like] Il existe deux termes officiels qui se ressemblent mais qui sont très différents. Assurez-vous de bien les comprendre pour éviter la confusion. - *Iterables* sont des objets qui implémentent la méthode `Symbol.iterator`, comme décrit ci-dessus. - *Array-likes* sont des objets qui ont des index et des `length`, ils ressemblent donc à des tableaux. Lorsque nous utilisons JavaScript pour des tâches pratiques dans un navigateur ou d'autres environnements, il est possible que nous rencontrions des objets qui sont iterables ou des array-like, ou les deux. Par exemple, les chaînes de caractères sont à la fois iterables (`for..of` fonctionne dessus) et des array-likes (elles ont des index numériques et une longueur). Mais un itérable peut ne pas ressembler à un array-like. Et inversement, un array-like peut ne pas être itérable. Par exemple, la `range` dans l'exemple ci-dessus est itérable, mais pas comme un array-like, car elle n'a pas de propriétés indexées et de `length`. Et voici l'objet qui ressemble à un tableau, mais pas itérable : ```js run let arrayLike = { // a des index et une longueur => semblable à un tableau 0: "Hello", 1: "World", length: 2 }; *!* // Erreur (pas de Symbol.iterator) for (let item of arrayLike) {} */!* ``` Les iterables et les array-likes ne sont généralement *pas des tableaux*, ils n'ont pas `push`, `pop`, etc. C'est plutôt gênant si nous avons un tel objet et que nous voulons travailler avec lui comme avec un tableau. Par exemple, nous aimerions travailler avec une plage en utilisant les méthodes de tableau. Comment y parvenir ? ## Array.from Il existe une méthode universelle [Array.from](mdn:js/Array/from) qui prend une valeur itérable ou array-like et en fait un "vrai" `Array`. Ensuite, nous pouvons appeler des méthodes de tableau dessus. Par exemple : ```js run let arrayLike = { 0: "Hello", 1: "World", length: 2 }; *!* let arr = Array.from(arrayLike); // (*) */!* alert(arr.pop()); // World (la méthode fonctionne) ``` `Array.from` à la ligne `(*)` prend l'objet, l'examine comme étant un objet itérable ou un array-like, crée ensuite un nouveau tableau et y copie tous les éléments. Il en va de même pour un itérable : ```js run // en supposant que cette "range" est tirée de l'exemple ci-dessus let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion fonctionne) ``` La syntaxe complète de `Array.from` permet aussi de fournir une fonction optionnelle de "mapping" : ```js Array.from(obj[, mapFn, thisArg]) ``` Le second argument `mapFn` peut être une fonction à appliquer à chaque élément avant de l'ajouter au tableau, et `thisArg` permet d'en définir le `this`. Par exemple : ```js run // en supposant que cette "range" est tirée de l'exemple ci-dessus // met au carré chaque nombre let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25 ``` Ici, nous utilisons `Array.from` pour transformer une chaîne en un tableau de caractères : ```js run let str = '𝒳😂'; // divise une chaîne en un tableau de caractères let chars = Array.from(str); alert(chars[0]); // 𝒳 alert(chars[1]); // 😂 alert(chars.length); // 2 ``` Contrairement à `str.split`, il repose sur la nature itérable de la chaîne et donc, tout comme `for..of`, fonctionne correctement avec des paires de substitution. Techniquement, il fait la même chose que : ```js run let str = '𝒳😂'; let chars = []; // Array.from en interne fait la même boucle for (let char of str) { chars.push(char); } alert(chars); ``` ...Mais c'est plus court. Nous pouvons même créer une fonction `slice` qui prend en compte les paires de substitution : ```js run function slice(str, start, end) { return Array.from(str).slice(start, end).join(''); } let str = '𝒳😂𩷶'; alert( slice(str, 1, 3) ); // 😂𩷶 // les méthodes native ne supporte pas les paires de substitution alert( str.slice(1, 3) ); // ordures (deux pièces de paires de substitution différentes) ``` ## Résumé Les objets pouvant être utilisés dans `for..of` s'appellent *iterable*. - Techniquement, les iterables doivent implémenter la méthode nommée `Symbol.iterator`. - Le résultat de `obj[Symbol.iterator]()` s'appelle un *itérateur*. Il gère le processus d'itération ultérieur. - Un itérateur doit avoir la méthode nommée `next()` qui retourne un objet `{done: Boolean, value: any}`, ici `done: true` dénote la fin du processus de l'itération, sinon `value` est la valeur suivante. - La méthode `Symbol.iterator` est appelée automatiquement par `for..of`, mais nous pouvons aussi le faire directement. - Les iterables intégrés tels que des chaînes de caractères ou des tableaux implémentent également `Symbol.iterator`. - L'itérateur de chaîne de caractères connaît les paires de substitution. Les objets qui ont des propriétés indexées et des `length` sont appelés *array-like*. De tels objets peuvent également avoir d'autres propriétés et méthodes, mais ne possèdent pas les méthodes intégrées des tableaux. Si nous regardons à l'intérieur de la spécification -- nous verrons que la plupart des méthodes intégrées supposent qu'elles fonctionnent avec des éléments iterables ou des array-like au lieu de "vrais" tableaux, car c'est plus abstrait. `Array.from(obj[, mapFn, thisArg])` créer un véritable `Array` à partir d'un `obj` itérable ou array-like, et nous pouvons ensuite utiliser des méthodes de tableau sur celui-ci. Les arguments optionnels `mapFn` et `thisArg` nous permettent d'appliquer une fonction à chaque élément. ================================================ FILE: 1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/solution.js ================================================ function unique(arr) { return Array.from(new Set(arr)); } ================================================ FILE: 1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/test.js ================================================ describe("unique", function() { it("removes non-unique elements", function() { let strings = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; assert.deepEqual(unique(strings), ["Hare", "Krishna", ":-O"]); }); it("does not change the source array", function() { let strings = ["Krishna", "Krishna", "Hare", "Hare"]; unique(strings); assert.deepEqual(strings, ["Krishna", "Krishna", "Hare", "Hare"]); }); }); ================================================ FILE: 1-js/05-data-types/07-map-set/01-array-unique-map/solution.md ================================================ ================================================ FILE: 1-js/05-data-types/07-map-set/01-array-unique-map/task.md ================================================ importance: 5 --- # Filtrer les membres uniques du tableau Disons que `arr` est un tableau. Créez une fonction `unique(arr)` qui devrait renvoyer un tableau avec les éléments uniques d'arr. Par exemple : ```js function unique(arr) { /* your code */ } let values = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; alert( unique(values) ); // Hare, Krishna, :-O ``` P.S. Ici, les chaînes de caractères sont utilisées, mais elles peuvent être des valeurs de n'importe quel type. P.P.S. Utilisez `Set` pour stocker des valeurs uniques. ================================================ FILE: 1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/solution.js ================================================ function aclean(arr) { let map = new Map(); for(let word of arr) { let sorted = word.toLowerCase().split("").sort().join(""); map.set(sorted, word); } return Array.from(map.values()); } ================================================ FILE: 1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/test.js ================================================ function intersection(arr1, arr2) { return arr1.filter(item => arr2.includes(item)); } describe("aclean", function() { it("returns exactly 1 word from each anagram set", function() { let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; let result = aclean(arr); assert.equal(result.length, 3); assert.equal(intersection(result, ["nap", "PAN"]).length, 1); assert.equal(intersection(result, ["teachers", "cheaters", "hectares"]).length, 1); assert.equal(intersection(result, ["ear", "era"]).length, 1); }); it("is case-insensitive", function() { let arr = ["era", "EAR"]; assert.equal(aclean(arr).length, 1); }); }); ================================================ FILE: 1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md ================================================ Pour trouver tous les anagrammes, divisons chaque mot en lettres et trions-les. Lorsque ils sont triés par lettres, tous les anagrammes sont identiques. Par exemple : ``` nap, pan -> anp ear, era, are -> aer cheaters, hectares, teachers -> aceehrst ... ``` Nous allons utiliser les variantes triées par lettre comme clés de map pour stocker une seule valeur pour chaque clé : ```js run function aclean(arr) { let map = new Map(); for (let word of arr) { // diviser le mot en lettres, les trier et les rejoindre *!* let sorted = word.toLowerCase().split('').sort().join(''); // (*) */!* map.set(sorted, word); } return Array.from(map.values()); } let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; alert( aclean(arr) ); ``` Le tri des lettres se fait par la chaîne d'appels en ligne `(*)`. Pour plus de commodité, divisons-le en plusieurs lignes : ```js let sorted = word // PAN .toLowerCase() // pan .split('') // ['p','a','n'] .sort() // ['a','n','p'] .join(''); // anp ``` Deux mots différents, `'PAN'` et `'nap'`, reçoivent la même forme de lettre triée `'anp'`. La ligne suivante place le mot dans le map : ```js map.set(sorted, word); ``` Si nous rencontrons à nouveau un mot trié de la même manière, il écrasera la valeur précédente avec la même clé dans le map. Nous aurons donc toujours au maximum un mot par lettre. À la fin `Array.from(map.values())` prends un itérable sur les valeurs du `Map` (nous n'avons pas besoin des clés dans le résultat) et renvoi un tableau avec celles-ci. Ici, nous pourrions également utiliser un objet simple au lieu du `Map`, car les clés sont des chaînes de caractères. Voilà à quoi la solution peut ressembler : ```js run demo function aclean(arr) { let obj = {}; for (let i = 0; i < arr.length; i++) { let sorted = arr[i].toLowerCase().split("").sort().join(""); obj[sorted] = arr[i]; } return Object.values(obj); } let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; alert( aclean(arr) ); ``` ================================================ FILE: 1-js/05-data-types/07-map-set/02-filter-anagrams/task.md ================================================ importance: 4 --- # Filter les anagrams [Anagrams](https://fr.wikipedia.org/wiki/Anagramme) sont des mots qui ont le même nombre de mêmes lettres, mais dans un ordre différent. Par exemple : ``` nap - pan ear - are - era cheaters - hectares - teachers ``` Ecrivez une fonction `aclean(arr)` qui retourne un tableau nettoyé des anagrammes. Par exemple : ```js let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; alert( aclean(arr) ); // "nap,teachers,ear" ou "PAN,cheaters,era" ``` De chaque groupe d’anagrammes ne devrait rester qu’un mot, peu importe lequel. ================================================ FILE: 1-js/05-data-types/07-map-set/03-iterable-keys/solution.md ================================================ C’est parce que `map.keys()` retourne un itérable, mais pas un tableau. Nous pouvons le convertir en tableau en utilisant `Array.from` : ```js run let map = new Map(); map.set("name", "John"); *!* let keys = Array.from(map.keys()); */!* keys.push("more"); alert(keys); // name, more ``` ================================================ FILE: 1-js/05-data-types/07-map-set/03-iterable-keys/task.md ================================================ importance: 5 --- # Clés Iterables Nous voulons obtenir un tableau de `map.keys()` dans une variable puis lui appliquer des méthodes spécifiques aux tableaux, par ex: `.push`. Mais cela ne fonctionne pas : ```js run let map = new Map(); map.set("name", "John"); let keys = map.keys(); *!* // Error: keys.push is not a function keys.push("more"); */!* ``` Pourquoi ? Comment pouvons-nous corriger le code pour que `keys.push` fonctionne ? ================================================ FILE: 1-js/05-data-types/07-map-set/article.md ================================================ # Map et Set Jusqu'à présent, nous avons découvert les structures de données complexes suivantes : - Les objets sont utilisés pour stocker des collections de clés. - Les tableaux sont utilisés pour stocker des collections ordonnées. Mais ce n'est pas suffisant pour la vie réelle. C'est pourquoi `Map` et `Set` existent également. ## Map Une [Map](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Map) est une collection d'éléments de données saisis, tout comme un `Object`. Mais la principale différence est que `Map` autorise les clés de tout type. Voici les méthodes et les propriétés d'une `Map` : - [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- créer la map. - [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stocke la valeur par la clé. - [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- renvoie la valeur par la clé, `undefined` si `key` n'existe pas dans la map. - [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- retourne `true` si la `key` existe, `false` sinon. - [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- supprime l'élément (la paire clé/valeur) par la clé. - [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- supprime tout de la map. - [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- renvoie le nombre d'éléments actuel. Par exemple : ```js run let map = new Map(); map.set('1', 'str1'); // une clé de type chaîne de caractère map.set(1, 'num1'); // une clé de type numérique map.set(true, 'bool1'); // une clé de type booléenne // souvenez-vous, dans un `Object`, les clés sont converties en chaîne de caractères // alors que `Map` conserve le type d'origine de la clé, // c'est pourquoi les deux appels suivants retournent des valeurs : alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3 ``` Au travers de cet exemple nous pouvons voir, qu'à la différence des `Objects`, les clés ne sont pas converties en chaîne de caractère. Il est donc possible d'utiliser n'importe quel type. ```smart header="`map[key]` n'est pas la bonne façon d'utiliser un `Map`" Bien que `map[key]` fonctionne également, par exemple nous pouvons définir `map[key] = 2`, cela traite `map` comme un objet JavaScript simple, ce qui implique toutes les limitations correspondantes (uniquement des clés chaîne de caractères/symbol etc.). Nous devons donc utiliser les méthodes de `map` : `set`, `get` et ainsi de suite. ``` **Map peut également utiliser des objets comme clés.** Par exemple : ```js run let john = { name: "John" }; // pour chaque utilisateur, nous stockons le nombre de visites let visitsCountMap = new Map(); // john est utilisé comme clé dans la map visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` Utiliser des objets comme clés est l'une des fonctionnalités les plus notables et les plus importantes de `Map`. La même chose ne compte pas pour `Object`. Une chaîne de caractères comme clé dans `Object` est très bien, mais nous ne pouvons pas utiliser un autre `Object` comme clé dans `Object`. Essayons de faire comme l'exemple précédent directement avec un `Object` : ```js run let john = { name: "John" }; let ben = { name: "Ben" }; let visitsCountObj = {}; // on créé notre object visitsCountObj[ben] = 234; // essayez d'utiliser l'objet ben comme clé visitsCountObj[john] = 123; // essayez d'utiliser l'objet john comme clé, l'objet ben sera remplacé *!* // C'est ce qui a été écrit! alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` Comme `visitesCountObj` est un objet, il convertit toutes les clés `Object`, telles que `john` et `ben` ci-dessus, en la même chaîne de caractères `"[object Object]"`. Certainement pas ce que nous voulons. ```smart header="Comment `Map` compare les clés" Pour tester l'égalité entre les clés, `Map` se base sur l'algorithme [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). C'est grosso modo la même chose que l'opérateur de stricte égalité `===`, à la différence que `NaN` est considéré comme étant égal à `NaN`. `NaN` peut donc être utilisé comme clé. Cet algorithme ne peut pas être modifié. ``` ````smart header="Chaînage" Chaque appel à `map.set` retourne la `map` elle-même, ce qui nous permet d'enchaîner les appels : ```js map.set('1', 'str1') .set(1, 'num1') .set(true, 'bool1'); ``` ```` ## Itération dans Map Il existe 3 façons de parcourir les éléments d'une `map` : - [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- renvoie un itérable pour les clés, - [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- renvoie un itérable pour les valeurs, - [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- renvoie un itérable pour les entrées `[key, value]`, il est utilisé par défaut dans `for..of`. Par exemple : ```js run let recipeMap = new Map([ ['cucumber', 500], ['tomatoes', 350], ['onion', 50] ]); // on parcourt les clés (les légumes) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } // on parcourt les valeurs (les montants) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } // on parcourt les entries (couple [clé, valeur]) for (let entry of recipeMap) { // équivalent à : recipeMap.entries() alert(entry); // cucumber,500 (etc.) } ``` ```smart header="L'ordre d'insertion est conservé" Contraitement aux `Object`, `Map` conserve l'ordre d'insertion des valeurs. ``` Il est aussi possible d'utiliser `forEach` avec `Map` comme on pourrait le faire avec un tableau : ```js // exécute la fonction pour chaque couple (key, value) recipeMap.forEach( (value, key, map) => { alert(`${key}: ${value}`); // cucumber: 500 etc. }); ``` ## Object.entries: Créer une Map à partir d'un objet Lorsqu'une `Map` est créée, nous pouvons passer un tableau (ou un autre itérable) contenant des paires clé/valeur pour l'initialisation, comme ceci : ```js run // tableau de paires [clé, valeur] let map = new Map([ ['1', 'str1'], [1, 'num1'], [true, 'bool1'] ]); alert( map.get('1') ); // str1 ``` Si nous avons un objet simple et que nous souhaitons en créer une `Map`, nous pouvons utiliser la méthode intégrée [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) qui renvoie un tableau de paires clé/valeur pour un objet exactement dans ce format. Nous pouvons donc créer une `Map` à partir d'un objet de la manière suivante : ```js run let obj = { name: "John", age: 30 }; *!* let map = new Map(Object.entries(obj)); */!* alert( map.get('name') ); // John ``` Ici, `Object.entries` renvoie le tableau de paires clé/valeur : `[ ["name", "John"], ["age", 30] ]`. C'est ce dont a besoin la `Map`. ## Object.fromEntries: Objet à partir d'une Map Nous venons de voir comment créer une `Map` à partir d'un objet simple avec `Object.entries(obj)`. Il existe une méthode `Object.fromEntries` qui fait l'inverse : étant donné un tableau de paires `[clé, valeur]`, elle crée un objet à partir de ces paires : ```js run let prices = Object.fromEntries([ ['banana', 1], ['orange', 2], ['meat', 4] ]); // maintenant, prices = { banana: 1, orange: 2, meat: 4 } alert(prices.orange); // 2 ``` Nous pouvons utiliser `Object.fromEntries` pour obtenir un objet simple à partir d'une `Map`. Par exemple, nous stockons les données dans une `Map`, mais nous devons les transmettre à un code tiers qui attend un objet simple. Voici comment procéder : ```js run let map = new Map(); map.set('banana', 1); map.set('orange', 2); map.set('meat', 4); *!* let obj = Object.fromEntries(map.entries()); // créer un objet simple (*) */!* // terminé! // obj = { banana: 1, orange: 2, meat: 4 } alert(obj.orange); // 2 ``` Un appel à `map.entries()` renvoie un itérable de paires clé/valeur, exactement dans le bon format pour `Object.fromEntries`. Nous pourrions également raccourcir la ligne (*) : ```js let obj = Object.fromEntries(map); // .entries() omis ``` C'est la même chose, car `Object.fromEntries` attend un objet itérable en argument. Pas nécessairement un tableau. Et l'itération standard pour une `map` renvoie les mêmes paires clé/valeur que `map.entries()`. Ainsi, nous obtenons un objet simple avec les mêmes clés/valeurs que la `map`. ## Set Un [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) est une collection de types spéciaux - "ensemble de valeurs" (sans clés), où chaque valeur ne peut apparaître qu'une seule fois. Ses principales méthodes sont : - [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- crée le set et si un objet `iterable` est fourni (généralement un tableau), en copie les valeurs dans le set. - [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- ajoute une valeur, renvoie le set lui-même. - [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- supprime la valeur, renvoie `true` si `value` existait au moment de l'appel, sinon `false`. - [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- renvoie `true` si la valeur existe dans le set sinon `false`. - [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- supprime tout du set. - [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- c'est le nombre d'éléments. Ce qu'il faut surtout savoir c'est que lorsque l'on appelle plusieurs fois `set.add(value)` avec la même valeur, la méthode ne fait rien. C'est pourquoi chaque valeur est unique dans un `Set`. Par exemple, nous souhaitons nous souvenir de tous nos visiteurs. Mais chaque visiteurs doit être unique. `Set` est exactement ce qu'il nous faut : ```js run let set = new Set(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; // visites, certains utilisateurs viennent plusieurs fois set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary); // set conserve une fois chaque visiteurs alert( set.size ); // 3 for (let user of set) { alert(user.name); // John (puis Pete et Mary) } ``` L'alternative à `Set` aurait pu être un tableau d'utilisateurs en vérifiant avant chaque insertion que l'élément n'existe pas en utilisant [arr.find](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/find). Cependant les performances auraient été moins bonnes car cette méthode parcours chaque élément du tableau. `Set` est beaucoup plus efficace car il est optimisé en interne pour vérifier l'unicité des valeurs. ## Parcourir un Set Nous pouvons parcourir les éléments d'un Set avec `for..of` ou en utilisant `forEach` : ```js run let set = new Set(["oranges", "apples", "bananas"]); for (let value of set) alert(value); // même chose en utilisant forEach: set.forEach((value, valueAgain, set) => { alert(value); }); ``` A noter que la fonction de callback utilisée par `forEach` prend 3 arguments en paramètres : une `value`, puis *la même valeur* `valueAgain`, et enfin le set lui-même. C'est pour la compatibilité avec `Map` où le callback `forEach` passé possède trois arguments. Ça a l'air un peu étrange, c'est sûr. Mais cela peut aider à remplacer facilement `Map` par `Set` dans certains cas, et vice versa. Les méthodes pour parcourir les éléments d'une `Map` peuvent être utilisées : - [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- renvoie un objet itérable pour les valeurs, - [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- identique à `set.keys()`, pour compatibilité avec `Map`, - [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- renvoie un objet itérable pour les entrées `[value, value]`, existe pour la compatibilité avec `Map`. ## Résumé [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- est une collection de valeurs-clés. Méthodes et propriétés : - [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- crée la map, avec un `iterable` facultatif (par exemple un tableau) de paires `[key, value]` pour l'initialisation. - [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stocke la valeur par la clé, renvoie la map elle-même. - [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- renvoie la valeur par la clé, `undefined` si `key` n'existe pas dans la map. - [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- retourne `true` si la `key` existe, `false` sinon. - [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- supprime l'élément par la clé, renvoie `true` si `key` existait au moment de l'appel, sinon `false`. - [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- supprime tout de la map. - [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- renvoie le nombre d'éléments actuel. La différence entre `Map` avec un objet traditionel : - N'importe quel type peut être utilisé comme clé. - Accès à des méthodes tels que `size`. [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- est une collection de valeurs uniques. Méthodes et propriétés : - [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- crée le set avec un `iterable` facultatif (par exemple un tableau) de valeurs pour l'initialisation. - [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- ajoute une valeur (ne fait rien si `value` existe), renvoie l'ensemble lui-même. - [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- supprime la valeur, renvoie `true` si `value` existait au moment de l'appel, sinon `false`. - [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- renvoie `true` si la valeur existe dans l'ensemble, sinon `false`. - [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- supprime tout du set. - [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- c'est le nombre d'éléments. On ne peut pas dire que les éléments dans une `Map` ou un `Set` sont désordonnés car ils sont toujours parcourut par ordre d'insertion. Il est cependant impossible de réorganiser les éléments ou bien de les retrouver par leur index. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md ================================================ Stockons les messages lus dans `WeakSet`: ```js run let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, {text: "See you soon", from: "Alice"} ]; let readMessages = new WeakSet(); // deux messages ont été lus readMessages.add(messages[0]); readMessages.add(messages[1]); // readMessages a 2 éléments // ...Relisons le premier message ! readMessages.add(messages[0]); // readMessages a encore 2 éléments uniques // réponse : le message[0] a-t-il été lu ? alert("Read message 0: " + readMessages.has(messages[0])); // true messages.shift(); // maintenant readMessages a 1 élément (techniquement, la mémoire peut être nettoyée plus tard) ``` Le `WeakSet` permet de stocker un ensemble de messages et de vérifier facilement l’existence d’un message dedans. Il se nettoie automatiquement. Le compromis est que nous ne pouvons pas le parcourir, nous ne pouvons pas obtenir "tous les messages lus" directement. Mais nous pouvons le faire en parcourant tous les messages et en filtrant ceux qui sont dans le set. Une autre solution pourrait consister à ajouter une propriété telle que `message.isRead = true` à un message après sa lecture. Comme les objets de messages sont gérés par un autre code, cela est généralement déconseillé, mais nous pouvons utiliser une propriété symbolique pour éviter les conflits. Comme ceci : ```js // la propriété symbolique n'est connue que de notre code let isRead = Symbol("isRead"); messages[0][isRead] = true; ``` Maintenant, le code tiers ne verra probablement pas notre propriété supplémentaire. Bien que les symboles permettent de réduire la probabilité de problèmes, l’utilisation de `WeakSet` est préférable du point de vue de l’architecture. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md ================================================ importance: 5 --- # Stocker les messages non-lus Il y a un tableau de messages : ```js let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, {text: "See you soon", from: "Alice"} ]; ``` Votre code peut y accéder, mais les messages sont gérés par le code d’une autre personne. De nouveaux messages sont ajoutés, les anciens sont régulièrement supprimés par ce code et vous ne connaissez pas le moment exact où cela se produit. Maintenant, quelle structure de données pouvez-vous utiliser pour stocker des informations si le message "a été lu" ? La structure doit être bien adaptée pour donner la réponse "a-t-il été lu ?" Pour l'objet de message donné. P.S. Lorsqu'un message est supprimé des `messages`, il doit également disparaître de votre structure. P.P.S. Nous ne devrions pas modifier les objets de message, leur ajouter nos propriétés. Comme ils sont gérés par le code de quelqu'un d'autre, cela peut avoir de mauvaises conséquences. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md ================================================ Pour stocker une date, nous pouvons utiliser `WeakMap`: ```js let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, {text: "See you soon", from: "Alice"} ]; let readMap = new WeakMap(); readMap.set(messages[0], new Date(2017, 1, 1)); // objet Date que nous étudierons plus tard ``` ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md ================================================ importance: 5 --- # Stocker les dates de lectures Il existe un tableau de messages comme dans la [previous task](info:task/recipients-read). La situation est similaire. ```js let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, {text: "See you soon", from: "Alice"} ]; ``` La question qui se pose maintenant est la suivante : quelle structure de données suggérez-vous pour stocker les informations : "quand le message a-t-il été lu ?". Dans la tâche précédente, nous n'avions besoin que de stocker le fait "oui/non". Nous devons maintenant stocker la date et elle ne doit rester en mémoire que tant que le message n’a pas été nettoyé. P.S. Les dates peuvent être stockées en tant qu'objets de la classe intégrée `Date`, que nous couvrirons plus tard. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/article.md ================================================ # WeakMap et WeakSet Comme nous le savons du chapitre , le moteur JavaScript stocke une valeur en mémoire pendant qu'elle est accessible et peut potentiellement être utilisée. Par exemple : ```js let john = { name: "John" }; // l'objet est accessible, john en est la référence // écraser la référence john = null; *!* // l'objet sera supprimé de la mémoire */!* ``` Habituellement, les propriétés d'un objet ou des éléments d'un tableau ou d'une autre structure de données sont considérées comme accessibles et conservées en mémoire pendant que cette structure de données est en mémoire. Par exemple, si nous mettons un objet dans un tableau, alors que le tableau est vivant, l'objet sera également vivant, même s'il n'y a pas d'autres références. Comme ceci : ```js let john = { name: "John" }; let array = [ john ]; john = null; // écraser la référence *!* // l'objet précédemment référencé par john est stocké dans le tableau // donc il ne sera pas nettoyé // nous pouvons l'obtenir sous forme de array[0] */!* ``` Semblable à cela, si nous utilisons un objet comme clé dans un `Map` classique, alors que le `Map` existe, cet objet existe également. Il occupe de la mémoire et ne peut pas être nettoyé (garbage collected). Par example : ```js let john = { name: "John" }; let map = new Map(); map.set(john, "..."); john = null; // écraser la référence *!* // John est stocké à l'intérieur du map // nous pouvons l'obtenir en utilisant map.keys() */!* ``` [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) est fondamentalement différent à cet égard. Cela n'empêche pas le garbage collection des objets clés. Voyons ce que cela signifie sur des exemples. ## WeakMap La première différences entre [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) et [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) est que les clés doivent être des objets, pas des valeurs primitives : ```js run let weakMap = new WeakMap(); let obj = {}; weakMap.set(obj, "ok"); // fonctionne bien (object key) *!* // ne peut pas utiliser une chaîne de caractères comme clé weakMap.set("test", "Whoops"); // Erreur, parce que "test" n'est pas un objet */!* ``` Maintenant, si nous utilisons un objet comme clé, et qu'il n'y a pas d'autres références à cet objet -- il sera automatiquement supprimé de la mémoire (et du map). ```js let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); john = null; // on écrase la référence // John est supprimé de la mémoire ! ``` Comparez-le avec l'exemple du `Map` ci-dessus. Maintenant, si `john` n'existe que comme clé de `WeakMap` -- il sera automatiquement supprimé du map (et de la mémoire). `WeakMap` ne prend pas en charge l'itération et les méthodes `keys()`, `values()`, `entries()`, il n'y a donc aucun moyen d'en obtenir toutes les clés ou valeurs. `WeakMap` n'a que les méthodes suivantes : - [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) - [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) - [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) - [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) Pourquoi une telle limitation ? C'est pour des raisons techniques. Si un objet a perdu toutes les autres références (comme `john` dans le code ci-dessus), il doit être automatiquement nettoyé. Mais techniquement, ce n'est pas exactement spécifié *quand le nettoyage a lieu*. Le moteur JavaScript décide de cela. Il peut choisir d'effectuer le nettoyage de la mémoire immédiatement ou d'attendre et de faire le nettoyage plus tard lorsque d'autres suppressions se produisent. Donc, techniquement, le nombre d'éléments actuel d'un `WeakMap` n'est pas connu. Le moteur peut l'avoir nettoyé ou non, ou l'a fait partiellement. Pour cette raison, les méthodes qui accèdent à toutes les clés/valeurs ne sont pas prises en charge. Maintenant, où avons-nous besoin d'une telle structure de données ? ## Cas d'utilisation : données supplémentaires Le principal domaine d'application de `WeakMap` est un *stockage de données supplémentaire*. Si nous travaillons avec un objet qui "appartient" à un autre code, peut-être même une bibliothèque tierce, et que nous souhaitons stocker certaines données qui lui sont associées, cela ne devrait exister que lorsque l'objet est vivant -- alors `WeakMap` est exactement ce qu'il nous faut. Nous plaçons les données dans un `WeakMap`, en utilisant l'objet comme clé, et lorsque l'objet est nettoyé, ces données disparaissent automatiquement également. ```js weakMap.set(john, "secret documents"); // si John meurt, les documents secrets seront détruits automatiquement ``` Regardons un exemple. Par exemple, nous avons un code qui conserve un nombre de visites pour les utilisateurs. Les informations sont stockées dans un map : un objet utilisateur est la clé et le nombre de visites est la valeur. Lorsqu'un utilisateur quitte (son objet est nettoyé), nous ne voulons plus stocker son nombre de visites. Voici un exemple d'une fonction de comptage avec `Map` : ```js // 📁 visitsCount.js let visitsCountMap = new Map(); // map: user => visits count // augmentons le nombre de visites function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` Et voici une autre partie du code, peut-être un autre fichier qui l'utilise : ```js // 📁 main.js let john = { name: "John" }; countUser(john); // compter ses visites // plus tard, John nous quitte john = null; ``` Maintenant, l'objet `john` doit être nettoyé, mais cependant, il reste en mémoire, parce que c'est une clé dans `visitesCountMap`. Nous devons nettoyer `visitesCountMap` lorsque nous supprimons des utilisateurs, sinon il augmentera indéfiniment en mémoire. Un tel nettoyage peut devenir une tâche fastidieuse dans des architectures complexes. Nous pouvons éviter cela en utilisant `WeakMap` : ```js // 📁 visitsCount.js let visitsCountMap = new WeakMap(); // weakmap: user => visits count // augmentons le nombre de visites function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` Maintenant, nous n'avons plus à nettoyer `visitesCountMap`. Après que l'objet `john` devienne inaccessible autrement que en tant que clé de `WeakMap`, il est supprimé de la mémoire, en même temps que les informations de cette clé dans `WeakMap`. ## Cas d'utilisation : mise en cache Un autre exemple courant est la mise en cache. Nous pouvons stocker ("cache") les résultats d'une fonction, afin que les futurs appels sur le même objet puissent le réutiliser. Pour y parvenir, nous pouvons utiliser `Map` (scénario non optimal) : ```js run // 📁 cache.js let cache = new Map(); // calculons et mémorisons le résultat function process(obj) { if (!cache.has(obj)) { let result = /* calculs du résultat pour */ obj; cache.set(obj, result); return result; } return cache.get(obj); } *!* // Maintenant, utilisons process() dans un autre fichier : */!* // 📁 main.js let obj = {/* disons que nous avons un objet */}; let result1 = process(obj); // calculé // … plus tard, d'un autre endroit du code … let result2 = process(obj); // résultat mémorisé provenant du cache // … plus tard, lorsque l'objet n'est plus nécessaire : obj = null; alert(cache.size); // 1 (Ouch ! L'objet est toujours dans le cache, prenant de la mémoire !) ``` Pour plusieurs appels de `process(obj)` avec le même objet, il ne calcule le résultat que la première fois, puis le prend simplement dans `cache`. L'inconvénient est que nous devons nettoyer le `cache` lorsque l'objet n'est plus nécessaire. Si nous remplaçons `Map` par `WeakMap`, alors ce problème disparaît : le résultat mis en cache sera automatiquement supprimé de la mémoire une fois que l'objet sera nettoyé. ```js run // 📁 cache.js *!* let cache = new WeakMap(); */!* // calculons et mémorisons le résultat function process(obj) { if (!cache.has(obj)) { let result = /* calculer le résultat pour */ obj; cache.set(obj, result); return result; } return cache.get(obj); } // 📁 main.js let obj = {/* un objet */}; let result1 = process(obj); let result2 = process(obj); // … plus tard, lorsque l'objet n'est plus nécessaire : obj = null; // Impossible d'obtenir cache.size, car c'est un WeakMap, // mais c'est 0 ou bientôt 0 // Lorsque obj est nettoyé, les données mises en cache seront également supprimées ``` ## WeakSet `WeakSet` se comporte de la même manière : - Il est analogue à `Set`, mais nous pouvons seulement ajouter des objets à `WeakSet` (pas de primitives). - Un objet existe dans le set tant qu'il est accessible ailleurs. - Comme `Set`, il prend en charge [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) et [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), mais pas `size`, `keys()` et aucune itération. Étant "weak" (faible), il sert également de stockage supplémentaire. Mais pas pour des données arbitraires, mais plutôt pour des faits "oui/non". Une appartenance à `WeakSet` peut signifier quelque chose à propos de l'objet. Par exemple, nous pouvons ajouter des utilisateurs à `WeakSet` pour garder une trace de ceux qui ont visité notre site : ```js run let visitedSet = new WeakSet(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; visitedSet.add(john); // John nous a rendu visite visitedSet.add(pete); // Ensuite Pete visitedSet.add(john); // John encore // visitedSet a 2 utilisateurs maintenant // vérifions si John est venu alert(visitedSet.has(john)); // true // vérifions si Mary est venue alert(visitedSet.has(mary)); // false john = null; // visitedSet sera nettoyé automatiquement ``` La limitation la plus notable de `WeakMap` et `WeakSet` est l'absence d'itérations et l'impossibilité d'obtenir tout le contenu actuel. Cela peut sembler gênant, mais n'empêche pas `WeakMap`/`WeakSet` de faire leur travail principal -- être un stockage "supplémentaire" de données pour les objets qui sont stockés/gérés à un autre endroit. ## Résumé [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) est une sorte de collection `Map` qui n'autorise que des objets comme clés et les supprime avec la valeur associée une fois qu'ils deviennent inaccessibles par d'autres moyens. [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) est une sorte de collection `Set` qui ne stocke que des objets et les supprime une fois qu'ils deviennent inaccessibles par d'autres moyens. Leurs principaux avantages sont qu'ils ont une faible référence aux objets, de sorte qu'ils peuvent facilement être supprimés par le garbage collector. Cela se fait au prix de ne pas avoir de support pour `clear`, `size`, `keys`, `values`... `WeakMap` et `WeakSet` sont utilisées comme structures de données "secondaires" en plus du stockage d'objets "principal". Une fois que l'objet est retiré du stockage principal, s'il n'est trouvé que comme clé de `WeakMap` ou dans un `WeakSet`, il sera nettoyé automatiquement. ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/solution.js ================================================ function sumSalaries(salaries) { let sum = 0; for (let salary of Object.values(salaries)) { sum += salary; } return sum; } ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/test.js ================================================ describe("sumSalaries", function() { it("returns sum of salaries", function() { let salaries = { "John": 100, "Pete": 300, "Mary": 250 }; assert.equal( sumSalaries(salaries), 650 ); }); it("returns 0 for the empty object", function() { assert.strictEqual( sumSalaries({}), 0); }); }); ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md ================================================ ```js run demo function sumSalaries(salaries) { let sum = 0; for (let salary of Object.values(salaries)) { sum += salary; } return sum; // 650 } let salaries = { "John": 100, "Pete": 300, "Mary": 250 }; alert( sumSalaries(salaries) ); // 650 ``` Ou, éventuellement, nous pourrions aussi obtenir la somme en utilisant `Object.values` et `reduce`: ```js // boucles de reduce sur les salaires, // en les additionnant // et retourne le résultat function sumSalaries(salaries) { return Object.values(salaries).reduce((a, b) => a + b, 0) // 650 } ``` ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md ================================================ importance: 5 --- # Additionner les propriétés Il existe un objet `salaries` avec un nombre arbitraire de salaires. Ecrivez la fonction `sumSalaries(salaries)` qui retourne la somme des salaires en utilisant `Object.values` et la boucle `for..of`. Si `salaries` est vide, le résultat doit être `0`. Par exemple: ```js let salaries = { "John": 100, "Pete": 300, "Mary": 250 }; alert( sumSalaries(salaries) ); // 650 ``` ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/solution.js ================================================ function count(obj) { return Object.keys(obj).length; } ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/test.js ================================================ describe("count", function() { it("counts the number of properties", function() { assert.equal( count({a: 1, b: 2}), 2 ); }); it("returns 0 for an empty object", function() { assert.equal( count({}), 0 ); }); it("ignores symbolic properties", function() { assert.equal( count({ [Symbol('id')]: 1 }), 0 ); }); }); ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/02-count-properties/solution.md ================================================ ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md ================================================ importance: 5 --- # Compter les propriétés Ecrivez la fonction `count(obj)` qui retourne le nombre de propriétés qu'il y a dans l'objet: ```js let user = { name: 'John', age: 30 }; alert( count(user) ); // 2 ``` Essayer d'écrire le code le plus petit possible. P.S: Ignorez les propriétés symboliques, ne comptez que les propriétés "normales". ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/article.md ================================================ # Object.keys, values, entries Éloignons-nous des structures de données individuelles et parlons des itérations les concernant. Dans le chapitre précédent, nous avons vu les méthodes `map.keys()`, `map.values()`, `map.entries()`. Ces méthodes sont génériques, il existe une convention de les utiliser pour les structures de données. Si nous devions créer notre propre structure de données, nous devrions aussi les implémenter. Ils sont pris en charge par : - `Map` - `Set` - `Array` Les objets simples prennent également en charge des méthodes similaires, mais la syntaxe est un peu différente. ## Object.keys, values, entries Pour les objets simples, les méthodes suivantes sont disponibles : - [Object.keys(obj)](mdn:js/Object/keys) -- retourne un tableau de clés. - [Object.values(obj)](mdn:js/Object/values) -- retourne un tableau de valeurs. - [Object.entries(obj)](mdn:js/Object/entries) -- retourne un tableau de paires `[clé, valeur]`. Veuillez noter les différences (par rapport à map par exemple) : | | Map | Object | |-----------------|---------------|-------------------------------------------| | Syntaxe d'appel | `map.keys()` | `Object.keys(obj)`, mais pas `obj.keys()` | | Retours | iterable | "vrai" Array | La première différence est que nous devons appeler `Object.keys(obj)` et non `obj.keys()`. Pourquoi cela ? La principale raison est la flexibilité. N'oubliez pas que les objets sont une base de toutes les structures complexes en JavaScript. Ainsi, nous pouvons avoir un objet à nous comme `data` qui implémente sa propre méthode `data.values()`. Et nous pouvons toujours appeler `Object.values(data)`. La seconde différence réside dans le fait que les méthodes `Object.*` retourne de "réels" objets de tableau, et pas seulement des objets itératifs. C'est principalement pour des raisons historiques. Par exemple : ```js let user = { name: "John", age: 30 }; ``` - `Object.keys(user) = [name, age]` - `Object.values(user) = ["John", 30]` - `Object.entries(user) = [ ["name","John"], ["age",30] ]` Voici un exemple d'utilisation d'`Object.values` pour boucler les valeurs de propriété : ```js run let user = { name: "John", age: 30 }; // boucle sur les valeurs for (let value of Object.values(user)) { alert(value); // John, ensuite 30 } ``` ```warn header="Object.keys/values/entries ignorer les propriétés symboliques" Tout comme une boucle `for..in`, ces méthodes ignorent les propriétés qui utilisent `Symbol(...)` comme clés. D'habitude c'est pratique. Mais si nous voulons aussi des clés symboliques, il existe une méthode distincte [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) qui retourne un tableau composé uniquement de clés symboliques. De plus, la méthde [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) renvoie *toutes* les clés. ``` ## Transformer des objets Les objets manquent de nombreuses méthodes existantes pour les tableaux, par exemple `map`, `filter` et autres. Si nous souhaitons leur appliquer ces méthodes, nous pouvons utiliser `Object.entries` suivis par `Object.fromEntries` : 1. Utilisons `Object.entries(obj)` pour obtenir un tableau de paires clé / valeur de `obj`. 2. Utilisons des méthodes de tableau sur ce tableau, par exemple `map`, pour transformer ces paires clé / valeur. 3. Utilisons `Object.fromEntries(array)` sur le tableau résultant pour le reconvertir en objet. Par exemple, nous avons un objet avec des prix et aimerions les doubler : ```js run let prices = { banana: 1, orange: 2, meat: 4, }; *!* let doublePrices = Object.fromEntries( // convertir les prix en tableau, mapper chaque paire clé / valeur dans une autre paire // puis fromEntries restitue l'objet Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) ); */!* alert(doublePrices.meat); // 8 ``` Cela peut sembler difficile au premier abord, mais cela devient facile à comprendre après l’avoir utilisé une ou deux fois. Nous pouvons faire de puissantes chaînes de transformations de cette façon. ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md ================================================ ```js run let user = { name: "John", years: 30 }; let {name, years: age, isAdmin = false} = user; alert( name ); // John alert( age ); // 30 alert( isAdmin ); // false ``` ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md ================================================ importance: 5 --- # L'affectation par décomposition Nous avons un objet : ```js let user = { name: "John", years: 30 }; ``` Écrivez l'affectation par décomposition qui se lit comme suit : - La propriété `name` dans la variable `name`. - La propriété `years` dans la variable `age`. - La propriété `isAdmin` dans la variable `isAdmin` (false si absent) Voici un exemple des valeurs après votre affectation : ```js let user = { name: "John", years: 30 }; // votre code à gauche :: // ... = user alert( name ); // John alert( age ); // 30 alert( isAdmin ); // false ``` ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js ================================================ function topSalary(salaries) { let maxSalary = 0; let maxName = null; for(const [name, salary] of Object.entries(salaries)) { if (maxSalary < salary) { maxSalary = salary; maxName = name; } } return maxName; } ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/test.js ================================================ describe("topSalary", function() { it("returns top-paid person", function() { let salaries = { "John": 100, "Pete": 300, "Mary": 250 }; assert.equal( topSalary(salaries), "Pete" ); }); it("returns null for the empty object", function() { assert.isNull( topSalary({}) ); }); }); ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/6-max-salary/solution.md ================================================ ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md ================================================ importance: 5 --- # Le salaire maximal Il y a un objet `salaries` : ```js let salaries = { "John": 100, "Pete": 300, "Mary": 250 }; ``` Créer la fonction `topSalary(salaries)` qui renvoie le nom de la personne la mieux payée. - Si `salaries` est vide, devrait retourner `null`. - S'il y a plusieurs personnes les mieux payées, renvoyez-en une. P.S. Utilisez `Object.entries` et la décomposition pour parcourir les paires clé / valeur. ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/article.md ================================================ # L'affectation par décomposition Les deux structures de données les plus utilisées en JavaScript sont `Object` et `Array`. - Les objets nous permettent de créer une seule entité qui stocke les éléments de données par clé. - Les tableaux nous permettent de rassembler des éléments de données dans une liste ordonnée. Mais lorsque nous transmettons ceux-ci à une fonction, il se peut que celle-ci n'ait pas besoin d'un objet / tableau dans son ensemble, mais plutôt de morceaux individuels. *L'affectation par décomposition* est une syntaxe spéciale qui nous permet de "décompresser" des tableaux ou des objets dans un ensemble de variables, ce qui est parfois plus pratique. La décomposition fonctionne également très bien avec des fonctions complexes comportant de nombreux paramètres, valeurs par défaut, etc. ## Décomposition d'un tableau Voici un exemple de la façon dont un tableau est décomposé en variables : ```js // nous avons un tableau avec le prénom et le nom let arr = ["John", "Smith"] *!* // l'affectation par décomposition // sets firstName = arr[0] // and surname = arr[1] let [firstName, surname] = arr; */!* alert(firstName); // John alert(surname); // Smith ``` Maintenant, nous pouvons travailler avec des variables plutôt que des parties du tableau. Cela a l'air pas mal quand c'est combiné avec `split` ou tout autre méthode de renvoi de tableau : ```js run let [firstName, surname] = "John Smith".split(' '); alert(firstName); // John alert(surname); // Smith ``` Comme vous pouvez le voir, la syntaxe est simple. Il y a cependant plusieurs détails particuliers. Voyons plus d'exemples, pour mieux le comprendre. ````smart header="\"Décomposition\" ne veut pas dire \"destruction\"." Cette manipulation est appelée "affectation par décomposition", car elle "se décompose" en copiant ses éléments dans des variables. Mais le tableau lui-même n'est pas modifié. C’est juste une façon plus courte d’écrire : ```js // let [firstName, surname] = arr; let firstName = arr[0]; let surname = arr[1]; ``` ```` ````smart header="Ignorer les éléments en utilisant des virgules" Les éléments indésirables du tableau peuvent également être supprimés via une virgule supplémentaire : ```js run *!* // second element is not needed let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; */!* alert( title ); // Consul ``` Dans le code ci-dessus, le deuxième élément du tableau est ignoré, le troisième est attribué à `title` et le reste du tableau est également ignoré (car il n'y a pas de variables pour eux). ```` ````smart header="Fonctionne avec n'importe quel itérable à droite" ...En fait, nous pouvons l’utiliser avec n’importe quel itérable, pas seulement les tableaux : ```js let [a, b, c] = "abc"; // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3]); ``` That works, because internally a destructuring assignment works by iterating over the right value. It's a kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values. ```` ````smart header="Attribuer n'importe quoi à la partie gauche" Nous pouvons utiliser n'importe quels "assignables" à gauche. Par exemple, une propriété d'objet : ```js run let user = {}; [user.name, user.surname] = "John Smith".split(' '); alert(user.name); // John alert(user.surname); // Smith ``` ```` ````smart header="Boucler avec .entries()" Dans le chapitre précédent, nous avons vu la méthode [Object.entries(obj)](mdn:js/Object/entries). Nous pouvons l'utiliser avec la décomposition pour boucler sur les clés et valeurs d'un objet : ```js run let user = { name: "John", age: 30 }; // boucler sur les clés et les valeurs *!* for (let [key, value] of Object.entries(user)) { */!* alert(`${key}:${value}`); // name:John, ensuite age:30 } ``` Le code similaire pour un `Map` est plus simple, car il est itérable : ```js run let user = new Map(); user.set("name", "John"); user.set("age", "30"); *!* // Map est itéré sous forme de paires [key, value], ce qui est très pratique pour la déstructuration for (let [key, value] of user) { */!* alert(`${key}:${value}`); // name:John, ensuite age:30 } ``` ```` ````smart header="Astuce d'échange de variables" Il existe une astuce bien connue pour permuter les valeurs de deux variables à l'aide d'une affectation de déstructuration : ```js run let guest = "Jane"; let admin = "Pete"; // Permutons les valeurs : guest=Pete, admin=Jane *!* [guest, admin] = [admin, guest]; */!* alert(`${guest} ${admin}`); // Pete Jane (échangé avec succès !) ``` Ici, nous créons un tableau temporaire de deux variables et le déstructurons immédiatement dans l'ordre permuté. Nous pouvons échanger plus de deux variables de cette façon. ```` ### Le rest '...' Habituellement, si le tableau est plus long que la liste à gauche, les éléments "supplémentaires" sont omis. Par exemple, ici, seuls deux éléments sont pris et le reste est simplement ignoré : ```js run let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert(name1); // Julius alert(name2); // Caesar // Les autres éléments ne sont affectés nulle part ``` Si nous souhaitons également rassembler tout ce qui suit, nous pouvons ajouter un paramètre supplémentaire qui permet d'obtenir "le reste" à l'aide de trois points `"..."` : ```js run let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* // reste est un tableau d'éléments, à partir du 3ème alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 */!* ``` La valeur de `rest` est le tableau des éléments du tableau restants. Nous pouvons utiliser n’importe quel autre nom de variable à la place de `rest`, assurez-vous simplement qu’il a trois points devant lui et soit placé en dernier dans l’affectation par décomposition. ```js run let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; // maintenant titles = ["Consul", "of the Roman Republic"] ``` ### Les valeurs par défaut Si le tableau est plus court que la liste des variables à gauche, il n'y aura aucune erreur. Les valeurs absentes sont considérées comme non définies : ```js run *!* let [firstName, surname] = []; */!* alert(firstName); // undefined alert(surname); // undefined ``` Si nous voulons qu'une valeur "par défaut" remplace la valeur manquante, nous pouvons la fournir en utilisant `=` : ```js run *!* // les valeurs par défaut let [name = "Guest", surname = "Anonymous"] = ["Julius"]; */!* alert(name); // Julius (depuis le tableau) alert(surname); // Anonymous (valeur par défaut) ``` Les valeurs par défaut peuvent être des expressions plus complexes ou même des appels de fonction. Ils ne sont évalués que si la valeur n'est pas fournie. Par exemple, nous utilisons ici la fonction `prompt` pour deux valeurs par défaut : ```js run // ne demande que le nom de famille let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"]; alert(name); // Julius (depuis le tableau) alert(surname); // saisi par l'utilisateur ``` Notez que le `prompt` ne s'exécutera que pour la valeur manquante (`surname`). ## Décomposition d'object L'affectation par décomposition fonctionne également avec les objets. La syntaxe de base est la suivante : ```js let {var1, var2} = {var1:…, var2:…} ``` Nous devrions avoir un objet existant sur le côté droit, que nous voulons diviser en variables. La partie gauche contient un "modèle" de type objet pour les propriétés correspondantes. Dans le cas le plus simple, c'est une liste de noms de variables dans `{...}`. Par exemple : ```js run let options = { title: "Menu", width: 100, height: 200 }; *!* let {title, width, height} = options; */!* alert(title); // Menu alert(width); // 100 alert(height); // 200 ``` Les propriétés `options.title`, `options.width` et `options.height` sont affectées aux variables correspondantes. L'ordre n'a pas d'importance. Cela fonctionne aussi : ```js // changé l'ordre dans let {...} let {height, width, title} = { title: "Menu", height: 200, width: 100 } ``` Le pattern à gauche peut être plus complexe et spécifier le mapping entre propriétés et variables. Si nous voulons affecter une propriété à une variable portant un autre nom, par exemple, `options.width` pour aller dans la variable nommée `w`, alors nous pouvons la définir en utilisant deux points : ```js run let options = { title: "Menu", width: 100, height: 200 }; *!* // { sourceProperty: targetVariable } let {width: w, height: h, title} = options; */!* // width -> w // height -> h // title -> title alert(title); // Menu alert(w); // 100 alert(h); // 200 ``` Les deux points montrent "quoi: va où". Dans l'exemple ci-dessus, la propriété `width` est définie sur `w`, la propriété `height` est définie sur `h` et le `title` est attribué au même nom. Pour les propriétés potentiellement manquantes, nous pouvons définir les valeurs par défaut à l'aide de `"="`, comme ceci : ```js run let options = { title: "Menu" }; *!* let {width = 100, height = 200, title} = options; */!* alert(title); // Menu alert(width); // 100 alert(height); // 200 ``` Comme pour les tableaux ou les paramètres de fonction, les valeurs par défaut peuvent être des expressions ou même des appels de fonction. Elles seront évaluées si la valeur n'est pas fournie. Le code `prompt` ci-dessous demande la `width`, mais pas le `title`. ```js run let options = { title: "Menu" }; *!* let {width = prompt("width?"), title = prompt("title?")} = options; */!* alert(title); // Menu alert(width); // saisi par l'utilisateur ``` Nous pouvons également combiner les deux points et l'égalité : ```js run let options = { title: "Menu" }; *!* let {width: w = 100, height: h = 200, title} = options; */!* alert(title); // Menu alert(w); // 100 alert(h); // 200 ``` Si nous avons un objet complexe avec de nombreuses propriétés, nous pouvons extraire que ce dont nous avons besoin : ```js run let options = { title: "Menu", width: 100, height: 200 }; // extraire uniquement le titre en tant que variable let { title } = options; alert(title); // Menu ``` ### Le pattern rest "..." Et si l'objet a plus de propriétés que de variables ? Peut-on en prendre puis assigner le "rest" quelque part ? Nous pouvons utiliser le modèle rest, comme nous l'avons fait avec les tableaux. Il n'est pas pris en charge par certains navigateurs plus anciens (IE, utilisez Babel pour le polyfiller), mais fonctionne avec les modernes. Cela ressemble à ceci : ```js run let options = { title: "Menu", height: 200, width: 100 }; *!* // title = propriété nommée title // rest = objet avec le reste des propriétés let {title, ...rest} = options; */!* // maintenant title="Menu", rest={height: 200, width: 100} alert(rest.height); // 200 alert(rest.width); // 100 ``` ````smart header="Attention, sans `let`, ça coince" Dans les exemples ci-dessus, les variables ont été déclarées juste avant l'affectation : `let {…} = {…}`. Bien sûr, nous pourrions aussi utiliser des variables existantes. Mais il y a un problème. Cela ne fonctionnera pas : ```js run let title, width, height; // erreur dans cette ligne {title, width, height} = {title: "Menu", width: 200, height: 100}; ``` Le problème est que JavaScript traite `{...}` dans le flux de code principal (pas dans une autre expression) en tant que bloc de code. De tels blocs de code peuvent être utilisés pour regrouper des instructions, comme ceci : ```js run { // un bloc de code let message = "Hello"; // ... alert( message ); } ``` Donc ici, JavaScript suppose que nous avons un bloc de code, c'est pourquoi il y a une erreur. Nous voulons plutôt la déstructuration. Pour montrer à JavaScript qu'il ne s'agit pas d'un bloc de code, nous pouvons envelopper l'expression entre parenthèses `(...)` : ```js run let title, width, height; // maintenant c'est bon *!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; alert( title ); // Menu ``` ```` ## Décomposition imbriquée Si un objet ou un tableau contient d'autres objets et tableaux imbriqués, nous pouvons utiliser des modèles à gauche plus complexes pour extraire des parties plus profondes. Dans le code ci-dessous, `options` a un autre objet dans la propriété `size` et un tableau dans la propriété `items`. Le modèle à gauche de l'affectation a la même structure pour en extraire des valeurs : ```js run let options = { size: { width: 100, height: 200 }, items: ["Cake", "Donut"], extra: true }; // affectation par décomposition divisée sur plusieurs lignes pour la clarté let { size: { // mettre la taille ici width, height }, items: [item1, item2], // attribuer des éléments ici title = "Menu" // non présent dans l'objet (la valeur par défaut est utilisée) } = options; alert(title); // Menu alert(width); // 100 alert(height); // 200 alert(item1); // Cake alert(item2); // Donut ``` Toutes les propriétés de l'objet `options`, à l'exception de `extra` qui est absente dans la partie gauche, sont affectés aux variables correspondantes : ![](destructuring-complex.svg) Finnalement nous avons `width`, `height`, `item1`, `item2` et `title` à partir de la valeur par défaut. Notez qu'il n'y a pas de variables pour `size` et `items`, car nous prenons leur contenu à la place. ## Paramètres de fonction intelligente Il peut arriver qu'une fonction ait plusieurs paramètres, dont la plupart sont facultatifs. C’est particulièrement vrai pour les interfaces utilisateur. Imaginez une fonction qui crée un menu. Il peut avoir une largeur, une hauteur, un titre, une liste d’articles, etc. Voici une mauvaise façon d’écrire ce genre de fonction : ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { // ... } ``` Dans la vraie vie, le problème est de savoir comment retenir l'ordre des arguments. Habituellement, les IDE essaient de nous aider, surtout si le code est bien documenté, mais quand même… Un autre problème est de savoir comment appeler une fonction lorsque la plupart des paramètres sont corrects par défaut. Comme ceci ? ```js // undefined où les valeurs par défaut sont adéquates showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` C’est moche. Et devient illisible lorsque nous traitons plus de paramètres. La décomposition vient à la rescousse ! Nous pouvons passer des paramètres sous forme d'objet, et la fonction les décomposent immédiatement en variables : ```js run // on passe un objet à fonction let options = { title: "My menu", items: ["Item1", "Item2"] }; // ...et il est immédiatement étendu aux variables function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []}*/!*) { // title, items – pris des options, // width, height – défauts utilisés alert( `${title} ${width} ${height}` ); // My Menu 200 100 alert( items ); // Item1, Item2 } showMenu(options); ``` Nous pouvons également utiliser une décomposition plus complexe avec des objets imbriqués et des mappings de deux points : ```js run let options = { title: "My menu", items: ["Item1", "Item2"] }; *!* function showMenu({ title = "Untitled", width: w = 100, // width va à w height: h = 200, // height va à h items: [item1, item2] // items premier élément va à item1, deuxième à item2 }) { */!* alert( `${title} ${w} ${h}` ); // My Menu 100 200 alert( item1 ); // Item1 alert( item2 ); // Item2 } showMenu(options); ``` La syntaxe complète est la même que pour une affectation par décomposition : ```js function({ incomingProperty: varName = defaultValue ... }) ``` Ensuite, pour un objet de paramètres, il y aura une variable `varName` pour la propriété `incomingProperty`, avec `defaultValue` par défaut. Veuillez noter qu'une telle déstructuration suppose que `showMenu()` a un argument. Si nous voulons toutes les valeurs par défaut, alors nous devrions spécifier un objet vide : ```js showMenu({}); // ok, toutes les valeurs par défaut showMenu(); // cela donnerait une erreur ``` Nous pouvons résoudre ce problème en faisant de `{}` la valeur par défaut pour l'objet entier de paramètres : ```js run function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { alert( `${title} ${width} ${height}` ); } showMenu(); // Menu 100 200 ``` Dans le code ci-dessus, la totalité des arguments objet est `{}` par défaut, il y a donc toujours quelque chose à décomposer. ## Résumé - L'affectation par décomposition permet de mapper instantanément un objet ou un tableau sur de nombreuses variables. - La syntaxe complète de l'objet : ```js let {prop : varName = default, ...rest} = object ``` Cela signifie que la propriété `prop` doit aller dans la variable `varName` et que, si aucune propriété de ce type n'existe, la valeur `default` doit être utilisée. Les propriétés d'objet sans mappage sont copiées dans l'objet `rest`. - La syntaxe complète du tableau : ```js let [item1 = default, item2, ...rest] = array ``` Le premier item va à `item1`; le second passe à `item2`, tout le reste du tableau correspond au `rest`. - Il est possible d'extraire des données de tableaux / objets imbriqués, pour cela le côté gauche doit avoir la même structure que le droit. ================================================ FILE: 1-js/05-data-types/11-date/1-new-date/solution.md ================================================ The `new Date` constructor uses the local time zone. So the only important thing to remember is that months start from zero. So February has number 1. Here's an example with numbers as date components: ```js run //new Date(year, month, date, hour, minute, second, millisecond) let d1 = new Date(2012, 1, 20, 3, 12); alert( d1 ); ``` We could also create a date from a string, like this: ```js run //new Date(datastring) let d2 = new Date("2012-02-20T03:12"); alert( d2 ); ``` ================================================ FILE: 1-js/05-data-types/11-date/1-new-date/task.md ================================================ importance: 5 --- # Créer une date Créez un objet `Date` pour la date: 20 février 2012, 3h12. Le fuseau horaire est local. Montrez-le en utilisant `alert`. ================================================ FILE: 1-js/05-data-types/11-date/2-get-week-day/_js.view/solution.js ================================================ function getWeekDay(date) { let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; return days[date.getDay()]; } ================================================ FILE: 1-js/05-data-types/11-date/2-get-week-day/_js.view/test.js ================================================ describe("getWeekDay", function() { it("3 January 2014 - friday", function() { assert.equal(getWeekDay(new Date(2014, 0, 3)), 'FR'); }); it("4 January 2014 - saturday", function() { assert.equal(getWeekDay(new Date(2014, 0, 4)), 'SA'); }); it("5 January 2014 - sunday", function() { assert.equal(getWeekDay(new Date(2014, 0, 5)), 'SU'); }); it("6 January 2014 - monday", function() { assert.equal(getWeekDay(new Date(2014, 0, 6)), 'MO'); }); it("7 January 2014 - tuesday", function() { assert.equal(getWeekDay(new Date(2014, 0, 7)), 'TU'); }); it("8 January 2014 - wednesday", function() { assert.equal(getWeekDay(new Date(2014, 0, 8)), 'WE'); }); it("9 January 2014 - thursday", function() { assert.equal(getWeekDay(new Date(2014, 0, 9)), 'TH'); }); }); ================================================ FILE: 1-js/05-data-types/11-date/2-get-week-day/solution.md ================================================ La méthode `date.getDay()` renvoie le numéro du jour de la semaine à partir du dimanche. Faisons un tableau des jours de la semaine afin d’obtenir le nom du jour par son numéro: ```js run demo function getWeekDay(date) { let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; return days[date.getDay()]; } let date = new Date(2014, 0, 3); // 3 Jan 2014 alert( getWeekDay(date) ); // FR ``` ================================================ FILE: 1-js/05-data-types/11-date/2-get-week-day/task.md ================================================ importance: 5 --- # Montrer un jour de la semaine Ecrivez une fonction `getWeekDay(date)` pour afficher le jour de la semaine sous forme abrégée: 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'. Par exemple: ```js no-beautify let date = new Date(2012, 0, 3); // 3 Janvier 2012 alert( getWeekDay(date) ); // devrait afficher "TU" ``` ================================================ FILE: 1-js/05-data-types/11-date/3-weekday/_js.view/solution.js ================================================ function getLocalDay(date) { let day = date.getDay(); if (day == 0) { // semaine 0 (dimanche) est 7 en européen day = 7; } return day; } ================================================ FILE: 1-js/05-data-types/11-date/3-weekday/_js.view/test.js ================================================ describe("getLocalDay returns the \"european\" weekday", function() { it("3 January 2014 - friday", function() { assert.equal(getLocalDay(new Date(2014, 0, 3)), 5); }); it("4 January 2014 - saturday", function() { assert.equal(getLocalDay(new Date(2014, 0, 4)), 6); }); it("5 January 2014 - sunday", function() { assert.equal(getLocalDay(new Date(2014, 0, 5)), 7); }); it("6 January 2014 - monday", function() { assert.equal(getLocalDay(new Date(2014, 0, 6)), 1); }); it("7 January 2014 - tuesday", function() { assert.equal(getLocalDay(new Date(2014, 0, 7)), 2); }); it("8 January 2014 - wednesday", function() { assert.equal(getLocalDay(new Date(2014, 0, 8)), 3); }); it("9 January 2014 - thursday", function() { assert.equal(getLocalDay(new Date(2014, 0, 9)), 4); }); }); ================================================ FILE: 1-js/05-data-types/11-date/3-weekday/solution.md ================================================ ================================================ FILE: 1-js/05-data-types/11-date/3-weekday/task.md ================================================ importance: 5 --- # Jour de la semaine européenne Les pays européens ont des jours de la semaine commençant par lundi (numéro 1), puis mardi (numéro 2) et jusqu'au dimanche (numéro 7). Ecrivez une fonction `getLocalDay(date)` qui renvoie le jour de la semaine "européen" pour `date`. ```js no-beautify let date = new Date(2012, 0, 3); // 3 Janvier 2012 alert( getLocalDay(date) ); // mardi, devrait afficher 2 ``` ================================================ FILE: 1-js/05-data-types/11-date/4-get-date-ago/_js.view/solution.js ================================================ function getDateAgo(date, days) { let dateCopy = new Date(date); dateCopy.setDate(date.getDate() - days); return dateCopy.getDate(); } ================================================ FILE: 1-js/05-data-types/11-date/4-get-date-ago/_js.view/test.js ================================================ describe("getDateAgo", function() { it("1 day before 02.01.2015 -> day 1", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 1), 1); }); it("2 days before 02.01.2015 -> day 31", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 2), 31); }); it("100 days before 02.01.2015 -> day 24", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 100), 24); }); it("365 days before 02.01.2015 -> day 2", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 365), 2); }); it("does not modify the given date", function() { let date = new Date(2015, 0, 2); let dateCopy = new Date(date); getDateAgo(dateCopy, 100); assert.equal(date.getTime(), dateCopy.getTime()); }); }); ================================================ FILE: 1-js/05-data-types/11-date/4-get-date-ago/solution.md ================================================ L'idée est simple: soustraire un nombre donné de jours à partir de la `date`: ```js function getDateAgo(date, days) { date.setDate(date.getDate() - days); return date.getDate(); } ``` ...Mais la fonction ne doit pas changer la `date`. C'est une chose importante, car le code externe qui nous donne la date ne s'attend pas à ce qu'il change. Pour le mettre en oeuvre, clonons la date, comme ceci: ```js run demo function getDateAgo(date, days) { let dateCopy = new Date(date); dateCopy.setDate(date.getDate() - days); return dateCopy.getDate(); } let date = new Date(2015, 0, 2); alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) ``` ================================================ FILE: 1-js/05-data-types/11-date/4-get-date-ago/task.md ================================================ importance: 4 --- # Quel jour du mois était il y a plusieurs jours ? Créez une fonction `getDateAgo(date, days)` pour renvoyer le `days` précédent la date `date`. Par exemple, si aujourd'hui on est le 20, alors `getDateAgo(new Date(), 1)` doit être le 19 et `getDateAgo(new Date(), 2)` doit être le 18. elle doit fonctionner de manière fiable sur plus de 365 jours. ```js let date = new Date(2015, 0, 2); alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) ``` P.S. La fonction ne doit pas modifier la `date` donnée. ================================================ FILE: 1-js/05-data-types/11-date/5-last-day-of-month/_js.view/solution.js ================================================ function getLastDayOfMonth(year, month) { let date = new Date(year, month + 1, 0); return date.getDate(); } ================================================ FILE: 1-js/05-data-types/11-date/5-last-day-of-month/_js.view/test.js ================================================ describe("getLastDayOfMonth", function() { it("last day of 01.01.2012 - 31", function() { assert.equal(getLastDayOfMonth(2012, 0), 31); }); it("last day of 01.02.2012 - 29 (leap year)", function() { assert.equal(getLastDayOfMonth(2012, 1), 29); }); it("last day of 01.02.2013 - 28", function() { assert.equal(getLastDayOfMonth(2013, 1), 28); }); }); ================================================ FILE: 1-js/05-data-types/11-date/5-last-day-of-month/solution.md ================================================ Créons une date en utilisant le mois suivant, mais passons zéro comme jour: ```js run demo function getLastDayOfMonth(year, month) { let date = new Date(year, month + 1, 0); return date.getDate(); } alert( getLastDayOfMonth(2012, 0) ); // 31 alert( getLastDayOfMonth(2012, 1) ); // 29 alert( getLastDayOfMonth(2013, 1) ); // 28 ``` Normalement, les dates commencent à 1, mais techniquement, nous pouvons passer n'importe quel nombre, la date s'ajustera automatiquement. Ainsi, lorsque nous passons 0, cela signifie "un jour avant le 1er jour du mois", autrement dit: "le dernier jour du mois précédent". ================================================ FILE: 1-js/05-data-types/11-date/5-last-day-of-month/task.md ================================================ importance: 5 --- # Dernier jour du mois ? Ecrivez une fonction `getLastDayOfMonth(year, month)` qui renvoie le dernier jour du mois. Parfois, c'est 30, 31 ou même 28/29 février. Paramètres: - `year` -- année à quatre chiffres, par exemple 2012. - `month` -- mois, de 0 à 11. Par exemple, `getLastDayOfMonth(2012, 1) = 29` (année bissextile, février). ================================================ FILE: 1-js/05-data-types/11-date/6-get-seconds-today/solution.md ================================================ Pour obtenir le nombre de secondes, nous pouvons générer une date à l'aide du jour et de l'heure en cours 00:00:00, puis la soustraire de "maintenant". La différence est le nombre de millisecondes à partir du début de la journée, qu'il faut diviser par 1000 pour obtenir les secondes: ```js run function getSecondsToday() { let now = new Date(); // crée un objet en utilisant le jour / mois / année en cours let today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); let diff = now - today; // ms difference return Math.round(diff / 1000); // arrondir en secondes } alert( getSecondsToday() ); ``` Une autre solution serait d’obtenir les heures / minutes / secondes et de les convertir en secondes: ```js run function getSecondsToday() { let d = new Date(); return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); } alert( getSecondsToday() ); ``` ================================================ FILE: 1-js/05-data-types/11-date/6-get-seconds-today/task.md ================================================ importance: 5 --- # Combien de secondes se sont écoulées aujourd'hui ? Ecrivez une fonction `getSecondsToday()` qui renvoie le nombre de secondes depuis le début de la journée. Par exemple, s'il est maintenant `10:00 am`, et qu'il n'y a pas de décalage de l'heure d'été, alors : ```js getSecondsToday() == 36000 // (3600 * 10) ``` La fonction devrait fonctionner dans n'importe quel jour. Autrement dit, il ne devrait pas avoir de valeur "aujourd'hui" codée en dur. ================================================ FILE: 1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md ================================================ Pour obtenir le nombre de millisecondes jusqu'à demain, nous pouvons, à partir de "demain 00:00:00", soustraire la date actuelle. Tout d'abord, nous générons ce "demain", puis nous le faisons: ```js run function getSecondsToTomorrow() { let now = new Date(); // date de demain let tomorrow = new Date(now.getFullYear(), now.getMonth(), *!*now.getDate()+1*/!*); let diff = tomorrow - now; // difference in ms return Math.round(diff / 1000); // convert to seconds } ``` solution alternative: ```js run function getSecondsToTomorrow() { let now = new Date(); let hour = now.getHours(); let minutes = now.getMinutes(); let seconds = now.getSeconds(); let totalSecondsToday = (hour * 60 + minutes) * 60 + seconds; let totalSecondsInADay = 86400; return totalSecondsInADay - totalSecondsToday; } ``` Veuillez noter que de nombreux pays ont l'heure d'été (DST), il peut donc y avoir des jours avec 23 ou 25 heures. Nous voudrons peut-être traiter ces jours séparément. ================================================ FILE: 1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md ================================================ importance: 5 --- # Combien de secondes jusqu'à demain ? Créez une focntion `getSecondsToTomorrow()` qui renvoie le nombre de secondes jusqu'à demain. Par exemple, s'il est maintenant `23:00`, alors: ```js getSecondsToTomorrow() == 3600 ``` P.S. La fonction devrait fonctionner à tout moment, le «aujourd'hui» n'est pas codé en dur. ================================================ FILE: 1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js ================================================ function formatDate(date) { let diff = new Date() - date; // the difference in milliseconds if (diff < 1000) { // less than 1 second return 'right now'; } let sec = Math.floor(diff / 1000); // convert diff to seconds if (sec < 60) { return sec + ' sec. ago'; } let min = Math.floor(diff / 60000); // convert diff to minutes if (min < 60) { return min + ' min. ago'; } // format the date // add leading zeroes to single-digit day/month/hours/minutes let d = date; d = [ '0' + d.getDate(), '0' + (d.getMonth() + 1), '' + d.getFullYear(), '0' + d.getHours(), '0' + d.getMinutes() ].map(component => component.slice(-2)); // take last 2 digits of every component // join the components into date return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); } ================================================ FILE: 1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js ================================================ describe("formatDate", function() { it("shows 1ms ago as \"right now\"", function() { assert.equal(formatDate(new Date(new Date - 1)), 'right now'); }); it('"30 seconds ago"', function() { assert.equal(formatDate(new Date(new Date - 30 * 1000)), "30 sec. ago"); }); it('"5 minutes ago"', function() { assert.equal(formatDate(new Date(new Date - 5 * 60 * 1000)), "5 min. ago"); }); it("older dates as DD.MM.YY HH:mm", function() { assert.equal(formatDate(new Date(2014, 2, 1, 11, 22, 33)), "01.03.14 11:22"); }); }); ================================================ FILE: 1-js/05-data-types/11-date/8-format-date-relative/solution.md ================================================ Pour obtenir l'heure à partir de la `date` jusqu'à maintenant -- allons soutraire les dates. ```js run demo function formatDate(date) { let diff = new Date() - date; // la différence en millisecondes if (diff < 1000) { // moins d'une seconde return 'right now'; } let sec = Math.floor(diff / 1000); // convertir la différence en secondes if (sec < 60) { return sec + ' sec. ago'; } let min = Math.floor(diff / 60000); // convertir la différence en minutes if (min < 60) { return min + ' min. ago'; } // formater la date // ajoute des zéros au premier jour / mois / heure / minutes let d = date; d = [ '0' + d.getDate(), '0' + (d.getMonth() + 1), '' + d.getFullYear(), '0' + d.getHours(), '0' + d.getMinutes() ].map(component => component.slice(-2)); // prend les 2 derniers chiffres de chaque composant // joindre les composants en date return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); } alert( formatDate(new Date(new Date - 1)) ); // "right now" alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" // date d'hier comme ceci 31.12.2016 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` solution alternative: ```js run function formatDate(date) { let dayOfMonth = date.getDate(); let month = date.getMonth() + 1; let year = date.getFullYear(); let hour = date.getHours(); let minutes = date.getMinutes(); let diffMs = new Date() - date; let diffSec = Math.round(diffMs / 1000); let diffMin = diffSec / 60; let diffHour = diffMin / 60; // formatage year = year.toString().slice(-2); month = month < 10 ? '0' + month : month; dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; hour = hour < 10 ? '0' + hour : hour; minutes = minutes < 10 ? '0' + minutes : minutes; if (diffSec < 1) { return 'right now'; } else if (diffMin < 1) { return `${diffSec} sec. ago` } else if (diffHour < 1) { return `${diffMin} min. ago` } else { return `${dayOfMonth}.${month}.${year} ${hour}:${minutes}` } } ``` ================================================ FILE: 1-js/05-data-types/11-date/8-format-date-relative/task.md ================================================ importance: 4 --- # Formater la date relative Créez une fonction `formatDate(date)` qui devrait formater la `date` comme ceci: - Si depuis la `date` il s'est passé moins de 1 seconde, alors `"right now"`. - Sinon, si il s'est passé moins d'une minute, alors `"n sec. ago"`. - Sinon, si c'est moins d'une heure, alors `"m min. ago"`. - Sinon, la date complète au format `"DD.MM.YY HH:mm"`. C'est à dire: `"day.month.year hours:minutes"`, le tout au format 2 chiffres, par exemple. `31.12.16 10:00`. Par exemple: ```js alert( formatDate(new Date(new Date - 1)) ); // "right now" alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" // date d'hier comme ceci 31.12.16 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` ================================================ FILE: 1-js/05-data-types/11-date/article.md ================================================ # Date et Temps Faisons connaissance avec un nouvel objet intégré : [Date](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date). Il stocke la date, l'heure et fournit des méthodes pour la gestion de la date et de l'heure. Par exemple, nous pouvons l'utiliser pour enregistrer les heures de création / modification, pour mesurer l'heure ou simplement pour imprimer la date du jour. ## Creation Pour créer un nouvel objet `Date`, appelez `new Date()` avec l'un des arguments suivants : `new Date()` : Sans arguments - crée un objet `Date` pour la date et l'heure actuelles. ```js run let now = new Date(); alert( now ); // affiche la date/heure actuelle ``` `new Date(millisecondes)` : Crée un objet `Date` avec l'heure correspondant au nombre de millisecondes (1/1000 de seconde) écoulée après le 1er janvier 1970 UTC. ```js run // 0 signifie 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 ); // maintenant, ajoutez 24 heures, cela devient 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 ); ``` Un nombre entier représentant le nombre de millisecondes écoulées depuis le début de 1970 est appelé un *timestamp* (horodatage). C’est une représentation numérique d’une date. Nous pouvons toujours créer une date à partir d'un *horodatage* à l'aide de `new Date(*horodatage*)` et convertir l'objet `Date` existant en un *horodatage* à l'aide de la méthode `date.getTime()` (voir ci-dessous). Les dates antérieures au 01.01.1970 ont des horodatages négatifs, par exemple : ```js run // 31 Dec 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 ); ``` `new Date(datestring)` : S'il existe un seul argument, et qu'il s'agit d'une chaîne, il est automatiquement analysé. L'algorithme est le même que celui utilisé par `Date.parse`, nous le couvrirons plus tard. ```js run let date = new Date("2017-01-26"); alert(date); // La partie heure de la date est supposée être minuit GMT et // est ajusté en fonction du fuseau horaire dans lequel le code est exécuté // Donc, le résultat pourrait être // jeu. 26 janv. 2017 11:00:00 GMT + 1100 (heure avancée de l'Est) // ou // mer. 25 janv. 2017 16:00:00 GMT-0800 (Heure standard du Pacifique) ``` `new Date(année, mois, date, heures, minutes, secondes, ms)` : Crée la date avec les composants donnés dans le fuseau horaire local. Seul le premier argument est obligatoire. Note : - L'année `year` doit avoir 4 chiffres. Pour des raisons de compatibilité, 2 chiffres sont également acceptés et considérés comme `19xx`, par ex. `98` est identique à `1998` ici, mais il est fortement recommandé d'utiliser toujours 4 chiffres. - Le décompte des mois `month` commence par `0` (Janvier) jusqu'à `11` (Décembre). - Le paramètre `date` est en fait le jour du mois, s'il est absent alors `1` est supposé. - Si `heures`/`minutes`/`seconds`/`ms` est absent, elles sont supposées égales à `0`. Par exemple : ```js new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00 new Date(2011, 0, 1); // la même chose car les heures etc sont égales à 0 par défaut ``` La précision maximale est de 1 ms (1/1000 sec) : ```js run let date = new Date(2011, 0, 1, 2, 3, 4, 567); alert( date ); // 1.01.2011, 02:03:04.567 ``` ## Composants de date d'accès Il existe de nombreuses méthodes pour accéder à l'année, au mois, etc. à partir de l'objet Date. [getFullYear()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getFullYear) : Obtenir l'année (4 chiffres) [getMonth()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getMonth) : Obtenir le mois, **de 0 à 11**. [getDate()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getDate) : Obtenir le jour du mois, de 1 à 31. [getHours()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getHours), [getMinutes()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getMinutes), [getSeconds()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getSeconds), [getMilliseconds()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getMilliseconds) : Obtenir l'heures / les minutes / les secondes / les millisecondes. ```warn header="Pas `getYear()`, mais `getFullYear()`" De nombreux moteurs JavaScript implémentent une méthode non standard `getYear()`. Cette méthode est obsolète. Elle retourne parfois l'année à 2 chiffres. S'il vous plaît ne l'utilisez jamais. Il y a `getFullYear()` pour l'année. ``` De plus, nous pouvons obtenir un jour de la semaine : [getDay()](mdn:js/Date/getDay) : Obtenir le jour de la semaine, de `0` (dimanche) à `6` (samedi). Le premier jour est toujours le dimanche, dans certains pays ce n’est pas le cas, mais ça ne peut pas être changé. **Toutes les méthodes ci-dessus renvoient les composants par rapport au fuseau horaire local.** Il existe également leurs homologues UTC, qui renvoient jour, mois, année, etc. pour le fuseau horaire UTC + 0 : [getUTCFullYear()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getUTCFullYear), [getUTCMonth()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getUTCMonth), [getUTCDay()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getUTCDay). Il suffit d'insérer le `UTC` juste après `get`. Si votre fuseau horaire local est décalé par rapport à UTC, le code ci-dessous indique différentes heures : ```js run // date actuel let date = new Date(); // l'heure dans votre fuseau horaire actuel alert( date.getHours() ); // l'heure dans le fuseau horaire UTC + 0 (heure de Londres sans heure avancée) alert( date.getUTCHours() ); ``` Outre les méthodes indiquées, il existe deux méthodes spéciales qui ne possèdent pas de variante UTC : [getTime()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/getTime) : Renvoie l'horodatage de la date - nombre de millisecondes écoulées à partir du 1er janvier 1970 UTC + 0. [getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) : Renvoie la différence entre le fuseau horaire local et l'heure UTC, en minutes : ```js run // si vous êtes dans le fuseau horaire UTC-1, génère 60 // si vous êtes dans le fuseau horaire UTC + 3, les sorties -180 alert( new Date().getTimezoneOffset() ); ``` ## Réglage des composants de date Les méthodes suivantes permettent de définir des composants date / heure : - [`setFullYear(year, [month], [date])`](mdn:js/Date/setFullYear) - [`setMonth(month, [date])`](mdn:js/Date/setMonth) - [`setDate(date)`](mdn:js/Date/setDate) - [`setHours(hour, [min], [sec], [ms])`](mdn:js/Date/setHours) - [`setMinutes(min, [sec], [ms])`](mdn:js/Date/setMinutes) - [`setSeconds(sec, [ms])`](mdn:js/Date/setSeconds) - [`setMilliseconds(ms)`](mdn:js/Date/setMilliseconds) - [`setTime(milliseconds)`](mdn:js/Date/setTime) (définit la date entière en millisecondes depuis 01.01.1970 UTC) Comme nous pouvons le constater, certaines méthodes peuvent définir plusieurs composants à la fois, par exemple `setHours`. Les composants non mentionnés ne sont pas modifiés. Par exemple : ```js run let today = new Date(); today.setHours(0); alert(today); // encore aujourd'hui, mais l'heure est changée à 0 today.setHours(0, 0, 0, 0); alert(today); // toujours aujourd'hui, maintenant 00:00:00 pile. ``` ## Auto-correction L'*auto-correction* est une fonctionnalité très pratique des objets Date. Nous pouvons définir des valeurs hors limites et le système s'ajustera automatiquement. Par exemple : ```js run let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? alert(date); // ...c'est le 1st Feb 2013! ``` Les composants de date hors limites sont traités automatiquement. Supposons que nous devions augmenter la date «28 février 2016» de 2 jours. Ce peut être «2 mars» ou «1 mars» dans le cas d'une année bissextile. Nous n’avons pas besoin d’y penser. Il suffit d'ajouter 2 jours. L'objet `Date` fera le reste : ```js run let date = new Date(2016, 1, 28); *!* date.setDate(date.getDate() + 2); */!* alert( date ); // 1 Mar 2016 ``` Cette fonctionnalité est souvent utilisée pour obtenir la date après la période donnée. Par exemple, obtenons la date «70 secondes après maintenant» : ```js run let date = new Date(); date.setSeconds(date.getSeconds() + 70); alert( date ); // montre la date correcte ``` Nous pouvons également définir zéro ou même des valeurs négatives. Par exemple : ```js run let date = new Date(2016, 0, 2); // 2 Jan 2016 date.setDate(1); // met le jour 1 du mois alert( date ); date.setDate(0); // la date minimum est le 1, le dernier jour du mois précédent devient alors la date alert( date ); // 31 Dec 2015 ``` ## De Date à numéro, différence de date Lorsqu'un objet `Date` est converti en nombre, il devient l'horodatage identique à `date.getTime()` : ```js run let date = new Date(); alert(+date); // le nombre de millisecondes, identique à date.getTime() ``` L'effet secondaire important : les dates peuvent être soustraites, le résultat est leur différence en ms. Cela peut être utilisé pour les mesures de temps : ```js run let start = new Date(); // démarre le compteur // fait le travail for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } let end = new Date(); // fin alert( `The loop took ${end - start} ms` ); ``` ## Date.now() Si nous voulons seulement mesurer la différence, nous n’avons pas besoin de l’objet Date. Il existe une méthode spéciale `Date.now()` qui renvoie l’horodatage actuel. Il est sémantiquement équivalent à `new Date().getTime()`, mais il ne crée pas d’objet `Date` intermédiaire. Donc, c’est plus rapide et cela n’exerce aucune pression sur le ramasse-miettes. Il est principalement utilisé pour des raisons de commodité ou lorsque les performances sont importantes, comme dans les jeux en JavaScript ou dans d'autres applications spécialisées. Donc c'est probablement mieux : ```js run *!* let start = Date.now(); // compteur en millisecondes depuis le 1 Jan 1970 */!* // fait le travail for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } *!* let end = Date.now(); // fin */!* alert( `The loop took ${end - start} ms` ); // soustrait des nombres, pas des dates ``` ## Benchmarking Si nous voulons une référence fiable en matière de fonction gourmande en ressources processeur, nous devons être prudents. Par exemple, mesurons deux fonctions qui calculent la différence entre deux dates : laquelle est la plus rapide ? Ces mesures de performance sont souvent appelées "benchmarks". ```js // nous avons date1 et date2, quelle fonction retourne plus rapidement leur différence en ms ? function diffSubtract(date1, date2) { return date2 - date1; } // ou function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } ``` Ces deux font exactement la même chose, mais l’un d’eux utilise un `date.getTime()` explicite pour obtenir la date en ms, et l’autre repose sur une transformation date à nombre. Leur résultat est toujours le même. Alors, lequel est le plus rapide ? La première idée peut être de les exécuter plusieurs fois de suite et de mesurer le décalage horaire. Pour notre cas, les fonctions sont très simples, nous devons donc le faire environ 100 000 fois. Mesurons : ```js run function diffSubtract(date1, date2) { return date2 - date1; } function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } function bench(f) { let date1 = new Date(0); let date2 = new Date(); let start = Date.now(); for (let i = 0; i < 100000; i++) f(date1, date2); return Date.now() - start; } alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' ); alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' ); ``` Wow! Utiliser `getTime()` est beaucoup plus rapide ! C’est parce qu’il n’y a pas de conversion de type, il est beaucoup plus facile pour JavaScript de faire le calcul. Ok, nous avons quelque chose. Mais ce n’est pas encore une bonne référence. Imaginons qu’au moment de l’exécution du processeur `bench(diffSubtract)`, on faisait quelque chose en parallèle et que cela prenait des ressources. Et au moment de l'exécution du `bench(diffGetTime)`, le travail est terminé. Un scénario assez réel pour un système d'exploitation moderne multi-processus. En conséquence, le premier test aura moins de ressources de processeur que le second. Cela peut conduire à des résultats erronés. **Pour un benchmarking plus fiable, l'ensemble des tests doit être réexécuté plusieurs fois.** Par exemple, comme ceci : ```js run function diffSubtract(date1, date2) { return date2 - date1; } function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } function bench(f) { let date1 = new Date(0); let date2 = new Date(); let start = Date.now(); for (let i = 0; i < 100000; i++) f(date1, date2); return Date.now() - start; } let time1 = 0; let time2 = 0; *!* // exécute bench(diffSubtract) et bench(diffGetTime) chacune 10 fois en alternance for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } */!* alert( 'Total time for diffSubtract: ' + time1 ); alert( 'Total time for diffGetTime: ' + time2 ); ``` Les moteurs JavaScript modernes commencent à appliquer des optimisations avancées uniquement au «code dynamique» qui s'exécute plusieurs fois (inutile d'optimiser les tâches rarement exécutées). Ainsi, dans l'exemple ci-dessus, les premières exécutions ne sont pas bien optimisées. Nous voudrons peut-être ajouter un test pour s'échauffer : ```js // ajouté pour "s'échauffer" avant la boucle principale bench(diffSubtract); bench(diffGetTime); // maintenant comparons for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } ``` ```warn header="Faites attention au micro-benchmarking" Les moteurs JavaScript modernes effectuent de nombreuses optimisations. Ils peuvent modifier les résultats des «tests artificiels» par rapport à «l'utilisation normale», en particulier lorsque nous comparons quelque chose de très petit. Donc, si vous voulez sérieusement comprendre les performances, alors étudiez le fonctionnement du moteur JavaScript. Et puis vous n’aurez probablement pas besoin de micro-points de repère. Un bon paquet d'article a propos de V8 se trouve ici . ``` ## Date.parse d'une chaîne de caractère La methode [Date.parse(str)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/parse) peut lire une date provenant d'une chaîne de caractères. Le format de la chaîne de caractères doit être : `YYYY-MM-DDTHH:mm:ss.sssZ`, où : - `YYYY-MM-DD` -- est la date : année-mois-jour. - Le caractère `"T"` est utilisé comme délimiteur. - `HH:mm:ss.sss` - correspond à l'heure : heures, minutes, secondes et millisecondes. - La partie optionnelle `Z` indique le fuseau horaire au format `+-hh:mm`. Une seule lettre `Z` qui signifierait UTC + 0. Des variantes plus courtes sont également possibles, telles que `YYYY-MM-DD` ou `YYYY-MM` ou même `YYYY`. L'appel à `Date.parse(str)` analyse la chaîne au format indiqué et renvoie l'horodatage (nombre de millisecondes à compter du 1er janvier 1970 UTC + 0). Si le format n'est pas valide, renvoie `NaN`. Par exemple : ```js run let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); alert(ms); // 1327611110417 (horodatage) ``` Nous pouvons créer instantanément un nouvel objet `Date` à partir de l'horodatage : ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); alert(date); ``` ## Résumé - La date et l'heure en JavaScript sont représentées avec l'objet `Date`. Nous ne pouvons pas créer une date «seule» ou une heure «seule» : les objets `Date` comportent toujours les deux. - Les mois sont comptés à partir de zéro (janvier est le mois zéro). - Les jours de la semaine dans `getDay()` sont également comptés à partir de zéro (c’est le dimanche). - L'objet `Date` se corrige automatiquement lorsque des composants hors plage sont définis. C'est pratique pour ajouter / soustraire des jours / mois / heures. - Les dates peuvent être soustraites, ce qui donne leur différence en millisecondes. En effet, une date devient un horodatage lorsqu'elle est convertie en nombre. - Utilisez `Date.now()` pour obtenir l’horodatage actuel rapidement. Notez que contrairement à de nombreux autres systèmes, les horodatages JavaScript sont exprimés en millisecondes et non en secondes. De plus, nous avons parfois besoin de mesures de temps plus précises. JavaScript lui-même ne permet pas de mesurer le temps en microsecondes (un millionième de seconde), mais la plupart des environnements le fournissent. Par exemple, le navigateur a [performance.now()](https://developer.mozilla.org/fr/docs/Web/API/Performance/now) qui donne le nombre de millisecondes à partir du début du chargement de la page avec une précision de l'ordre de la microseconde (3 chiffres après le point) : ```js run alert(`Loading started ${performance.now()}ms ago`); // Quelque chose comme : "Le chargement a commencé il y a 34731.26000000001ms" // .26 indique les microsecondes (260 microsecondes) // plus de 3 chiffres après le point décimal sont des erreurs de précision, seuls les 3 premiers sont corrects ``` Node.js a un module `microtime` et d'autres moyens. Techniquement, presque tous les appareils et environnements permettent d'obtenir plus de précision, ce n'est pas seulement dans `Date`. ================================================ FILE: 1-js/05-data-types/12-json/1-serialize-object/solution.md ================================================ ```js let user = { name: "John Smith", age: 35 }; *!* let user2 = JSON.parse(JSON.stringify(user)); */!* ``` ================================================ FILE: 1-js/05-data-types/12-json/1-serialize-object/task.md ================================================ importance: 5 --- # Transforme l'objet en JSON et revenez en arrière Transformez `l’utilisateur` en JSON puis relisez-le dans une autre variable. ```js let user = { name: "John Smith", age: 35 }; ``` ================================================ FILE: 1-js/05-data-types/12-json/2-serialize-event-circular/solution.md ================================================ ```js run let room = { number: 23 }; let meetup = { title: "Conference", occupiedBy: [{name: "John"}, {name: "Alice"}], place: room }; room.occupiedBy = meetup; meetup.self = meetup; alert( JSON.stringify(meetup, function replacer(key, value) { return (key != "" && value == meetup) ? undefined : value; })); /* { "title":"Conference", "occupiedBy":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */ ``` Ici, nous devons également tester la `key==""` pour exclure le premier appel où il est normal que la `valeur` soit `meetup`. ================================================ FILE: 1-js/05-data-types/12-json/2-serialize-event-circular/task.md ================================================ importance: 5 --- # Exclure les backreferences Dans les cas simples de références circulaires, nous pouvons exclure une propriété incriminée de la sérialisation par son nom. Mais parfois, nous ne pouvons pas simplement utiliser le nom, car il peut être utilisé à la fois dans les références circulaires et dans les propriétés normales. Ainsi, nous pouvons vérifier la propriété par sa valeur. Écrivez la fonction de `remplacement` pour tout stringify, mais supprimez les propriétés qui font référence à `meetup`: ```js run let room = { number: 23 }; let meetup = { title: "Conference", occupiedBy: [{name: "John"}, {name: "Alice"}], place: room }; *!* // circular references room.occupiedBy = meetup; meetup.self = meetup; */!* alert( JSON.stringify(meetup, function replacer(key, value) { /* your code */ })); /* result should be: { "title":"Conference", "occupiedBy":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */ ``` ================================================ FILE: 1-js/05-data-types/12-json/article.md ================================================ # JSON methods, toJSON Supposons que nous avons un objet complexe et que nous aimerions le convertir en chaîne, l'envoyer par le réseau ou simplement le rendre (l'output) à des fins de journalisation. Naturellement, une telle chaîne devrait inclure toutes les propriétés importantes. Nous pourrions implémenter la conversion comme ceci : ```js run let user = { name: "John", age: 30, *!* toString() { return `{name: "${this.name}", age: ${this.age}}`; } */!* }; alert(user); // {name: "John", age: 30} ``` ... Mais au cours du développement, de nouvelles propriétés sont ajoutées, les anciennes propriétés sont renommées et supprimées. Mettre à jour un tel `toString` à chaque fois peut devenir pénible. Nous pourrions essayer de passer en boucle sur les propriétés qu'il contient, mais que se passe-t-il si l'objet est complexe et qu'il contient des objets imbriqués ? Nous aurions également besoin de mettre en œuvre leur conversion. Heureusement, il n'est pas nécessaire d'écrire le code pour gérer tout cela. La tâche a déjà été résolue. ## JSON.stringify Le [JSON](http://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) est un format général pour représenter les valeurs et les objets. Il est décrit comme tel dans le standard [RFC 4627](http://tools.ietf.org/html/rfc4627). Initialement, il était conçu pour JavaScript, mais de nombreux autres langages disposent également de bibliothèques pour le gérer. Il est donc facile d’utiliser JSON pour l’échange de données lorsque le client utilise JavaScript et que le serveur est écrit sur Ruby/PHP/Java et bien d'autres. JavaScript fournit des méthodes : - `JSON.stringify` pour convertir des objets en JSON. - `JSON.parse` pour reconvertir JSON en objet. Par exemple, nous allons `JSON.stringify` un student (étudiant) : ```js run let student = { name: 'John', age: 30, isAdmin: false, courses: ['html', 'css', 'js'], spouse: null }; *!* let json = JSON.stringify(student); */!* alert(typeof json); // nous avons une string ! alert(json); *!* /* Objet encodé en JSON : { "name": "John", "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], "spouse": null } */ */!* ``` La méthode `JSON.stringify(student)` prend l'objet et le convertit en une chaîne. La chaine `json` résultante est appelé un objet *JSON-encoded* ou *serialized* (sérialisé) ou *stringified* ou *marshalled*. Nous sommes prêts à l'envoyer par le câble ou à le placer dans un simple stockage de données. Veuillez noter qu'un objet JSON-encoded a plusieurs différences importantes par rapport au objet littéral : - Les chaînes utilisent des guillemets doubles. Il n'y a pas de guillemets simples ni de backticks en JSON. Donc `'John'` deviendra `"John"`. - Les propriété d'objet sont également en guillemets doubles. C'est obligatoire. Donc `age:30` deviendra `"age":30`. `JSON.stringify` peut aussi être appliqué aux primitives. JSON prend en charge les types de données suivants : - Objets `{ ... }` - Tableaux `[ ... ]` - Primitives : - chaînes, - nombres, - valeurs booléennes `true`/`false`, - `null`. Par exemple : ```js run // un nombre en JSON est juste un nombre alert( JSON.stringify(1) ) // 1 // une chaîne en JSON est toujours une chaîne, mais entre guillemets alert( JSON.stringify('test') ) // "test" alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] ``` JSON est une spécification indépendante du langage et ne contenant que des données. Par conséquent, certaines propriétés d'objet spécifiques à JavaScript sont ignorées par `JSON.stringify`. À savoir : - Propriétés de fonction (méthodes). - Clés et valeurs symboliques. - Propriétés qui stockent `undefined`. ```js run let user = { sayHi() { // ignorée alert("Hello"); }, [Symbol("id")]: 123, // ignorée something: undefined // ignorée }; alert( JSON.stringify(user) ); // {} (objet vide) ``` D'habitude ça va. Si ce n'est pas ce que nous souhaitons, nous verrons bientôt comment personnaliser le processus. Le grand avantage est que les objets imbriqués sont pris en charge et convertis automatiquement. Par exemple : ```js run let meetup = { title: "Conference", *!* room: { number: 23, participants: ["john", "ann"] } */!* }; alert( JSON.stringify(meetup) ); /* La structure entière est stringified : { "title":"Conference", "room":{"number":23,"participants":["john","ann"]}, } */ ``` La limitation importante est qu'il ne doit pas y avoir de références circulaires. Par exemple : ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: ["john", "ann"] }; meetup.place = room; // meetup references room room.occupiedBy = meetup; // room references meetup *!* JSON.stringify(meetup); // Erreur: Conversion d'une structure circulaire en JSON */!* ``` Ici, la conversion échoue à cause d'une référence circulaire : `room.occupiedBy` references `meetup`, et `meetup.place` references `room` : ![](json-meetup.svg) ## Exclure et transformer : replacer La syntaxe complète de `JSON.stringify` est : ```js let json = JSON.stringify(value[, replacer, space]) ``` Value : Une valeur à encoder. Replacer : Tableau de propriétés à encoder ou une fonction de mapping `function(key, value)`. Space : Quantité d'espace à utiliser pour le formatage La plupart du temps, `JSON.stringify` est utilisé avec le premier argument uniquement. Mais si nous devons affiner le processus de remplacement, préférez filtrer les références circulaires, nous pouvons utiliser le deuxième argument de `JSON.stringify`. Si nous lui passons un tableau de propriétés, seules ces propriétés seront encodées. Par exemple : ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup references room }; room.occupiedBy = meetup; // room references meetup alert( JSON.stringify(meetup, *!*['title', 'participants']*/!*) ); // {"title":"Conference","participants":[{},{}]} ``` Ici, nous sommes probablement trop strictes. La liste de propriétés est appliquée à la structure entière de l'objet. Donc, les objets dans `participants` sont vides, parce que `name` n'est pas dans la liste. Incluons dans la liste toutes les propriétés sauf `room.occupiedBy` qui provoquerait la référence circulaire : ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup references room }; room.occupiedBy = meetup; // room references meetup alert( JSON.stringify(meetup, *!*['title', 'participants', 'place', 'name', 'number']*/!*) ); /* { "title":"Conference", "participants":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */ ``` Maintenant tout sauf `occupiedBy` est serialisé. Mais la liste des propriétés est assez longue. Heureusement, nous pouvons utiliser une fonction au lieu d'un tableau comme `replacer`. La fonction sera appelée pour chaque paire de `(key, value)` et devrait renvoyer la valeur "remplacée", qui sera utilisée à la place de celle d'origine. Ou `undefined` si la valeur doit être ignorée. Dans notre cas, nous pouvons retourner une `value` "en l'état" pour tout sauf `occupiedBy`. Pour ignorer `occupiedBy`, le code ci-dessous retourne `undefined` : ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup references room }; room.occupiedBy = meetup; // room references meetup alert( JSON.stringify(meetup, function replacer(key, value) { alert(`${key}: ${value}`); return (key == 'occupiedBy') ? undefined : value; })); /* key:value pairs that come to replacer: : [object Object] title: Conference participants: [object Object],[object Object] 0: [object Object] name: John 1: [object Object] name: Alice place: [object Object] number: 23 occupiedBy: [object Object] */ ``` Veuillez noter que la fonction `replacer` récupère chaque paire clé/valeur, y compris les objets imbriqués et les éléments de tableau. Il est appliqué de manière récursive. La valeur `this` dans `replacer` est l'objet qui contient la propriété actuelle. Le premier appel est spécial. Il est fabriqué en utilisant un "objet wrapper" spécial: `{"": meetup}`. En d'autres termes, la première paire `(key, value)` a une clé vide, et la valeur est l'objet cible dans son ensemble. C'est pourquoi la première ligne est `":[object Object]"` dans l'exemple ci-dessus. L’idée est de fournir autant de puissance pour `replacer` que possible : il a une chance d'analyser et de remplacer/ignorer même l'objet entier si nécessaire. ## Formatage : space Le troisième argument de `JSON.stringify(value, replacer, space)` est le nombre d'espaces à utiliser pour un joli formatage. Auparavant, tous les objets stringifiés n’avaient pas d'indentation ni d’espace supplémentaire. C'est bien si nous voulons envoyer un objet sur un réseau. L'arguement `space` est utilisé exclusivement pour une belle sortie. Ici `space = 2` indique à JavaScript d'afficher des objets imbriqués sur plusieurs lignes, avec l'indentation de 2 espaces à l'intérieur d'un objet : ```js run let user = { name: "John", age: 25, roles: { isAdmin: false, isEditor: true } }; alert(JSON.stringify(user, null, 2)); /* indentation de 2 espaces : { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */ /* pour JSON.stringify(user, null, 4) le résultat serait plus indenté : { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */ ``` Le troisième argument peut également être une chaîne de caractères. Dans ce cas, la chaîne de caractères est utilisée pour l'indentation au lieu d'un certain nombre d'espaces. Le paramètre `space` est utilisé uniquement à des fins de journalisation et de sortie agréable. ## "toJSON" personnalisé Comme `toString` pour la conversion de chaîne, un objet peut fournir une méthode `toJSON` pour une conversion en JSON. `JSON.stringify` appelle automatiquement si il est disponible. Par exemple : ```js run let room = { number: 23 }; let meetup = { title: "Conference", date: new Date(Date.UTC(2017, 0, 1)), room }; alert( JSON.stringify(meetup) ); /* { "title":"Conference", *!* "date":"2017-01-01T00:00:00.000Z", // (1) */!* "room": {"number":23} // (2) } */ ``` Ici on peut voir que `date` `(1)` est devenu une chaîne. C’est parce que toutes les dates ont une méthode `toJSON` intégrée qui retourne ce genre de chaîne. Ajoutons maintenant un `toJSON` personnalisé pour notre objet `room` `(2)` : ```js run let room = { number: 23, *!* toJSON() { return this.number; } */!* }; let meetup = { title: "Conference", room }; *!* alert( JSON.stringify(room) ); // 23 */!* alert( JSON.stringify(meetup) ); /* { "title":"Conference", *!* "room": 23 */!* } */ ``` Comme on peut le voir, `toJSON` est utilisé à la fois pour l'appel direct `JSON.stringify(room)` et quand `room` est imbriqué dans un autre objet encodé. ## JSON.parse Pour décoder une chaîne JSON, nous avons besoin d'une autre méthode nommée [JSON.parse](mdn:js/JSON/parse). La syntaxe : ```js let value = JSON.parse(str, [reviver]); ``` str : La chaîne JSON à parse. reviver : Fonction optionnelle (clé, valeur) qui sera appelée pour chaque paire `(key, value)` et peut transformer la valeur. Par exemple : ```js run // stringified array let numbers = "[0, 1, 2, 3]"; numbers = JSON.parse(numbers); alert( numbers[1] ); // 1 ``` Ou pour les objets imbriqués : ```js run let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; let user = JSON.parse(userData); alert( user.friends[1] ); // 1 ``` Le JSON peut être aussi complexe que nécessaire, les objets et les tableaux peuvent inclure d'autres objets et tableaux. Mais ils doivent obéir au même format JSON. Voici des erreurs typiques dans JSON écrit à la main (nous devons parfois l'écrire à des fins de débogage) : ```js let json = `{ *!*name*/!*: "John", // Erreur: nom de propriété sans guillemets "surname": *!*'Smith'*/!*, // Erreur: guillemets simples en valeur (doit être double) *!*'isAdmin'*/!*: false // Erreur: guillemets simples dans la clé (doit être double) "birthday": *!*new Date(2000, 2, 3)*/!*, // Erreur: aucun "nouveau" n'est autorisé, seules les valeurs nues "friends": [0,1,2,3] // Ici tout va bien }`; ``` En outre, JSON ne prend pas en charge les commentaires. L'ajout d'un commentaire à JSON le rend invalide. Il y a un autre format nommé [JSON5](https://json5.org/) qui autorise les clés non commentées, les commentaires, etc. Mais il s’agit d’une bibliothèque autonome, pas dans la spécification du langage. Le JSON standard est très strict, non pas parce que ses développeurs sont paresseux, mais pour permettre une implémentation facile, fiable et très rapide de l'algorithme de conversion. ## Utiliser Reviver Imaginez, nous avons un objet stringified `meetup` sur le serveur. Cela ressemble à ça : ```js // title: (meetup title), date: (meetup date) let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; ``` ... Et maintenant, nous devons le *deserialize*, pour le retourner en objet JavaScript. Faisons-le en appelant `JSON.parse` : ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str); *!* alert( meetup.date.getDate() ); // Error! */!* ``` Whoops ! Une erreur ! La valeur de `meetup.date` est une chaîne et non un objet `Date`. Comment `JSON.parse` pourrait-il savoir qu'il devrait transformer cette chaîne en `Date` ? Passons à `JSON.parse` la fonction de réactivation en tant que second argument, qui renvoie toutes les valeurs "en l'état", mais `date` deviendra une `Date` : ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; *!* let meetup = JSON.parse(str, function(key, value) { if (key == 'date') return new Date(value); return value; }); */!* alert( meetup.date.getDate() ); // ça fonctionne maintenant ! ``` À propos, cela fonctionne aussi pour les objets imbriqués : ```js run let schedule = `{ "meetups": [ {"title":"Conference","date":"2017-11-30T12:00:00.000Z"}, {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"} ] }`; schedule = JSON.parse(schedule, function(key, value) { if (key == 'date') return new Date(value); return value; }); *!* alert( schedule.meetups[1].date.getDate() ); // ça fonctionne ! */!* ``` ## Résumé - JSON est un format de données qui possède son propre standard indépendant et ses propres bibliothèques pour la plupart des langages de programmation. - JSON prend en charge les objets simples, les tableaux, les chaînes, les nombres, les booléens et `null`. - JavaScript fournit des méthodes [JSON.stringify](mdn:js/JSON/stringify) pour sérialiser en JSON et [JSON.parse](mdn:js/JSON/parse) pour lire depuis JSON. - Les deux méthodes prennent en charge les fonctions du transformateur pour une lecture/écriture intelligente. - Si un objet a `toJSON`, alors il est appelé par `JSON.stringify`. ================================================ FILE: 1-js/05-data-types/index.md ================================================ # Types de données Plus de structures de données et une étude plus approfondie des types. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md ================================================ La solution utilisant une boucle: ```js run function sumTo(n) { let sum = 0; for (let i = 1; i <= n; i++) { sum += i; } return sum; } alert( sumTo(100) ); ``` La solution utilisant la récursion: ```js run function sumTo(n) { if (n == 1) return 1; return n + sumTo(n - 1); } alert( sumTo(100) ); ``` La solution utilisant la formule: `sumTo(n) = n*(n+1)/2`: ```js run function sumTo(n) { return n * (n + 1) / 2; } alert( sumTo(100) ); ``` P.S. Naturellement, la formule est la solution la plus rapide. Elle n’utilise que 3 opérations pour n’importe quel nombre `n`. Le calcul aide! La variante de boucle est la seconde en termes de vitesse. Dans la variante récursive et la variante de boucle, nous additionnons les mêmes nombres. Mais la récursion implique des appels imbriqués et la gestion de la pile d'exécution. Donc, cela prend des ressources, donc c'est plus lent. P.P.S. Certains moteurs prennent en charge l'optimisation "tail call" (dernier appel) : si un appel récursif est le tout dernier dans la fonction, sans autres calculs effectués, alors la fonction externe n'aura pas besoin de reprendre l'exécution, donc le moteur n'a pas besoin de se souvenir son contexte d'exécution. Cela supprime le fardeau de la mémoire. Mais si le moteur JavaScript ne prend pas en charge l'optimisation des appels de queue (la plupart d'entre eux ne le font pas), il y aura une erreur : taille maximale de la pile dépassée, car il y a généralement une limitation sur la taille totale de la pile. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/01-sum-to/task.md ================================================ importance: 5 --- # Additionner tous les nombres jusqu'à celui donné Écrire une fonction `sumTo(n)` qui calcule la somme des nombres `1 + 2 + ... + n`. Par exemple: ```js no-beautify sumTo(1) = 1 sumTo(2) = 2 + 1 = 3 sumTo(3) = 3 + 2 + 1 = 6 sumTo(4) = 4 + 3 + 2 + 1 = 10 ... sumTo(100) = 100 + 99 + ... + 2 + 1 = 5050 ``` Faites 3 variantes de solution: 1. Utiliser une boucle for. 2. Utiliser une récursion, avec `sumTo(n) = n + sumTo(n-1)` pour `n > 1`. 3. Utiliser la formule de [progression arithmétique](https://en.wikipedia.org/wiki/Arithmetic_progression). Un exemple de résultat: ```js function sumTo(n) { /*... ton code ... */ } alert( sumTo(100) ); // 5050 ``` P.S. Quelle solution est la plus rapide? La plus lente? Pourquoi? P.P.S. Peut-on utiliser la récursion pour compter `sumTo(100000)`? ================================================ FILE: 1-js/06-advanced-functions/01-recursion/02-factorial/solution.md ================================================ Par définition, une factorielle est `n!` peut être écrit `n * (n-1)!`. En d'autres termes, le résultat de `factorial(n)` peut être calculé comme `n` multiplié par le résultat de `factorial(n-1)`. Et l'appel de `n-1` peut récursivement descendre plus bas, et plus bas, jusqu'à `1`. ```js run function factorial(n) { return (n != 1) ? n * factorial(n - 1) : 1; } alert( factorial(5) ); // 120 ``` La base de la récursivité est la valeur `1`. Nous pouvons aussi faire de `0` la base ici, ça importe peu, mais donne une étape récursive supplémentaire: ```js run function factorial(n) { return n ? n * factorial(n - 1) : 1; } alert( factorial(5) ); // 120 ``` ================================================ FILE: 1-js/06-advanced-functions/01-recursion/02-factorial/task.md ================================================ importance: 4 --- # Calcule factoriel Le [factorielle](https://en.wikipedia.org/wiki/Factorial) d'un nombre naturel est multiplié par `"nombre moins un"`, ensuite par `"nombre moins deux"`, et ainsi de suite jusqu'à `1`. La factorielle de `n` est noté comme `n!` Nous pouvons écrire une définition de factorielle comme ceci: ```js n! = n * (n - 1) * (n - 2) * ...*1 ``` Valeurs des factorielles pour des `n` différents: ```js 1! = 1 2! = 2 * 1 = 2 3! = 3 * 2 * 1 = 6 4! = 4 * 3 * 2 * 1 = 24 5! = 5 * 4 * 3 * 2 * 1 = 120 ``` La tâche est d'écrire une fonction `factorial(n)` qui calcule `n!` en utilisant des appels récursifs. ```js alert( factorial(5) ); // 120 ``` P.S. Indice: `n!` peut être écrit `n * (n-1)!` Par exemple: `3! = 3*2! = 3*2*1! = 6` ================================================ FILE: 1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md ================================================ La première solution que nous pourrions essayer ici est la solution récursive. Les nombres de Fibonacci sont récursifs par définition: ```js run function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); } alert( fib(3) ); // 2 alert( fib(7) ); // 13 // fib(77); // Sera extrêmement lent! ``` ...Mais pour les grandes valeurs de `n` c'est très lent. Par exemple, `fib(77)` peut bloquer le moteur pendant un certain temps en consommant toutes les ressources du processeur. C'est parce que la fonction crée trop de sous-appels. Les mêmes valeurs sont réévaluées encore et encore. Par exemple, voyons un calcul pour `fib(5)`: ```js no-beautify ... fib(5) = fib(4) + fib(3) fib(4) = fib(3) + fib(2) ... ``` Ici, nous pouvons voir que la valeur de `fib(3)` est nécessaire pour les deux `fib(5)` et `fib(4)`. Alors `fib(3)` sera appelé et évalué deux fois de manière totalement indépendante. Voici l'arbre de récursion complet: ![fibonacci recursion tree](fibonacci-recursion-tree.svg) Nous pouvons clairement remarquer que `fib(3)` est évalué deux fois et `fib(2)` est évalué trois fois. La quantité totale de calculs augmente beaucoup plus vite que `n`, le rendant énorme même pour `n=77`. Nous pouvons optimiser cela en nous rappelant les valeurs déjà évaluées: si une valeur de `fib(3)` est calculé une fois, alors nous pouvons simplement le réutiliser dans les calculs futurs. Une autre variante consisterait à abandonner la récursion et à utiliser un algorithme totalement différent basé sur des boucles. Au lieu de partir de `n` jusqu'à des valeurs plus basses, nous pouvons faire une boucle qui commence à partir de `1` et `2`, puis obtient `fib(3)` comme leur somme, ensuite `fib(4)` comme la somme de deux valeurs précédentes, ensuite `fib(5)` et monte, jusqu'à ce qu'il atteigne la valeur nécessaire. À chaque étape, il suffit de rappeler deux valeurs précédentes. Voici les étapes du nouvel algorithme en détails. Le début: ```js // a = fib(1), b = fib(2), ces valeurs sont par définition 1 let a = 1, b = 1; // obtien c = fib(3) comme leur somme let c = a + b; /* nous avons maintenant fib(1), fib(2), fib(3) a b c 1, 1, 2 */ ``` Maintenant, nous voulons obtenir `fib(4) = fib(2) + fib(3)`. Passons aux variables: `a,b` aura `fib(2),fib(3)`, et `c` obtiendra leur somme: ```js no-beautify a = b; // maintenant a = fib(2) b = c; // maintenant b = fib(3) c = a + b; // c = fib(4) /* maintenant nous avons la séquence: a b c 1, 1, 2, 3 */ ``` L'étape suivante donne un autre numéro de séquence: ```js no-beautify a = b; // maintenant a = fib(3) b = c; // maintenant b = fib(4) c = a + b; // c = fib(5) /* maintenant la séquence est (encore un numéro): a b c 1, 1, 2, 3, 5 */ ``` ...Et ainsi de suite jusqu'à l'obtention de la valeur nécessaire. C'est beaucoup plus rapide que la récursion et n'implique aucun calcul en double. Le code complet: ```js run function fib(n) { let a = 1; let b = 1; for (let i = 3; i <= n; i++) { let c = a + b; a = b; b = c; } return b; } alert( fib(3) ); // 2 alert( fib(7) ); // 13 alert( fib(77) ); // 5527939700884757 ``` La boucle commence par `i=3`, parce que les première et deuxième valeurs de séquence sont codées en dur dans des variables `a=1`, `b=1`. Cette approche s'appelle la [programmation dynamique de bas en haut](https://fr.wikipedia.org/wiki/Programmation_dynamique). ================================================ FILE: 1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md ================================================ importance: 5 --- # Numéros de Fibonacci La séquence des [Numéros de Fibonacci](https://fr.wikipedia.org/wiki/Nombre_de_Fibonacci) a la formule Fn = Fn-1 + Fn-2. En d'autres termes, le nombre suivant est la somme des deux précédents. Les deux premiers chiffres sont `1`, puis `2(1+1)`, ensuite `3(1+2)`, `5(2+3)` etc: `1, 1, 2, 3, 5, 8, 13, 21...`. Les nombres de Fibonacci sont liés au [nombre d'or](https://fr.wikipedia.org/wiki/Nombre_d%27or) et de nombreux phénomènes naturels autour de nous. Écrire une fonction `fib(n)` qui retourne le Numéro de Fibonacci `n-th`. Un exemple de travail: ```js function fib(n) { /* votre code */ } alert(fib(3)); // 2 alert(fib(7)); // 13 alert(fib(77)); // 5527939700884757 ``` P.S. La fonction devrait être rapide. L'appel de `fib(77)` devrait prendre pas plus d'une fraction de seconde. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md ================================================ # Solution basée sur la boucle La variante de la solution basée sur la boucle: ```js run let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; function printList(list) { let tmp = list; while (tmp) { alert(tmp.value); tmp = tmp.next; } } printList(list); ``` Veuillez noter que nous utilisons une variable temporaire `tmp` pour parcourir la liste. Techniquement, nous pourrions utiliser un paramètre de fonction `list` à la place: ```js function printList(list) { while(*!*list*/!*) { alert(list.value); list = list.next; } } ``` ...Mais ce ne serait pas sage. Dans le futur, nous allons peut-être devoir étendre une fonction, faire autre chose avec la liste. Si nous changeons `list`, alors nous perdons cette capacité. Parlant des bons noms de variables, `list` est la liste elle-même. Le premier élément de celui-ci. Et ça devrait rester comme ça. C'est clair et fiable. De l’autre côté, le rôle de `tmp` est exclusivement une liste de traversée, comme `i` dans la boucle `for`. # Solution récursive La variante récursive de `printList(list)` suit une logique simple: pour afficher une liste, il faut afficher l'élément courant `list`, puis faire de même pour `list.next`: ```js run let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; function printList(list) { alert(list.value); // affiche l'élément en cours if (list.next) { printList(list.next); // fait la même chose pour le reste de la liste } } printList(list); ``` Maintenant qu'est-ce qui est le mieux? Techniquement, la boucle est plus efficace. Ces deux variantes font la même chose, mais la boucle ne dépense pas de ressources pour les appels de fonction imbriqués. De l'autre côté, la variante récursive est plus courte et parfois plus facile à comprendre. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md ================================================ importance: 5 --- # Produire une liste de simple lien Disons que nous avons une liste de simple lien (comme décrit dans le chapitre ): ```js let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; ``` Écrire une fonction `printList(list)` qui sort les éléments de la liste un par un. Faites deux variantes de la solution: en utilisant une boucle et en utilisant la récursion. Qu'est-ce qui est le mieux: Avec ou sans récursion ? ================================================ FILE: 1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md ================================================ # Utiliser une récursion La logique récursive est un peu délicate ici. Nous devons d'abord afficher le reste de la liste et *ensuite* afficher l'actuel: ```js run let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; function printReverseList(list) { if (list.next) { printReverseList(list.next); } alert(list.value); } printReverseList(list); ``` # En utilisant une boucle La variante de boucle est aussi un peu plus compliquée que la sortie directe. Il n'y a aucun moyen d'obtenir la dernière valeur de notre `list`. Nous ne pouvons pas non plus "revenir en arrière". Nous pouvons donc commencer par parcourir les éléments dans l'ordre direct et les mémoriser dans un tableau, puis afficher ce que nous nous sommes rappelés dans l'ordre inverse: ```js run let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; function printReverseList(list) { let arr = []; let tmp = list; while (tmp) { arr.push(tmp.value); tmp = tmp.next; } for (let i = arr.length - 1; i >= 0; i--) { alert( arr[i] ); } } printReverseList(list); ``` Veuillez noter que la solution récursive fait exactement la même chose: elle suit la liste, mémorise les éléments de la chaîne d'appels imbriqués (dans la pile de contexte d'exécution), puis les affiches. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md ================================================ importance: 5 --- # Afficher une liste à lien unique dans l'ordre inverse Afficher une liste à lien unique de la tâche précédente dans l'ordre inverse. Faites deux solutions: en utilisant une boucle et en utilisant une récursion. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/article.md ================================================ # Récursion et pile Revenons aux fonctions et étudions-les plus en profondeur. Notre premier sujet sera la *recursion*. Si vous n’êtes pas novice en programmation, cela vous est probablement familier et vous pouvez sauter ce chapitre. La récursion est un modèle de programmation utile dans les situations où une tâche peut être naturellement divisée en plusieurs tâches du même type, mais plus simple. Ou lorsqu'une tâche peut être simplifiée en une action facile plus une variante plus simple de la même tâche. Ou, comme nous le verrons bientôt, pour traiter certaines structures de données. Lorsqu'une fonction résout une tâche, elle peut appeler de nombreuses autres fonctions. Cela se produit partiellement lorsqu'une fonction s'appelle *elle-même*. Cela s'appelle la *récursion*. ## Deux façons de penser Prenons quelque chose de simple pour commencer -- écrivons une fonction `pow(x, n)` qui élève `x` à une puissance naturel de `n`. En d'autres termes, multiplie `x` par lui-même `n` fois. ```js pow(2, 2) = 4 pow(2, 3) = 8 pow(2, 4) = 16 ``` Il y a deux façons de le mettre en œuvre. 1. La pensée itérative: la boucle `for`: ```js run function pow(x, n) { let result = 1; // multiplier le résultat par x n fois dans la boucle for (let i = 0; i < n; i++) { result *= x; } return result; } alert( pow(2, 3) ); // 8 ``` 2. La pensée récursive: simplifie la tâche et s'appele elle-même: ```js run function pow(x, n) { if (n == 1) { return x; } else { return x * pow(x, n - 1); } } alert( pow(2, 3) ); // 8 ``` Veuillez noter en quoi la variante récursive est fondamentalement différente. Quand `pow(x, n)` est appelé, l'exécution se scinde en deux branches: ```js if n==1 = x / pow(x, n) = \ else = x * pow(x, n - 1) ``` 1. Si `n == 1`, alors tout est trivial. On l'appelle *la base* de la récursion, car elle produit immédiatement le résultat évident: `pow(x, 1)` équivaut à `x`. 2. Sinon, nous pouvons représenter `pow(x, n)` comme `x * pow(x, n - 1)`. En maths, on écrirait xn = x * xn-1. Ceci s'appelle *une étape récursive*: nous transformons la tâche en une action plus simple (multiplication par `x`) et un appel plus simple de la même tâche (`pow` avec le petit `n`). Les prochaines étapes le simplifient de plus en plus jusqu’à ce que `n` atteigne `1`. On peut aussi dire que `pow` *s'appelle récursivement* jusqu'à ce que `n == 1`. ![diagramme récursif de puissance](recursion-pow.svg) Par exemple, pour calculer `pow(2, 4)` la variante récursive effectue ces étapes: 1. `pow(2, 4) = 2 * pow(2, 3)` 2. `pow(2, 3) = 2 * pow(2, 2)` 3. `pow(2, 2) = 2 * pow(2, 1)` 4. `pow(2, 1) = 2` Ainsi, la récursion réduit un appel de fonction à un processus plus simple, puis -- à un processus encore plus simple, etc. jusqu'à ce que le résultat devienne évident. ````smart header="La récursion est généralement plus courte" Une solution récursive est généralement plus courte qu'une solution itérative. Ici, nous pouvons réécrire la même chose en utilisant l'opérateur conditionnel `?` Au lieu de `if` pour rendre `pow (x, n)` plus concis et toujours très lisible: ```js run function pow(x, n) { return (n == 1) ? x : (x * pow(x, n - 1)); } ``` ```` Le nombre maximal d'appels imbriqués (y compris le premier) est appelé la *profondeur de récursivité*. Dans notre cas, ce sera exactement `n`. La profondeur maximale de récursion est limitée par le moteur JavaScript. Nous sommes sur qu'il va jusqu'à 10000, certains moteurs en autorisent plus, mais 100000 est probablement hors limite pour la majorité d'entre eux. Il existe des optimisations automatiques qui aident à atténuer ce problème ("optimisation des appels de queue"), mais elles ne sont pas encore prises en charge partout et ne fonctionnent que dans des cas simples. Cela limite l'application de la récursion, mais cela reste très large. Il y a beaucoup de tâches pour lesquelles la pensée récursive donne un code plus simple et plus facile à gérer. ## Le contexte d'exécution et la pile Voyons maintenant comment fonctionnent les appels récursifs. Pour cela, nous allons regarder sous le capot des fonctions. Les informations sur le processus d'exécution d'une fonction en cours d'exécution sont stockées dans son *contexte d'exécution*. Le [contexte d'exécution](https://tc39.github.io/ecma262/#sec-execution-contexts) est une structure de données interne contenant des détails sur l'exécution d'une fonction: où le flux de contrôle est maintenant, les variables actuelles, la valeur de `this` (nous ne l'utilisons pas ici) et quelques autres détails internes. Un appel de fonction est associé à exactement un contexte d'exécution. Lorsqu'une fonction effectue un appel imbriqué, les événements suivants se produisent: - La fonction en cours est suspendue. - Le contexte d’exécution qui lui est associé est mémorisé dans une structure de données spéciale appelée *pile de contexte d’exécution*. - L'appel imbriqué s'exécute. - Une fois terminé, l'ancien contexte d'exécution est extrait de la pile et la fonction externe reprend à partir de son point d'arrêt. Voyons ce qui se passe pendant l'appel de `pow(2, 3)`. ### pow(2, 3) Au début de l'appel de `pow(2, 3)` le contexte d'exécution stockera des variables: `x = 2, n = 3`, le flux d'exécution est à la ligne `1` de la fonction. Nous pouvons l'esquisser comme:
  • Context: { x: 2, n: 3, at line 1 } pow(2, 3)
C'est à ce moment que la fonction commence à s'exécuter. La condition`n == 1` est faux, donc le flux continue dans la deuxième branche de `if`: ```js run function pow(x, n) { if (n == 1) { return x; } else { *!* return x * pow(x, n - 1); */!* } } alert( pow(2, 3) ); ``` Les variables sont les mêmes, mais la ligne change, le contexte est donc le suivant:
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
Pour calculer `x * pow(x, n - 1)`, nous devons faire un sous-appel de `pow` avec de nouveaux arguments `pow(2, 2)`. ### pow(2, 2) Pour effectuer un appel imbriqué, JavaScript se souvient du contexte d'exécution actuel dans le *contexte d'exécution de la pile*. Ici, nous appelons la même fonction `pow`, mais cela n’a absolument aucune importance. Le processus est le même pour toutes les fonctions: 1. Le contexte actuel est "mémorisé" en haut de la pile. 2. Le nouveau contexte est créé pour le sous-appel. 3. Quand le sous-appel est fini -- le contexte précédent est extrait de la pile et son exécution se poursuit. Voici la pile de contexte lorsque nous sommes entrés dans le sous-appel `pow(2, 2)`:
  • Context: { x: 2, n: 2, at line 1 } pow(2, 2)
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
Le nouveau contexte d'exécution actuel est en haut (et en gras) et les contextes précédemment mémorisés sont en dessous. Quand on termine le sous-appel -- il est facile de reprendre le contexte précédent, car il conserve les deux variables et l'emplacement exact du code où il s'est arrêté. ```smart Ici, dans l'image, nous utilisons le mot "line", comme dans notre exemple, il n'y a qu'un seul sous-appel en ligne, mais généralement une seule ligne de code peut contenir plusieurs sous-appels, comme `pow(…) + pow(…) + somethingElse(…)`. Il serait donc plus précis de dire que l'exécution reprend "immédiatement après le sous-appel". ``` ### pow(2, 1) Le processus se répète: un nouveau sous-appel est fait à la ligne `5`, maintenant avec des arguments `x=2`, `n=1`. Un nouveau contexte d'exécution est créé, le précédent est placé en haut de la pile:
  • Context: { x: 2, n: 1, at line 1 } pow(2, 1)
  • Context: { x: 2, n: 2, at line 5 } pow(2, 2)
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
Il y a 2 anciens contextes et 1 en cours d'exécution pour `pow(2, 1)`. ### La sortie Pendant l'exécution de `pow(2, 1)`, contrairement à avant, la condition `n == 1` est la vérité, donc la première branche de `if` fonctionne: ```js function pow(x, n) { if (n == 1) { *!* return x; */!* } else { return x * pow(x, n - 1); } } ``` Il n'y a plus d'appels imbriqués, donc la fonction se termine en renvoyant`2`. Lorsque la fonction se termine, son contexte d'exécution n'est plus nécessaire, il est donc supprimé de la mémoire. La précédente est restaurée en haut de la pile:
  • Context: { x: 2, n: 2, at line 5 } pow(2, 2)
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
L'exécution de `pow(2, 2)` est repris. Il a le résultat du sous-appel `pow(2, 1)`, de sorte qu'il peut également terminer l'évaluation de `x * pow(x, n - 1)`, retournant `4`. Ensuite, le contexte précédent est restauré:
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
Quand il se termine, nous avons un résultat de `pow(2, 3) = 8`. La profondeur de récursion dans ce cas était: **3**. Comme nous pouvons le voir dans les illustrations ci-dessus, la profondeur de récursion est égale au nombre maximal de contextes dans la pile. Notez les besoins en mémoire. Les contextes prennent de la mémoire. Dans notre cas, augmenter à la puissance de `n` nécessite en réalité de la mémoire pour les contextes `n`, pour toutes les valeurs inférieures de `n`. Un algorithme basé sur des boucles est plus économe en mémoire: ```js function pow(x, n) { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } ``` Le `pow` itératif utilise un contexte unique qui change les processus `i` et `result` dans le processus. Ses besoins en mémoire sont faibles, fixes et ne dépendent pas de `n`. **Toute récursion peut être réécrite sous forme de boucle. La variante de boucle peut généralement être rendue plus efficace.** ..Parfois, la réécriture n’est pas triviale, en particulier lorsque la fonction utilise différents sous-appels récursifs en fonction des conditions et fusionne leurs résultats ou lorsque la création de branche est plus complexe. Et l'optimisation risque de ne pas être nécessaire et de ne pas valoir la peine. La récursion peut donner un code plus court, plus facile à comprendre et à supporter. Les optimisations ne sont pas nécessaires à chaque endroit, nous avons surtout besoin d'un bon code, c'est pourquoi il est utilisé. ## Traversées récursives Une autre grande application de la récursion est une traversée récursive. Imaginez, nous avons une entreprise. La structure du personnel peut être présentée comme un objet: ```js let company = { sales: [{ name: 'John', salary: 1000 }, { name: 'Alice', salary: 1600 }], development: { sites: [{ name: 'Peter', salary: 2000 }, { name: 'Alex', salary: 1800 }], internals: [{ name: 'Jack', salary: 1300 }] } }; ``` En d'autres termes, une entreprise a des départements. - Un département peut avoir un tableau de personnel. Par exemple, le département des ventes compte 2 employés: John et Alice. - Ou bien un département peut être divisé en sous-départements, comme `development` a deux branches: `sites` et `internes`. Chacun d'entre eux a son propre personnel. - Il est également possible que lorsqu'un sous-département s'agrandisse, il se divise en sous-départements (ou équipes). Par exemple, le département `sites` peut être divisé en équipes pour les sites `siteA` et `siteB`. Et, potentiellement, ils peuvent être diviser encore plus. Ce n'est pas sur la photo, c'est juste quelque chose qu'ont pourrait immaginer. Maintenant, disons que nous voulons une fonction pour obtenir la somme de tous les salaires. Comment peut-on faire ça? Une approche itérative n’est pas facile, car la structure n’est pas simple. La première idée peut être de créer une boucle `for` sur `company` avec une sous-boucle imbriqué sur les départements de premier niveau. Mais ensuite, nous avons besoin de plus de sous-boucles imbriquées pour parcourir le personnel des départements de second niveau, tels que les `sites`... Et puis une autre sous-boucle dans ceux des départements de 3ème niveau qui pourraient apparaître dans le futur ? Si nous mettons 3-4 sous-boucles imbriquées dans le code pour traverser un seul objet, cela devient plutôt moche. Essayons la récursion. Comme nous pouvons le constater, lorsque notre fonction demande à un département de faire la somme, il existe deux cas possibles: 1. S’il s’agit d’un "simple" département avec un *tableau* de personnes, nous pouvons alors additionner les salaires en une simple boucle. 2. Ou bien *c'est un objet* avec `N` sous-départements -- alors nous pouvons faire des appels `N` récursifs pour obtenir la somme de chaque sous-étape et combiner les résultats. Le premier cas est la base de la récursivité, le cas trivial, lorsque nous obtenons un tableau. Le 2ème cas où nous obtenons un objet est l'étape récursive. Une tâche complexe est divisée en sous-tâches pour les plus petits départements. Ils peuvent à leur tour se séparer à nouveau, mais tôt ou tard, la scission se terminera à (1). L'algorithme est probablement encore plus facile à lire à partir du code: ```js run let company = { // le même objet, compressé pour la brièveté sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }], development: { sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }], internals: [{name: 'Jack', salary: 1300}] } }; // La fonction pour faire le travail *!* function sumSalaries(department) { if (Array.isArray(department)) { // case (1) return department.reduce((prev, current) => prev + current.salary, 0); // additionne le tableau } else { // case (2) let sum = 0; for (let subdep of Object.values(department)) { sum += sumSalaries(subdep); // appel récursivement pour les sous-départements, additionnez les résultats } return sum; } } */!* alert(sumSalaries(company)); // 7700 ``` Le code est court et facile à comprendre (tout va bien?). C'est le pouvoir de la récursion. Cela fonctionne également pour tous les niveaux d'imbrication de sous-départements. Voici le schéma des appels: ![salaires récursifs](recursive-salaries.svg) On peut facilement voir le principe: pour un objet `{...}` les sous-appels sont faits, alors que les tableaux `[...]` sont les "feuilles" de l'arbre de récurrence, elles donnent un résultat immédiat. Notez que le code utilise des fonctionnalités intelligentes que nous avons déjà abordées: - La méthode `arr.reduce` a été expliquée dans le chapitre pour obtenir la somme du tableau. - La boucle `for(val of Object.values(obj))` itérer sur les valeurs d'objet: `Object.values` retourne un tableau d'eux-mêmes. ## Structures récursives Une structure de données récursive (définie de manière récursive) est une structure qui se réplique par parties. Nous venons de le voir dans l'exemple d'une structure d'entreprise ci-dessus. Un *département* d'entreprise est: - Soit un éventail de personnes. - Ou un objet avec des *départements*. Pour les développeurs Web, il existe des exemples bien mieux connus: les documents HTML et XML. Dans le document HTML, une balise *HTML* peut contenir une liste de: - Morceaux de texte. - Commentaires HTML. - Autres *balises HTML* (pouvant à leur tour contenir des morceaux de texte/commentaires ou d’autres balises, etc.). C'est encore une définition récursive. Pour une meilleure compréhension, nous allons couvrir une autre structure récursive nommée "Liste chaînée" qui pourrait être une meilleure alternative aux tableaux dans certains cas. ### Liste chaînée Imaginez, nous voulons stocker une liste ordonnée d'objets. Le choix naturel serait un tableau: ```js let arr = [obj1, obj2, obj3]; ``` ... Mais il y a un problème avec les tableaux. Les opérations "delete element" et "insert element" sont coûteuses. Par exemple, l'opération `arr.unshift(obj)` doit renuméroter tous les éléments pour faire de la place pour un nouvel `obj`, et si le tableau est grand, cela prend du temps. Même chose avec `arr.shift()`. Les seules modifications structurelles ne nécessitant pas de renumérotation en masse sont celles qui fonctionnent avec la fin du tableau: `arr.push/pop`. Ainsi, un tableau peut être assez lent pour les grandes files d'attente, lorsque nous devons travailler avec sont début. Alternativement, si nous avons vraiment besoin d'une insertion/suppression rapide, nous pouvons choisir une autre structure de données appelée la [Liste chaînée](https://fr.wikipedia.org/wiki/Liste_cha%C3%AEn%C3%A9e). *L'élémentde la liste liée* est défini de manière récursive en tant qu'objet avec: - `value`. - `next` propriété référençant le prochain *élément de liste liée* ou `null` si c'est la fin. Par exemple: ```js let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; ``` Représentation graphique de la liste: ![linked list](linked-list.svg) An alternative code for creation: ```js no-beautify let list = { value: 1 }; list.next = { value: 2 }; list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; list.next.next.next.next = null; ``` Ici, nous pouvons voir encore plus clairement qu'il y a plusieurs objets, chacun ayant les valeurs `value` et `next` pointant vers le voisin. La variable `list` est le premier objet de la chaîne. Par conséquent, en suivant les pointeurs `next`, nous pouvons atteindre n'importe quel élément. La liste peut être facilement divisée en plusieurs parties et ultérieurement réunie: ```js let secondList = list.next.next; list.next.next = null; ``` ![linked list split](linked-list-split.svg) Pour joindre: ```js list.next.next = secondList; ``` Et nous pouvons sûrement insérer ou retirer des objets n’importe où. Par exemple, pour ajouter une nouvelle valeur, nous devons mettre à jour la tête de la liste: ```js let list = { value: 1 }; list.next = { value: 2 }; list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; *!* // ajoute la nouvelle valeur à la liste list = { value: "new item", next: list }; */!* ``` ![linked list](linked-list-0.svg) Pour supprimer une valeur du milieu, changez le `next` de la précédente: ```js list.next = list.next.next; ``` ![linked list](linked-list-remove-1.svg) `List.next` a sauté `1` à la valeur `2`. La valeur `1` est maintenant exclue de la chaîne. Si elle n'est pas stocké ailleurs, elle sera automatiquement supprimé de la mémoire. Contrairement aux tableaux, il n'y a pas de renumérotation en masse, nous pouvons facilement réorganiser les éléments. Naturellement, les listes ne sont pas toujours meilleures que les tableaux. Sinon, tout le monde n'utiliserait que des listes. Le principal inconvénient est que nous ne pouvons pas facilement accéder à un élément par son numéro. Dans un tableau simple: `arr [n]` est une référence directe. Mais dans la liste, nous devons commencer à partir du premier élément et aller `next``N` fois pour obtenir le Nième élément. ...Mais nous n’avons pas toujours besoin de telles opérations. Par exemple, quand on a besoin d’une file d’attente ou même d’un [deque](https://en.wikipedia.org/wiki/Double-ended_queue) -- la structure ordonnée qui doit permettre l'ajout/suppression très rapide d'éléments des deux extrémités, mais l'accès au milieu n'est pas nécessaire. Les listes peuvent être améliorées: - Nous pouvons ajouter la propriété `prev` en plus de `next` pour référencer l'élément précédent, pour revenir facilement. - Nous pouvons également ajouter une variable nommée `tail` faisant référence au dernier élément de la liste (et la mettre à jour lors de l'ajout/suppression d'éléments de la fin). - ... La structure de données peut varier en fonction de nos besoins. ## Résumé Terms: - *Recursion* est un terme de programmation qui signifie q'une fonction s'appelle elle-même. Les fonctions récursives peuvent être utilisées pour résoudre des tâches de manière élégante. Lorsqu'une fonction s'appelle elle-même, cela s'appelle une *étape de récursion*. La *base* de la récursion est constituée par les arguments de la fonction qui rendent la tâche si simple que la fonction ne fait plus d'appels. - Une structure de données de [Type récursif](https://fr.wikipedia.org/wiki/Type_r%C3%A9cursif) est une structure de données qui peut être définie à l'aide de elle-même. Par exemple, la liste chaînée peut être définie comme une structure de données consistant en un objet référençant une liste (ou null). ```js list = { value, next -> list } ``` Les arbres tels que l’arbre des éléments HTML ou l’arbre des départements de ce chapitre sont également naturellement récursifs: ils ont des branches et chaque branche peut avoir d’autres branches. Des fonctions récursives peuvent être utilisées pour les parcourir, comme nous l'avons vu dans l'exemple `sumSalary`. Toute fonction récursive peut être réécrite en une fonction itérative. Et c'est parfois nécessaire pour optimiser les choses. Mais pour de nombreuses tâches, une solution récursive est assez rapide et plus facile à écrire et à supporter. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/head.html ================================================ ================================================ FILE: 1-js/06-advanced-functions/02-rest-parameters-spread/article.md ================================================ # Les paramètres Rest et la syntaxe spread De nombreuses fonctions intégrées à JavaScript prennent en charge un nombre arbitraire d'arguments. Par exemple : - `Math.max(arg1, arg2, ..., argN)` -- renvoie le plus grand des arguments. - `Object.assign(dest, src1, ..., srcN)` -- copie les propriétés de `src1..N` dans `dest`. - ... etc. Dans ce chapitre, nous apprendrons à faire de même. Et aussi, comment passer des tableaux en paramètre à de telles fonctions. ## Les paramètres Rest `...` Une fonction peut être appelée avec un nombre quelconque d'arguments, peu importe comment elle a été définie. Comme ici : ```js run function sum(a, b) { return a + b; } alert( sum(1, 2, 3, 4, 5) ); ``` Il n'y aura pas d'erreur à cause d'arguments "excessifs". Mais bien sûr, dans le résultat, seuls les deux premiers seront comptés, donc le résultat dans le code ci-dessus est `3`. Le reste des paramètres peut être inclus dans la définition de la fonction en utilisant trois points `...` suivis du nom du tableau qui les contiendra. Les points signifient littéralement "rassemblez les paramètres restants dans un tableau". Par exemple, pour rassembler tous les arguments dans un tableau `args` : ```js run function sumAll(...args) { // args est le nom du tableau let sum = 0; for (let arg of args) sum += arg; return sum; } alert( sumAll(1) ); // 1 alert( sumAll(1, 2) ); // 3 alert( sumAll(1, 2, 3) ); // 6 ``` Nous pouvons choisir d’obtenir les premiers paramètres sous forme de variables et de ne rassembler que le reste. Ici, les deux premiers arguments vont dans les variables et le reste dans le tableau `titles` : ```js run function showName(firstName, lastName, ...titles) { alert( firstName + ' ' + lastName ); // Julius Caesar // le reste va dans le tableau titles // i.e. titles = ["Consul", "Imperator"] alert( titles[0] ); // Consul alert( titles[1] ); // Imperator alert( titles.length ); // 2 } showName("Julius", "Caesar", "Consul", "Imperator"); ``` ````warn header="Les paramètres rest doivent être à la fin" Les paramètres rest regroupent tous les arguments restants. Par conséquent, ce qui suit n'a pas de sens et génère une erreur : ```js function f(arg1, ...rest, arg2) { // arg2 après ...rest ?! // error } ``` Le `...rest` doit toujours être le dernier. ```` ## La variable "arguments" Il existe également un objet spécial array-like nommé `arguments` qui contient tous les arguments en fonction de leur index. Par exemple : ```js run function showName() { alert( arguments.length ); alert( arguments[0] ); alert( arguments[1] ); // c'est iterable // for(let arg of arguments) alert(arg); } // affiche : 2, Julius, Caesar showName("Julius", "Caesar"); // affiche : 1, Ilya, undefined (pas de second argument) showName("Ilya"); ``` Autrefois, les paramètres rest n'existaient pas dans le langage, et utiliser `arguments` était le seul moyen d'obtenir tous les arguments de la fonction. Et cela fonctionne toujours, on peut le trouver dans l'ancien code. Mais l’inconvénient est que, bien que `arguments` ressemblent à un tableau et qu’ils soient itératifs, ce n’est pas un tableau. Il ne supporte pas les méthodes de tableau, nous ne pouvons donc pas appeler `arguments.map(...)` par exemple. De plus, il contient toujours tous les arguments. Nous ne pouvons pas les capturer partiellement, comme nous l’avons fait avec les paramètres rest. Ainsi, lorsque nous avons besoin de ces fonctionnalités, les paramètres rest sont préférés. ````smart header="Les fonctions fléchées n'ont pas d'`\"arguments\"`" Si nous accédons à l'objet `arguments` à partir d'une fonction fléchée, il le prend à la fonction externe "normale". Voici un exemple : ```js run function f() { let showArg = () => alert(arguments[0]); showArg(); } f(1); // 1 ``` Comme nous nous en souvenons, les fonctions fléchées n’ont pas leur propre `this`. Nous savons maintenant qu’ils n’ont pas non plus l’objet spécial `arguments`. ```` ## Spread syntax [#spread-syntax] Nous venons de voir comment obtenir un tableau à partir de la liste de paramètres. Mais parfois, nous devons faire exactement l'inverse. Par exemple, il existe une fonction intégrée [Math.max](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Math/max) qui renvoie le plus grand nombre d'une liste : ```js run alert( Math.max(3, 5, 1) ); // 5 ``` Maintenant, disons que nous avons un tableau `[3, 5, 1]`. Comment appelons-nous `Math.max` avec ? Le passer "tel quel" ne fonctionnera pas, car `Math.max` attend une liste d’arguments numériques et non un tableau : ```js run let arr = [3, 5, 1]; *!* alert( Math.max(arr) ); // NaN */!* ``` Et nous ne pouvons sûrement pas lister manuellement les éléments dans le code `Math.max(arr[0], arr[1], arr[2])`, parce que nous pouvons ne pas savoir combien il y en a. Au fur et à mesure que notre script s'exécute, il peut y en avoir beaucoup ou pas du tout. Et ça deviendrait moche. *La sytaxe Spread* à la rescousse ! Il ressemble aux paramètres rest, en utilisant également `...`, mais fait tout le contraire. Quand `...arr` est utilisé dans l'appel de fonction, il "développe" un objet itérable `arr` dans la liste des arguments. Pour `Math.max` : ```js run let arr = [3, 5, 1]; alert( Math.max(...arr) ); // 5 (spread transforme un tableau en une liste d'arguments) ``` Nous pouvons aussi passer plusieurs iterables de cette façon : ```js run let arr1 = [1, -2, 3, 4]; let arr2 = [8, 3, -8, 1]; alert( Math.max(...arr1, ...arr2) ); // 8 ``` On peut même combiner la sytaxe spread avec des valeurs normales : ```js run let arr1 = [1, -2, 3, 4]; let arr2 = [8, 3, -8, 1]; alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25 ``` De plus, la sytaxe spread peut être utilisée pour fusionner des tableaux : ```js run let arr = [3, 5, 1]; let arr2 = [8, 9, 15]; *!* let merged = [0, ...arr, 2, ...arr2]; */!* alert(merged); // 0,3,5,1,2,8,9,15 (0, ensuite arr, ensuite 2, ensuite arr2) ``` Dans les exemples ci-dessus, nous avons utilisé un tableau pour illustrer la syntaxe spread, mais tout itérable fera l'affaire. Par exemple, nous utilisons ici la syntaxe spread pour transformer le string en tableau de caractères : ```js run let str = "Hello"; alert( [...str] ); // H,e,l,l,o ``` La syntaxe spread utilise en interne des itérateurs pour rassembler les éléments, de la même manière que `for..of`. Donc, pour une chaine de caractères, `for..of` retourne des caractères et `...str` devient `"H","e","l","l","o"`. La liste de caractères est transmise à l'initialiseur de tableau `[...str]`. Pour cette tâche particulière, nous pourrions également utiliser `Array.from`, car il convertit un itérable (comme une chaîne de caractères) en un tableau : ```js run let str = "Hello"; // Array.from convertit un itérable en tableau alert( Array.from(str) ); // H,e,l,l,o ``` Le résultat est le même que `[...str]`. Mais il existe une différence subtile entre `Array.from(obj)` et `[...obj]` : - `Array.from` fonctionne à la fois sur les tableaux et les iterables. - La syntaxe spread ne fonctionne que sur des iterables. Donc, pour transformer quelque chose en tableau, `Array.from` tend à être plus universel. ## Copier un tableau/objet Vous souvenez-vous quand nous avons parlé de `Object.assign()` [par le passé](info:object-copy#cloning-and-merging-object-assign) ? Il est possible de faire la même chose avec la syntaxe spread ! ```js run let arr = [1, 2, 3]; *!* let arrCopy = [...arr]; // répartir le tableau dans une liste de paramètres // puis mettre le résultat dans un nouveau tableau */!* // les tableaux ont-ils le même contenu ? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true // les tableaux sont-ils égaux ? alert(arr === arrCopy); // false (pas la même référence) // la modification de notre tableau initial ne modifie pas la copie : arr.push(4); alert(arr); // 1, 2, 3, 4 alert(arrCopy); // 1, 2, 3 ``` Notez qu'il est possible de faire la même chose pour faire une copie d'un objet : ```js run let obj = { a: 1, b: 2, c: 3 }; *!* let objCopy = { ...obj }; // répartir l'objet dans une liste de paramètres // puis retourne le résultat dans un nouvel objet */!* // les objets ont-ils le même contenu ? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true // les objets sont-ils égaux ? alert(obj === objCopy); // false (pas la même référence) // la modification de notre objet initial ne modifie pas la copie : obj.d = 4; alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3} ``` Cette façon de copier un objet est beaucoup plus courte que `let objCopy = Object.assign({}, obj)` ou pour un tableau `let arrCopy = Object.assign([], arr);` nous préférons donc l'utiliser chaque fois que nous le pouvons. ## Résumé Quand on voit `"..."` dans le code, il s’agit soit des paramètres rest ou de la syntaxe spread. Il existe un moyen facile de les distinguer : - Lorsque `...` se trouve à la fin des paramètres de fonction, il s'agit des "paramètres rest" et rassemble le reste de la liste des arguments dans un tableau. - Lorsque `...` est présent dans un appel de fonction ou similaire, on l'appelle "la syntaxe spread" (syntaxe de propagation) et étend un tableau en une liste. Modèles d'utilisation : - Les paramètres rest permettent de créer des fonctions acceptant un nombre quelconque d'arguments. - La syntaxe spread est utilisée pour passer un tableau à des fonctions nécessitant normalement une liste d'arguments. Ensemble, ils permettent de voyager facilement entre une liste et un tableau de paramètres. Tous les arguments d'un appel de fonction sont également disponibles dans la vriable `arguments` "à l'ancienne" : objet itérable array-like. ================================================ FILE: 1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md ================================================ La réponse est : **Pete**. Une fonction obtient des variables externes telles qu'elles sont maintenant, elle utilise les valeurs les plus récentes. Les anciennes valeurs de variable ne sont enregistrées nulle part. Lorsqu'une fonction veut une variable, elle prend la valeur actuelle de son propre environnement lexical ou de l'environnement externe. ================================================ FILE: 1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md ================================================ importance: 5 --- # Une fonction récupère-t-elle les dernières modifications ? La fonction sayHi utilise un nom de variable externe. Lorsque la fonction s'exécute, quelle valeur va-t-elle utiliser ? ```js let name = "John"; function sayHi() { alert("Hi, " + name); } name = "Pete"; sayHi(); // qu'affichera-t-elle : "John" ou "Pete" ? ``` De telles situations sont courantes à la fois dans le développement côté navigateur et côté serveur. Une fonction peut être programmée pour s'exécuter plus tard qu'elle n'est créée, par exemple après une action de l'utilisateur ou une demande réseau. Donc, la question est : reprend-elle les derniers changements ? ================================================ FILE: 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/solution.js ================================================ function makeArmy() { let shooters = []; for(let i = 0; i < 10; i++) { let shooter = function() { // shooter function alert( i ); // should show its number }; shooters.push(shooter); } return shooters; } ================================================ FILE: 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/source.js ================================================ function makeArmy() { let shooters = []; let i = 0; while (i < 10) { let shooter = function() { // shooter function alert( i ); // should show its number }; shooters.push(shooter); i++; } return shooters; } /* let army = makeArmy(); army[0](); // the shooter number 0 shows 10 army[5](); // and number 5 also outputs 10... // ... all shooters show 10 instead of their 0, 1, 2, 3... */ ================================================ FILE: 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/test.js ================================================ describe("army", function() { let army; before(function() { army = makeArmy(); window.alert = sinon.stub(window, "alert"); }); it("army[0] shows 0", function() { army[0](); assert(alert.calledWith(0)); }); it("army[5] shows 5", function() { army[5](); assert(alert.calledWith(5)); }); after(function() { window.alert.restore(); }); }); ================================================ FILE: 1-js/06-advanced-functions/03-closure/10-make-army/solution.md ================================================ Examinons exactement ce qui se fait à l'intérieur de `makeArmy`, et la solution deviendra évidente. 1. Elle crée un tableau vide `shooters`: ```js let shooters = []; ``` 2. Le remplit avec des fonctions dans la boucle via `shooters.push(function)`. Chaque élément est une fonction, le tableau résultant ressemble à ceci : ```js no-beautify shooters = [ function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); } ]; ``` 3. Le tableau est renvoyé par la fonction. Puis, plus tard, l'appel à n'importe quel membre, par ex. `Army[5]()` récupérera l'élément `army[5]` du tableau (qui est une fonction) et l'appellera. Maintenant, pourquoi toutes ces fonctions affichent-elles la même valeur, `10` ? C'est parce qu'il n'y a pas de variable locale `i` dans les fonctions de `shooter`. Lorsqu'une telle fonction est appelée, elle prend `i` de son environnement lexical externe. Alors, quelle sera la valeur de `i` ? Si nous regardons la source : ```js function makeArmy() { ... let i = 0; while (i < 10) { let shooter = function() { // fonction shooter alert( i ); // should show its number }; shooters.push(shooter); // ajoute une fonction au tableau i++; } ... } ``` Nous pouvons voir que toutes les fonctions `shooter` sont créées dans l'environnement lexical de la fonction `makeArmy()`. Mais quand `army[5]()` est appelé, `makeArmy` a déjà terminé son travail, et la valeur finale de `i` est `10` (`while` s'arrête à `i=10`). En conséquence, toutes les fonctions `shooter` obtiennent la même valeur de l'environnement lexical externe et c'est-à-dire la dernière valeur, `i=10`. ![](lexenv-makearmy-empty.svg) Comme vous pouvez le voir ci-dessus, à chaque itération d'un bloc `while {...}`, un nouvel environnement lexical est créé. Donc, pour résoudre ce problème, nous pouvons copier la valeur de `i` dans une variable dans le bloc `while {...}`, comme ceci : ```js run function makeArmy() { let shooters = []; let i = 0; while (i < 10) { *!* let j = i; */!* let shooter = function() { // fonction shooter alert( *!*j*/!* ); // devrait afficher son numéro }; shooters.push(shooter); i++; } return shooters; } let army = makeArmy(); // Maintenant, le code fonctionne correctement army[0](); // 0 army[5](); // 5 ``` Ici, `let j = i` déclare une variable "itération-locale" `j` et y copie `i`. Les primitives sont copiées "par valeur", donc nous obtenons en fait une copie indépendante de `i`, appartenant à l'itération de boucle courante. Les shooters fonctionnent correctement, car la valeur de `i` vit maintenant un peu plus près. Pas dans l'environnement lexical `makeArmy()`, mais dans l'environnement lexical qui correspond à l'itération de la boucle actuelle : ![](lexenv-makearmy-while-fixed.svg) Ce genre de problème pourrait également être évité si nous utilisions `for` au début, comme ceci : ```js run demo function makeArmy() { let shooters = []; *!* for(let i = 0; i < 10; i++) { */!* let shooter = function() { // fonction shooter alert( i ); // devrait afficher son numéro }; shooters.push(shooter); } return shooters; } let army = makeArmy(); army[0](); // 0 army[5](); // 5 ``` C'est essentiellement la même chose, car `for` génère un nouvel environnement lexical à chaque itération avec sa propre variable `i`. Ainsi, le `shooter` généré à chaque itération fait référence à son propre `i`, à partir de cette itération même. ![](lexenv-makearmy-for-fixed.svg) Maintenant que vous avez déployé tant d'efforts pour lire ceci, et que la recette finale est si simple - utilisez simplement `for`, vous vous demandez peut-être - cela en valait-il la peine ? Eh bien, si vous pouviez facilement répondre à la question, vous ne liriez pas la solution. Donc, j'espère que cette tâche doit vous avoir aidé à comprendre un peu mieux les choses. En outre, il existe en effet des cas où l'on préfère `while` à `for`, et d'autres scénarios où de tels problèmes sont réels. ================================================ FILE: 1-js/06-advanced-functions/03-closure/10-make-army/task.md ================================================ importance: 5 --- # Armée de fonctions Le code suivant crée un tableau de `shooters`. Chaque fonction est censée sortir son numéro. Mais quelque chose ne va pas … ```js run function makeArmy() { let shooters = []; let i = 0; while (i < 10) { let shooter = function() { // créer une fonction shooter alert( i ); // qui devrait afficher son numéro }; shooters.push(shooter); // and add it to the array i++; } // ...and return the array of shooters return shooters; } let army = makeArmy(); *!* // tous les shooters affichent 10 au lieu de leurs numéros 0, 1, 2, 3 ... army[0](); // 10 du shooter numéro 0 army[1](); // 10 du shooter numéro 1 army[2](); // 10 ...etc. */!* ``` Pourquoi tous les shooters affichent-ils la même valeur ? Corrigez le code pour qu'il fonctionne comme prévu. ================================================ FILE: 1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md ================================================ La réponse est : **Pete**. La fonction `work()` dans le code ci-dessous obtient `name` du lieu de son origine via la référence d'environnement lexical externe : ![](lexenv-nested-work.svg) Donc, le résultat est `"Pete"` ici. Mais s'il n'y avait pas de `let name` dans `makeWorker()`, alors la recherche irait à l'extérieur et prendrait la variable globale comme nous pouvons le voir dans la chaîne ci-dessus. Dans ce cas, le résultat serait `"John"`. ================================================ FILE: 1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md ================================================ importance: 5 --- # Quelles variables sont disponibles ? La fonction `makeWorker` ci-dessous crée une autre fonction et la renvoie. Cette nouvelle fonction peut être appelée ailleurs. Aura-t-elle accès aux variables externes depuis son lieu de création, ou depuis le lieu d'invocation, ou les deux ? ```js function makeWorker() { let name = "Pete"; return function() { alert(name); }; } let name = "John"; // créons une fonction let work = makeWorker(); // appelons-la work(); // que va-t-elle afficher ? ``` Quelle valeur va-t-elle afficher ? "Pete" ou "John" ? ================================================ FILE: 1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md ================================================ La réponse : **0,1.** Les fonctions `counter` et `counter2` sont créées par différentes invocations de `makeCounter`. Elles ont donc des environnements lexicaux externes indépendants, chacun ayant son propre `count`. ================================================ FILE: 1-js/06-advanced-functions/03-closure/3-counter-independent/task.md ================================================ importance: 5 --- # Les compteurs sont-ils indépendants ? Ici, nous faisons deux compteurs : `counter` et `counter2` en utilisant la même fonction `makeCounter`. Sont-ils indépendants ? Que va montrer le deuxième compteur ? `0,1` ou `2,3` ou autre chose ? ```js function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); let counter2 = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 *!* alert( counter2() ); // ? alert( counter2() ); // ? */!* ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md ================================================ Cela fonctionnera sûrement très bien. Les deux fonctions imbriquées sont créées dans le même environnement Lexical externe. Elles partagent donc l'accès à la même variable `count` : ```js run function Counter() { let count = 0; this.up = function() { return ++count; }; this.down = function() { return --count; }; } let counter = new Counter(); alert( counter.up() ); // 1 alert( counter.up() ); // 2 alert( counter.down() ); // 1 ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/4-counter-object-independent/task.md ================================================ importance: 5 --- # Objet compteur Ici, un objet compteur est créé à l'aide de la fonction constructeur. Est-ce que cela fonctionnera ? Que va-t-elle afficher ? ```js function Counter() { let count = 0; this.up = function() { return ++count; }; this.down = function() { return --count; }; } let counter = new Counter(); alert( counter.up() ); // ? alert( counter.up() ); // ? alert( counter.down() ); // ? ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md ================================================ Le résultat est **une erreur**. La fonction `sayHi` est déclarée à l'intérieur du `if`, elle ne vit donc qu'à l'intérieur. Il n'y a pas de `sayHi` dehors. ================================================ FILE: 1-js/06-advanced-functions/03-closure/5-function-in-if/task.md ================================================ importance: 5 --- # Fonction dans if Regardez ce code. Quel sera le résultat de l'appel à la dernière ligne ? ```js run let phrase = "Hello"; if (true) { let user = "John"; function sayHi() { alert(`${phrase}, ${user}`); } } *!* sayHi(); */!* ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md ================================================ Pour que les secondes parenthèses fonctionnent, les premières doivent renvoyer une fonction. Comme ceci : ```js run function sum(a) { return function(b) { return a + b; // prend "a" de l'environnement lexical externe }; } alert( sum(1)(2) ); // 3 alert( sum(5)(-1) ); // 4 ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/6-closure-sum/task.md ================================================ importance: 4 --- # Somme avec des closures Écrivez une fonction `sum` qui fonctionne comme ceci :` sum(a)(b) = a + b`. Oui, exactement de cette façon, en utilisant des doubles parenthèses (ce n'est pas une faute de frappe). Par exemple : ```js sum(1)(2) = 3 sum(5)(-1) = 4 ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/7-let-scope/solution.md ================================================ Le résultat est : **error**. Essayez de l'exécuter : ```js run let x = 1; function func() { *!* console.log(x); // ReferenceError: Cannot access 'x' before initialization */!* let x = 2; } func(); ``` Dans cet exemple, nous pouvons observer la différence particulière entre une variable "non existante" et une variable "non initialisée". Comme vous l'avez peut-être lu dans l'article [](info:closure), une variable démarre à l'état "non initialisée" à partir du moment où l'exécution entre dans un bloc de code (ou une fonction). Et elle reste non initialisée jusqu'à la déclaration `let` correspondante. En d'autres termes, une variable existe techniquement, mais ne peut pas être utilisée avant `let`. Le code ci-dessus le démontre. ```js function func() { *!* // la variable locale x est connue du moteur depuis le début de la fonction, // mais "non initialisée" (inutilisable) jusqu'à let ("zone morte") // d'où l'erreur */!* console.log(x); // ReferenceError: Cannot access 'x' before initialization let x = 2; } ``` Cette zone d'inutilisabilité temporaire d'une variable (du début du bloc de code jusqu'à `let`) est parfois appelée "dead zone" (zone morte). ================================================ FILE: 1-js/06-advanced-functions/03-closure/7-let-scope/task.md ================================================ importance: 4 --- # La variable est-elle visible ? Quel sera le résultat de ce code ? ```js let x = 1; function func() { console.log(x); // ? let x = 2; } func(); ``` P.S. Il y a un piège dans cette tâche. La solution n'est pas évidente. ================================================ FILE: 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/solution.js ================================================ function inArray(arr) { return x => arr.includes(x); } function inBetween(a, b) { return x => (x >= a && x <= b); } ================================================ FILE: 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/source.js ================================================ let arr = [1, 2, 3, 4, 5, 6, 7]; function inBetween(a, b) { // ...your code... } function inArray(arr) { // ...your code... } ================================================ FILE: 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/test.js ================================================ describe("inArray", function() { let arr = [1, 2, 3, 4, 5, 6, 7]; it("returns the filter for values in array", function() { let filter = inArray(arr); assert.isTrue(filter(5)); assert.isFalse(filter(0)); }); }); describe("inBetween", function() { it("returns the filter for values between", function() { let filter = inBetween(3, 6); assert.isTrue(filter(5)); assert.isFalse(filter(0)); }); }); ================================================ FILE: 1-js/06-advanced-functions/03-closure/8-filter-through-function/solution.md ================================================ # Filter inBetween ```js run function inBetween(a, b) { return function(x) { return x >= a && x <= b; }; } let arr = [1, 2, 3, 4, 5, 6, 7]; alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 ``` # Filter inArray ```js run demo function inArray(arr) { return function(x) { return arr.includes(x); }; } let arr = [1, 2, 3, 4, 5, 6, 7]; alert( arr.filter(inArray([1, 2, 10])) ); // 1,2 ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md ================================================ importance: 5 --- # Filtrer par fonction Nous avons une méthode intégrée `arr.filter(f)` pour les tableaux. Elle filtre tous les éléments à travers la fonction `f`. S'elle renvoie `true`, cet élément est renvoyé dans le tableau résultant. Créez un ensemble de filtres "prêts à l'emploi": - `inBetween(a, b)` -- entre `a` et `b` ou égal à eux (inclusivement). - `inArray([...])` -- dans le tableau donné. L'usage doit être comme ceci : - `arr.filter(inBetween(3,6))` -- sélectionne uniquement les valeurs entre 3 et 6. - `arr.filter(inArray([1,2,3]))` -- sélectionne uniquement les éléments correspondant à l'un des membres de `[1,2,3]`. Par exemple : ```js /* .. votre code pour inBetween et inArray */ let arr = [1, 2, 3, 4, 5, 6, 7]; alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 alert( arr.filter(inArray([1, 2, 10])) ); // 1,2 ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/solution.js ================================================ function byField(fieldName){ return (a, b) => a[fieldName] > b[fieldName] ? 1 : -1; } ================================================ FILE: 1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/source.js ================================================ function byField(fieldName){ // Your code goes here. } ================================================ FILE: 1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js ================================================ describe("byField", function(){ let users = [ { name: "John", age: 20, surname: "Johnson" }, { name: "Pete", age: 18, surname: "Peterson" }, { name: "Ann", age: 19, surname: "Hathaway" }, ]; it("sorts users by name", function(){ let nameSortedKey = [ { name: "Ann", age: 19, surname: "Hathaway" }, { name: "John", age: 20, surname: "Johnson"}, { name: "Pete", age: 18, surname: "Peterson" }, ]; let nameSortedAnswer = users.sort(byField("name")); assert.deepEqual(nameSortedKey, nameSortedAnswer); }); it("sorts users by age", function(){ let ageSortedKey = [ { name: "Pete", age: 18, surname: "Peterson" }, { name: "Ann", age: 19, surname: "Hathaway" }, { name: "John", age: 20, surname: "Johnson"}, ]; let ageSortedAnswer = users.sort(byField("age")); assert.deepEqual(ageSortedKey, ageSortedAnswer); }); it("sorts users by surname", function(){ let surnameSortedKey = [ { name: "Ann", age: 19, surname: "Hathaway" }, { name: "John", age: 20, surname: "Johnson"}, { name: "Pete", age: 18, surname: "Peterson" }, ]; let surnameSortedAnswer = users.sort(byField("surname")); assert.deepEqual(surnameSortedAnswer, surnameSortedKey); }); }); ================================================ FILE: 1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md ================================================ ================================================ FILE: 1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md ================================================ importance: 5 --- # Trier par champ Nous avons un tableau d'objets à trier : ```js let users = [ { name: "John", age: 20, surname: "Johnson" }, { name: "Pete", age: 18, surname: "Peterson" }, { name: "Ann", age: 19, surname: "Hathaway" } ]; ``` La manière habituelle de le faire serait : ```js // par nom (Ann, John, Pete) users.sort((a, b) => a.name > b.name ? 1 : -1); // par age (Pete, Ann, John) users.sort((a, b) => a.age > b.age ? 1 : -1); ``` Peut-on le rendre encore moins verbeux, comme ceci ? ```js users.sort(byField('name')); users.sort(byField('age')); ``` Donc, au lieu d’écrire une fonction, il suffit de mettre `byField(fieldName)`. Ecrivez la fonction `byField` qui peut être utilisée pour cela. ================================================ FILE: 1-js/06-advanced-functions/03-closure/article.md ================================================ # Variable scope, closure JavaScript est un langage orienté vers le fonctionnel. Cela nous donne beaucoup de liberté. Une fonction peut être créée dynamiquement, passée en argument à une autre fonction et appelée ultérieurement à partir d'un code totalement différent. Nous savons déjà qu'une fonction peut accéder à des variables en dehors de celle-ci (variables externes). Mais que se passe-t-il si les variables externes changent depuis la création d'une fonction ? La fonction obtiendra-t-elle des valeurs plus récentes ou les anciennes ? Et si une fonction est transmise en tant que paramètre et appelée depuis un autre endroit du code, aura-t-elle accès aux variables externes au nouvel endroit ? Développons maintenant nos connaissances pour inclure des scénarios plus complexes. ```smart header="Nous parlerons ici des variables `let`/`const`" En JavaScript, il y a 3 façons de déclarer une variable : `let`, `const` (les modernes) et `var` (le vestige du passé). - Dans cet article, nous utiliserons des variables `let` dans les exemples. - Les variables, déclarées avec `const`, se comportent de la même manière, donc cet article concerne également `const`. - L'ancien `var` a quelques différences notables, elles seront traitées dans l'article . ``` ## Code blocks Si une variable est déclarée à l'intérieur d'un bloc de code `{...}`, elle n'est visible qu'à l'intérieur de ce bloc. Par exemple : ```js run { // faire un travail avec des variables locales qui ne devraient pas être vues à l'extérieur let message = "Hello"; // visible uniquement dans ce bloc alert(message); // Hello } alert(message); // Error: message is not defined ``` Nous pouvons l'utiliser pour isoler un morceau de code qui fait sa propre tâche, avec des variables qui lui appartiennent uniquement : ```js run { // show message let message = "Hello"; alert(message); } { // show another message let message = "Goodbye"; alert(message); } ``` ````smart header="Il y aurait une erreur sans blocs" Veuillez noter que sans blocs séparés, il y aurait une erreur, si nous utilisons `let` avec le nom de variable existant : ```js run // show message let message = "Hello"; alert(message); // show another message *!* let message = "Goodbye"; // Error: variable already declared */!* alert(message); ``` ```` Pour `if`, `for`, `while` et ainsi de suite, les variables déclarées dans `{...}` ne sont également visibles qu'à l'intérieur : ```js run if (true) { let phrase = "Hello!"; alert(phrase); // Hello! } alert(phrase); // Error, no such variable! ``` Ici, après la fin du `if`, l'`alert` ci-dessous ne verra pas `phrase`, d'où l'erreur. C'est super, car cela nous permet de créer des variables locales, spécifiques à une branche `if`. La même chose vaut pour les boucles `for` et `while` : ```js run for (let i = 0; i < 3; i++) { // la variable i n'est visible que dans ce for alert(i); // 0, ensuite 1, ensuite 2 } alert(i); // Error, no such variable ``` Visuellement, `let i` est à l'extérieur de `{...}`. Mais la construction de `for` est spéciale ici : la variable déclarée à l'intérieur est considérée comme faisant partie du bloc. ## Fonctions imbriquées Une fonction est appelée "imbriquée" lorsqu'elle est créée dans une autre fonction. Il est possible de faire cela facilement avec JavaScript. Nous pouvons l'utiliser pour organiser notre code, comme ceci : ```js function sayHiBye(firstName, lastName) { // helper nested function to use below function getFullName() { return firstName + " " + lastName; } alert( "Hello, " + getFullName() ); alert( "Bye, " + getFullName() ); } ``` Ici, la fonction *imbriquée* `getFullName()` est faite pour plus de commodité. Elle peut accéder aux variables externes et peut donc renvoyer le nom complet. Les fonctions imbriquées sont assez courantes dans JavaScript. Ce qui est beaucoup plus intéressant, une fonction imbriquée peut être retournée : soit en tant que propriété d'un nouvel objet, soit en tant que résultat par elle-même. Elle peut ensuite être utilisée ailleurs. Peu importe où, elle a toujours accès aux mêmes variables externes. Ci-dessous, `makeCounter` crée la fonction "counter" qui renvoie le nombre suivant à chaque appel : ```js run function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2 ``` Bien que simples, des variantes légèrement modifiées de ce code ont des utilisations pratiques, par exemple un [générateur de nombres aléatoires](https://fr.wikipedia.org/wiki/G%C3%A9n%C3%A9rateur_de_nombres_pseudo-al%C3%A9atoires) pour générer des valeurs aléatoires pour des tests automatisés. Comment cela marche-t-il ? Si nous créons plusieurs compteurs, seront-ils indépendants ? Que se passe-t-il avec les variables ici ? La compréhension de ce genre de choses est excellente pour la connaissance globale de JavaScript et bénéfique pour les scénarios plus complexes. Allons donc un peu en profondeur. ## Lexical Environment ```warn header="Voilà des dragons !" L'explication technique approfondie reste à venir. Bien que je souhaiterai éviter les détails de bas niveau du langage, toute compréhension sans eux serait manquante et incomplète, alors préparez-vous. ``` Pour plus de clarté, l'explication est divisée en plusieurs étapes. ### Étape 1. Variables En JavaScript, chaque fonction en cours d'exécution, bloc de code `{...}` et le script dans son ensemble ont un objet associé interne (caché) connu sous le nom de *Environnement Lexical*. L'objet environnement lexical se compose de deux parties : 1. *Environment Record* -- un objet qui stocke toutes les variables locales comme ses propriétés (et quelques autres informations comme la valeur de `this`). 2. Une référence à *l'environnement lexical externe*, celui associé au code externe. **Une "variable" est juste une propriété de l'objet interne spécial `Environment Record`. "Pour obtenir ou modifier une variable" signifie "pour obtenir ou modifier une propriété de cet objet".** Dans ce code simple sans fonctions, il n'y a qu'un seul environnement lexical : ![lexical environment](lexical-environment-global.svg) Il s'agit de l'environnement Lexical dit *global*, associé à l'ensemble du script. Sur l'image ci-dessus, le rectangle signifie Environment Record (stockage de variable) et la flèche signifie la référence externe. L'environnement lexical global n'a pas de référence externe, c'est pourquoi la flèche pointe vers `null`. À mesure que le code commence à s'exécuter et se poursuit, l'environnement lexical change. Voici un peu plus de code : ![lexical environment](closure-variable-phrase.svg) Les rectangles sur le côté droit montrent comment l'environnement lexical global change pendant l'exécution : 1. Lorsque le script démarre, l'environnement lexical est prérempli avec toutes les variables déclarées. - Initialement, elles sont à l'état "non initialisé". C'est un état interne spécial, cela signifie que le moteur connaît la variable, mais elle ne peut pas être référencée tant qu'elle n'a pas été déclarée avec `let`. C'est presque la même chose que si la variable n'existait pas. 2. Ensuite, la définition de `let phrase` apparaît. Il n'y a pas encore d'affectation, donc sa valeur est `undefined`. Nous pouvons utiliser la variable depuis ce moment. 3. `phrase` se voit attribuer une valeur. 4. `phrase` change de valeur. Tout semble simple pour l'instant, non ? - Une variable est la propriété d'un objet interne spécial, associé au bloc/fonction/script en cours d'exécution. - Travailler avec des variables, c'est travailler avec les propriétés de cet objet. ```smart header="L'environnement lexical est un objet de spécification" "L'environnement lexical" est un objet de spécification : il n'existe que "théoriquement" dans la [spécification du langage](https://tc39.es/ecma262/#sec-lexical-environments) pour décrire comment les choses fonctionnent. nous ne pouvons pas obtenir cet objet dans notre code et le manipuler directement. Les moteurs JavaScript peuvent également l'optimiser, supprimer les variables inutilisées pour économiser de la mémoire et effectuer d'autres opérations internes, tant que le comportement visible reste conforme à la description. ``` ### Step 2. Fonctions Declarations Une fonction est également une valeur, comme une variable. **La différence est qu'une fonction déclaration est instantanément et complètement initialisée.** Lorsqu'un environnement lexical est créé, une fonction déclaration devient immédiatement une fonction prête à l'emploi (contrairement à `let`, qui est inutilisable jusqu'à la déclaration). C'est pourquoi nous pouvons utiliser une fonction, déclarée comme fonction déclaration, avant même la déclaration elle-même. Par exemple, voici l'état initial de l'environnement lexical global lorsque nous ajoutons une fonction : ![](closure-function-declaration.svg) Naturellement, ce comportement ne s'applique qu'aux fonctions déclarations, pas aux fonctions expressions où nous attribuons une fonction à une variable, telle que `let say = function(name)...`. ### Step 3. Environnement lexical intérieur et extérieur Lorsqu'une fonction s'exécute, au début de l'appel, un nouvel environnement lexical est créé automatiquement pour stocker les variables locales et les paramètres de l'appel. Par exemple, pour `say("John")`, cela ressemble à ceci (l'exécution est à la ligne, marquée d'une flèche) : ![](lexical-environment-simple.svg) Pendant l'appel de la fonction, nous avons deux environnements lexicaux : l'intérieur (pour l'appel de la fonction) et l'extérieur (global) : - L'environnement lexical interne correspond à l'exécution actuelle de `say`. Il a une seule propriété : `name`, l'argument de la fonction. Nous avons appelé `say("John")`, donc la valeur de `name` est `"John"`. - L'environnement lexical externe est l'environnement lexical global. Il a la variable `phrase` et la fonction elle-même. L'environnement lexical intérieur a une référence à l'environnement `outer` (extérieur). **Lorsque le code veut accéder à une variable - l'environnement lexical interne est recherché en premier, puis celui externe, puis le plus externe et ainsi de suite jusqu'à celui global.** Si une variable n'est trouvée nulle part, c'est une erreur en mode strict (sans `use strict` une affectation à une variable non existante crée une nouvelle variable globale, pour la compatibilité avec l'ancien code). Dans cet exemple, la recherche se déroule comme ceci : - Pour la variable `name`, l'`alert` à l'intérieur de `say` la trouve immédiatement dans l'environnement lexical interne. - Lorsqu'elle veut accéder à `phrase`, il n'y a pas de `phrase` localement, elle suit donc la référence à l'environnement lexical externe et la trouve là. ![lexical environment lookup](lexical-environment-simple-lookup.svg) ### Step 4. Retourner une fonction Revenons à l'exemple `makeCounter`. ```js function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); ``` Au début de chaque appel `makeCounter()`, un nouvel objet environnement lexical est créé, pour stocker les variables pour cette exécution `makeCounter`. Nous avons donc deux environnements lexicaux imbriqués, comme dans l'exemple ci-dessus : ![](closure-makecounter.svg) Ce qui est différent, c'est que, pendant l'exécution de `makeCounter()`, une minuscule fonction imbriquée est créée à partir d'une seule ligne : `return count++`. Nous ne l'exécutons pas encore, nous créons seulement. Toutes les fonctions se souviennent de l'environnement lexical dans lequel elles ont été créées. Techniquement, il n'y a pas de magie ici : toutes les fonctions ont la propriété cachée nommée `[[Environment]]`, qui garde la référence à l'environnement lexical où la fonction a été créée : ![](closure-makecounter-environment.svg) Ainsi, `counter.[[Environment]]` a la référence à l'environnement lexical `{count: 0}`. C'est ainsi que la fonction se souvient de l'endroit où elle a été créée, quel que soit son nom. La référence `[[Environnement]]` est définie une fois pour toutes au moment de la création de la fonction. Plus tard, lorsque `counter()` est appelé, un nouvel environnement lexical est créé pour l'appel, et sa référence externe à l'environnement lexical est tirée de `counter.[[Environnement]]` : ![](closure-makecounter-nested-call.svg) Maintenant, lorsque le code à l'intérieur de `counter()` recherche la variable `count`, il recherche d'abord son propre environnement lexical (vide, car il n'y a pas de variables locales), puis l'environnement lexical de l'appel externe `makeCounter()`, où il la trouve et la change. **Une variable est mise à jour dans l'environnement lexical où elle se trouve.** Voici l'état après l'exécution : ![](closure-makecounter-nested-call-2.svg) Si nous appelons `counter()` plusieurs fois, la variable `count` sera augmentée à `2`, `3` et ainsi de suite, au même endroit. ```smart header="Closure" Il existe un terme général de programmation "closure", que les développeurs devraient généralement connaître. Une [closure](https://fr.wikipedia.org/wiki/Fermeture_(informatique)) est une fonction qui se souvient de ses variables externes et peut y accéder. Dans certains langages, ce n'est pas possible, ou une fonction doit être écrite d'une manière spécifique pour y arriver. Mais comme expliqué ci-dessus, en JavaScript, toutes les fonctions sont naturellement des fermetures (il n'y a qu'une seule exception, à couvrir dans ). C'est-à-dire : elles se souviennent automatiquement de l'endroit où elles ont été créées en utilisant une propriété cachée `[[Environnement]]`, puis leur code peut accéder aux variables externes. Lors d'un entretien d'embauche, un développeur frontend reçoit assez souvent une question du genre "qu'est-ce qu'une closure ?". Une réponse valide serait une définition de la closure ainsi qu'une explication sur le fait que toutes les fonctions en JavaScript sont des closures, et peut-être quelques mots de plus sur les détails techniques : la propriété `[[Environment]]` et comment fonctionnent les environnements lexicaux. ``` ## Garbage collection Habituellement, un environnement lexical est supprimé de la mémoire avec toutes les variables une fois l'appel de fonction terminé. C'est parce qu'il n'y a plus aucune référence à cela. Comme tout objet JavaScript, il n'est conservé en mémoire que lorsqu'il est accessible. Cependant, s'il y a une fonction imbriquée qui est toujours accessible après la fin d'une fonction, alors elle a la propriété `[[Environment]]` qui fait référence à l'environnement lexical. Dans ce cas, l'environnement lexical est toujours accessible même après la fin de la fonction, il reste donc en vie. Par exemple : ```js function f() { let value = 123; return function() { alert(value); } } let g = f(); // g.[[Environment]] stocke une référence à l'environnement lexical // de l'appel f() correspondant ``` Veuillez noter que si `f()` est appelé plusieurs fois et que les fonctions résultantes sont sauvegardées, tous les objets correspondants de l'environnement lexical seront également conservés en mémoire. Dans le code ci-dessous, ils sont tous les trois : ```js function f() { let value = math.random(); return function() { alert(value); }; } // 3 fonctions dans un tableau, chacune d'entre elles étant liée à l'environnement lexical // à partir de l'exécution de f() correspondante let arr = [f(), f(), f()]; ``` Un objet environnement lexical meurt lorsqu'il devient inaccessible (comme tout autre objet). En d'autres termes, il n'existe que s'il existe au moins une fonction imbriquée qui le référence. Dans le code ci-dessous, une fois que la fonction imbriquée est supprimée, son environnement lexical englobant (et donc la `value`) est nettoyé de la mémoire : ```js function f() { let value = 123; return function() { alert(value); } } let g = f(); // tant que la fonction g existe, la valeur reste en mémoire g = null; // … et maintenant la mémoire est nettoyée ``` ### Optimisations réelles Comme nous l'avons vu, en théorie, lorsqu'une fonction est vivante, toutes les variables externes sont également conservées. Mais dans la pratique, les moteurs JavaScript tentent d'optimiser cela. Ils analysent l'utilisation des variables et s'il est évident d'après le code qu'une variable externe n'est pas utilisée -- elle est supprimée. **Un effet secondaire important dans V8 (Chrome, Edge, Opera) est qu’une telle variable ne sera plus disponible lors du débogage.** Essayez d'exécuter l'exemple ci-dessous sous Chrome avec les outils de développement ouverts. Quand il se met en pause, dans la console, tapez `alert(value)`. ```js run function f() { let value = Math.random(); function g() { debugger; // dans la console : tapez alert(value); No such variable! } return g; } let g = f(); g(); ``` Comme vous avez pu le constater, cette variable n'existe pas ! En théorie, elle devrait être accessible, mais le moteur l'a optimisée. Cela peut conduire à des problèmes de débogage amusants (voire fastidieux). L'un d'eux -- nous pouvons voir une variable externe portant le même nom au lieu de celle attendue : ```js run global let value = "Surprise!"; function f() { let value = "the closest value"; function g() { debugger; // dans la console : tapez alert(value); Surprise! } return g; } let g = f(); g(); ``` Cette fonctionnalité du V8 est bonne à savoir. Si vous déboguez avec Chrome/Edge/Opera, tôt ou tard vous la rencontrerez. Ce n'est pas un bogue dans le débogueur, mais plutôt une caractéristique spéciale de V8. Peut-être que cela sera changé un jour. Vous pouvez toujours le vérifier en exécutant les exemples sur cette page. ================================================ FILE: 1-js/06-advanced-functions/04-var/article.md ================================================ # L'ancien "var" ```smart header="Cet article est pour comprendre les anciens scripts" Les informations contenues dans cet article sont utiles pour comprendre les anciens scripts. Ce n'est pas ainsi que nous écrivons du nouveau code. ``` Dans le tout premier chapitre qui parle des [variables](info:variables), nous avons mentionné trois façons pour déclarer une variable : 1. `let` 2. `const` 3. `var` La déclaration `var` est similaire à `let`. La plupart du temps, nous pouvons remplacer `let` par `var` ou vice-versa et nous attendre à ce que les choses fonctionnent : ```js run var message = "Hi"; alert(message); // Hi ``` Mais en interne, `var` est une bête très différente, originaire de très vieux temps. Il n'est généralement pas utilisé dans les scripts modernes, mais se cache toujours dans les anciens. Si vous ne prévoyez pas de rencontrer de tels scripts, vous pouvez même sauter ce chapitre ou le reporter. D'un autre côté, il est important de comprendre les différences lors de la migration d'anciens scripts de `var` vers `let`, pour éviter des erreurs étranges. ## "var" n'a pas de portée limitée aux blocs Les variables, déclarées avec `var`, ont une portée fonction ou globale. Ils sont visibles à travers des blocs. Par exemple : ```js run if (true) { var test = true; // utilise "var" au lieu "let" } *!* alert(test); // vrai, la variable existe après if */!* ``` Comme `var` ignore les blocs de code, nous avons une variable globale `test`. Si nous aurions utilisé `let test` au lieu de `var test`, la variable aurait seulement été visible à l'intérieur de `if` : ```js run if (true) { let test = true; // utilise "let" } *!* alert(test); // ReferenceError: test is not defined */!* ``` Même principe pour les boucles : `var` ne peut pas être locale pour les blocs ni les boucles : ```js run for (var i = 0; i < 10; i++) { var one = 1; // ... } *!* alert(i); // 10, "i" est visible après la boucle, c'est une variable globale alert(one); // 1, "one" est visible après la boucle, c'est une variable globale */!* ``` Si un bloc de code est à l'intérieur d'une fonction, `var` devient une variable à l'échelle de la fonction : ```js run function sayHi() { if (true) { var phrase = "Hello"; } alert(phrase); // fonctionne } sayHi(); alert(phrase); // ReferenceError: phrase is not defined ``` Comme nous pouvons le constater, `var` pénètre à travers `if`, `for` ou les autres blocs de code. C'est parce que, il y a longtemps, les blocs de JavaScript n'avaient pas d'environnements lexicaux, et `var` est un vestige de ce dernier. ## "var" tolère les redéclarations Si nous déclarons la même variable avec `let` deux fois dans la même portée, c'est une erreur : ```js run let user; let user; // SyntaxError: 'user' has already been declared ``` Avec `var`, nous pouvons redéclarer une variable autant de fois que nécessaire. Si nous utilisons `var` avec une variable déjà déclarée, elle est simplement ignorée : ```js run var user = "Pete"; var user = "John"; // ce "var" ne fait rien (déjà déclaré) // ...ça ne déclenche pas d'erreur alert(user); // John ``` ## "var" les variables peuvent être déclarées sous leur utilisation Les déclarations `var` sont traitées quand la fonction commence (ou quand le script commence pour le cas global). En d'autres mots, les variables `var` sont définies au début de la fonction, peu importe où la définition se retrouve (présumant que la définition n'est pas dans une fonction imbriquée). Alors ce code : ```js run function sayHi() { phrase = "Hello"; alert(phrase); *!* var phrase; */!* } sayHi(); ``` ...est techniquement identique à ceci (nous avons simplement bougé `var phrase` du code juste avant) : ```js run function sayHi() { *!* var phrase; */!* phrase = "Hello"; alert(phrase); } sayHi(); ``` ...ou même ceci (souvenez-vous, les blocs de code sont ignorés) : ```js run function sayHi() { phrase = "Hello"; // (*) *!* if (false) { var phrase; } */!* alert(phrase); } sayHi(); ``` Certains nomment ce comportement "hoisting" (hisser) parce que toutes les `var` sont "hoisted" (hissées) jusqu'en haut de la fonction. Ainsi, dans l'exemple ci-dessus, la branche `if (false)` ne s'exécute jamais, mais cela n'a pas d'importance. La `var` qu'elle contient est traitée au début de la fonction, donc au moment de `(*)` la variable existe. **Les déclarations sont hissées, mais les affectations ne le sont pas.** Cela est mieux démontré avec un exemple : ```js run function sayHi() { alert(phrase); *!* var phrase = "Hello"; */!* } sayHi(); ``` La ligne `var phrase = "Hello"` contient deux actions : 1. Déclaration de la variable `var` 2. Affectation de la variable `=`. La déclaration est traitée au début de l'exécution de la fonction ("hoisted"), mais l'affectation fonctionne toujours à l'endroit où elle apparaît. Essentiellement, le code fonctionne comme ceci : ```js run function sayHi() { *!* var phrase; // déclaration fonctionne au début... */!* alert(phrase); // undefined *!* phrase = "Hello"; // ...affectation - quand l'exécution y parvient. */!* } sayHi(); ``` Parce que toutes les déclarations `var` sont traitées au début de la fonction, nous pouvons y faire référence n'importe où. Mais les variables sont indéfinies jusqu'aux affectations. Dans les deux exemples au dessus, `alert` fonctionne sans erreur parce que la variable `phrase` existe. Mais sa valeur n'est pas encore affectée, alors cela donne `undefined`. ## IIFE Comme par le passé, il n'y avait que `var`, et qu'il n'a pas de visibilité au niveau du bloc, les programmeurs ont inventé un moyen de l'imiter. Ce qu'ils ont fait a été appelé "expressions de fonction immédiatement invoquées" (en abrégé IIFE). Ce n'est pas quelque chose que nous devrions utiliser de nos jours, mais vous pouvez les trouver dans d'anciens scripts. Un IIFE ressemble à ceci : ```js run (function() { var message = "Hello"; alert(message); // Hello })(); ``` Ici, une fonction expression est créée et immédiatement appelée. Ainsi, le code s'exécute immédiatement et possède ses propres variables privées. La fonction expression est entourée de parenthèses `(fonction {...})`, car lorsque JavaScript rencontre `"function"` dans le flux de code principal, il le comprend comme le début d'une fonction déclaration. Mais une fonction déclaration doit avoir un nom, donc ce type de code donnera une erreur : ```js run // Essayons de déclarer et d'appeler immédiatement une fonction function() { // <-- SyntaxError: Les instructions de fonction nécessitent un nom de fonction var message = "Hello"; alert(message); // Hello }(); ``` Même si nous disons : "d'accord, ajoutons un nom", cela ne fonctionnera toujours pas, parce que JavaScript ne permet pas d'appeler immédiatement les fonctions déclarations : ```js run // erreur de syntaxe à cause des parenthèses ci-dessous function go() { }(); // <-- ne peut pas appeler la fonction déclaration immédiatement ``` Ainsi, les parenthèses autour de la fonction sont une astuce pour montrer à JavaScript que la fonction est créée dans le contexte d'une autre expression, et donc c'est une fonction expression : elle n'a pas besoin de nom et peut être appelée immédiatement. Il existe d'autres façons que les parenthèses pour dire à JavaScript que nous souhaitons une fonction expression : ```js run // Façons de créer une IIFE *!*(*/!*function() { alert("Parentheses around the function"); }*!*)*/!*(); *!*(*/!*function() { alert("Parentheses around the whole thing"); }()*!*)*/!*; *!*!*/!*function() { alert("Bitwise NOT operator starts the expression"); }(); *!*+*/!*function() { alert("Unary plus starts the expression"); }(); ``` Dans tous les cas ci-dessus, nous déclarons une fonction expression et l'exécutons immédiatement. Notons encore : de nos jours il n'y a aucune raison d'écrire un tel code. ## Résumé Il y a deux différences majeures entre `var` et `let`/`const`: 1. Les variables `var` n'ont pas de portée de bloc, leur visibilité est étendue à la fonction actuelle, ou globale, si elle est déclarée hors fonction. 2. Les déclarations `var` sont traitées au début de la fonction (ou au début du script pour le cas global). Il y a une autre différence mineure associée à l'objet global, mais nous traiterons ce point dans le prochain chapitre. Ces différences rendent `var` pire que `let` dans la plupart des cas. Les variables au niveau des blocs sont extraordinaires. C'est pourquoi `let` a été introduit au standard il y a longtemps et c'est maintenant un moyen majeur (avec `const`) pour déclarer une variable. ================================================ FILE: 1-js/06-advanced-functions/05-global-object/article.md ================================================ # L'objet global L'objet global fournit des variables et des fonctions qui sont disponibles partout. Par défaut, celles qui sont intégrées au langage ou à l'environnement. Dans un navigateur, c'est appelé `window`, pour Node.js c'est `global`, et pour les autres environnements, il peut porter un autre nom. Récemment, `globalThis` a été ajouté au langage comme un nom standardisé pour l'objet global et devrait être supporté à travers tous les environnements. Il est pris en charge dans tous les principaux navigateurs. Nous allons utiliser `window` ici, en supposant que notre environnement est un navigateur. Si votre script peut s'exécuter dans d'autres environnements, il est préférable d'utiliser `globalThis` à la place. Toutes les propriétés de l'objet global sont directement accessibles : ```js run alert("Hello"); // is the same as window.alert("Hello"); ``` Dans un navigateur, les fonctions globales et les variables déclarées avec `var` (pas `let`/`const` !) deviennent la propriété de l'objet global : ```js run untrusted refresh var gVar = 5; alert(window.gVar); // 5 (var est devenue une propriété de l'objet global) ``` Les fonctions déclarations ont le même effet (instructions avec le mot clé `function` dans le flux de code principal, pas les fonctions expressions). Ne comptez pas là-dessus ! Ce comportement existe pour des raisons de compatibilité. Les scripts modernes utilisent les [modules JavaScript](info:modules) où une telle chose ne se produit pas. Si nous utilisions `let` la place, une telle chose ne se produirait pas : ```js run untrusted refresh let gLet = 5; alert(window.gLet); // undefined (ne devient pas une propriété de l'objet global) ``` Si une valeur est si importante que vous voulez qu'elle soit disponible de façon global, écrivez la directement comme une propriété : ```js run *!* // rendre l'information de l'utilisateur actuel globale pour permettre à tous les scripts de l'accéder. window.currentUser = { name: "John" }; */!* // ailleurs dans le code alert(currentUser.name); // John // ou, si nous avons une variable locale avec le nom "currentUser" // obtenez la de window explicitement (c'est sécuritaire !) alert(window.currentUser.name); // John ``` Cela dit, l'utilisation de variables globales est généralement déconseillée. Il devrait y avoir le moins de variables globales que possible. La conception du code où une fonction reçoit des variables de saisies (input) et produit certains résultats est plus claire, moins susceptible aux erreurs et plus facile à tester que si elle utilise des variables externes ou globales. ## Utilisation avec les polyfills Nous utilisons l'objet global pour tester le support des fonctionnalités du langage moderne. Par exemple, nous pouvons tester si l'objet natif `Promise` existe (il n'existe pas dans les navigateurs très anciens) : ```js run if (!window.Promise) { alert("Your browser is really old!"); } ``` S'il n'y en a pas (disons que nous sommes dans un navigateur ancien), nous pouvons créer des "polyfills". Les "polyfills" ajoutent des fonctions qui ne sont pas supportés par l'environnement, mais qui existent dans le standard moderne. ```js run if (!window.Promise) { window.Promise = ... // implémentation personnalisée de la fonctionnalité du langage moderne } ``` ## Résumé - L'objet global contient des variables qui devraient être disponibles partout. Ceci inclut les objets natifs de JavaScript, tels que `Array` et des valeurs spécifiques à l'environnement, comme `window.innerHeight` -- l'hauteur de la fenêtre dans le navigateur. - L'objet global porte un nom universel `globalThis`. ...Mais il est plus souvent appelé par des noms spécifiques à l'environnement de la vieille école, comme `window` (navigateur) et `global` (Node.js). - Nous devons seulement stocker des valeurs dans l'objet global si elles sont réellement globales pour notre projet. Et gardez la quantité de ces valeurs à un minimum. - Dans les navigateurs, à moins que nous utilisons des [modules](info:modules), les fonctions et variables globales déclarées avec `var` deviennent une propriété de l'objet global. - Pour que notre code soit à l'épreuve du temps et plus facile à comprendre, nous devons accéder les propriétés de l'objet global directement, en utilisant `window.x`. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/_js.view/solution.js ================================================ function makeCounter() { let count = 0; function counter() { return count++; } counter.set = value => count = value; counter.decrease = () => count--; return counter; } ================================================ FILE: 1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/_js.view/source.js ================================================ function makeCounter() { let count = 0; // ... votre code ... } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 counter.set(10); // définir le nouveau "count" alert( counter() ); // 10 counter.decrease(); // diminuer de 1 le "count" alert( counter() ); // 10 (au lieu de 11) ================================================ FILE: 1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/_js.view/test.js ================================================ describe("counter", function() { it("increases from call to call", function() { let counter = makeCounter(); assert.equal( counter(), 0 ); assert.equal( counter(), 1 ); assert.equal( counter(), 2 ); }); describe("counter.set", function() { it("sets the count", function() { let counter = makeCounter(); counter.set(10); assert.equal( counter(), 10 ); assert.equal( counter(), 11 ); }); }); describe("counter.decrease", function() { it("decreases the count", function() { let counter = makeCounter(); counter.set(10); assert.equal( counter(), 10 ); counter.decrease(); assert.equal( counter(), 10 ); }); }); }); ================================================ FILE: 1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/solution.md ================================================ La solution utilise `count` dans la variable locale, mais les méthodes d'addition sont écrites directement dans le `compteur`. Ils partagent le même environnement lexical extérieur et peuvent également accéder au `count` actuel. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/task.md ================================================ importance: 5 --- # Un mutateur et diminution pour counter Modifiez le code de `makeCounter()` afin que le compteur puisse également diminuer et définir le nombre: - `counter()` devrait retourner le nombre suivant (comme avant). - `counter.set(value)` devrait définir le compteur à `value`. - `counter.decrease()` devrait décrémenter le compteur de 1. Voir le code sandbox pour un exemple d'utilisation complet. P.S. Vous pouvez utiliser une fermeture ou la propriété de fonction pour maintenir le nombre actuel. Ou écrivez les deux variantes. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/solution.js ================================================ function sum(a) { let currentSum = a; function f(b) { currentSum += b; return f; } f.toString = function() { return currentSum; }; return f; } ================================================ FILE: 1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/source.js ================================================ function sum(a){ // Your code goes here. } /* sum(1)(2) == 3; // 1 + 2 sum(1)(2)(3) == 6; // 1 + 2 + 3 sum(5)(-1)(2) == 6 sum(6)(-1)(-2)(-3) == 0 sum(0)(1)(2)(3)(4)(5) == 15 */ ================================================ FILE: 1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/test.js ================================================ describe("sum", function(){ it("sum(1)(2) == 3", function(){ assert.equal(3, sum(1)(2)); }); it("sum(5)(-1)(2) == 6", function(){ assert.equal(6, sum(5)(-1)(2)); }); it("sum(6)(-1)(-2)(-3) == 0", function(){ assert.equal(0, sum(6)(-1)(-2)(-3)); }); it("sum(0)(1)(2)(3)(4)(5) == 15", function(){ assert.equal(15, sum(0)(1)(2)(3)(4)(5)); }); }); ================================================ FILE: 1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md ================================================ 1. Pour que tout fonctionne * de toute façon *, le résultat de `sum` doit être fonction. 2. Cette fonction doit garder en mémoire la valeur actuelle entre les appels. 3. Selon la tâche, la fonction doit devenir le numéro lorsqu'elle est utilisée dans `==`. Les fonctions étant des objets, la conversion s'effectue comme décrit dans le chapitre , et nous pouvons fournir notre propre méthode qui renvoie le nombre. Maintenant le code: ```js demo run function sum(a) { let currentSum = a; function f(b) { currentSum += b; return f; } f.toString = function() { return currentSum; }; return f; } alert( sum(1)(2) ); // 3 alert( sum(5)(-1)(2) ); // 6 alert( sum(6)(-1)(-2)(-3) ); // 0 alert( sum(0)(1)(2)(3)(4)(5) ); // 15 ``` Veuillez noter que la fonction `sum` ne fonctionne réellement qu'une fois. Il renvoie la fonction `f`. Ensuite, à chaque appel suivant, `f` ajoute son paramètre à la somme `currentSum`, et se renvoie lui-même. **Il n'y a pas de récursion dans la dernière ligne de `f`.** Voici à quoi ressemble la récursion: ```js function f(b) { currentSum += b; return f(); // <-- appel récursif } ``` Et dans notre cas, nous renvoyons simplement la fonction, sans l'appeler: ```js function f(b) { currentSum += b; return f; // <-- ne s'appelle pas, se renvoie } ``` Ce `f` sera utilisé lors du prochain appel et se renvera lui-même autant de fois que nécessaire. Ensuite, lorsqu'il est utilisé sous forme de nombre ou de chaîne de caractères, le `toString` renvoie le `currentSum`. Nous pourrions aussi utiliser `Symbol.toPrimitive` ou `valueOf` ici pour la conversion. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/task.md ================================================ importance: 2 --- # La somme avec une quantité arbitraire de parenthèses Écrivez la fonction `sum` qui fonctionnerait comme ceci: ```js sum(1)(2) == 3; // 1 + 2 sum(1)(2)(3) == 6; // 1 + 2 + 3 sum(5)(-1)(2) == 6 sum(6)(-1)(-2)(-3) == 0 sum(0)(1)(2)(3)(4)(5) == 15 ``` P.S. Indice: vous devrez peut-être configurer une conversion d'objet à primitive personnalisé pour votre fonction. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/article.md ================================================ # L'objet Function, NFE Comme nous le savons déjà, une fonction en JavaScript est une valeur. Chaque valeur en JavaScript a un type. De quel type est une fonction ? Pour JavaScript, les fonctions sont des objets. Un bon moyen d’imaginer des fonctions est en tant que des "objets d’action" qu'on peut appeler. Nous pouvons non seulement les appeler, mais aussi les traiter comme des objets : ajouter/supprimer des propriétés, passer par référence, etc. ## La propriété "name" Les objets Fonction contiennent des propriétés utilisables. Par exemple, le nom d'une fonction est accessible en tant que propriété "name" : ```js run function sayHi() { alert("Hi"); } alert(sayHi.name); // sayHi ``` Ce qui est drôle, c'est que la logique d'attribution de noms est intelligente. Elle attribue également le nom correct à une fonction même si elle est créée sans, puis immédiatement attribué : ```js run let sayHi = function() { alert("Hi"); }; alert(sayHi.name); // sayHi (il y a un nom !) ``` Cela fonctionne aussi si l’affectation est faite avec une valeur par défaut : ```js run function f(sayHi = function() {}) { alert(sayHi.name); // sayHi (ça marche !) } f(); ``` Dans la spécification, cette fonctionnalité est appelée "contextual name". Si la fonction n'en fournit pas, elle est déterminée à partir du contexte lors de l'affectation. Les méthodes d'objet ont aussi des noms : ```js run let user = { sayHi() { // ... }, sayBye: function() { // ... } } alert(user.sayHi.name); // sayHi alert(user.sayBye.name); // sayBye ``` Cependant c'est pas magique. Il y a des cas où il n'y a aucun moyen de trouver le bon nom. Dans ce cas, la propriété name est vide, comme ci-dessous : ```js // fonction créée dans un tableau let arr = [function() {}]; alert( arr[0].name ); // // le moteur n'a aucun moyen de définir le bon nom. Donc, il n'y en a pas ``` Par contre, en pratique la plupart des fonctions ont un nom. ## La propriété "length" Il existe une autre propriété native, "length", qui renvoie le nombre de paramètres de la fonction, par exemple : ```js run function f1(a) {} function f2(a, b) {} function many(a, b, ...more) {} alert(f1.length); // 1 alert(f2.length); // 2 alert(many.length); // 2 ``` Nous pouvons voir que les paramètres du reste ne sont pas comptés. La propriété `length` est parfois utilisée pour la [réfléxion (introspection en anglais)](https://fr.wikipedia.org/wiki/R%C3%A9flexion_(informatique)) dans des fonctions qui opèrent sur d'autres fonctions. Par exemple, dans le code ci-dessous, la fonction `ask` accepte une `question` à poser et un nombre arbitraire de fonctions `handler` (gestionnaires) à appeler. Une fois qu'un utilisateur a fourni sa réponse, la fonction appelle les gestionnaires. Nous pouvons transmettre deux types de gestionnaires : - Une fonction sans argument, qui n'est appelée que lorsque l'utilisateur donne une réponse positive. - Une fonction avec des arguments, appelée dans les deux cas et renvoyant une réponse. Pour appeler `handler` correctement, nous examinons la propriété `handler.length`. L'idée est que nous avons une syntaxe de gestionnaire simple, sans argument, pour les cas positifs (variante la plus fréquente), mais que nous pouvons également prendre en charge les gestionnaires universels : ```js run function ask(question, ...handlers) { let isYes = confirm(question); for(let handler of handlers) { if (handler.length == 0) { if (isYes) handler(); } else { handler(isYes); } } } // pour une réponse positive, les deux gestionnaires sont appelés // pour une réponse négative, seulement le second ask("Question?", () => alert('You said yes'), result => alert(result)); ``` Ceci est un cas particulier de ce qu'on appelle le [polymorphism](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)) -- le traitement des arguments différemment selon leur type ou, dans notre cas, en fonction de la `length`. Cette approche est utilisée dans les bibliothèques JavaScript. ## Propriétés personnalisées Nous pouvons également ajouter nos propres propriétés. Nous ajoutons ici la propriété `counter` pour suivre le nombre total d'appels : ```js run function sayHi() { alert("Hi"); *!* // comptons combien de fois nous executons sayHi.counter++; */!* } sayHi.counter = 0; // valeur initiale sayHi(); // Hi sayHi(); // Hi alert( `Called ${sayHi.counter} times` ); // Appelée 2 fois ``` ```warn header="Une propriété n'est pas une variable" Une propriété affectée à une fonction comme `sayHi.counter = 0` *ne définit pas* une variable locale `counter` à l'intérieur de celle-ci. En d'autres termes, une propriété `counter` et une variable `let counter` sont deux choses indépendantes. On peut traiter une fonction comme un objet, y stocker des propriétés, mais cela n’a aucun effet sur son exécution. Les variables ne sont pas des propriétés de fonction et inversement. Ce sont des mondes parallèles. ``` Les propriétés de fonction peuvent parfois remplacer les fermetures. Par exemple, nous pouvons réécrire l’exemple de fonction de compteur du chapitre pour utiliser une propriété de fonction : ```js run function makeCounter() { // au lieu de : // let count = 0 function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 ``` Le `count` est maintenant stocké dans la fonction directement, pas dans son environnement lexical externe. Est-ce meilleur ou pire que d'utiliser une fermeture ? La principale différence est que si la valeur de `count` réside dans une variable externe, le code externe ne peut pas y accéder. Seules les fonctions imbriquées peuvent le modifier. Et si c'est lié à une fonction, une telle chose est possible : ```js run function makeCounter() { function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); *!* counter.count = 10; alert( counter() ); // 10 */!* ``` Le choix dépend donc de nos objectifs. ## Fonction Expression Nommée (NFE) Fonction Expression Nommée, ou NFE ("Named Function Expression" en anglais), est un terme pour les fonctions expressions qui ont un nom. Par exemple, prenons une fonction expression ordinaire : ```js let sayHi = function(who) { alert(`Hello, ${who}`); }; ``` Et ajoutons un nom à cela : ```js let sayHi = function *!*func*/!*(who) { alert(`Hello, ${who}`); }; ``` Avons-nous réalisé quelque chose ici ? Quel est le but de ce nom supplémentaire `"func"` ? Notons d'abord que nous avons toujours une expression de fonction. L'ajout du nom `"func"` après `function` n'en a pas fait une déclaration de fonction, car il est toujours créé dans le cadre d'une expression d'affectation. L'ajout d'un tel nom n'a également rien cassé. La fonction est toujours disponible sous la forme `sayHi()` : ```js run let sayHi = function *!*func*/!*(who) { alert(`Hello, ${who}`); }; sayHi("John"); // Hello, John ``` Il y a deux particularités à propos du nom `func`, voici les raisons : 1. Il permet à la fonction de se référencer en interne. 2. Il n'est pas visible en dehors de la fonction. Par exemple, la fonction `sayHi` ci-dessous s’appelle à nouveau avec `"Guest"` si aucun `who` est fourni : ```js run let sayHi = function *!*func*/!*(who) { if (who) { alert(`Hello, ${who}`); } else { *!* func("Guest"); // utilise func pour se rappeler */!* } }; sayHi(); // Hello, Guest // Mais ceci ne marchera pas : func(); // Error, func is not defined (pas visible à l'extérieur de la fonction) ``` Pourquoi utilisons-nous `func` ? Peut-être juste utiliser `sayHi` pour l'appel imbriqué ? En fait, dans la plupart des cas, nous pouvons : ```js let sayHi = function(who) { if (who) { alert(`Hello, ${who}`); } else { *!* sayHi("Guest"); */!* } }; ``` Le problème avec ce code est que `sayHi` peut changer dans le code externe. Si la fonction est assignée à une autre variable, le code commencera à donner des erreurs : ```js run let sayHi = function(who) { if (who) { alert(`Hello, ${who}`); } else { *!* sayHi("Guest"); // Error: sayHi is not a function */!* } }; let welcome = sayHi; sayHi = null; welcome(); // Error, l'appel sayHi imbriqué ne fonctionne plus ! ``` Cela se produit parce que la fonction tire `sayHi` de son environnement lexical externe. Il n'y a pas de `sayHi` local, donc la variable externe est utilisée. Et au moment de l'appel, ce `sayHi` extérieur est `null`. Le nom optionnel que nous pouvons mettre dans l’expression de fonction est destiné à résoudre exactement ce type de problèmes. Utilisons-le pour corriger notre code : ```js run let sayHi = function *!*func*/!*(who) { if (who) { alert(`Hello, ${who}`); } else { *!* func("Guest"); // Maintenant tout va bien */!* } }; let welcome = sayHi; sayHi = null; welcome(); // Hello, Guest (l'appel imbriqué fonctionne) ``` Maintenant cela fonctionne, car le nom `'func'` est local à la fonction. Il n'est pas pris de l'extérieur (et non visible là-bas). La spécification garantit qu'elle fera toujours référence à la fonction actuelle. Le code externe a toujours sa variable `sayHi` ou `welcome`. Et `func` est un "nom de fonction interne", la façon dont la fonction peut s'appeler de manière fiable. ```smart header="Il n'y a rien de tel pour la déclaration de fonction" La fonctionnalité "nom interne" décrite ici n'est disponible que pour les expressions de fonction, pas pour les déclarations de fonction. Pour les déclarations de fonctions, il n’y a aucune possibilité de syntaxe d’ajouter un nom "interne" supplémentaire. Parfois, lorsque nous avons besoin d’un nom interne fiable, c’est la raison pour laquelle nous réécrivons une déclaration de fonction en tant qu'expression de fonction nommée. ``` ## Résumé Les fonctions sont des objets. Ici nous avons couvert leurs propriétés : - `name` - le nom de la fonction. Habituellement tiré de la définition de la fonction, mais s’il n’en existe pas, JavaScript essaie de le deviner à partir du contexte (par exemple, une affectation). - `length` - le nombre d'arguments dans la définition de la fonction. Les paramètres du reste ne sont pas comptés. Si la fonction est déclarée en tant qu'expression de fonction (et non dans le flux du code principal) et qu'elle porte un nom, elle est appelée expression de fonction nommée. Le nom peut être utilisé à l'intérieur pour se référencer, pour des appels récursifs ou autres. Les fonctions peuvent également comporter des propriétés supplémentaires. De nombreuses bibliothèques JavaScript bien connues font un grand usage de cette fonctionnalité. Elles créent une fonction "principale" et y attachent de nombreuses autres fonctions "d'assistance". Par exemple, la bibliothèque [jQuery](https://jquery.com) crée une fonction nommée `$`. La bibliothèque [lodash](https://lodash.com) crée une fonction `_` et ajoute ensuite `_.clone`, `_.keyBy` et d'autres propriétés (voir la [doc](https://lodash.com/docs) lorsque vous souhaitez en savoir plus à leur sujet). En fait, elles le font pour réduire leur pollution de l'espace global, de sorte qu'une seule bibliothèque ne donne qu'une seule variable globale. Cela réduit la possibilité de conflits de noms. Ainsi, une fonction peut faire un travail utile par elle-même et aussi porter un tas d’autres fonctionnalités dans les propriétés. ================================================ FILE: 1-js/06-advanced-functions/07-new-function/article.md ================================================ # La syntaxe "new Function" Il existe encore un autre moyen de créer une fonction. C'est rarement utilisé, mais parfois il n'y a pas d'alternative. ## Syntaxe La syntaxe pour créer une fonction : ```js let func = new Function ([arg1, arg2, ...argN], functionBody); ``` La fonction est créée avec les arguments `arg1...argN` et le `functionBody` donné. C'est plus facile à comprendre en regardant un exemple. Voici une fonction avec deux arguments : ```js run let sum = new Function('a', 'b', 'return a + b'); alert( sum(1, 2) ); // 3 ``` Et voici une fonction sans arguments, seulement le corps de la fonction : ```js run let sayHi = new Function('alert("Hello")'); sayHi(); // Hello ``` La différence majeure par rapport aux autres méthodes que nous avons déjà vu est que la fonction est créée littéralement à partir d'une chaîne de caractères passée au moment de l'exécution. Toutes les déclarations précédentes nous demandait, nous les programmeurs, d'écrire le code de la fonction dans le script. Mais `new Function` nous permet de convertir n'importe qu'elle chaine de caractères en fonction. Par exemple, nous pouvons recevoir une nouvelle fonction d’un serveur puis l’exécuter : ```js let str = ... receive the code from a server dynamically ... let func = new Function(str); func(); ``` C'est utilisé dans des cas très spécifiques, comme lorsque nous recevons du code d'un serveur ou pour compiler dynamiquement une fonction à partir d'un modèle dans des applications web complexes. ## Fermeture Normalement, une fonction se souvient du lieu de sa naissance dans la propriété spéciale `[[Environment]]`. Elle fait référence à l'environnement lexical à partir duquel la fonction a été créée (nous avons couvert cela dans le chapitre ). Mais quand une fonction est créée en utilisant `new Function`, son `[[Environment]]` est configuré pour faire référence non pas à l'environnement lexical actuel, mais à l'environnement global. Donc, une telle fonction n'a pas accès aux variables externes, mais uniquement aux variables globales. ```js run function getFunc() { let value = "test"; *!* let func = new Function('alert(value)'); */!* return func; } getFunc()(); // error: value is not defined ``` Comparez-le avec le comportement habituel : ```js run function getFunc() { let value = "test"; *!* let func = function() { alert(value); }; */!* return func; } getFunc()(); // *!*"test"*/!*, provenant de l'Environnement Lexical de getFunc ``` Cette particularité de `new Function` paraît étrange, mais semble très utile dans la pratique. Imaginons que nous devions créer une fonction à partir d'une chaîne de caractères. Le code de cette fonction n'est pas connu au moment de l'écriture du script (c'est pourquoi nous n'utilisons pas de fonctions standard), mais sera connu dans le processus d'exécution. Nous pouvons le recevoir du serveur ou d'une autre source. Notre nouvelle fonction doit interagir avec le script principal. Et si il pouvait accéder aux variables externes ? Le problème est qu'avant la publication de JavaScript en production, JavaScript est compressé à l'aide d'un *minifier*, un programme spécial qui rétrécit le code en supprimant les commentaires, espaces supplémentaires et, plus particulièrement, renomme les variables locales pour quelles soient plus courtes. Par exemple, si une fonction a `let userName`, le minifier la remplace par `let a` (ou une autre lettre si celle-ci est déjà prise), et le fait partout. C’est généralement une chose sûre à faire, car la variable est locale, rien en dehors de la fonction ne peut y accéder. Et à l'intérieur de la fonction, le minifier remplace chaque mention. Les minifiers sont intelligents, ils analysent la structure du code pour ne rien casser. Ils ne sont pas juste une simple fonction de recherche et remplacement. Donc, si `new Function` avait accès aux variables externes, il serait impossible de trouver le nom d'utilisateur `userName` renommé. **Si `new Function` avait accès aux variables externes, elle aurait des problèmes avec les minifiers.** En outre, ce code serait mauvais sur le plan architectural et sujet aux erreurs. Pour passer quelque chose à une fonction créée par `new Function`, nous devons utiliser ses arguments. ## Résumé La syntaxe : ```js let func = new Function ([arg1, arg2, ...argN], functionBody); ``` Pour des raisons historiques, les arguments peuvent également être fournis sous forme de liste séparée par des virgules. Ces trois déclarations signifient la même chose : ```js new Function('a', 'b', 'return a + b'); // syntax de base new Function('a,b', 'return a + b'); // séparés par des virgules new Function('a , b', 'return a + b'); // séparés par des virgules et espacés ``` Les fonctions créées avec `new Function` ont leur `[[Environment]]` qui fait référence à l’environnement lexical global, et non à l’environnement externe. Par conséquent, elles ne peuvent pas utiliser de variables externes. Mais c’est bien, parce que cela nous protège des erreurs. Passer explicitement des paramètres est une méthode bien meilleure sur le plan architectural et ne pose aucun problème avec les minifiers. ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/solution.md ================================================ Avec `setInterval`: ```js run function printNumbers(from, to) { let current = from; let timerId = setInterval(function() { alert(current); if (current == to) { clearInterval(timerId); } current++; }, 1000); } // usage: printNumbers(5, 10); ``` Utilisation de `setTimeout` imbriqué : ```js run function printNumbers(from, to) { let current = from; setTimeout(function go() { alert(current); if (current < to) { setTimeout(go, 1000); } current++; }, 1000); } // utilisation : printNumbers(5, 10); ``` Notons que, dans les deux solutions, il y a un délai initial avant le premier résultat. En effet, la fonction est appelée pour la première fois au bout de `1000ms`. Afin d'exécuter la fonction immédiatement, on peut ajouter un autre appel avant `setInterval`. ```js run function printNumbers(from, to) { let current = from; function go() { alert(current); if (current == to) { clearInterval(timerId); } current++; } *!* go(); */!* let timerId = setInterval(go, 1000); } printNumbers(5, 10); ``` ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/task.md ================================================ importance: 5 --- # Un résultat par seconde Écrire une fonction `printNumbers(from, to)` qui affiche un nombre par seconde, en partant de `from` jusqu'à `to`. Faites deux variantes de la solution : 1. utilisant `setInterval`, 2. Utilisation de `setTimeout` imbriqué. ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md ================================================ ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md ================================================ importance: 4 --- # Réécrire setTimeout avec setInterval Voici une fonction qui utilise un `setTimeout` imbriqué pour découper une tâche en petit bouts. Réécrire le bloc suivant en utilisant `setInterval`: ```js run let i = 0; let start = Date.now(); function count() { if (i == 1000000000) { alert("Done in " + (Date.now() - start) + 'ms'); } else { setTimeout(count); } // un morceau d'une très grosse tâche for(let j = 0; j < 1000000; j++) { i++; } } count(); ``` ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/4-settimeout-result/solution.md ================================================ `setTimeout` ne peut s'exécuter qu'une fois le bloc de code courant terminé. Le `i` sera donc le dernier : `100000000`. ```js run let i = 0; setTimeout(() => alert(i), 100); // 100000000 // on considère que cette fonction met plus de 100ms à s'exécuter for(let j = 0; j < 100000000; j++) { i++; } ``` ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/4-settimeout-result/task.md ================================================ importance: 5 --- # Que va afficher setTimeout ? Dans le code ci-dessous il y a une exécution planifié par `setTimeout`, suivie par un calcul conséquent qui prend plus de 100ms à tourner. Quand la fonction planifiée va-t-elle s'exécuter ? 1. Après la boucle. 2. Avant la boucle. 3. Au début de la boucle. Qu'est-ce que `alert` va afficher ? ```js let i = 0; setTimeout(() => alert(i), 100); // ? // on considère que cette fonction met plus de 100ms à s'exécuter for(let j = 0; j < 100000000; j++) { i++; } ``` ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/article.md ================================================ # L'ordonnancement avec setTimeout et setInterval Peut-être que nous ne voulons pas exécuter une fonction tout de suite, mais à un certain moment dans le futur. Cela s'appelle "ordonnancer (ou planifier) un appel de fonction". Il existe deux méthodes pour cela : - `setTimeout` permet d'exécuter une fonction une unique fois après un certain laps de temps. - `setInterval` nous permet d'exécuter une fonction de manière répétée, en commençant après l'intervalle de temps, puis en répétant continuellement à cet intervalle. Ces méthodes ne font pas partie de la spécification JavaScript. Mais la plupart des environnements ont un planificateur interne et fournissent ces méthodes. En particulier, elles sont supportées par tous les navigateurs et Node.js. ## setTimeout La syntaxe : ```js let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) ``` Les paramètres : `func|code` : Fonction ou chaîne de caractères représentant du code à exécuter. En général, c'est une fonction. Pour des raisons historiques, une chaîne de caractères représentant du code peut être donnée en argument, mais ce n'est pas recommandé. `delay` : La durée d'attente avant l'exécution, en millisecondes (1000ms = 1 seconde), par défaut 0. `arg1`, `arg2`... : Arguments pour la fonction Par exemple, le code ci-dessous appelle la fonction `sayHi()` une unique fois au bout de 1 seconde : ```js run function sayHi() { alert('Hello'); } *!* setTimeout(sayHi, 1000); */!* ``` Dans le cas où fonction `sayHi()` requiert des arguments : ```js run function sayHi(phrase, who) { alert( phrase + ', ' + who ); } *!* setTimeout(sayHi, 1000, "Bonjour", "Jean"); // Bonjour, Jean */!* ``` Si le premier argument est une chaîne de caractères, JavaScript crée alors une fonction à partir de celle-ci. Ce qui fait que le code ci-dessous fonctionne aussi : ```js run no-beautify setTimeout("alert('Bonjour')", 1000); ``` Cependant, utiliser des chaînes de caractères n'est pas recommandé, il est préférable d'utiliser des fonctions fléchées à la place, comme ceci : ```js run no-beautify setTimeout(() => alert('Bonjour'), 1000); ``` ````smart header="Passer une fonction, mais sans l'exécuter" Les développeurs novices font parfois l'erreur d'ajouter des parenthèses `()` après la fonction : ```js // Faux! setTimeout(sayHi(), 1000); ``` Cela ne fonctionne pas car `setTimeout` attend une référence à une fonciton. Ici `sayHi()` appelle la fonction et le *résultat de cette exécution* est passé à `setTimeout`. Dans notre cas, le résultat de `sayHi()` est `undefined` (la fonction ne renvoie rien), du coup, rien n'est planifié. ```` ### Annuler une tâche avec clearTimeout Un appel à `setTimeout` renvoie un "identifiant de timer" `timerId` que l'on peut utiliser pour annuler l'exécution de la fonction. La syntaxe pour annuler une tâche planifiée est la suivante : ```js let timerId = setTimeout(...); clearTimeout(timerId); ``` Dans le code ci-dessous, nous planifions l'appel à la fonction avant de l'annuler, au final rien ne s'est passé : ```js run no-beautify let timerId = setTimeout(() => alert("Je n'arriverai jamais"), 1000); alert(timerId); // Identifiant du timer clearTimeout(timerId); alert(timerId); // Le même identifiant (ne devient pas null après l'annulation) ``` Comme on peut le voir dans les résultats des `alert`, dans notre navigateur, l'identifiant du timer est un nombre. Selon l'environnement, il peut être d'un autre type. Par exemple, Node.js renvoie un objet timer équipé d'autres méthodes. Encore une fois, il n'y a pas de spécification universelle pour ces méthodes, donc ce n'est pas gênant. Pour les navigateurs, les timers sont décrits dans la [section des timers](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) de HTML Living Standard. ## setInterval La méthode `setInterval` a la même syntaxe que `setTimeout`: ```js let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...) ``` Tous ses arguments ont la même signfication que précédemment, mais contrairement à `setTimeout`, `setInterval` appelle la fonction non pas une fois, mais périodiquement après un interval de temps donné. Afin d'annuler les appels futurs à la fonction, il est nécessaire d'appeler `clearInterval(timerId)`. L'exemple suivant affiche le message toutes les 2 secondes, puis arrête la tâche au bout de 5 secondes : ```js run // Se répète toutes les 2 secondes let timerId = setInterval(() => alert('tick'), 2000); // S'arrête après 5 secondes setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000); ``` ```smart header="Le temps continue de s'écouler pendant que `alert` est affiché" Dans la majorité des navigateurs, dont Chrome et Firefox, le timer interne continue à s'incrémenter pendant qu'un message est affiché (via `alert`, `confirm` ou `prompt`). Donc, si vous exécutez le code ci-dessus et que vous ne fermez pas la fenêtre `alert` pendant un certain temps, la prochaine `alert` sera affichée immédiatement lorsque vous le faites. L'intervalle réel entre les alertes sera inférieur à 2 secondes. ``` ## setTimeout imbriqué Il y a deux façon d'ordonnancer l'exécution périodique d'une tâche. L'un est `setInterval`. L'autre est un `setTimeout` imbriqué, comme ceci : ```js /** Au lieu de : let timerId = setInterval(() => alert('tick'), 2000); */ let timerId = setTimeout(function tick() { alert('tick'); *!* timerId = setTimeout(tick, 2000); // (*) */!* }, 2000); ``` Le `setTimeout` ci-dessus planifie le prochain appel de la fonction à la fin de l'appel en cours `(*)`. Le `setTimeout` imbriqué est une méthode plus flexible que `setInterval`. Ainsi, le prochain appel peut être programmé différemment, en fonction des résultats de l'appel en cours. Par exemple, on peut avoir besoin d'implémenter un service qui envoie une requête à un serveur toutes les 5 secondes pour récupérer de la donnée, mais dans le cas où le serveur est surchargé, on doit augmenter le délai à 10 secondes, puis 20 secondes, 40 secondes... Voici le pseudo-code correspondant : ```js let delay = 5000; let timerId = setTimeout(function request() { ...send request... if (request failed due to server overload) { // Augmente l'intervalle avant le prochain appel delay *= 2; } timerId = setTimeout(request, delay); }, delay); ``` Ou par exemple, si les fonction qu'on souhaite planifier demandent beaucoup de ressources CPU, on peut alors mesurer leur temps d'exécution et planifier le prochain appel en fonction. Et si les fonctions que nous planifions sont gourmandes en ressources processeur, nous pouvons mesurer le temps pris par l'exécution et planifier le prochain appel tôt ou tard. **Un `setTimeout` imbriqué permet de définir le délai entre les exécutions plus précisément que `setInterval`.** Comparons deux blocs de codes, le premier utilise `setInterval` : ```js let i = 1; setInterval(function() { func(i++); }, 100); ``` Le second utilise un `setTimeout` imbriqué : ```js let i = 1; setTimeout(function run() { func(i++); setTimeout(run, 100); }, 100); ``` Dans le cas du `setInterval` l'ordonnanceur interne va appeler `func(i++)` toutes les 100ms : ![](setinterval-interval.svg) Rien d'étrange ? **Le vrai délai entre deux appels à `func` est plus court que dans le code.** C'est normal car le temps d'exécution de `func` "consomme" une partie de ce délai. Il est donc possible que le temps d'exécution de `func` soit plus long que prévu et prenne plus de 100ms. Dans ce cas le moteur interne attend que l'exécution de `func` soit terminée, puis consulte l'ordonnanceur et si le délai est déjà "consommé", il réexécute la fonction *immédiatement*. Dans ce cas extrême, si la fonction qui s'exécute met toujours plus de temps que `delay` ms, alors les appels successifs vont s'effectuer sans aucun temps de pause. Et voici l'image pour le `setTimeout` imbriqué : ![](settimeout-interval.svg) **Le `setTimeout` imbriqué garantit le délai fixé (ici 100 ms).** Dans ce cas, c'est parce que le nouvel appel est planifié à la fin du précédent. ````smart header="Le ramasse-miettes et le callback setInterval/setTimeout" Quand une fonction est passée à `setInterval`/`setTimeout`, une référence interne à cette fonction est créée et conservée dans l'ordonnanceur. Cela empêche que la fonction soit détruite par le ramasse-miettes, même si il n'y a pas d'autres références à cette dernière. ```js // La fonction reste en mémoire jusqu'à ce que l'ordonnanceur l'exécute setTimeout(function() {...}, 100); ``` Pour `setInterval`, la fonction reste en mémoire jusqu'à ce qu'on appelle `clearInterval`. Mais il y a un effet de bord, une fonction référence l'environement lexical extérieur, donc tant qu'elle existe, les variables extérieures existent aussi. Ces variables peuvent occuper autant d'espace mémoire que la fonction elle-même. De ce fait quand on n'a plus besoin d'une fonction planifiée, il est préférable de l'annuler, même si elle est courte. ```` ## setTimeout sans délai Il y a un cas d'usage particulier : `setTimeout(func, 0)` ou plus simplement `setTimeout(func)`. Ceci programme l'exécution de `func` dès que possible. Mais le planificateur ne l'invoquera qu'une fois le script en cours d'exécution terminé. La fonction est donc programmée pour s'exécuter "juste après" le script en cours. Par exemple, le code ci dessous affiche "Hello", et immédiatement après, "World" : ```js run setTimeout(() => alert("World")); alert("Hello"); ``` La première ligne "met l'appel dans le calendrier après 0 ms". Mais le planificateur "vérifiera le calendrier" uniquement une fois le script en cours terminé. `"Hello"` est donc le premier, et `"World"` -- après. Il y a aussi d'autres cas d'usage avancés d'ordonnancement à délai nul, spécifique au cas des navigateurs web, dont nous parlerons dans le chapitre . ````smart header="Un délai nul n'est pas vraiment nul (pour un navigateur)" Dans le navigateur, la fréquence d'exécution des timers imbriqués est limitée. Le [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) indique : "après cinq timers imbriqués, l'intervalle est forcé d'être d'au moins 4 millisecondes.". Nous allons illustrer ce que cela veut dire dans l'exemple ci-dessous. L'appel à `setTimeout` s'y ré-ordonnance lui-même avec un délai nul. Chaque appel se souvient de l'heure de l'appel précédent grâce au tableau `times`. Cela va nous permettre de mesurer les délais réels entre les exécutions : ```js run let start = Date.now(); let times = []; setTimeout(function run() { times.push(Date.now() - start); // on garde en mémoire le délai depuis l'appel précédent if (start + 100 < Date.now()) alert(times); // on affiche les délais si plus de 100ms se sont écoulées else setTimeout(run); // sinon on planifie un nouvel appel }); // voici un exemple de résultat : // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100 ``` Les 4 premiers timers s'exécutent immédiatemment (comme indiqué dans la spécification), ensuite on peut voir `9, 15, 20, 24...`. Le délai minimum de 4ms entre appel entre alors en jeu. Cette même limitation s'applique si on utilise `setInterval` au lieu de `setTimeout` : `setInterfal(f)` appelle `f` un certain nombre de fois avec un délai nul avant d'observer un délai d'au moins 4ms. Cette limitation est l'héritage d'un lointain passé et beaucoup de scripts se basent dessus, d'où la nécessité de cette limitation pour des raisons historiques. Pour le JavaScript côté serveur, cette limitation n'existe pas, et il existe d'autres façon de planifier immédiatement des tâches asynchrones, notamment [setImmediate](https://nodejs.org/api/timers.html) pour Node.js. Il faut donc garder à l'esprit que ce nota bene est spécifique aux navigateurs web. ```` ## Résumé - Les méthodes `setInterval(func, delay, ...args)` et `setTimeout(func, delay, ...args)` permettent d'exécuter `func` respectivement une seul fois/périodiquement après `delay` millisecondes. - Pour annuler l'exécution, nous devons appeler `clearInterval/clearTimeout` avec la valeur renvoyée par `setInterval/setTimeout`. - Les appels de `setTimeout` imbriqués sont une alternative plus flexible à `setInterval`, ils permettent de configurer le temps *entre* les exécution plus précisément. - L'ordonnancement à délai nul avec `setTimeout(func, 0)` (le même que `setTimeout(func)`) permet de planifier l'exécution "dès que possible, mais seulement une fois que le bloc de code courant a été exécuté". - Le navigateur limite le délai minimal pour cinq appels imbriqués ou plus de `setTimeout` ou pour `setInterval` (après le 5ème appel) à 4 ms. C'est pour des raisons historiques. Veuillez noter que toutes les méthodes de planification ne garantissent pas le délai exact. Par exemple, le timer interne au navigateur peut être ralenti pour de nombreuses raisons : - Le CPU est surchargé. - L'onglet du navigateur est en tâche de fond. - L'ordinateur est en mode économie d'énergie. Tout ceci peut augmenter la résolution de l'horloge (le délai minimum) jusqu'à 300ms voire 1000ms en fonction du navigateur et des paramètres de performance au niveau du système d'exploitation. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js ================================================ function spy(func) { function wrapper(...args) { // using ...args instead of arguments to store "real" array in wrapper.calls wrapper.calls.push(args); return func.apply(this, args); } wrapper.calls = []; return wrapper; } ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js ================================================ function spy(func) { // votre code } ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js ================================================ describe("spy", function() { it("records calls into its property", function() { function work() {} work = spy(work); assert.deepEqual(work.calls, []); work(1, 2); assert.deepEqual(work.calls, [ [1, 2] ]); work(3, 4); assert.deepEqual(work.calls, [ [1, 2], [3, 4] ]); }); it("transparently wraps functions", function() { let sum = sinon.spy((a, b) => a + b); let wrappedSum = spy(sum); assert.equal(wrappedSum(1, 2), 3); assert(sum.calledWith(1, 2)); }); it("transparently wraps methods", function() { let calc = { sum: sinon.spy((a, b) => a + b) }; calc.wrappedSum = spy(calc.sum); assert.equal(calc.wrappedSum(1, 2), 3); assert(calc.sum.calledWith(1, 2)); assert(calc.sum.calledOn(calc)); }); }); ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md ================================================ Le wrapper renvoyé par `spy(f)` doit stocker tous les arguments, puis utiliser `f.apply` pour transférer l'appel. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md ================================================ importance: 5 --- # Décorateur spy Créez un décorateur `spy(func)` qui devrait renvoyer un wrapper qui enregistre tous les appels à la fonction dans sa propriété `calls`. Chaque appel est enregistré sous la forme d'un tableau d'arguments. Par exemple: ```js function work(a, b) { alert( a + b ); // work est une fonction ou une méthode arbitraire } *!* work = spy(work); */!* work(1, 2); // 3 work(4, 5); // 9 for (let args of work.calls) { alert( 'call:' + args.join() ); // "call:1,2", "call:4,5" } ``` P.S. Ce décorateur est parfois utile pour les tests unitaires. Sa forme avancée est `sinon.spy` dans la bibliothèque [Sinon.JS](http://sinonjs.org/). ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/solution.js ================================================ function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; }; ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js ================================================ describe("delay", function() { before(function() { this.clock = sinon.useFakeTimers(); }); after(function() { this.clock.restore(); }); it("calls the function after the specified timeout", function() { let start = Date.now(); function f(x) { assert.equal(Date.now() - start, 1000); } f = sinon.spy(f); let f1000 = delay(f, 1000); f1000("test"); this.clock.tick(2000); assert(f.calledOnce, 'calledOnce check fails'); }); it("passes arguments and this", function() { let start = Date.now(); let user = { sayHi: function(phrase, who) { assert.equal(this, user); assert.equal(phrase, "Hello"); assert.equal(who, "John"); assert.equal(Date.now() - start, 1500); } }; user.sayHi = sinon.spy(user.sayHi); let spy = user.sayHi; user.sayHi = delay(user.sayHi, 1500); user.sayHi("Hello", "John"); this.clock.tick(2000); assert(spy.calledOnce, 'calledOnce check failed'); }); }); ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md ================================================ La solution: ```js run demo function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; } let f1000 = delay(alert, 1000); f1000("test"); // montre "test" après 1000ms ``` Veuillez noter comment une fonction fléchée est utilisée ici. Comme nous le savons, les fonctions fléchées ne possèdent pas leurs propres `this` et `arguments`, aussi `f.apply(this, arguments)` prend `this` et `arguments` du wrapper. Si nous passons une fonction régulière, `setTimeout` l'appellera sans arguments et `this = window` (en supposant que nous sommes dans le navigateur). Nous pouvons toujours passer le bon `this` en utilisant une variable intermédiaire, mais c'est un peu plus lourd: ```js function delay(f, ms) { return function(...args) { let savedThis = this; // stocker "this" dans une variable intermédiaire setTimeout(function() { f.apply(savedThis, args); // utilisez-le ici }, ms); }; } ``` ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md ================================================ importance: 5 --- # Décorateur de retardement Créez un décorateur `delay(f, ms)` qui retarde chaque appel de `f` de `ms` millisecondes. Par exemple: ```js function f(x) { alert(x); } // créer des wrappers let f1000 = delay(f, 1000); let f1500 = delay(f, 1500); f1000("test"); // montre "test" après 1000ms f1500("test"); // montre "test" après 1500ms ``` En d'autres termes, `delay(f, ms)` renvoie une variante "retardée de `ms`" de `f`. Dans le code ci-dessus, `f` est une fonction d'un seul argument, mais votre solution doit transmettre tous les arguments et le contexte `this`. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js ================================================ function debounce(func, ms) { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, arguments), ms); }; } ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js ================================================ describe('debounce', function () { before(function () { this.clock = sinon.useFakeTimers(); }); after(function () { this.clock.restore(); }); it('for one call - runs it after given ms', function () { const f = sinon.spy(); const debounced = debounce(f, 1000); debounced('test'); assert(f.notCalled, 'not called immediately'); this.clock.tick(1000); assert(f.calledOnceWith('test'), 'called after 1000ms'); }); it('for 3 calls - runs the last one after given ms', function () { const f = sinon.spy(); const debounced = debounce(f, 1000); debounced('a'); setTimeout(() => debounced('b'), 200); // ignored (too early) setTimeout(() => debounced('c'), 500); // runs (1000 ms passed) this.clock.tick(1000); assert(f.notCalled, 'not called after 1000ms'); this.clock.tick(500); assert(f.calledOnceWith('c'), 'called after 1500ms'); }); it('keeps the context of the call', function () { let obj = { f() { assert.equal(this, obj); }, }; obj.f = debounce(obj.f, 1000); obj.f('test'); this.clock.tick(5000); }); }); ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html ================================================ Function handler is called on this input:

Debounced function debounce(handler, 1000) is called on this input:

================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md ================================================ ```js demo function debounce(func, ms) { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, arguments), ms); }; } ``` Un appel à `debounce` renvoie un wrapper. Lorsqu'il est appelé, il planifie l'appel de la fonction d'origine après `ms` donné et annule le délai d'expiration précédent. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md ================================================ importance: 5 --- # Décorateur debounce Le résultat du décorateur `debounce(f, ms)` est un wrapper qui suspend les appels à `f` jusqu'à ce qu'il y ait `ms` millisecondes d'inactivité (pas d'appels, "période de cooldown"), puis invoque `f` une fois avec les derniers arguments. En d'autres termes, `debounce` est comme une secrétaire qui accepte les "appels téléphoniques", et attend jusqu'à ce qu'il y ait des millisecondes de silence. Et alors seulement, elle transfère les dernières informations d'appel au "boss" (appellez le "f" réel). Par exemple, nous avons eu une fonction `f` et l'avons remplacée par `f = debounce(f, 1000)`. Ensuite, si la fonction encapsulée est appelée à 0ms, 200ms et 500ms, et qu'il n'y a aucun appel, alors le `f` actuel ne sera appelé qu'une seule fois, à 1500 ms. Autrement dit: après la période de temps de recharge de 1000 ms à partir du dernier appel. ![](debounce.svg) ... Et il récupérera les arguments du tout dernier appel, les autres appels sont ignorés. Voici le code pour cela (utilise le décorateur debounce de la [librairie Lodash](https://lodash.com/docs/4.17.15#debounce)): ```js let f = _.debounce(alert, 1000); f("a"); setTimeout( () => f("b"), 200); setTimeout( () => f("c"), 500); // la fonction debounce attend 1000ms après le dernier appel puis exécute : alert("c") ``` Maintenant, un exemple pratique. Disons que l'utilisateur tape quelque chose et que nous aimerions envoyer une requête au serveur lorsque l'entrée est terminée. Il ne sert à rien d'envoyer la requête pour chaque caractère saisi. Au lieu de cela, nous aimerions attendre, puis traiter l'ensemble du résultat. Dans un navigateur Web, nous pouvons configurer un gestionnaire d'événements - une fonction qui est appelée à chaque modification d'un champ de saisie. Normalement, un gestionnaire d'événements est appelé très souvent, pour chaque touche tapée. Mais si on le `debounce` de 1000ms, il ne sera appelé qu'une seule fois, après 1000ms après la dernière entrée. ```online Dans cet exemple en live, le gestionnaire place le résultat dans une case ci-dessous, essayez-le : [iframe border=1 src="debounce" height=200] Vous voyez ? La deuxième entrée appelle la fonction "debounced", donc son contenu est traité après 1000ms à partir de la dernière entrée. ``` Donc, `debounce` est un excellent moyen de traiter une séquence d'événements: que ce soit une séquence de touches, de mouvements de souris ou autre. Il attend le temps donné après le dernier appel, puis exécute sa fonction, qui peut traiter le résultat. La tâche est d'implémenter le décorateur `debounce`. Indice : ce ne sont que quelques lignes si vous y réfléchissez :) ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js ================================================ function throttle(func, ms) { let isThrottled = false, savedArgs, savedThis; function wrapper() { if (isThrottled) { // mémo derniers arguments à appeler après le temps de recharge savedArgs = arguments; savedThis = this; return; } // sinon aller en état de recharge func.apply(this, arguments); isThrottled = true; // prévoir de réinitialiser isThrottled après le délai setTimeout(function() { isThrottled = false; if (savedArgs) { // s'il y a eu des appels, savedThis/savedArgs auront le dernier // appel récursif exécute la fonction et redéfinit le temps de recharge wrapper.apply(savedThis, savedArgs); savedArgs = savedThis = null; } }, ms); } return wrapper; } ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js ================================================ describe("throttle(f, 1000)", function() { let f1000; let log = ""; function f(a) { log += a; } before(function() { this.clock = sinon.useFakeTimers(); f1000 = throttle(f, 1000); }); it("the first call runs now", function() { f1000(1); // runs now assert.equal(log, "1"); }); it("then calls are ignored till 1000ms when the last call works", function() { f1000(2); // (throttling - less than 1000ms since the last run) f1000(3); // (throttling - less than 1000ms since the last run) // after 1000 ms f(3) call is scheduled assert.equal(log, "1"); // right now only the 1st call done this.clock.tick(1000); // after 1000ms... assert.equal(log, "13"); // log==13, the call to f1000(3) is made }); it("the third call waits 1000ms after the second call", function() { this.clock.tick(100); f1000(4); // (throttling - less than 1000ms since the last run) this.clock.tick(100); f1000(5); // (throttling - less than 1000ms since the last run) this.clock.tick(700); f1000(6); // (throttling - less than 1000ms since the last run) this.clock.tick(100); // now 100 + 100 + 700 + 100 = 1000ms passed assert.equal(log, "136"); // the last call was f(6) }); after(function() { this.clock.restore(); }); }); describe('throttle', () => { it('runs a forwarded call once', done => { let log = ''; const f = str => log += str; const f10 = throttle(f, 10); f10('once'); setTimeout(() => { assert.equal(log, 'once'); done(); }, 20); }); }); ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md ================================================ ```js demo function throttle(func, ms) { let isThrottled = false, savedArgs, savedThis; function wrapper() { if (isThrottled) { // (2) savedArgs = arguments; savedThis = this; return; } isThrottled = true; func.apply(this, arguments); // (1) setTimeout(function() { isThrottled = false; // (3) if (savedArgs) { wrapper.apply(savedThis, savedArgs); savedArgs = savedThis = null; } }, ms); } return wrapper; } ``` Un appel à `throttle(func, ms)` retourne `wrapper`. 1. Lors du premier appel, le `wrapper` exécute simplement `func` et définit l'état de charge (`isThrottled = true`). 2. Dans cet état, tous les appels sont mémorisés dans `savedArgs/savedThis`. Veuillez noter que le contexte et les arguments sont d'égale importance et doivent être mémorisés. Nous avons besoin d'eux simultanément pour reproduire l'appel. 3. Après `ms` millisecondes, `setTimeout` se déclenche. L'état de charge est supprimé (`isThrottled = false`), et si nous avions ignoré les appels, alors `wrapper` est exécuté avec les derniers arguments et contextes mémorisés. La 3ème étape n’exécute pas `func`, mais `wrapper`, car nous devons non seulement exécuter `func`, mais encore une fois entrer dans l’état de charge et configurer le délai d’expiration pour le réinitialiser. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md ================================================ importance: 5 --- # Décorateur d'accélération Créez un décorateur "d'accélération" `throttle(f, ms)` -- qui retourne un wrapper. Lorsqu'il est appelé plusieurs fois, il passe l'appel à `f` au maximum une fois par `ms` millisecondes. La différence avec debounce est que c'est un décorateur complètement différent : - `debounce` exécute la fonction une fois après la période de "cooldown". Bon pour traiter le résultat final. - `throttle` ne l'exécute pas plus souvent que le temps donné en `ms`. Bon pour les mises à jour régulières qui ne devraient pas être très fréquentes. En d'autres termes, `throttle` est comme une secrétaire qui accepte les appels téléphoniques, mais qui dérange le patron (appellez le `f` réel) pas plus d'une fois par `ms` millisecondes. Examinons l'application réelle pour mieux comprendre cette exigence et voir d'où elle vient. **Par exemple, nous voulons suivre les mouvements de la souris.** Dans le navigateur, nous pouvons configurer une fonction à exécuter à chaque mouvement de la souris et obtenir l’emplacement du pointeur à mesure qu’il se déplace. Pendant une utilisation active de la souris, cette fonction est généralement utilisée très souvent et peut atteindre 100 fois par seconde (toutes les 10 ms). **Nous aimerions mettre à jour certaines informations sur la page Web lorsque le pointeur se déplace.** ... Mais la mise à jour de la fonction `update()` est trop lourde pour tous les micro-mouvements. Il est également inutile de mettre à jour plus d'une fois toutes les 100 ms. Nous allons donc l'envelopper dans le décorateur : utilisez `throttle(update, 100)` comme fonction à exécuter à chaque déplacement de souris à la place de `update()` d'origine. Le décorateur sera appelé souvent, mais `update()` sera appelé au maximum une fois toutes les 100 ms. Visuellement, cela ressemblera à ceci: 1. Pour le premier mouvement de souris, la variante décorée passe l'appel à `update`. Cela est important, l'utilisateur voit notre réaction à leur mouvement immédiatement. 2. Puis, alors que la souris continue d'avancer, il ne se passe plus rien jusqu'à `100ms`. La variante décorée ignore les appels. 3. À la fin de `100ms` - une autre `update` se produit avec les dernières coordonnées. 4. Enfin, la souris s’arrête quelque part. La variante décorée attend que `100ms` expire, puis lance `update` avec les dernières coordonnées. Donc, peut-être le plus important, les coordonnées finales de la souris sont traitées. Un exemple de code: ```js function f(a) { console.log(a); } // f1000 passe les appels à f au maximum une fois toutes les 1000 ms let f1000 = throttle(f, 1000); f1000(1); // montre 1 f1000(2); // (étranglement, 1000ms pas encore écoulée) f1000(3); // (étranglement, 1000ms pas encore écoulée) // quand 1000ms expirent... // ...sort 3, la valeur intermédiaire 2 a été ignorée ``` P.S. Les arguments et le contexte `this` transmis à `f1000` doivent être transmis à `f` d'origine. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/article.md ================================================ # Décorateurs et transferts, call/apply JavaScript offre une flexibilité exceptionnelle dans le traitement des fonctions. Elles peuvent être échangées, utilisés comme objets, et maintenant nous allons voir comment *transférer* les appels entre eux et *les décorer*. ## Cache transparent Disons que nous avons une fonction `slow(x)` qui nécessite beaucoup de ressources processeur, mais ses résultats sont stables. En d'autres termes, pour le même `x`, le résultat est toujours le même. Si la fonction est appelée souvent, nous voudrons peut-être mettre en mémoire cache (mémoriser) les résultats pour éviter de passer plus de temps sur les re-calculs. Mais au lieu d’ajouter cette fonctionnalité à `slow()`, nous allons créer une fonction wrapper qui ajoute la mise en cache. Comme nous le verrons, cela présente de nombreux avantages. Voici le code, et les explications suivent : ```js run function slow(x) { // il peut y avoir un travail gourmand en ressources processeur ici alert(`Called with ${x}`); return x; } function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { // s'il y a une telle clé dans le cache return cache.get(x); // lire le résultat } let result = func(x); // sinon appeler func cache.set(x, result); // et mettre le résultat en cache return result; }; } slow = cachingDecorator(slow); alert( slow(1) ); // slow(1) est mis en cache et le résultat est renvoyé alert( "Again: " + slow(1) ); // le résultat slow(1) est retourné à partir du cache alert( slow(2) ); // slow(2) est mis en cache et le résultat est renvoyé alert( "Again: " + slow(2) ); // le résultat slow(2) est retourné à partir du cache ``` Dans le code ci-dessus, `cachingDecorator` est un *décorateur* : une fonction spéciale qui prend une autre fonction et modifie son comportement. L'idée est que nous pouvons appeler `cachingDecorator` pour n'importe quelle fonction, ce qui renverra le wrapper de mise en cache. C'est formidable, car nous pouvons avoir de nombreuses fonctions qui pourraient utiliser une telle fonctionnalité, et tout ce que nous avons à faire est de leur appliquer `cachingDecorator`. En séparant la mise en cache du code de la fonction principale, nous simplifions également le code principal. Le résultat de `cachingDecorator(func)` est un "wrapper" : `function(x)` qui "encapsule" l'appel de `func(x)` dans la logique de mise en cache : ![](decorator-makecaching-wrapper.svg) Depuis un code extérieur, la fonction encapsulée `slow` fait toujours la même chose. Un comportement de mise en cache vient d’être ajouté à son comportement. Pour résumer, il y a plusieurs avantages à utiliser un `cachingDecorator` distinct au lieu de modifier le code de `slow` lui-même : - Le `cachingDecorator` est réutilisable. Nous pouvons l'appliquer à une autre fonction. - La logique de mise en cache est séparée, elle n’a pas augmenté la complexité de `slow` lui-même (s’il en existait). - Nous pouvons combiner plusieurs décorateurs si nécessaire (d'autres décorateurs suivront). ## Utilisation de "func.call" pour le contexte Le décorateur de mise en cache mentionné ci-dessus n'est pas adapté pour travailler avec des méthodes d'objet. Par exemple, dans le code ci-dessous `worker.slow()` cesse de fonctionner après la décoration : ```js run // on ajoutera une fonctionalité de cache à worker.slow let worker = { someMethod() { return 1; }, slow(x) { // tâche lourde et effrayante pour le CPU ici alert("Called with " + x); return x * this.someMethod(); // (*) } }; // même code que précédemment function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } *!* let result = func(x); // (**) */!* cache.set(x, result); return result; }; } alert( worker.slow(1) ); // la méthode originale fonctionne worker.slow = cachingDecorator(worker.slow); // ajoute la mise en cache *!* alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined */!* ``` L'erreur se produit dans la ligne `(*)` qui tente d'accéder à `this.someMethod` et échoue. Pouvez-vous voir pourquoi ? La raison en est que le wrapper appelle la fonction d'origine sous la forme `func(x)` dans la ligne `(**)`. Et, lorsqu'elle est appelée comme ça, la fonction obtient `this = undefined`. Nous observerions un symptôme similaire si nous essayions d'executer : ```js let func = worker.slow; func(2); ``` Ainsi, le wrapper passe l'appel à la méthode d'origine, mais sans le contexte `this`. D'où l'erreur. Réparons-le. Il existe une méthode de fonction intégrée spéciale [func.call(context, ...args)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/call) qui permet d'appeler explicitement une fonction en définissant `this`. La syntaxe est la suivante : ```js func.call(context, arg1, arg2, ...) ``` Il exécute `func` en fournissant `this` comme le premier argument et les suivants en tant qu'arguments. Pour le dire simplement, ces deux appels font presque la même chose : ```js func(1, 2, 3); func.call(obj, 1, 2, 3) ``` Ils appellent tous les deux `func` avec les arguments `1`, `2` et `3`. La seule différence est que `func.call` définit également `this` sur `obj`. Par exemple, dans le code ci-dessous, nous appelons `sayHi` dans le contexte de différents objets : `sayHi.call(user)` exécute `sayHi` fournissant `this = user`, et la ligne suivante définit `this = admin` : ```js run function sayHi() { alert(this.name); } let user = { name: "John" }; let admin = { name: "Admin" }; // utilisons call pour passer différents objets en tant que "this" sayHi.call( user ); // John sayHi.call( admin ); // Admin ``` Et ici, nous utilisons `call` pour appeler `say` avec le contexte et la phrase donnés : ```js run function say(phrase) { alert(this.name + ': ' + phrase); } let user = { name: "John" }; // user devient this, et "Hello" devient le premier argument say.call( user, "Hello" ); // John: Hello ``` Dans notre cas, nous pouvons utiliser `call` dans le wrapper pour passer le contexte à la fonction d'origine : ```js run let worker = { someMethod() { return 1; }, slow(x) { alert("Called with " + x); return x * this.someMethod(); // (*) } }; function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } *!* let result = func.call(this, x); // "this" est passé correctement maintenant */!* cache.set(x, result); return result; }; } worker.slow = cachingDecorator(worker.slow); // ajoute la mise en cache alert( worker.slow(2) ); // ça fonctione alert( worker.slow(2) ); // ça fonctionne, n'appelle pas l'original (mis en cache) ``` Maintenant, tout va bien. Pour que tout soit clair, voyons plus en détail comment `this` est passé : 1. Après la décoration, `worker.slow` est désormais le wrapper `function(x) {...}`. 2. Ainsi, lorsque `worker.slow(2)` est exécuté, le wrapper obtient `2` en argument et `this = worker` (c'est l'objet avant le point). 3. Dans le wrapper, en supposant que le résultat ne soit pas encore mis en cache, `func.call(this, x)` passe le `this` (`= worker`) actuel et l'argument actuel (`= 2`) à la méthode d'origine. ## Passer plusieurs arguments Rendons maintenant `cachingDecorator` encore plus universel. Jusqu'à présent, il ne travaillait qu'avec des fonctions à un seul argument. Maintenant, comment mettre en cache la méthode multi-argument `worker.slow` ? ```js let worker = { slow(min, max) { return min + max; // la tâche est supposée lourde } }; // devrait se rappeler des appels au mêmes arguments worker.slow = cachingDecorator(worker.slow); ``` Auparavant, pour un seul argument, `x`, nous pouvions simplement `cache.set(x, result)` pour enregistrer le résultat et `cache.get(x)` pour le récupérer. Mais maintenant, nous devons nous rappeler le résultat pour une *combinaison d'arguments* `(min, max)`. Le `Map` natif prend une valeur unique en tant que clé. Il y a beaucoup de solutions possibles : 1. Mettre en œuvre une nouvelle structure de données similaire à `Map` (ou utiliser une par une tierce partie) plus polyvalent et permetant l'utilisation de plusieurs clés. 2. Utilisez des maps imbriquées : `cache.set(min)` sera un `Map` qui stocke la paire `(max, result)`. Donc, nous pouvons obtenir `result` avec `cache.get (min).get(max)`. 3. Joignez deux valeurs en une. Dans notre cas particulier, nous pouvons simplement utiliser la chaîne `"min, max"` comme clé pour `Map`. Pour plus de flexibilité, nous pouvons permettre de fournir une *fonction de hachage* au décorateur, qui sait créer une valeur parmi plusieurs. Pour de nombreuses applications pratiques, la 3ème variante est suffisante, nous allons donc nous y tenir. Nous devons également transmettre non seulement `x`, mais tous les arguments dans `func.call`. Rappelons que dans une `function()` on peut obtenir un pseudo-tableau de ses arguments comme `arguments`, donc `func.call(this, x)` doit être remplacé par `func.call(this, ...arguments)`. Voici un `cachingDecorator` plus puissant : ```js run let worker = { slow(min, max) { alert(`Called with ${min},${max}`); return min + max; } }; function cachingDecorator(func, hash) { let cache = new Map(); return function() { *!* let key = hash(arguments); // (*) */!* if (cache.has(key)) { return cache.get(key); } *!* let result = func.call(this, ...arguments); // (**) */!* cache.set(key, result); return result; }; } function hash(args) { return args[0] + ',' + args[1]; } worker.slow = cachingDecorator(worker.slow, hash); alert( worker.slow(3, 5) ); // ça marche alert( "Again " + worker.slow(3, 5) ); // pareil (mis en cache) ``` Maintenant, cela fonctionne avec n'importe quel nombre d'arguments (bien que la fonction de hachage doive également être ajustée pour permettre n'importe quel nombre d'arguments. Une façon intéressante de gérer cela sera traitée ci-dessous). Il y a deux changements : - Dans la ligne `(*)`, il appelle `hash` pour créer une clé unique à partir de `arguments`. Ici, nous utilisons une simple fonction "d'assemblage" qui transforme les arguments `(3, 5)` en la clé `"3,5"`. Les cas plus complexes peuvent nécessiter d'autres fonctions de hachage. - Ensuite `(**)` utilise `func.call(this, ...arguments)` pour transmettre le contexte et tous les arguments obtenus par le wrapper (pas seulement le premier) à la fonction d'origine. ## func.apply Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`. The syntax of built-in method [func.apply](mdn:js/Function/apply) is: ```js func.apply(context, args) ``` It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments. The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them. So these two calls are almost equivalent: ```js func.call(context, ...args); func.apply(context, args); ``` They perform the same call of `func` with given context and arguments. There's only a subtle difference regarding `args`: - The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. - The `apply` accepts only *array-like* `args`. ...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. Passing all arguments along with the context to another function is called *call forwarding*. That's the simplest form of it: ```js let wrapper = function() { return func.apply(this, arguments); }; ``` When an external code calls such `wrapper`, it is indistinguishable from the call of the original function `func`. ## Emprunter une méthode [#method-borrowing] Maintenant, apportons une autre amélioration mineure à la fonction de hachage : ```js function hash(args) { return args[0] + ',' + args[1]; } ``` Pour l'instant, cela ne fonctionne que sur deux arguments. Ce serait mieux s'il pouvait coller un nombre quelconque de `args`. La solution naturelle serait d'utiliser la méthode [arr.join](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/join) : ```js function hash(args) { return args.join(); } ``` ... Malheureusement, ça ne marchera pas. Parce que nous appelons `hash(arguments)` et l’objet `arguments` est à la fois itérable et semblable à un tableau, mais pas un vrai tableau. Donc, appeler `join` échouerait, comme on peut le voir ci-dessous : ```js run function hash() { *!* alert( arguments.join() ); // Error: arguments.join is not a function */!* } hash(1, 2); ``` Néanmoins, il existe un moyen simple d’utiliser `join` : ```js run function hash() { *!* alert( [].join.call(arguments) ); // 1,2 */!* } hash(1, 2); ``` L'astuce s'appelle *method borrowing* (empruntage de méthode). Nous prenons (empruntons) la méthode `join` d'un tableau régulier (`[].join`) et utilisons `[].join.call` pour l'exécuter dans le contexte des `arguments`. Pourquoi ça marche ? C'est parce que l'algorithme interne de la méthode native `arr.join(glue)` est très simple. Tiré de la spécification presque "tel quel" : 1. Soit `glue` le premier argument ou, s’il n’ya pas d’argument, une virgule `","`. 2. Soit `result` une chaîne de caractères vide. 3. Ajoutez `this[0]` à `result`. 4. Ajoutez `glue` et `this[1]`. 5. Ajoutez `glue` et `this[2]`. 6. ... Faites-le jusqu'à ce que `this.length` éléments soient collés. 7. Retournez `result`. Donc, techniquement, cela prend `this` et associe `this[0]`, `this[1]`... etc. Il est intentionnellement écrit de manière à permettre à tout type de tableau `this` (ce n'est pas une coïncidence, de nombreuses méthodes suivent cette pratique). C'est pourquoi cela fonctionne aussi avec `this = arguments`. ## Décorateurs et propriétés fonctionnelles Il est généralement prudent de remplacer une fonction ou une méthode par une fonction décorée, à une exception près. Si la fonction d'origine comportait des propriétés, telles que `func.calledCount` ou autre, la fonction décorée ne les fournira pas. Parce que c'est un wrapper. Il faut donc faire attention si on les utilise. Dans l'exemple ci-dessus, si la fonction `slow` avait des propriétés, alors `cachingDecorator(slow)` est un wrapper sans elles. Certains décorateurs peuvent fournir leurs propres propriétés. Par exemple un décorateur peut compter le nombre de fois où une fonction a été appelée et combien de temps cela a pris, et exposer ces informations via les propriétés du wrapper. Il existe un moyen de créer des décorateurs qui conservent l'accès aux propriétés de la fonction, mais cela nécessite l'utilisation d'un objet `Proxy` spécial pour envelopper une fonction. Nous en discuterons plus tard dans l'article . ## Résumé *Decorator* est un wrapper autour d'une fonction qui modifie son comportement. Le travail principal est toujours effectué par la fonction. Les décorateurs peuvent être considérés comme des "caractéristiques" ou des "aspects" pouvant être ajoutés à une fonction. Nous pouvons en ajouter un ou en ajouter plusieurs. Et tout ça sans changer son code ! Pour implémenter `cachingDecorator`, nous avons étudié les méthodes : - [func.call(context, arg1, arg2...)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/call) -- appelle `func` avec un contexte et des arguments donnés. - [func.apply(context, args)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/apply) -- appelle `func` en passant `context` comme `this` et `args` sous forme de tableau dans une liste d'arguments. Le renvoi d'appel, *call forwarding*, est généralement effectué avec `apply` : ```js let wrapper = function() { return original.apply(this, arguments); }; ``` Nous avons également vu un exemple d'empruntage de méthode, *method borrowing*, lorsque nous prenons une méthode à partir d'un objet et que nous l'appelons dans le contexte d'un autre objet. Il est assez courant de prendre des méthodes de tableau et de les appliquer à `arguments`. L'alternative consiste à utiliser l'objet de paramètres du reste qui est un vrai tableau. Il y a beaucoup de décorateurs dans la nature. Vérifiez si vous les avez bien obtenus en résolvant les tâches de ce chapitre. ================================================ FILE: 1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md ================================================ The answer: `null`. ```js run function f() { alert( this ); // null } let user = { g: f.bind(null) }; user.g(); ``` The context of a bound function is hard-fixed. There's just no way to further change it. So even while we run `user.g()`, the original function is called with `this=null`. ================================================ FILE: 1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md ================================================ importance: 5 --- # Bound function as a method What will be the output? ```js function f() { alert( this ); // ? } let user = { g: f.bind(null) }; user.g(); ``` ================================================ FILE: 1-js/06-advanced-functions/10-bind/3-second-bind/solution.md ================================================ The answer: **John**. ```js run no-beautify function f() { alert(this.name); } f = f.bind( {name: "John"} ).bind( {name: "Pete"} ); f(); // John ``` The exotic [bound function](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects) object returned by `f.bind(...)` remembers the context (and arguments if provided) only at creation time. A function cannot be re-bound. ================================================ FILE: 1-js/06-advanced-functions/10-bind/3-second-bind/task.md ================================================ importance: 5 --- # Second bind Can we change `this` by additional binding? What will be the output? ```js no-beautify function f() { alert(this.name); } f = f.bind( {name: "John"} ).bind( {name: "Ann" } ); f(); ``` ================================================ FILE: 1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md ================================================ The answer: `undefined`. The result of `bind` is another object. It does not have the `test` property. ================================================ FILE: 1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md ================================================ importance: 5 --- # Function property after bind There's a value in the property of a function. Will it change after `bind`? Why, or why not? ```js run function sayHi() { alert( this.name ); } sayHi.test = 5; *!* let bound = sayHi.bind({ name: "John" }); alert( bound.test ); // what will be the output? why? */!* ``` ================================================ FILE: 1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md ================================================ The error occurs because `ask` gets functions `loginOk/loginFail` without the object. When it calls them, they naturally assume `this=undefined`. Let's `bind` the context: ```js run function askPassword(ok, fail) { let password = prompt("Password?", ''); if (password == "rockstar") ok(); else fail(); } let user = { name: 'John', loginOk() { alert(`${this.name} logged in`); }, loginFail() { alert(`${this.name} failed to log in`); }, }; *!* askPassword(user.loginOk.bind(user), user.loginFail.bind(user)); */!* ``` Now it works. An alternative solution could be: ```js //... askPassword(() => user.loginOk(), () => user.loginFail()); ``` Usually that also works and looks good. It's a bit less reliable though in more complex situations where `user` variable might change *after* `askPassword` is called, but *before* the visitor answers and calls `() => user.loginOk()`. ================================================ FILE: 1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md ================================================ importance: 5 --- # Fix a function that loses "this" The call to `askPassword()` in the code below should check the password and then call `user.loginOk/loginFail` depending on the answer. But it leads to an error. Why? Fix the highlighted line for everything to start working right (other lines are not to be changed). ```js run function askPassword(ok, fail) { let password = prompt("Password?", ''); if (password == "rockstar") ok(); else fail(); } let user = { name: 'John', loginOk() { alert(`${this.name} logged in`); }, loginFail() { alert(`${this.name} failed to log in`); }, }; *!* askPassword(user.loginOk, user.loginFail); */!* ``` ================================================ FILE: 1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md ================================================ 1. Soit utiliser une fonction wrapper, une fléchée pour être concis: ```js askPassword(() => user.login(true), () => user.login(false)); ``` Maintenant, elle obtient `user` des variables externes et l'exécute normalement. 2. Ou créez une fonction partielle à partir de `user.login` qui utilise `user` comme contexte et a le bon premier argument: ```js askPassword(user.login.bind(user, true), user.login.bind(user, false)); ``` ================================================ FILE: 1-js/06-advanced-functions/10-bind/6-ask-partial/task.md ================================================ importance: 5 --- # Application de fonction partielle pour login La tâche est une variante un peu plus complexe de . L'objet `user` a été modifié. Maintenant, au lieu de deux fonctions `loginOk/loginFail`, il a une seule fonction `user.login(true/false)`. Que faire passer à `askPassword` dans le code ci-dessous, de sorte qu'il appelle `user.login(true)` comme `ok` et `user.login(false)` comme `fail` ? ```js function askPassword(ok, fail) { let password = prompt("Password?", ''); if (password == "rockstar") ok(); else fail(); } let user = { name: 'John', login(result) { alert( this.name + (result ? ' logged in' : ' failed to log in') ); } }; *!* askPassword(?, ?); // ? */!* ``` Vos modifications doivent uniquement modifier le fragment surligné. ================================================ FILE: 1-js/06-advanced-functions/10-bind/article.md ================================================ libs: - lodash --- # Le "bind" de fonction Lorsque l'on transmet des méthodes objets en tant que callbacks, par exemple à `setTimeout`, il y a un problème connu : "la perte du `this`". Dans ce chapitre nous verrons les façons de régler ça. ## La perte du "this" Nous avons déjà vu des exemples de la perte du `this`. Une fois qu'une méthode est passée quelque part séparement de l'objet -- `this` est perdu. Voici comment cela pourrait arriver avec `setTimeout` : ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; *!* setTimeout(user.sayHi, 1000); // Hello, undefined! */!* ``` Comme nous pouvons le voir, la sortie n'affiche pas "John" pour `this.firstName`, mais `undefined` ! C'est car `setTimeout` a eu la fonction `user.sayHi`, séparement de l'objet. La dernière ligne pourrait être réécrite comme : ```js let f = user.sayHi; setTimeout(f, 1000); // Perte du contexte d'user ``` La méthode `setTimeout` dans le navigateur est un peu spéciale : elle définit `this=window` pour l'appel à la fonction (pour Node.js, `this` devient un objet "timer", mais ça n'a pas d'importance ici). Donc pour `this.firstName` il essaye de récuperer `window.firstName`, qui n'existe pas. Dans d'autres cas similaires, `this` devient généralement `undefined`. Cette tâche est plutôt commune -- on veut transmettre une méthode objet quelque part ailleurs (ici -- au scheduler) où elle sera appelée. Comment s'assurer qu'elle sera appelée dans le bon contexte ? ## Solution 1 : Un wrapper La solution la plus simple est d'utiliser une fonction enveloppée : ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; *!* setTimeout(function() { user.sayHi(); // Hello, John! }, 1000); */!* ``` Maintenant ça fonctionne, car elle reçoit `user` depuis un environnement lexical extérieur, et donc les appels à la fonction se font normalement. La même chose mais en plus court : ```js setTimeout(() => user.sayHi(), 1000); // Hello, John! ``` Ça à l'air bon, mais une légère vulnérabilité apparaît dans la structure de notre code. Que se passe t-il si avant le déclenchement de `setTimeout` (il y une seconde de délai) `user` changeait de valeur ? Alors, soudainement, ça appelera le mauvais objet ! ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(() => user.sayHi(), 1000); // ...La valeur d'user dans 1 seconde user = { sayHi() { alert("Another user in setTimeout!"); } }; // Un autre user est dans le setTimeout ! ``` La prochaine solution garantit que ce genre de chose n'arrivera pas ## Solution 2 : "bind" Les fonctions fournissent une méthode intégrée, [bind](mdn:js/Function/bind) qui permet de corriger `this`. La syntaxe basique est : ```js // Une syntaxe plus complexe arrivera bientot let boundFunc = func.bind(context); ``` Le résultat de `func.bind(context)` est une "objet exotique" dans le style d'une fonction, qui est appellable comme une fonction et qui transmet l'appel à `func` en définissant `this=context` de façon transparente. En d'autres termes, appeller `boundFunc` équivaut à `func` avec un `this` corrigé. Par exemple, ici `funcUser` passe l'appel à `this` tel que `this=user` : ```js run let user = { firstName: "John" }; function func() { alert(this.firstName); } *!* let funcUser = func.bind(user); funcUser(); // John */!* ``` Ici `func.bind(user)` en tant "variante liée" de `func`, avec `this=user`. Tous les arguments sont passés à l'originale `func` "tels quels", par exemple : ```js run let user = { firstName: "John" }; function func(phrase) { alert(phrase + ', ' + this.firstName); } // Lie this à user let funcUser = func.bind(user); *!* funcUser("Hello"); // Hello, John (l'argument "Hello" est passé, et this=user) */!* ``` Maintenant essayons avec une méthode objet : ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; *!* let sayHi = user.sayHi.bind(user); // (*) */!* // Peut s'exécuter sans objet sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! // Mème si la valeur de user change dans 1 seconde // sayHi utilise la valeur pré-liée, laquelle fait référence à l'ancien objet user user = { sayHi() { alert("Another user in setTimeout!"); } }; ``` Sur la ligne `(*)` nous prenons la méthode `user.sayHi` en nous la lions à `user`. La méthode `sayHi` est une fonction "liée", qui peut être appelée seule ou être transmise à `setTimeout` -- ça n'a pas d'importance, le contexte sera le bon. Ici, nous pouvons voir que les arguments passés "tels quels", seulement `this` est corrigé par `bind` : ```js run let user = { firstName: "John", say(phrase) { alert(`${phrase}, ${this.firstName}!`); } }; let say = user.say.bind(user); say("Hello"); // Hello, John! (l'argument "Hello" est passé à say) say("Bye"); // Bye, John! (l'argument "Bye" est passé à say) ``` ````smart header="La méthode pratique : `bindAll`" Si un objet a plusieurs méthodes et que nous prévoyons de le transmettre plusieurs fois, alors on pourrait toutes les lier dans une boucle : ```js for (let key in user) { if (typeof user[key] == 'function') { user[key] = user[key].bind(user); } } ``` Les librairies JavaScript fournissent aussi des fonctions pratiques pour les liaisons de masse, e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) avec lodash. ```` ## Les fonctions partielles Jusqu'à maintenant nous avons parlé uniquement de lier `this`. Allons plus loin. Nous pouvons lier `this`, mais aussi des arguments. C'est rarement utilisé, mais ça peut être pratique. La syntaxe complète de `bind` : ```js let bound = func.bind(context, [arg1], [arg2], ...); ``` Elle permet de lier le contexte en tant que `this` et de démarrer les arguments de la fonction. Par exemple, nous avons une fonction de multiplication `mul(a, b)` : ```js function mul(a, b) { return a * b; } ``` Utilisons `bind` pour créer une fonction `double` sur cette base : ```js run function mul(a, b) { return a * b; } *!* let double = mul.bind(null, 2); */!* alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10 ``` L'appel à `mul.bind(null, 2)` créer une nouvelle fonction `double` qui transmet les appels à `mul`, corrigeant `null` dans le contexte et `2` comme premier argument. Les arguments sont passés "tels quels" plus loin. Ça s'appelle [l'application de fonction partielle](https://en.wikipedia.org/wiki/Partial_application) -- nous créeons une nouvelle fonction en corrigeant certains paramètres d'une fonction existante. Veuillez noter que nous n'utilisons actuellement pas `this` ici. Mais `bind` en a besoin, donc nous devrions mettre quelque chose dedans comme `null`. La fonction `triple` dans le code ci-dessous triple la valeur : ```js run function mul(a, b) { return a * b; } *!* let triple = mul.bind(null, 3); */!* alert( triple(3) ); // = mul(3, 3) = 9 alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15 ``` Pourquoi faisons nous généralement une fonction partielle ? L'avantage de faire ça est que nous pouvons créer une fonction indépendante avec un nom lisible (`double`, `triple`). Nous pouvons les utiliser et ne pas fournir de premier argument à chaque fois puisque c'est corrigé par `bind`. Dans d'autres cas, les fonctions partielles sont utiles quand nous avons des fonctions vraiment génériques et que nous voulons une variante moins universelle pour des raisons pratiques. Par exemple, nous avons une fonction `send(from, to, text)`. Alors, dans un objet `user` nous pourrions vouloir en utiliser une variante partielle : `sendTo(to, text)` qui envoie depuis l'utilisateur actuel. ## Aller dans les partielles sans contexte Que se passerait t-il si nous voulions corriger certains arguments, mais pas le contexte `this` ? Par exemple, pour une méthode objet. La fonction `bind` native ne permet pas ça. Nous ne pouvons pas juste omettre le contexte et aller directement aux arguments. Heureusement, une fonction `partial` pour lier seulement les arguments peut être facilement implémentée. Comme ça : ```js run *!* function partial(func, ...argsBound) { return function(...args) { // (*) return func.call(this, ...argsBound, ...args); } } */!* // Utilisation : let user = { firstName: "John", say(time, phrase) { alert(`[${time}] ${this.firstName}: ${phrase}!`); } }; // Ajoute une méthode partielle avec time corrigé user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Quelque chose du genre : // [10:00] John: Hello! ``` Le résultat de l'appel `partial(func[, arg1, arg2...])` est une enveloppe `(*)` qui appelle `func` avec : - Le même `this` qu'il récupère (pour `user.sayNow` l'appel est `user`) - Alors il donne `...argsBound` -- les arguments provenant de l'appel de `partial` (`"10:00"`) - Alors il donne `...args` -- les arguments donnés à l'enveloppe (`"Hello"`) Alors, c'est simple à faire avec la spread syntaxe, pas vrai ? Aussi il y a une implémentation de [_.partial](https://lodash.com/docs#partial) prête à l'emploi dans les librairies lodash. ## Résumé La méthode `func.bind(context, ...args)` retourne une "variante liée" de la fonction `func` qui corrige le contexte de `this` et des premiers arguments s'ils sont donnés. Nous appliquons généralement `bind` pour corriger `this` pour une méthode objet, comme ça nous pouvons la passer ailleurs. Par exemple, à `setTimeout`. Quand nous corrigeons certains arguments d'une fonction existante, la fonction (moins universelle) en résultant est dite *partiellement appliquée* ou *partielle*. Les fonctions partielles sont pratiques quand nous ne voulons pas répéter le même argument encore et encore. Comme si nous avions une fonction `send(from, to)`, et que `from` devait être toujours le même pour notre tâche, nous pourrions récupérer une partielle et continuer. ================================================ FILE: 1-js/06-advanced-functions/10-bind/head.html ================================================ ================================================ FILE: 1-js/06-advanced-functions/12-arrow-functions/article.md ================================================ # Les fonctions fléchées revisitées Revisitons les fonctions fléchées. Les fonctions fléchées ne sont pas simplement un "raccourci" pour écrire de petites choses. Elles ont des fonctionnalités très spécifiques et utiles. JavaScript est plein de situations dans lesquelles nous devons écrire une petite fonction exécutée ailleurs. Par exemple : - `arr.forEach(func)` -- `func` est exécutée par `forEach` pour chaque élément du tableau. - `setTimeout(func)` -- `func` est exécuté par le planificateur intégré. - ... plus à suivre. C'est dans l'esprit même de JavaScript de créer une fonction et de la transmettre quelque part. Et dans de telles fonctions, nous ne voulons généralement pas quitter le contexte actuel. C'est là que les fonctions fléchées sont utiles. ## Les fonctions fléchées n'ont pas de "this" Comme nous nous en souvenons du chapitre , les fonctions fléchées n'ont pas de `this`. Si `this` est utilisé, il est pris de l'extérieur. Par exemple, nous pouvons l'utiliser pour itérer à l'intérieur d'une méthode d'objet : ```js run let group = { title: "Our Group", students: ["John", "Pete", "Alice"], showList() { *!* this.students.forEach( student => alert(this.title + ': ' + student) ); */!* } }; group.showList(); ``` Ici, dans `forEach`, la fonction fléchée est utilisée, donc `this.title` est exactement la même chose que dans la méthode externe `showList`. C'est-à-dire `group.title`. Si nous utilisions une fonction "régulière", il y aurait une erreur : ```js run let group = { title: "Our Group", students: ["John", "Pete", "Alice"], showList() { *!* this.students.forEach(function(student) { // Error: Cannot read property 'title' of undefined alert(this.title + ': ' + student); }); */!* } }; group.showList(); ``` L'erreur se produit parce que `forEach` exécute des fonctions avec `this = undefined` par défaut, ce qui entraîne une tentative d'accès à `undefined.title`. Cela n’affecte pas les fonctions fléchées, car elles n’ont tout simplement pas de `this`. ```warn header="Les fonctions fléchées ne peuvent pas fonctionner avec `new`" Ne pas avoir de `this` signifie naturellement une autre limitation : les fonctions fléchées ne peuvent pas être utilisées en tant que constructeurs. Elles ne peuvent pas être appelées avec `new`. ``` ```smart header="Les fonctions fléchées VS bind" Il y a une différence subtile entre une fonction fléchée `=>` et une fonction régulière appelée avec `.bind(this)` : - `.bind(this)` crée une "version liée" de la fonction. - La flèche `=>` ne crée aucune liaison. La fonction n'a tout simplement pas de `this`. La recherche de `this` est faite exactement de la même manière qu’une recherche de variable normale : dans l’environnement lexical externe. ``` ## Les fonctions fléchées n'ont pas "d'arguments" Les fonctions fléchées n'ont pas non plus de variable `arguments`. C'est très bien pour les décorateurs, quand nous devons transférer un appel avec les `arguments` et `this` actuels. Par exemple, `defer(f, ms)` obtient une fonction et retourne un wrapper qui retarde l'appel de `ms` millisecondes : ```js run function defer(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(who) { alert('Hello, ' + who); } let sayHiDeferred = defer(sayHi, 2000); sayHiDeferred("John"); // Hello, John après 2 secondes ``` La même chose sans une fonction fléchée ressemblerait à ceci : ```js function defer(f, ms) { return function(...args) { let ctx = this; setTimeout(function() { return f.apply(ctx, args); }, ms); }; } ``` Ici, nous avons dû créer des variables additionnelles `args` et `ctx` afin que la fonction à l'intérieur de `setTimeout` puisse les prendre. ## Résumé Les fonctions fléchées : - N'ont pas de `this` - N'ont pas d'`arguments` - Ne peuvent pas être appelées avec `new` - Elles n'ont pas non plus de `super`, mais nous ne l'avons pas encore étudié. Nous le ferons dans le chapitre . C'est parce qu'elles sont destinées à de courts morceaux de code qui n'ont pas leur propre "contexte", mais qui fonctionnent dans le contexte actuel. Et elles brillent vraiment dans ce cas d'utilisation. ================================================ FILE: 1-js/06-advanced-functions/index.md ================================================ # Travail avancé avec les fonctions ================================================ FILE: 1-js/07-object-properties/01-property-descriptors/article.md ================================================ # Attributs et descripteurs de propriétés Comme nous le savons, les objets peuvent stocker des propriétés. Jusqu'à présent, une propriété était pour nous une simple paire "clé-valeur". Mais une propriété d'objet est en réalité une chose plus flexible et plus puissante. Dans ce chapitre, nous étudierons des options de configuration supplémentaires et, dans le prochain, nous verrons comment les transformer de manière invisible en fonctions de accesseur / mutateur. ## Attributs de propriétés Les propriétés des objets, outre que **`valeur`**, ont trois attributs spéciaux (appelés drapeaux, ou "flags" en anglais): - **`writable`** -- si `true`, la valeur peut être changée, sinon c'est en lecture seule. - **`enumerable`** -- si `true`, alors listé dans les boucles, sinon non listé. - **`configurable`** -- si `true`, la propriété peut être supprimée et ces attributs peuvent être modifiés, sinon non. Nous ne les avons pas encore vues, car généralement elles ne se présentent pas. Lorsque nous créons une propriété "de la manière habituelle", ils sont tous `true`. Mais nous pouvons aussi les changer à tout moment. Voyons d’abord comment obtenir ces "flags". La methode [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/getOwnPropertyDescriptor) permet d'interroger les informations *complètes* à propos d'une propriété. La syntaxe est la suivante : ```js let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); ``` `obj` : L'objet à partir duquel obtenir des informations. `propertyName` : Le nom de la propriété. La valeur renvoyée est un objet dit "descripteur de propriété" : il contient la valeur et tous les descripteurs. Par exemple : ```js run let user = { name: "John" }; let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* property descriptor: { "value": "John", "writable": true, "enumerable": true, "configurable": true } */ ``` Pour changer les attributs, on peut utiliser [Object.defineProperty](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/defineProperty). La syntaxe est la suivante : ```js Object.defineProperty(obj, propertyName, descriptor) ``` `obj`, `propertyName` : L'objet et sa propriété pour appliquer le descripteur. `descriptor` : Descripteur de propriété d'objet à appliquer. Si la propriété existe, `defineProperty` met à jour ses attributs. Sinon, il crée la propriété avec la valeur et les descripteurs donnés. Dans ce cas, si aucun drapeau n'est fourni, il est supposé `false`. Par exemple, ici, une propriété `name` est créée avec tous les attributs falsy : ```js run let user = {}; *!* Object.defineProperty(user, "name", { value: "John" }); */!* let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "value": "John", *!* "writable": false, "enumerable": false, "configurable": false */!* } */ ``` Comparez-le avec `user.name` "normalement créé" ci-dessus : maintenant tous les attributs sont falsy. Si ce n'est pas ce que nous voulons, nous ferions mieux de leur attribuer la valeur `true` dans `descriptor`. Voyons maintenant les effets des attributs par exemple. ## Lecture seule Rendons `user.name` en lecture seule (ne peut pas être réaffecté) en modifiant l'indicateur `writeable` : ```js run let user = { name: "John" }; Object.defineProperty(user, "name", { *!* writable: false */!* }); *!* user.name = "Pete"; // Error: Cannot assign to read only property 'name' */!* ``` Maintenant, personne ne peut changer le nom de notre utilisateur, à moins qu’ils appliquent leur propre `defineProperty` pour remplacer le nôtre. ```smart header="Les erreurs apparaissent uniquement en mode strict" En mode non strict, aucune erreur ne se produit lors de l'écriture dans des propriétés non inscriptibles et autres. Mais l'opération ne réussira toujours pas. Les actions violant l'indicateur sont simplement ignorées en silence dans les non-stricts. ``` Voici le même exemple, mais la propriété est créée à partir de zéro : ```js run let user = { }; Object.defineProperty(user, "name", { *!* value: "John", // pour les nouvelles propriétés, nous devons lister explicitement ce qui est vrai enumerable: true, configurable: true */!* }); alert(user.name); // John user.name = "Pete"; // Error ``` ## Non énumérable Ajoutons maintenant un `toString` personnalisé à `user`. Normalement, un `toString` intégré pour les objets n'est pas énumérable, il n'apparaît pas dans `for..in`. Mais si nous ajoutons notre propre `toString`, alors, par défaut, il apparaît dans `for..in`, comme ceci : ```js run let user = { name: "John", toString() { return this.name; } }; // Par défaut, nos deux propriétés sont répertoriées : for (let key in user) alert(key); // name, toString ``` Si nous n'aimons pas cela, alors nous pouvons définir `enumerable: false`. Ensuite, il n'apparaîtra pas dans la boucle `for..in`, comme dans la boucle intégrée : ```js run let user = { name: "John", toString() { return this.name; } }; Object.defineProperty(user, "toString", { *!* enumerable: false */!* }); *!* // Maintenant notre toString disparaît : */!* for (let key in user) alert(key); // name ``` Les propriétés non énumérables sont également exclues de `Object.keys` : ```js alert(Object.keys(user)); // name ``` ## Non configurable Le descripteur non configurable (`configurable: false`) est parfois prédéfini pour les objets et propriétés intégrés. Une propriété non configurable ne peut pas être supprimée, ses attributs ne peuvent pas être modifiés. Par exemple, `Math.PI` est en lecture seule, non énumérable et non configurable : ```js run let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "value": 3.141592653589793, "writable": false, "enumerable": false, "configurable": false } */ ``` Ainsi, un programmeur est incapable de changer la valeur de `Math.PI` ou de le remplacer. ```js run Math.PI = 3; // Error, because it has writable: false // supprimer Math.PI ne fonctionnera pas non plus ``` Nous ne pouvons pas non plus changer `Math.PI` pour qu'il soit à nouveau `writable` (éditable) : ```js run // Error, parce que configurable: false Object.defineProperty(Math, "PI", { writable: true }); ``` Il n'y a absolument rien que nous puissions faire avec `Math.PI`. Rendre une propriété non configurable est une voie à sens unique. Nous ne pouvons pas le modifier avec `defineProperty`. **Veuillez noter : `configurable: false` empêche les changements d'indicateurs de propriété et sa suppression, tout en permettant de changer sa valeur.** Ici, `user.name` n'est pas configurable, mais nous pouvons toujours le changer (car il est accessible en écriture) : ```js run let user = { name: "John" }; Object.defineProperty(user, "name", { configurable: false }); user.name = "Pete"; // ça fonctionne delete user.name; // Error ``` Ici, nous faisons de `user.name` une constante "scellée à jamais", tout comme `Math.PI` : ```js run let user = { name: "John" }; Object.defineProperty(user, "name", { writable: false, configurable: false }); // ne pourra pas changer user.name ou ses indicateurs // tout cela ne fonctionnera pas : user.name = "Pete"; delete user.name; Object.defineProperty(user, "name", { value: "Pete" }); ``` ```smart header="Le seul changement d'attribut possible : writable true -> false" Il existe une exception mineure concernant la modification des indicateurs. Nous pouvons changer `writable: true` en `false` pour une propriété non configurable, empêchant ainsi la modification de sa valeur (pour ajouter une autre couche de protection). ``` ## Object.defineProperties Il y a une méthode [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/defineProperties) qui permet de définir plusieurs propriétés à la fois. La syntaxe est la suivante : ```js Object.defineProperties(obj, { prop1: descriptor1, prop2: descriptor2 // ... }); ``` Par exemple : ```js Object.defineProperties(user, { name: { value: "John", writable: false }, surname: { value: "Smith", writable: false }, // ... }); ``` Nous pouvons donc définir plusieurs propriétés à la fois. ## Object.getOwnPropertyDescriptors Pour obtenir tous les descripteurs de propriété à la fois, nous pouvons utiliser la méthode [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/getOwnPropertyDescriptors). Avec `Object.defineProperties`, elle peut être utilisé comme moyen de cloner un objet en tenant compte des attributs : ```js let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj)); ``` Normalement, lorsque nous clonons un objet, nous utilisons une affectation pour copier les propriétés, comme ceci : ```js for (let key in user) { clone[key] = user[key] } ``` ...Mais cela ne copie pas les attributs. Donc, si nous voulons un "meilleur" clone, alors `Object.defineProperties` est préféré. Une autre différence est que `for..in` ignore les propriétés symboliques, mais que `Object.getOwnPropertyDescriptors` renvoie *tous* les descripteurs de propriété, y compris ceux symboliques et non énumérables. ## Sceller un objet globalement Les descripteurs de propriété fonctionnent au niveau des propriétés individuelles. Il existe également des méthodes qui limitent l'accès à l'objet *entier* : [Object.preventExtensions(obj)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/preventExtensions) : Interdit l'ajout de nouvelles propriétés à l'objet. [Object.seal(obj)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/seal) : Interdit l'ajout/la suppression de propriétés. Définit `configurable: false` pour toutes les propriétés existantes. [Object.freeze(obj)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/freeze) : Interdit l'ajout/la suppression/la modification de propriétés. Définit `configurable: false, writeable: false` pour toutes les propriétés existantes. Et aussi il y a des tests pour eux : [Object.isExtensible(obj)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/isExtensible) : Retourne `false` si l'ajout de propriétés est interdit, sinon `true`. [Object.isSealed(obj)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/isSealed) : Renvoie `true` si l'ajout/la suppression de propriétés est interdite et que toutes les propriétés existantes ont `configurable: false`. [Object.isFrozen(obj)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/isFrozen) : Retourne `true` si l'ajout/la suppression/la modification de propriétés est interdite et si toutes les propriétés actuelles sont `configurable: false, writable: false`. Ces méthodes sont rarement utilisées dans la pratique. ================================================ FILE: 1-js/07-object-properties/02-property-accessors/article.md ================================================ # Getters et Setters de propriété Il y a deux sortes de proriétés d'objet. Le premier type est *les propriétés de données*. Nous savons déjà comment travaillez avec. Toutes les propriétés que nous avons utilisés jusqu'à maintenant étaient des propriétés de données. Le second type de propriété est quelque chose de nouveau. C'est un accesseur de propriété. Ce sont essentiellement des fonctions qui exécutent une récupération ou une déclaration de valeur, mais qui ressemblent à une propriété normale pour le code extérieur. ## Getters et Setters Les accesseurs de propriétés sont représentés par des méthodes "getter" et "setter". Dans un objet littéral elles se demarquent par `get` et `set` : ```js let obj = { *!*get propName()*/!* { // Getter, le code va récupérer obj.propName }, *!*set propName(value)*/!* { // Setter, le code va définir obj.propName = value } }; ``` Le getter fonctionne quand `obj.propName` est lu, le setter -- quand il s'agit d'une assignation. Par exemple, nous avons un objet `user` avec `name` et `surname` : ```js let user = { name: "John", surname: "Smith" }; ``` Maintenant nous voulons ajouter une propriété `fullName`, qui devrait être `"John Smith"`. Bien sûr, nous ne voulons pas copier-coller l'information existante, donc nous pouvons implémenter un accesseur : ```js run let user = { name: "John", surname: "Smith", *!* get fullName() { return `${this.name} ${this.surname}`; } */!* }; *!* alert(user.fullName); // John Smith */!* ``` De l'extérieur, un accesseur de propriété ressemble à une propriété normale. C'est l'idée d'un accesseur. Nous n'*appellons* pas `user.fullName` comme une fonction, nous la *lisons* normalement : le getter agit en arrière plan. Pour l'instant, `fullName` n'a qu'un getter. Si nous essayons d'assigner `user.fullName=`, il y aura une erreur : ```js run let user = { get fullName() { return `...`; } }; *!* user.fullName = "Test"; // Erreur (la propriété n'a qu'un getter) */!* ``` Corrigeons cela en ajoutant un setter pour `user.fullName` : ```js run let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, *!* set fullName(value) { [this.name, this.surname] = value.split(" "); } */!* }; // Le setter est exécuté avec la valeur donnée. user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper ``` Comme résultat, nous avons une propriété "virtuelle" `fullName`. Elle est lisible et ecrivable. ## Descripteurs d'accesseur Les descripteurs d'accesseur de propriété sont différents de ceux pour les propriété de données. Pour les accesseurs de propriétés, il n'y a pas de `value` ou `writable`, à la place il y a les fonctions `get` et `set`. Un descripteur d'accesseur peut avoir : - **`get`** -- une fonction sans arguments, pour la lecture de propriété, - **`set`** -- une fonction avec un argument, qui fonctionne lorsque la propriété change de valeur, - **`enumerable`** -- identique aux propriétés de données - **`configurable`** -- identique aux propriétés de données Par exemple, pour créer un accesseur `fullName` avec `defineProperty`, on peut passer un descripteur avec `get` et `set` : ```js run let user = { name: "John", surname: "Smith" }; *!* Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { [this.name, this.surname] = value.split(" "); } */!* }); alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ``` Veuillez notez qu'une propriété peut être soit un accesseur (qui a les méthodes `get/set`) ou une propriété de données (qui a `value`), pas les deux. Si nous essayons de fournir les deux `get` and `value` dans le même descripteur, il y aura une erreur : ```js run *!* // Erreur : Descripteur de propriété invalide. */!* Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2 }); ``` ## Des getters/setters plus intelligents Les Getters/setters peuvent être utilisés comme des enveloppes autour des "réelles" valeurs de propriété pour gagner plus de contrôles sur leurs opérations. Par exemple, si nous voulions interdire les noms trop court pour `user`, nous pourrions avoir un setter `name` et garder la valeur dans une propriété séparée `_name` : ```js run let user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; } }; user.name = "Pete"; alert(user.name); // Pete user.name = ""; // Le nom est trop court... ``` Donc, le nom est stocké dans la propriété `_name`, et l'accés est fait par le getter et le setter. Techniquement, le code extérieur est capable d'accéder directement à la propriété en utilisant `user._name`. Mais il y a une convention très connue, selon laquelle les propriétés commençant par un underscore `"_"` sont internes et ne devraient pas être touchées depuis l'extérieur des objets. ## Utilisation pour la compatibilité Un des avantages de l'utilisation des accesseurs et qu'ils permettent de prendre le contrôle sur un propriété de données "normale" à tout moment, en la remplaçant par un getter et un setter et modifiant son comportement. Imaginons que nous commencions des objets utilisateur en utilisant des propriétés de données `name` et `age` : ```js function User(name, age) { this.name = name; this.age = age; } let john = new User("John", 25); alert( john.age ); // 25 ``` ...Mais tôt ou tard, les choses pourraient changer. Au lieu d'`age` on pourrait decider de stocker `birthday`, parce que c'est plus précis et plus pratique : ```js function User(name, birthday) { this.name = name; this.birthday = birthday; } let john = new User("John", new Date(1992, 6, 1)); ``` Maintenant que fait-on avec l'ancien code qui utilise toujours la propriété `age` ? On peut essayer de trouver tous les endroits où on utilisent `age` et les modifier, mais ça prend du temps et ça peut être compliqué à faire si le code est utilisé par plusieurs personnes. En plus, `age` est une bonne chose à avoir dans `user`, n'est ce pas ? Gardons-le. Ajoutons un getter pour `age` et résolvons le problème : ```js run no-beautify function User(name, birthday) { this.name = name; this.birthday = birthday; *!* // age est calculé à partir de la date actuelle et de birthday Object.defineProperty(this, "age", { get() { let todayYear = new Date().getFullYear(); return todayYear - this.birthday.getFullYear(); } }); */!* } let john = new User("John", new Date(1992, 6, 1)); alert( john.birthday ); // birthday est disponible alert( john.age ); // ...Ainsi que l'age ``` Maintenant l'ancien code fonctionne toujours et nous avons une propriété additionnelle. ================================================ FILE: 1-js/07-object-properties/index.md ================================================ # Configuration des propriétés d'objet Dans cette section, nous revenons sur les objets et étudions leurs propriétés encore plus en profondeur. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md ================================================ 1. `true`, tiré de `rabbit`. 2. `null`, tiré de `animal`. 3. `undefined`, il n'y a plus une telle propriété. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md ================================================ importance: 5 --- # Travailler avec prototype Voici le code qui crée une paire d'objets, puis les modifie. Quelles sont les valeurs affichées dans le processus ? ```js let animal = { jumps: null }; let rabbit = { __proto__: animal, jumps: true }; alert( rabbit.jumps ); // ? (1) delete rabbit.jumps; alert( rabbit.jumps ); // ? (2) delete animal.jumps; alert( rabbit.jumps ); // ? (3) ``` Il devrait y avoir 3 réponses. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md ================================================ 1. Ajoutons `__proto__`: ```js run let head = { glasses: 1 }; let table = { pen: 3, __proto__: head }; let bed = { sheet: 1, pillow: 2, __proto__: table }; let pockets = { money: 2000, __proto__: bed }; alert( pockets.pen ); // 3 alert( bed.glasses ); // 1 alert( table.money ); // undefined ``` 2. Dans les moteurs modernes, en termes de performances, il n’ya pas de différence selon que l’on prend une propriété d’un objet ou de son prototype. Ils se souviennent du lieu où la propriété a été trouvée et le réutilisent à la demande suivante. Par exemple, pour `pockets.glasses` ils se souviennent où ils ont trouvé `glasses` (dans `head`), et la prochaine fois rechercheront là. Ils sont également assez intelligents pour mettre à jour les caches internes en cas de changement, de sorte que l'optimisation est sécurisée. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md ================================================ importance: 5 --- # Algorithme de recherche La tâche comporte deux parties. Étant donné les objets suivants : ```js let head = { glasses: 1 }; let table = { pen: 3 }; let bed = { sheet: 1, pillow: 2 }; let pockets = { money: 2000 }; ``` 1. Utilisez `__proto__` pour attribuer des prototypes de manière à ce que toute recherche de propriété suive le chemin:`pockets` -> `bed` -> `table` -> `head`. Par exemple, `pocket.pen` devrait être `3` (trouvé dans `table`), et `bed.glasses` devrait être `1` (trouvé dans `head`). 2. Répondez à la question: est-il plus rapide d’obtenir `glasses` en tant que `pockets.glasses` ou `head.glasses`? Analyse si nécessaire. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md ================================================ **La réponse: `rabbit`.** C'est parce que `this` est un objet avant le point, donc `rabbit.eat()` modifie `rabbit`. La recherche et l'exécution de propriétés sont deux choses différentes. La méthode `rabbit.eat` est d'abord trouvée dans le prototype, puis exécutée avec `this=rabbit`. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md ================================================ importance: 5 --- # Où est-ce écrit ? Nous avons `rabbit` héritant de `animal`. Si nous appelons `rabbit.eat()`, quel objet reçoit la propriété `full`: `animal` ou `rabbit`? ```js let animal = { eat() { this.full = true; } }; let rabbit = { __proto__: animal }; rabbit.eat(); ``` ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md ================================================ Examinons attentivement ce qui se passe dans l'appel `speedy.eat("apple")`. 1. La méthode `speedy.eat` se trouve dans le prototype (`=hamster`), puis exécutée avec `this=speedy` (l'objet avant le point). 2. Ensuite, `this.stomach.push()` doit trouver la propriété `stomach` et appeler `push` dessus. Il cherche `stomach` dans `this` (`=speedy`), mais rien n'est trouvé. 3. Ensuite, il suit la chaîne de prototypes et trouve `stomach` dans `hamster`. 4. Ensuite, il appelle `push` dessus, en ajoutant la nourriture dans *stomach du prototype*. Tous les hamsters partagent donc un seul estomac! Tant pour `lazy.stomach.push(...)` et `speedy.stomach.push()`, la propriété `stomach` se trouve dans le prototype (comme il est pas dans l'objet lui-même), alors les nouvelles données sont poussé dedans. Veuillez noter qu'une telle chose ne se produit pas dans le cas d'une simple affectation `this.stomach=`: ```js run let hamster = { stomach: [], eat(food) { *!* // assigner à this.stomach au lieu de this.stomach.push this.stomach = [food]; */!* } }; let speedy = { __proto__: hamster }; let lazy = { __proto__: hamster }; // Speedy a trouvé la nourriture speedy.eat("apple"); alert( speedy.stomach ); // apple // L'estomac de Lazy est vide alert( lazy.stomach ); // ``` Maintenant, tout fonctionne bien, car `this.stomach=` n'effectue pas de recherche de `stomach`. La valeur est écrite directement dans l'objet `this`. Nous pouvons également éviter le problème en nous assurant que chaque hamster a son propre stomach : ```js run let hamster = { stomach: [], eat(food) { this.stomach.push(food); } }; let speedy = { __proto__: hamster, *!* stomach: [] */!* }; let lazy = { __proto__: hamster, *!* stomach: [] */!* }; // Speedy a trouvé la nourriture speedy.eat("apple"); alert( speedy.stomach ); // apple // L'estomac de Lazy est vide alert( lazy.stomach ); // ``` En tant que solution commune, toutes les propriétés qui décrivent l'état d'un objet particulier, comme `stomach` ci-dessus, devraient être écrits dans cet objet. Cela empêche de tels problèmes. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md ================================================ importance: 5 --- # Pourquoi deux hamsters sont rassasiés ? Nous avons deux hamsters: `speedy` et `lazy` héritant de l'objet général `hamster`. Lorsque nous nourrissons l'un d'eux, l'autre est également rassasié. Pourquoi ? Comment y remédier ? ```js run let hamster = { stomach: [], eat(food) { this.stomach.push(food); } }; let speedy = { __proto__: hamster }; let lazy = { __proto__: hamster }; // Celui-ci a trouvé la nourriture speedy.eat("apple"); alert( speedy.stomach ); // apple // Celui-ci l'a aussi, pourquoi ? Merci de corriger cela. alert( lazy.stomach ); // apple ``` ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/article.md ================================================ # Héritage prototypal En programmation, nous voulons souvent prendre quelque chose et l’étendre. Par exemple, nous avons un objet `user` avec ses propriétés et méthodes et souhaitons en faire des variantes `admin` et `guest` légèrement modifiées. Nous aimerions réutiliser ce que nous avons dans `user`, et non pas copier/réimplémenter ses méthodes, mais simplement créer un nouvel objet par-dessus. *L'héritage prototypal* est une fonctionnalité de langage qui aide à cela. ## [[Prototype]] En JavaScript, les objets ont une propriété cachée spéciale `[[Prototype]]` (comme indiqué dans la spécification), qui est soit `null` ou fait référence à un autre objet. Cet objet s'appelle "un prototype" : ![prototype](object-prototype-empty.svg) Lorsque nous lisons une propriété depuis `object`, et qu'elle est manquante, JavaScript la prend automatiquement du prototype. En programmation, une telle chose est appelée "héritage prototypal". Et bientôt, nous étudierons de nombreux exemples d'un tel héritage, ainsi que des fonctionnalités de langage plus cool qui en découlent. La propriété `[[Prototype]]` est interne et cachée, mais il y a plusieurs façons de la définir. L'un d'eux est d'utiliser le nom spécial `__proto__`, comme ceci : ```js run let animal = { eats: true }; let rabbit = { jumps: true }; *!* rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal */!* ``` Si nous recherchons une propriété dans `rabbit`, et qu'elle en manque, JavaScript la prend automatiquement à partir de `animal`. Par exemple : ```js let animal = { eats: true }; let rabbit = { jumps: true }; *!* rabbit.__proto__ = animal; // (*) */!* // nous pouvons maintenant trouver les deux propriétés dans rabbit : *!* alert( rabbit.eats ); // true (**) */!* alert( rabbit.jumps ); // true ``` Ici, la ligne `(*)` définit `animal` comme le prototype de `lapin`. Ensuite, lorsque `alert` essaie de lire la propriété `rabbit.eats` `(**)`, ce n'est pas dans `rabbit`, donc JavaScript suit la référence `[[Prototype]]` et la trouve dans `animal` (regarde de bas en haut) : ![](proto-animal-rabbit.svg) Ici, nous pouvons dire que "`animal` est le prototype de `rabbit`" ou que "`rabit` hérite de manière prototypal de `animal`". Donc, si `animal` a beaucoup de propriétés et de méthodes utiles, elles deviennent automatiquement disponibles dans `rabbit`. De telles propriétés sont appelées "héritées". Si nous avons une méthode dans `animal`, elle peut être appelée sur `rabbit` : ```js run let animal = { eats: true, *!* walk() { alert("Animal walk"); } */!* }; let rabbit = { jumps: true, __proto__: animal }; // walk est prise à partir du prototype *!* rabbit.walk(); // Animal walk */!* ``` La méthode est automatiquement prise à partir du prototype, comme ceci : ![](proto-animal-rabbit-walk.svg) La chaîne de prototypes peut être plus longue : ```js run let animal = { eats: true, walk() { alert("Animal walk"); } }; let rabbit = { jumps: true, *!* __proto__: animal */!* }; let longEar = { earLength: 10, *!* __proto__: rabbit */!* }; // walk est prise à partir de la chaîne de prototype longEar.walk(); // Animal walk alert(longEar.jumps); // true (de rabbit) ``` ![](proto-animal-rabbit-chain.svg) Maintenant, si nous lisons quelque chose de `longEar`, et qu'il est manquant, JavaScript le recherchera dans `rabbit`, puis dans `animal`. Il n'y a que deux limitations : 1. Les références ne peuvent pas tourner en rond. JavaScript va générer une erreur si nous essayons d'assigner `__proto__` dans un cercle. 2. La valeur de `__proto__` peut être un objet ou `null`. Les autres types sont ignorés. Cela peut aussi être évident, mais quand même : il ne peut y avoir qu'un seul `[[Prototype]]`. Un objet ne peut pas hériter de deux autres. ```smart header="`__proto__` est un getter/setter historique pour [[Prototype]]`" C'est une erreur courante des développeurs novices de ne pas connaître la différence entre les deux. Veuillez noter que `__proto__` n'est *pas la même* que la propriété interne `[[Prototype]]`. C'est un getter/setter pour `[[Prototype]]`. Plus tard, nous verrons des situations où cela compte, pour l'instant gardons cela à l'esprit, alors que nous construisons notre compréhension du langage JavaScript. La propriété `__proto__` est un peu obsolète. Elle existe pour des raisons historiques, le JavaScript moderne suggère que nous devrions utiliser les fonctions `Object.getPrototypeOf/Object.setPrototypeOf` à la place pour obtenir/définir le prototype. Nous aborderons également ces fonctions plus tard. Selon la spécification, `__proto__` ne doit être pris en charge que par les navigateurs. En fait cependant, tous les environnements, y compris côté serveur, prennent en charge `__proto__`, donc nous sommes assez sûrs de l'utiliser. Comme la notation `__proto__` est un peu plus évidente, nous l'utilisons dans les exemples. ``` ## L'écriture n'utilise pas de prototype Le prototype n'est utilisé que pour la lecture des propriétés. Les opérations d'écriture/suppression fonctionnent directement avec l'objet. Dans l'exemple ci-dessous, nous affectons sa propre méthode `walk` à `rabbit` : ```js run let animal = { eats: true, walk() { /* cette méthode ne sera pas utilisée par rabbit */ } }; let rabbit = { __proto__: animal }; *!* rabbit.walk = function() { alert("Rabbit! Bounce-bounce!"); }; */!* rabbit.walk(); // Rabbit! Bounce-bounce! ``` A partir de maintenant, l'appel `rabbit.walk()` trouve la méthode immédiatement dans l'objet et l'exécute, sans utiliser le prototype : ![](proto-animal-rabbit-walk-2.svg) Les propriétés d'accesseur constituent une exception, car l'affectation est gérée par une fonction mutateur. Donc, écrire dans une telle propriété revient en fait à appeler une fonction. Pour cette raison, `admin.fullName` fonctionne correctement dans le code ci-dessous : ```js run let user = { name: "John", surname: "Smith", set fullName(value) { [this.name, this.surname] = value.split(" "); }, get fullName() { return `${this.name} ${this.surname}`; } }; let admin = { __proto__: user, isAdmin: true }; alert(admin.fullName); // John Smith (*) // le mutateur se déclanche ! admin.fullName = "Alice Cooper"; // (**) alert(admin.fullName); // Alice Cooper, l'état de admin est modifié alert(user.fullName); // John Smith, l'état de user est protégé ``` Ici dans la ligne `(*)` la propriété `admin.fullName` a un accesseur dans le prototype `user`, il est donc appelé. Et dans la ligne `(**)` la propriété a un mutateur dans le prototype, il est donc appelé. ## La valeur de "this" Une question intéressante peut se poser dans l'exemple ci-dessus : quelle est la valeur de `this` dans `set fullName(value)` ? Où sont écrites les propriétés `this.name` et `this.surname` : dans `user` ou `admin` ? La réponse est simple : `this` n'est pas du tout affecté par les prototypes. **Peu importe où la méthode est trouvée : dans un objet ou son prototype. Dans un appel de méthode, `this` est toujours l'objet avant le point.** Ainsi, l'appel du groupe `admin.fullName=` utilise `admin` comme `this`, pas `user`. C'est en fait une chose très importante, car nous pouvons avoir un gros objet avec de nombreuses méthodes et en hériter. Ensuite, les objets hérités peuvent exécuter ces méthodes héritées, ils ne modifieront que leurs propres états, pas l'état du gros objet. Par exemple, ici `animal` représente un "stockage de méthode" et `rabbit` en fait usage. L'appel `rabbit.sleep()` définit `this.isSleeping` sur l'objet `rabbit` : ```js run // animal a des méthodes let animal = { walk() { if (!this.isSleeping) { alert(`I walk`); } }, sleep() { this.isSleeping = true; } }; let rabbit = { name: "White Rabbit", __proto__: animal }; // modifie rabbit.isSleeping rabbit.sleep(); alert(rabbit.isSleeping); // true alert(animal.isSleeping); // undefined (aucune propriété de ce type dans le prototype) ``` L'image résultante : ![](proto-animal-rabbit-walk-3.svg) Si nous avions d'autres objets tels que `bird`, `snake` etc. héritant de `animal`, ils auraient également accès aux méthodes de `animal`. Mais `this` dans chaque appel de méthode serait l'objet correspondant, évalué au moment de l'appel (avant le point), et non `animal`. Ainsi, lorsque nous écrivons des données dans `this`, elles sont stockées dans ces objets. En conséquence, les méthodes sont partagées, mais pas l'état d'objet. ## La boucle for..in La boucle `for..in` itère aussi sur les propriétés héritées. Par exemple : ```js run let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; *!* // Object.keys ne renvoie que ses propres clés alert(Object.keys(rabbit)); // jumps */!* *!* // for..in boucle sur les clés propres et héritées for(let prop in rabbit) alert(prop); // jumps, puis eats */!* ``` Si ce n'est pas ce que nous voulons et que nous aimerions exclure les propriétés héritées, il existe une méthode intégrée [obj.hasOwnProperty(key)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/hasOwnProperty) : elle renvoie `true` si `obj` a sa propre propriété (non héritée) nommée `key`. Nous pouvons donc filtrer les propriétés héritées (ou faire autre chose avec elles) : ```js run let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; for(let prop in rabbit) { let isOwn = rabbit.hasOwnProperty(prop); if (isOwn) { alert(`Our: ${prop}`); // Our : jumps } else { alert(`Inherited: ${prop}`); // Inherited: eats } } ``` Nous avons ici la chaîne d'héritage suivante : `rabbit` hérite de `animal`, qui lui hérite de `Object.prototype` (car `animal` est un objet littéral `{...}`, donc c'est par défaut), puis `null` au-dessus : ![](rabbit-animal-object.svg) Remarque, il y a une chose amusante. D'où vient la méthode `rabbit.hasOwnProperty` ? Nous ne l'avons pas défini. En regardant la chaîne, nous pouvons voir que la méthode est fournie par `Object.prototype.hasOwnProperty`. En d'autres termes, c'est hérité. ...Mais pourquoi `hasOwnProperty` n'apparaît pas dans la boucle `for..in`, comme `eats` et `jumps`, s'il répertorie toutes les propriétés héritées. La réponse est simple : ce n'est pas énumérable. Comme toutes les autres propriétés de `Object.prototype`, il possède l'attribut `enumerable: false`. C'est pourquoi ils ne sont pas répertoriés. Et `for..in` ne répertorie que les propriétés énumérables. C'est pourquoi elle et le reste des propriétés de `Object.prototype` ne sont pas listés. ```smart header="Presque toutes les autres méthodes d'obtention de clé/valeur ignorent les propriétés héritées" Presque toutes les autres méthodes d'obtention de clé/valeur, telles que `Object.keys`, `Object.values` et ainsi de suite ignorent les propriétés héritées. Elles ne fonctionnent que sur l'objet lui-même. Les propriétés du prototype ne sont *pas* prises en compte. ``` ## Résumé - En JavaScript, tous les objets ont une propriété masquée `[[Prototype]]` qui est soit un autre objet, soit `null`. - Nous pouvons utiliser `obj.__ proto__` pour y accéder (un accesseur/mutateur historique, il existe d'autres moyens, à couvrir bientôt). - L'objet référencé par `[[Prototype]]` s'appelle un "prototype". - Si nous voulons lire une propriété de `obj` ou appeler une méthode, et que celle-ci n'existe pas, alors JavaScript essaye de la trouver dans le prototype. - Les opérations d'écriture/suppression agissent directement sur l'objet, elles n'utilisent pas le prototype (en supposant qu'il s'agisse d'une propriété de données, et non d'un setter). - Si nous appelons `obj.method()`, et que la `méthode` est extraite du prototype, `this` fait toujours référence à `obj`. Les méthodes fonctionnent donc toujours avec l'objet actuel, même si elles sont héritées. - La boucle `for..in` parcourt les propriétés propres et héritées. Toutes les autres méthodes d'obtention de clé / valeur ne fonctionnent que sur l'objet lui-même. ================================================ FILE: 1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md ================================================ Réponses : 1. `true`.      L'affectation à `Rabbit.prototype` configure `[[Prototype]]` pour les nouveaux objets, mais n'affecte pas les objets existants. 2. `false`.      Les objets sont assignés par référence. L'objet de `Rabbit.prototype` n'est pas dupliqué, mais un objet unique est référencé à la fois par `Rabbit.prototype` et par le `[[Prototype]]` de `rabbit`.      Ainsi, lorsque nous modifions son contenu par l’une des références, il est visible par l’autre. 3. `true`.      Toutes les opérations `delete` sont appliquées directement à l'objet. `delete rabbit.eats` tente ici de supprimer la propriété `eats` de `rabbit`, mais ne l’a pas. Donc l'opération n'aura aucun effet. 4. `undefined`.      La propriété `eats` est supprimée du prototype, elle n’existe plus. ================================================ FILE: 1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md ================================================ importance: 5 --- # Changement de "prototype" Dans le code ci-dessous, nous créons `new Rabbit`, puis essayons de modifier son prototype. Au début, nous avons ce code : ```js run function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); alert( rabbit.eats ); // true ``` 1. Nous avons ajouté une chaîne de caractères supplémentaire (surlignée), qu'affiche `alert` maintenant ? ```js function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); *!* Rabbit.prototype = {}; */!* alert( rabbit.eats ); // ? ``` 2. ...Et si le code est comme ça (une ligne remplacée) ? ```js function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); *!* Rabbit.prototype.eats = false; */!* alert( rabbit.eats ); // ? ``` 3. Et comme ceci (une ligne remplacée) ? ```js function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); *!* delete rabbit.eats; */!* alert( rabbit.eats ); // ? ``` 4. La dernière variante : ```js function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); *!* delete Rabbit.prototype.eats; */!* alert( rabbit.eats ); // ? ``` ================================================ FILE: 1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md ================================================ Nous pouvons utiliser cette approche si nous sommes sûrs que la propriété `"constructeur"` a la valeur correcte. Par exemple, si nous ne touchons pas le `"prototype"` par défaut, alors ce code fonctionne à coup sûr : ```js run function User(name) { this.name = name; } let user = new User('John'); let user2 = new user.constructor('Pete'); alert( user2.name ); // Pete (ça marche !) ``` Cela a fonctionné, car `User.prototype.constructor == User`. ..Mais si quelqu'un, pour ainsi dire, écrase `User.prototype` et oublie de recréer `constructor` pour faire référence à `User`, il échouera. Par exemple : ```js run function User(name) { this.name = name; } *!* User.prototype = {}; // (*) */!* let user = new User('John'); let user2 = new user.constructor('Pete'); alert( user2.name ); // undefined ``` Pourquoi `user2.name` est `undefined` ? Voici comment `new user.constructor('Pete')` fonctionne : 1. Tout d'abord, il cherche `constructor` dans `user`. Rien. 2. Ensuite, il suit la chaîne de prototypes. Le prototype de `user` est `User.prototype`, et il n'a pas non plus de `constructor` (parce que nous avons "oublié" de le régler correctement !). 3. En remontant la chaîne, `User.prototype` est un objet simple, son prototype est le `Object.prototype` intégré. 4. Enfin, pour le `Object.prototype` intégré, il existe un `Object.prototype.constructor == Object` intégré. Il est donc utilisé. En fin de compte, nous avons `let user2 = new Object('Pete')`. Ce n'est probablement pas ce que nous voulons. Nous aimerions créer un `new user`, pas un `new Object`. C'est le résultat du `constructor` manquant. (Juste au cas où vous seriez curieux, l'appel `new Object(...)` convertit son argument en un objet. C'est une chose théorique, en pratique personne n'appelle `new Object` avec une valeur, et généralement nous n’utilisons pas `new Object` pour créer des objets). ================================================ FILE: 1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md ================================================ importance: 5 --- # Créer un objet avec le même constructeur Imaginez nous avons un objet arbitraire `obj`, créé par une fonction constructeur - nous ne savons pas lequel, mais nous aimerions créer un nouvel objet à l'aide de celui-ci. Pouvons-nous le faire comme ça ? ```js let obj2 = new obj.constructor(); ``` Donne un exemple de fonction constructeur pour `obj` qui laisse ce code fonctionner correctement. Et un exemple qui fait que ça marche mal. ================================================ FILE: 1-js/08-prototypes/02-function-prototype/article.md ================================================ # F.prototype Rappelez-vous que de nouveaux objets peuvent être créés avec une fonction constructeur, comme `new F()`. Si `F.prototype` est un objet, alors l'opérateur `new` l'utilise pour définir `[[Prototype]]` pour le nouvel objet. ```smart header="Veuillez noter" JavaScript avait l'héritage prototypique depuis le début. C'était l'une des caractéristiques principales du langage. Mais dans le passé, il n'y avait pas d'accès direct. La seule chose qui fonctionnait de manière fiable est une propriété `"prototype"` de la fonction constructeur décrite dans ce chapitre. Donc, il y a beaucoup de scripts qui l'utilisent encore. ``` Veuillez noter que `F.prototype` signifie ici une propriété régulière nommée `"prototype"` sur `F`. Cela ressemble quelque peu au terme "prototype", mais nous entendons ici une propriété régulière portant ce nom. Voici l'exemple : ```js run let animal = { eats: true }; function Rabbit(name) { this.name = name; } *!* Rabbit.prototype = animal; */!* let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true ``` Définir `Rabbit.prototype=animal` énonce littéralement ce qui suit : "Lorsqu'un `new Rabbit` est créé, assigner son `[[Prototype]]` à `animal`". Voici l'image résultante : ![](proto-constructor-animal-rabbit.svg) Sur l'image, `"prototype"` est une flèche horizontale, ce qui signifie une propriété normale, et `[[Prototype]]` est vertical, ce qui signifie l'héritage de `rabbit` de `animal`. ```smart header="`F.prototype` utilisé uniquement pendant `new F`" La propriété `F.prototype` est utilisée uniquement lorsque `new F` est appelé, elle attribue `[[Prototype]]` du nouvel objet. Après cela, il n'y a plus de connexion entre `F.prototype` et le nouvel objet. Si, après la création, la propriété `F.prototype` change (`F.prototype = `), les nouveaux objets créés par `new F` auront un autre objet comme `[[Prototype]]`, mais les objets déjà existants conservent l'ancien. ``` ## F.prototype par défaut, propriété du constructeur Chaque fonction a la propriété `"prototype"` même si nous ne la fournissons pas. Le `"prototype"` par défaut est un objet avec comme seule propriété `constructor` qui renvoie à la fonction elle-même. Comme ça : ```js function Rabbit() {} /* prototype par défaut Rabbit.prototype = { constructor: Rabbit }; */ ``` ![](function-prototype-constructor.svg) Nous pouvons le vérifier : ```js run function Rabbit() {} // par défaut: // Rabbit.prototype = { constructor: Rabbit } alert( Rabbit.prototype.constructor == Rabbit ); // true ``` Naturellement, si nous ne faisons rien, la propriété `constructor` est disponible pour tous les "rabbits" via `[[Prototype]]` : ```js run function Rabbit() {} // par défaut: // Rabbit.prototype = { constructor: Rabbit } let rabbit = new Rabbit(); // hérite de {constructor: Rabbit} alert(rabbit.constructor == Rabbit); // true (de prototype) ``` ![](rabbit-prototype-constructor.svg) Nous pouvons utiliser la propriété `constructor` pour créer un nouvel objet en utilisant le même constructeur que l'existant. Comme ici : ```js run function Rabbit(name) { this.name = name; alert(name); } let rabbit = new Rabbit("White Rabbit"); *!* let rabbit2 = new rabbit.constructor("Black Rabbit"); */!* ``` C'est pratique lorsque nous avons un objet, ne sachant pas quel constructeur a été utilisé pour cela (par exemple, il provient d'une bibliothèque externe), et nous devons en créer un autre du même type. Mais probablement la chose la plus importante à propos de `"constructor"` est que... **...JavaScript lui-même n'assure pas la bonne valeur de `"constructor"`.** Oui, il existe dans le `"prototype"` par défaut des fonctions, mais c'est tout. Ce qu'il en adviendra par la suite dépend entièrement de nous. En particulier, si nous remplaçons le prototype par défaut dans son ensemble, il ne contiendra pas de "constructor". Par exemple : ```js run function Rabbit() {} Rabbit.prototype = { jumps: true }; let rabbit = new Rabbit(); *!* alert(rabbit.constructor === Rabbit); // false */!* ``` Donc, pour garder le bon `"constructor"`, nous pouvons choisir d'ajouter/supprimer des propriétés au `"prototype"` par défaut au lieu de l'écraser dans son ensemble : ```js function Rabbit() {} // Ne pas écraser Rabbit.prototype totalement // juste y ajouter Rabbit.prototype.jumps = true // le Rabbit.prototype.constructor par défaut est conservé ``` Ou bien, recréez manuellement la propriété `constructor` : ```js Rabbit.prototype = { jumps: true, *!* constructor: Rabbit */!* }; // maintenant le constructeur est également correct, car nous l'avons ajouté ``` ## Résumé Dans ce chapitre, nous avons brièvement décrit la manière de définir un `[[Prototype]]` pour les objets créés via une fonction constructeur. Plus tard, nous verrons des modèles de programmation plus avancés qui en dépendent. Tout est assez simple, juste quelques précisions pour clarifier les choses : - La propriété `F.prototype` (ne pas confondre avec `[[Prototype]]`) définit `[[Prototype]]` sur les nouveaux objets lorsque `new F()` est appelée. - La valeur de `F.prototype` devrait soit être un objet ou `null` : les autres valeurs ne fonctionneront pas. - La propriété `"prototype"` n'a cet effet spécial que lorsqu'elle est définie dans une fonction constructeur et invoquée avec `new`. Sur les objets ordinaires, le `prototype` n'a rien de spécial : ```js let user = { name: "John", prototype: "Bla-bla" // pas de magie }; ``` Par défaut, toutes les fonctions ont `F.prototype={constructor:F}`, nous pouvons donc obtenir le constructeur d'un objet en accédant à sa propriété `"constructor"`. ================================================ FILE: 1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/solution.md ================================================ ```js run Function.prototype.defer = function(ms) { setTimeout(this, ms); }; function f() { alert("Hello!"); } f.defer(1000); // montre "Hello!" après 1 seconde ``` ================================================ FILE: 1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md ================================================ importance: 5 --- # Ajouter la méthode "f.defer(ms)" aux fonctions Ajoutez au prototype de toutes les fonctions la méthode `defer(ms)`, qui exécute la fonction après `ms` millisecondes. Une fois que vous le faites, ce code devrait fonctionner : ```js function f() { alert("Hello!"); } f.defer(1000); // montre "Hello!" après 1 seconde ``` ================================================ FILE: 1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md ================================================ ```js run Function.prototype.defer = function(ms) { let f = this; return function(...args) { setTimeout(() => f.apply(this, args), ms); } }; // vérification function f(a, b) { alert( a + b ); } f.defer(1000)(1, 2); // montre 3 après 1 seconde ``` Notez que nous utilisons `this` dans `f.apply` pour que notre décoration fonctionne pour les méthodes d'objets. Ainsi, si la fonction wrapper est appelée en tant que méthode d'objet, alors `this` est passé à la méthode originale `f`. ```js run Function.prototype.defer = function(ms) { let f = this; return function(...args) { setTimeout(() => f.apply(this, args), ms); } }; let user = { name: "John", sayHi() { alert(this.name); } } user.sayHi = user.sayHi.defer(1000); user.sayHi(); ``` ================================================ FILE: 1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md ================================================ importance: 4 --- # Ajouter la décoration "defer()" aux fonctions Ajoutez au prototype de toutes les fonctions la méthode `defer(ms)`, qui renvoie un wrapper, retardant l’appel de `ms` millisecondes. Voici un exemple de la façon dont cela devrait fonctionner : ```js function f(a, b) { alert( a + b ); } f.defer(1000)(1, 2); // montre 3 après 1 seconde ``` Veuillez noter que les arguments doivent être passés à la fonction d'origine. ================================================ FILE: 1-js/08-prototypes/03-native-prototypes/article.md ================================================ # Prototypes natifs La propriété `"prototype"` est largement utilisée au centre de JavaScript lui-même. Toutes les fonctions constructeurs intégrées l'utilisent. Nous verrons d’abord les détails, puis comment l’utiliser pour ajouter de nouvelles fonctionnalités aux objets intégrés. ## Object.prototype Disons que nous produisons un objet vide : ```js run let obj = {}; alert( obj ); // "[object Object]" ? ``` Où est le code qui génère la chaîne `"[object Object]"` ? C'est une méthode `toString` intégrée, mais où est-elle ? L'objet `obj` est vide ! ...Mais la notation abrégée `obj = {}` est identique à `obj = new Object()`, où `Object` est une fonction constructeur de l'objet intégrée, avec son propre `prototype` référençant un énorme objet avec `toString` et d'autres méthodes. Voici ce qui se passe : ![](object-prototype.svg) Lorsque `new Object()` est appelé (ou un objet littéral `{...}` est créé), le `[[Prototype]]` de celui-ci est défini sur `Object.prototype` conformément à la règle dont nous avons parlé dans le chapitre précédent : ![](object-prototype-1.svg) Ainsi, quand on appelle `obj.toString()`, la méthode est extraite de `Object.prototype`. Nous pouvons le vérifier comme ceci : ```js run let obj = {}; alert(obj.__proto__ === Object.prototype); // true alert(obj.toString === obj.__proto__.toString); //true alert(obj.toString === Object.prototype.toString); //true ``` Veuillez noter qu'il n'y a plus de `[[Prototype]]` dans la chaîne au dessus de `Object.prototype` : ```js run alert(Object.prototype.__proto__); // null ``` ## Autres prototypes intégrés D'autres objets intégrés, tels que `Array`, `Date`, `Function` et autres, conservent également des méthodes dans des prototypes. Par exemple, lorsque nous créons un tableau `[1, 2, 3]`, le constructeur `new Array()` par défaut est utilisé en interne. Donc `Array.prototype` devient son prototype et fournit des méthodes. C'est très efficace en mémoire. Par spécification, tous les prototypes intégrés ont `Object.prototype` en haut. C'est pourquoi certaines personnes disent que "tout hérite d'objets". Voici la vue d'ensemble : ![](native-prototypes-classes.svg) Vérifions les prototypes manuellement : ```js run let arr = [1, 2, 3]; // il hérite de Array.prototype ? alert( arr.__proto__ === Array.prototype ); // true // puis de Object.prototype ? alert( arr.__proto__.__proto__ === Object.prototype ); // true // et null tout en haut. alert( arr.__proto__.__proto__.__proto__ ); // null ``` Certaines méthodes dans les prototypes peuvent se chevaucher, par exemple, `Array.prototype` a son propre `toString` qui répertorie les éléments délimités par des virgules : ```js run let arr = [1, 2, 3] alert(arr); // 1,2,3 <-- le résultat de Array.prototype.toString ``` Comme nous l'avons vu précédemment, `Object.prototype` a aussi `toString`, mais `Array.prototype` est plus proche dans la chaîne, la variante de tableau est donc utilisée. ![](native-prototypes-array-tostring.svg) Les outils intégrés au navigateur, tels que la console de développement Chrome, affichent également l'héritage (il faut éventuellement utiliser `console.dir` pour les objets intégrés) : ![](console_dir_array.png) Les autres objets intégrés fonctionnent également de la même manière. Même les fonctions - ce sont des objets d'un constructeur intégré `Function`, et leurs méthodes (`call`/`apply` et autres) sont extraites de `Function.prototype`. Les fonctions ont aussi leur propre `toString`. ```js run function f() {} alert(f.__proto__ == Function.prototype); // true alert(f.__proto__.__proto__ == Object.prototype); // true, hérite d'objets ``` ## Primitives Une chose complexe se produit avec les chaînes, les nombres et les booléens. Comme on s'en souvient, ce ne sont pas des objets. Mais si nous essayons d'accéder à leurs propriétés, des objets wrapper temporaires sont créés à l'aide des constructeurs intégrés `String`, `Number` et `Boolean`, ils fournissent les méthodes et disparaissent. Ces objets sont créés de manière invisible pour nous et la plupart des moteurs les optimisent, mais la spécification le décrit exactement de cette façon. Les méthodes de ces objets résident également dans des prototypes, disponibles sous les noms `String.prototype`, `Number.prototype` et `Boolean.prototype`. ```warn header="Les valeurs `null` et `undefined` n'ont pas de wrappers d'objet" Les valeurs spéciales `null` et `undefined` se démarquent. Elles n'ont pas de wrapper d'objet, donc les méthodes et les propriétés ne sont pas disponibles pour eux. Et il n'y a pas non plus de prototypes correspondants. ``` ## Modification des prototypes natifs [#native-prototype-change] Les prototypes natifs peuvent être modifiés. Par exemple, si nous ajoutons une méthode à `String.prototype`, elle devient disponible pour toutes les chaînes : ```js run String.prototype.show = function() { alert(this); }; "BOOM!".show(); // BOOM! ``` Au cours du processus de développement, nous pouvons avoir des idées de nouvelles méthodes intégrées que nous aimerions avoir et nous pourrions être tentés de les ajouter à des prototypes natifs. Mais c'est généralement une mauvaise idée. ```warn Les prototypes sont globaux, il est donc facile de créer un conflit. Si deux bibliothèques ajoutent une méthode `String.prototype.show`, l'une d'elles remplacera la méthode de l'autre. Donc, généralement, modifier un prototype natif est considéré comme une mauvaise idée. ``` **Dans la programmation moderne, il n'y a qu'un seul cas où la modification de prototypes natifs est approuvée. Le polyfilling.** Polyfilling est un terme utilisé pour remplacer une méthode existante dans la spécification JavaScript, mais qui n'est pas encore prise en charge par un moteur JavaScript particulier. Ensuite, nous pouvons l’implémenter manuellement et y ajouter le prototype intégré. Par exemple : ```js run if (!String.prototype.repeat) { // s'il n'y a pas une telle méthode // ajouter le au prototype String.prototype.repeat = function(n) { // répéter la chaîne n fois // en fait, le code devrait être un peu plus complexe que cela // (l'algorithme complet est dans la spécification) // mais même un polyfill imparfait est souvent considéré comme suffisant return new Array(n + 1).join(this); }; } alert( "La".repeat(3) ); // LaLaLa ``` ## Emprunt de prototypes Dans le chapitre nous avons parlé de l'emprunt de méthode. C'est quand nous prenons une méthode d'un objet et le copions dans un autre. Certaines méthodes de prototypes natifs sont souvent empruntées. Par exemple, si nous créons un objet semblable à un tableau, nous voudrons peut-être y copier des méthodes `Array`. Par exemple : ```js run let obj = { 0: "Hello", 1: "world!", length: 2, }; *!* obj.join = Array.prototype.join; */!* alert( obj.join(',') ); // Hello,world! ``` Cela fonctionne car l'algorithme interne de la méthode `join` intégrée ne se préoccupe que des index corrects et de la propriété `length`. Il ne vérifie pas que l'objet est bien un tableau. Et beaucoup de méthodes intégrées sont comme ça. Une autre possibilité consiste à hériter en fixant `obj.__proto__` sur `Array.prototype`, afin que toutes les méthodes `Array` soient automatiquement disponibles dans `obj`. Mais c'est impossible si `obj` hérite déjà d'un autre objet. N'oubliez pas que nous ne pouvons hériter que d'un objet à la fois. L'emprunt des méthodes est flexible, cela permet de mélanger les fonctionnalités provenants d'objets différents en cas de besoin. ## Résumé - Tous les objets intégrés suivent le même schéma : - Les méthodes sont stockées dans le prototype (`Array.prototype`, `Object.prototype`, `Date.prototype`, etc.). - L'objet lui-même ne stocke que les données (éléments de tableau, propriétés de l'objet, date). - Les primitives stockent également des méthodes dans des prototypes d'objets wrapper : `Number.prototype`, `String.prototype`, `Boolean.prototype`. Seuls `undefined` et `null` n'ont pas d'objets wrapper. - Les prototypes intégrés peuvent être modifiés ou remplis avec de nouvelles méthodes. Mais il n'est pas recommandé de les changer. La seule cause possible est probablement l’ajout d’un nouveau standard, mais pas encore pris en charge par le moteur JavaScript. ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md ================================================ La méthode peut prendre toutes les clés énumérables en utilisant `Object.keys` et afficher leur liste. Pour rendre `toString` non-énumérable, définissons-le à l'aide d'un descripteur de propriété. La syntaxe de `Object.create` nous permet de fournir un objet avec des descripteurs de propriété comme second argument. ```js run *!* let dictionary = Object.create(null, { toString: { // définir la propriété toString value() { // la valeur est une fonction return Object.keys(this).join(); } } }); */!* dictionary.apple = "Apple"; dictionary.__proto__ = "test"; // apple et __proto__ sont dans la boucle for(let key in dictionary) { alert(key); // "apple", puis "__proto__" } // liste de propriétés séparées par des virgules par toString alert(dictionary); // "apple,__proto__" ``` Lorsque nous créons une propriété à l'aide d'un descripteur, ses indicateurs sont `false` par défaut. Donc, dans le code ci-dessus, `dictionary.toString` est non énumérable. Voir le chapitre [](info:property-descriptors) pour revoir. ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md ================================================ importance: 5 --- # Ajouter toString au dictionnaire Il existe un objet `dictionary`, créé en tant que `Object.create(null)`, pour stocker toutes les paires `clé`/`valeur`. Ajoutez la méthode `dictionary.toString()`, qui devrait renvoyer une liste de clés délimitée par des virgules. Votre `toString` ne devrait pas apparaître dans la boucle `for..in` sur l'objet. Voici comment cela devrait fonctionner : ```js let dictionary = Object.create(null); *!* // votre code pour ajouter la méthode dictionary.toString */!* // add some data dictionary.apple = "Apple"; dictionary.__proto__ = "test"; // __proto__ est une clé de propriété régulière ici // seulement apple et __proto__ sont dans la boucle for(let key in dictionary) { alert(key); // "apple", puis "__proto__" } // votre toString en action alert(dictionary); // "apple,__proto__" ``` ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md ================================================ Le premier appel a `this==rabbit`, les autres ont `this` égal à `Rabbit.prototype`, car il s'agit en fait de l'objet avant le point. Ainsi, seul le premier appel indique `Rabbit`, les autres affichent `undefined` : ```js run function Rabbit(name) { this.name = name; } Rabbit.prototype.sayHi = function() { alert( this.name ); } let rabbit = new Rabbit("Rabbit"); rabbit.sayHi(); // Rabbit Rabbit.prototype.sayHi(); // undefined Object.getPrototypeOf(rabbit).sayHi(); // undefined rabbit.__proto__.sayHi(); // undefined ``` ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md ================================================ importance: 5 --- # La différence entre les appels Créons un nouvel objet `rabbit` : ```js function Rabbit(name) { this.name = name; } Rabbit.prototype.sayHi = function() { alert(this.name); }; let rabbit = new Rabbit("Rabbit"); ``` Ces appels font-ils la même chose ou non ? ```js rabbit.sayHi(); Rabbit.prototype.sayHi(); Object.getPrototypeOf(rabbit).sayHi(); rabbit.__proto__.sayHi(); ``` ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/article.md ================================================ # Méthodes de prototypes, objets sans __proto__ Dans le premier chapitre de cette section, nous avons indiqué qu'il existe des méthodes modernes pour configurer un prototype. La définition ou la lecture du prototype avec `obj.__proto__` est considérée comme obsolète et dépréciée (déplacée dans la soi-disant "annexe B" de la norme JavaScript, destinée uniquement aux navigateurs). Les méthodes modernes pour obtenir/définir un prototype sont : - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- retourn le `[[Prototype]]` de `obj`. - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- configure le `[[Prototype]]` de `obj` à `proto`. La seule utilisation de `__proto__`, qui n'est pas mal vue, est en tant que propriété lors de la création d'un nouvel objet : `{ __proto__: ... }`. Bien qu'il existe également une méthode spéciale pour cela : - [Object.create(proto, [descriptors])](mdn:js/Object/create) -- crée un objet vide avec `proto` donné comme `[[Prototype]]` et des descripteurs de propriété facultatifs. Par exemple : ```js run let animal = { eats: true }; // créer un nouvel objet avec animal comme prototype *!* let rabbit = Object.create(animal); // identique à {__proto__: animal} */!* alert(rabbit.eats); // true *!* alert(Object.getPrototypeOf(rabbit) === animal); // true */!* *!* Object.setPrototypeOf(rabbit, {}); // change le prototype de rabbit en {} */!* ``` La méthode `Object.create` est un peu plus puissante, car elle a un deuxième argument facultatif : les descripteurs de propriété. Nous pouvons fournir des propriétés supplémentaires au nouvel objet, comme ceci : ```js run let animal = { eats: true }; let rabbit = Object.create(animal, { jumps: { value: true } }); alert(rabbit.jumps); // true ``` Les descripteurs sont dans le même format que décrit dans le chapitre . Nous pouvons utiliser `Object.create` pour effectuer un clonage d'objet plus puissant que la copie des propriétés dans la boucle `for..in` : ```js let clone = Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) ); ``` Cet appel crée une copie véritablement exacte de `obj`, y compris de toutes les propriétés : énumérable et non-énumérable, des propriétés de données et des accesseurs/mutateurs - tout, et avec le bon `[[Prototype]]`. ## Bref historique Il y a tellement de façons de gérer `[[Prototype]]`. Comment est-ce arrivé ? Pourquoi ? C'est pour des raisons historiques. L'héritage prototypal était dans le langage depuis son aube, mais les façons de le gérer ont évolué au fil du temps. - La propriété `prototype` d'une fonction constructeur fonctionne depuis des temps très anciens. C'est la manière la plus ancienne de créer des objets avec un prototype donné. - Plus tard, en 2012, `Object.create` est apparu dans la norme. Il a donné la possibilité de créer des objets avec un prototype donné, mais n'a pas fourni la possibilité de l'obtenir/le définir. Certains navigateurs ont implémenté l'accesseur non standard `__proto__` qui permettait à l'utilisateur d'obtenir/définir un prototype à tout moment, pour donner plus de flexibilité aux développeurs. - Plus tard, en 2015, `Object.setPrototypeOf` et `Object.getPrototypeOf` ont été ajoutés à la norme, pour exécuter la même fonctionnalité que `__proto__`. Comme `__proto__` était de facto implémenté partout, il était en quelque sorte obsolète et a fait son chemin vers l'annexe B de la norme, c'est-à-dire facultatif pour les environnements sans navigateur. - Plus tard, en 2022, il a été officiellement autorisé d'utiliser `__proto__` dans les objets littéraux `{...}` (sortie de l'annexe B), mais pas en tant que getter/setter `obj.__proto__` (toujours dans l'annexe B). Pourquoi `__proto__` a été remplacé par les fonctions `getPrototypeOf`/`setPrototypeOf` ? Pourquoi `__proto__` a-t-il été partiellement réhabilité et son utilisation autorisée dans `{...}`, mais pas en tant que getter/setter ? C'est une question intéressante, qui nous oblige à comprendre pourquoi `__proto__` est mauvais. Et bientôt nous aurons la réponse. ```warn header="Ne changez pas `[[Prototype]]` sur des objets existants si la vitesse est importante" Techniquement, nous pouvons accéder/muter `[[Prototype]]` à tout moment. Mais en général, nous ne le définissons qu’une fois au moment de la création de l’objet, puis nous ne le modifions pas : `rabbit` hérite de `animal`, et cela ne changera pas. Et les moteurs JavaScript sont hautement optimisés pour cela. Changer un prototype "à la volée" avec `Object.setPrototypeOf` ou `obj.__ proto __=` est une opération très lente, elle rompt les optimisations internes pour des opérations d'accès aux propriétés d'objet. Alors évitez-la à moins que vous ne sachiez ce que vous faites, ou que la vitesse de JavaScript n'a pas d'importance pour vous. ``` ## Objets "très simples" [#very-plain] Comme nous le savons, les objets peuvent être utilisés en tant que tableaux associatifs pour stocker des paires clé/valeur. ...Mais si nous essayons de stocker des clés *fournies par l'utilisateur* (par exemple, un dictionnaire saisi par l'utilisateur), nous verrons un petit problème intéressant : toutes les clés fonctionnent très bien, sauf `"__proto __"`. Découvrez l'exemple : ```js run let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; alert(obj[key]); // [object Object], pas "some value" ! ``` Ici, si l'utilisateur tape `__proto__`, l'assignation à la ligne 4 est ignorée ! Cela pourrait sûrement être surprenant pour un non-développeur, mais assez compréhensible pour nous. La propriété `__proto__` est spéciale : elle doit être soit un objet, soit `null`. Une chaîne de caractères ne peut pas devenir un prototype. C'est pourquoi une affectation d'une chaîne à `__proto__` est ignorée. Mais nous n'avions pas *l'intention* de mettre en œuvre un tel comportement, non ? Nous voulons stocker des paires clé/valeur, et la clé nommée `"__proto__"` n'a pas été correctement enregistrée. Donc c'est un bug ! Ici les conséquences ne sont pas terribles. Mais dans d'autres cas, nous pouvons stocker des objets au lieu de chaînes dans `obj`, puis le prototype sera effectivement modifié. En conséquence, l'exécution ira mal de manière totalement inattendue. Ce qui est pire -- généralement les développeurs ne pensent pas du tout à cette possibilité. Cela rend ces bugs difficiles à remarquer et même à les transformer en vulnérabilités, en particulier lorsque JavaScript est utilisé côté serveur. Des choses inattendues peuvent également se produire lors de l'affectation à `obj.toString`, car il s'agit d'une méthode d'objet intégrée. Comment pouvons-nous éviter ce problème ? Tout d'abord, nous pouvons simplement passer à l'utilisation de `Map` pour le stockage au lieu d'objets simples, puis tout va bien. ```js run let map = new Map(); let key = prompt("What's the key?", "__proto__"); map.set(key, "some value"); alert(map.get(key)); // "some value" (comme prévu) ``` ...Mais la syntaxe `Object` est souvent plus attrayante, car elle est plus concise. Heureusement, nous *pouvons* utiliser des objets, car les créateurs du langage ont réfléchi à ce problème il y a longtemps. Comme nous le savons, `__proto__` n'est pas une propriété d'un objet, mais un accesseur sur la propriété `Object.prototype` : ![](object-prototype-2.svg) Ainsi, si `obj.__proto__` est lu ou muté, l'accésseur/mutateur correspondant est appelé à partir de son prototype et il accède/mute `[[Prototype]]`. Comme il a été dit au début de cette section de tutoriel : `__proto__` est un moyen d'accéder `[[Prototype]]`, il n'est pas `[[Prototype]]` lui-même. Maintenant, si nous avons l'intention d'utiliser un objet comme tableau associatif et de ne pas avoir de tels problèmes, nous pouvons le faire avec une petite astuce : ```js run *!* let obj = Object.create(null); // ou : obj = { __proto__: null } */!* let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; alert(obj[key]); // "some value" ``` `Object.create(null)` crée un objet vide sans prototype (`[[Prototype]]` est `null`) : ![](object-prototype-null.svg) Donc, il n'y a pas d'accésseur/mutateur hérité pour `__proto__`. Maintenant, il est traité comme une propriété de données normale, ainsi l'exemple ci-dessus fonctionne correctement. Nous pouvons appeler de tels objets des objets "très simples" ou "dictionnaire pur", car ils sont encore plus simples que les objets simples ordinaires `{...}`. L'inconvénient est que de tels objets ne possèdent aucune méthode d'objet intégrée, par exemple `toString` : ```js run *!* let obj = Object.create(null); */!* alert(obj); // Error (pas de toString) ``` ...Mais c'est généralement acceptable pour les tableaux associatifs. Notez que la plupart des méthodes liées aux objets sont `Object.quelquechose(...)`, comme `Object.keys(obj)` - elles ne sont pas dans le prototype, elles continueront donc à travailler sur de tels objets : ```js run let chineseDictionary = Object.create(null); chineseDictionary.hello = "你好"; chineseDictionary.bye = "再见"; alert(Object.keys(chineseDictionary)); // hello,bye ``` ## Résumé - Pour créer un objet avec le prototype donné, utilisez : - la syntaxe littérale : `{ __proto__: ... }`, permet de spécifier plusieurs propriétés - ou [Object.create(proto, [descriptors])](mdn:js/Object/create), permet de spécifier des descripteurs de propriété. Le `Object.create` fournit un moyen simple de copier superficiellement un objet avec tous les descripteurs : ```js let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` - Les méthodes modernes pour obtenir/définir le prototype sont : - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- renvoie le `[[Prototype]]` de `obj` (identique au getter `__proto__`). - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- définit le `[[Prototype]]` de `obj` à `proto` (identique au setter `__proto__`). - Obtenir/définir le prototype en utilisant le getter/setter intégré. `__proto__` n'est pas recommandé, il est maintenant dans l'annexe B de la spécification. - Nous avons également couvert les objets sans prototype, créés avec `Object.create(null)` ou `{__proto__: null}`. Ces objets sont utilisés comme dictionnaires, pour stocker toutes les clés (éventuellement générées par l'utilisateur). Normalement, les objets héritent des méthodes intégrées et du getter/setter `__proto__` de `Object.prototype`, rendant les clés correspondantes "occupées" et provoquant potentiellement des effets secondaires. Avec le prototype "null", les objets sont vraiment vides. ================================================ FILE: 1-js/08-prototypes/index.md ================================================ # Prototypes, héritage ================================================ FILE: 1-js/09-classes/01-class/1-rewrite-to-class/_js.view/solution.js ================================================ class Clock { constructor({ template }) { this.template = template; } render() { let date = new Date(); let hours = date.getHours(); if (hours < 10) hours = '0' + hours; let mins = date.getMinutes(); if (mins < 10) mins = '0' + mins; let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; let output = this.template .replace('h', hours) .replace('m', mins) .replace('s', secs); console.log(output); } stop() { clearInterval(this.timer); } start() { this.render(); this.timer = setInterval(() => this.render(), 1000); } } let clock = new Clock({template: 'h:m:s'}); clock.start(); ================================================ FILE: 1-js/09-classes/01-class/1-rewrite-to-class/_js.view/source.js ================================================ function Clock({ template }) { let timer; function render() { let date = new Date(); let hours = date.getHours(); if (hours < 10) hours = '0' + hours; let mins = date.getMinutes(); if (mins < 10) mins = '0' + mins; let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; let output = template .replace('h', hours) .replace('m', mins) .replace('s', secs); console.log(output); } this.stop = function() { clearInterval(timer); }; this.start = function() { render(); timer = setInterval(render, 1000); }; } let clock = new Clock({template: 'h:m:s'}); clock.start(); ================================================ FILE: 1-js/09-classes/01-class/1-rewrite-to-class/solution.md ================================================ ================================================ FILE: 1-js/09-classes/01-class/1-rewrite-to-class/task.md ================================================ importance: 5 --- # Réécrire en classe La classe `Clock` (voir la sandbox) est écrite en style fonctionnelle. Réécrivez la en syntaxe de "classe". P.S. La montre doit tictaquer dans la console, ouvrez la pour la voir. ================================================ FILE: 1-js/09-classes/01-class/article.md ================================================ # Syntaxe de base de la Classe ```quote author="Wikipedia" En langage orienté objet, une *classe* est un modèle de code programme extensible servant à créer des objets. Elle fournit les valeurs initiales de l'état (les variables membres) et de l'implémentation du comportement (les fonctions ou méthodes membres). ``` En pratique, nous avons souvent besoin de créer beaucoup d'objets de même type, tels que des utilisateurs, des biens ou toute autre chose. Comme nous le savons dans le chapitre , `new function` peut nous aider à faire cela. Mais dans le JavaScript moderne, il y a une construction de la "classe" plus avancée qui introduit de nombreux nouveaux aspects utiles en langage orienté objet. ## La syntaxe de "classe" La syntaxe de base est : ```js class MyClass { // Les méthodes de la classe constructor() { ... } method1() { ... } method2() { ... } method3() { ... } ... } ``` Vous pouvez ensuite utiliser `new MyClass()` pour créer un nouvel objet ayant toute la liste des méthodes. La méthode `constructor()` est automatiquement appelée par `new`, donc nous pouvons initialiser l'objet à ce niveau. Par exemple : ```js run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // Usage: let user = new User("John"); user.sayHi(); ``` Lorsque `new User("John")` est appelé : 1. Un nouvel objet est créé. 2. Le `constructor` s'exécute avec les arguments qui lui sont passés et assigne `this.name` a l'objet. ...ensuite nous pouvons appeler les méthodes de l'objet, tel que `user.sayHi()`. ```warn header="Pas de virgule entre les méthodes de la classe" Un piège fréquent des développeurs novices est de mettre une virgule entre les méthodes de la classe, entrainant ainsi une erreur syntaxique. La notation ici ne doit pas être confondue avec les objets littéraux. A l'intérieure d'une classe, aucune virgule n'est requise. ``` ## Qu'est-ce qu'une classe ? Alors, c'est quoi exactement une `class` ? Ce n'est pas totalement une nouvelle entité au niveau du langage, comme on pourrait le penser. Dévoilons maintenant la magie et regardons ce qu'est réellement une classe. Cela va nous aider à comprendre plusieurs aspects complexes. En JavaScript, une classe est une sorte de fonction. Regardons ici : ```js run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // La preuve : User est une fonction *!* alert(typeof User); // function */!* ``` Ce que la construction de la classe `classe User {...}` fait réellement, c'est : 1. Créer une fonction nommée `User`, qui devient le résultat de la déclaration de la classe. Le code de la fonction est tirée de la méthode `constructor` (considérée comme étant vide au cas ou cette méthode n'est pas écrite). 2. Garde les méthodes de la classe, telle que `sayHi`, dans `User.prototype`. Après la création de `new User`, lorsque nous appelons sa méthode, elle est extraite du prototype, comme décrit dans le chapitre . Donc, l'objet a accès aux méthodes de classe. Nous pouvons illustrer le résultat de la déclaration de `class User` ainsi : ![](class-user.svg) Voici le code pour une introspection : ```js run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // classe est une fonction alert(typeof User); // function // ...ou, plus précisément, le constructeur de la méthode alert(User === User.prototype.constructor); // true // Les méthodes sont dans User.prototype, par exemple : alert(User.prototype.sayHi); // le code de la méthode sayHi // Il y a exactement deux méthodes dans le prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructeur, sayHi ``` ## Pas simplement un sucre syntaxique Parfois certaines personnes disent que la notion de `class` est un "sucre syntaxique" (une syntaxe qui est destinée à rendre la lecture plus facile, mais elle n'introduit rien de nouveau), parce qu'en réalité nous pouvons déclarer la même chose sans utiliser le mot clé `class` : ```js run // Réécriture de class User en fonctions pures // 1. Créer la fonction constructeur function User(name) { this.name = name; } // un prototype de fonction a une propriété constructeur par défaut, // nous n'avons donc pas besoin de le créer // 2. Ajouter la méthode au prototype User.prototype.sayHi = function() { alert(this.name); }; // Usage: let user = new User("John"); user.sayHi(); ``` Le résultat de cette définition est à peu près la même chose. Donc, il y a bien des raisons de vouloir considérer `class` comme pouvant être un sucre syntaxique pour définir un constructeur ensemble avec ses méthodes de prototype. Cependant, il existe des différences importantes. 1. Premièrement, une fonction créée par `class` est labellisée par une propriété interne spéciale `[[IsClassConstructor]]: true`. Ce n'est donc pas tout à fait la même chose que de le créer manuellement. Le langage vérifie cette propriété à divers endroits. Par exemple, contrairement à une fonction régulière, elle doit être appelée avec `new` : ```js run class User { constructor() {} } alert(typeof User); // fonction User(); // Erreur: le constructeur Class User ne peut être invoque sans 'new' ``` Aussi, la représentation en chaîne de caractères d'un constructeur de class dans la plupart des moteurs de JavaScript commence avec "class..." : ```js run class User { constructor() {} } alert(User); // class User { ... } ``` Il y a d'autres différences, nous les verrons bientôt. 2. Les méthodes de Class sont non-énumérable. Une définition de la classe attribue `false` à la propriété `enumerable` pour toutes les méthodes du `"prototype"`. C'est bien, parce que si nous exécutons un `for..in` sur un Object, souvent nous ne voulons pas accéder aux méthodes de sa classe. 3. Les Classes utilisent toujours `use strict`. Tout code à l'intérieur de la construction de la classe est automatiquement en mode strict. En outres, la syntaxe `classe` apporte beaucoup d'autres caractéristiques que nous allons explorer plus tard. ## L'Expression Class Tout comme les fonctions, les classes peuvent être définies a l'intérieur d'une autre expression, passées en paramètres, retournées, assignées etc. Voici un exemple d'expression d'une classe : ```js let User = class { sayHi() { alert("Hello"); } }; ``` Similairement aux Fonction Expressions nommées, les expressions de classe peuvent avoir un nom. Si une expression de classe a un nom, il est visible à l'intérieur de la classe uniquement : ```js run // "Expression de Classe nommée" // (Terme non existant dans la spécification, mais elle est similaire a une Expression de Fonction nommée) let User = class *!*MyClass*/!* { sayHi() { alert(MyClass); // le nom MyClass est seulement visible dans la classe } }; new User().sayHi(); // ça fonctionne, montre la définition de MyClass alert(MyClass); // erreur, le nom MyClass n'est pas visible en dehors de la classe ``` Nous pouvons même créer les classes dynamiquement "à la demande", comme suit : ```js run function makeClass(phrase) { // déclare une classe et la retourne return class { sayHi() { alert(phrase); } }; } // Crée une nouvelle classe let User = makeClass("Hello"); new User().sayHi(); // Hello ``` ## Accesseurs/Mutateurs Tout comme les objets littéraux, les classes peuvent inclure des accesseurs/mutateurs, des propriétés évaluées etc. Voici un exemple pour `user.name` implémenté en utilisant les propriétés `get`/`set` : ```js run class User { constructor(name) { // invoque l'accesseur (the setter) this.name = name; } *!* get name() { */!* return this._name; } *!* set name(value) { */!* if (value.length < 4) { alert("Name is too short."); return; } this._name = value; } } let user = new User("John"); alert(user.name); // John user = new User(""); // le nom est trop court. ``` Techniquement, une telle déclaration de classe fonctionne en créant des getters et des setters dans `User.prototype`. ## Computed names [...] Voici un exemple avec un nom de méthode calculé utilisant des crochets `[...]` : ```js run class User { *!* ['say' + 'Hi']() { */!* alert("Hello"); } } new User().sayHi(); ``` Ces caractéristiques sont faciles à retenir, car elles ressemblent à celles d'objets littéraux. ## Champs de classe ```warn header="Les anciens navigateurs peuvent avoir besoin de polyfill" Les propriétés de classe sont un ajout récent au langage. ``` Auparavant, nos classes n'avaient que des méthodes. Les "Class fields" (champs de classe) sont une syntaxe qui permet d'ajouter des propriétés. Par exemple, ajoutons la propriété `name` à `class User` : ```js run class User { *!* name = "John"; */!* sayHi() { alert(`Hello, ${this.name}!`); } } new User().sayHi(); // Hello, John! ``` Il suffit donc d'écrire " = " dans la déclaration, et c'est tout. La différence importante des champs de classe est qu'ils sont définis sur des objets individuels, et non sur `User.prototype` : ```js run class User { *!* name = "John"; */!* } let user = new User(); alert(user.name); // John alert(User.prototype.name); // undefined ``` Nous pouvons également attribuer des valeurs à l'aide d'expressions et d'appels de fonctions plus complexes : ```js run class User { *!* name = prompt("Name, please?", "John"); */!* } let user = new User(); alert(user.name); // John ``` ### Création de méthodes liées avec des champs de classe Comme démontré dans le chapitre les fonctions en JavaScript ont un `this` dynamique. Cela dépend du contexte de l'appel. Donc, si une méthode objet est contournée et appelée dans un autre contexte, `this` ne sera plus une référence à son objet. Par exemple, ce code affichera `undefined` : ```js run class Button { constructor(value) { this.value = value; } click() { alert(this.value); } } let button = new Button("hello"); *!* setTimeout(button.click, 1000); // undefined */!* ``` Le problème est appelé "perdre le `this`". Il existe deux approches pour le corriger, comme indiqué dans le chapitre : 1. Passer une fonction wrapper, telle que `setTimeout(() => button.click(), 1000)`. 2. Lier la méthode à l'objet, par exemple dans le constructeur. Les champs de classe fournissent une syntaxe plus élégante : ```js run class Button { constructor(value) { this.value = value; } *!* click = () => { alert(this.value); } */!* } let button = new Button("hello"); setTimeout(button.click, 1000); // hello ``` Le champ de classe `click = () => {...}` est créé par objet, il y a une fonction distincte pour chaque objet `Button`, avec `this` à l'intérieur référençant cet objet. Nous pouvons passer `button.click` n'importe où, et la valeur de `this` sera toujours correcte. C'est particulièrement utile dans un environnement de navigateur, pour les écouteurs d'événements. ## Résumé La syntaxe de base d'une classe ressemble à ceci : ```js class MyClass { prop = value; // propriété constructor(...) { // constructeur // ... } method(...) {} // méthode get something(...) {} // méthode définie avec un accesseur set something(...) {} // méthode définie avec un mutateur [Symbol.iterator]() {} // méthode avec un nom évalué (symbole ici) // ... } ``` `MyClass` est techniquement une fonction (celle que nous fournissons en tant que `constructor`), tandis que les méthodes, accesseurs et mutateurs sont écrits dans `MyClass.prototype`. Dans les prochains chapitres nous apprendrons plus à propos des classes, y compris la notion d'héritage et les autres caractéristiques. ================================================ FILE: 1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md ================================================ C'est parce que le constructeur de l'enfant doit appeler `super()`. Voici le code corrigé: ```js run class Animal { constructor(name) { this.name = name; } } class Rabbit extends Animal { constructor(name) { *!* super(name); */!* this.created = Date.now(); } } *!* let rabbit = new Rabbit("White Rabbit"); // OK maintenant */!* alert(rabbit.name); // White Rabbit ``` ================================================ FILE: 1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md ================================================ importance: 5 --- # Erreur lors de la création d'une instance Voici le code avec `Rabbit` étendant `Animal`. Malheureusement, des objets `Rabbit` ne peuvent pas être créés. Qu'est-ce qui ne va pas ? Répare-le. ```js run class Animal { constructor(name) { this.name = name; } } class Rabbit extends Animal { constructor(name) { this.name = name; this.created = Date.now(); } } *!* let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined */!* alert(rabbit.name); ``` ================================================ FILE: 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md ================================================ [js src="solution.view/extended-clock.js"] ================================================ FILE: 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/clock.js ================================================ class Clock { constructor({ template }) { this.template = template; } render() { let date = new Date(); let hours = date.getHours(); if (hours < 10) hours = '0' + hours; let mins = date.getMinutes(); if (mins < 10) mins = '0' + mins; let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; let output = this.template .replace('h', hours) .replace('m', mins) .replace('s', secs); console.log(output); } stop() { clearInterval(this.timer); } start() { this.render(); this.timer = setInterval(() => this.render(), 1000); } } ================================================ FILE: 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js ================================================ class ExtendedClock extends Clock { constructor(options) { super(options); let { precision = 1000 } = options; this.precision = precision; } start() { this.render(); this.timer = setInterval(() => this.render(), this.precision); } }; ================================================ FILE: 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/index.html ================================================ ================================================ FILE: 1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/clock.js ================================================ class Clock { constructor({ template }) { this.template = template; } render() { let date = new Date(); let hours = date.getHours(); if (hours < 10) hours = '0' + hours; let mins = date.getMinutes(); if (mins < 10) mins = '0' + mins; let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; let output = this.template .replace('h', hours) .replace('m', mins) .replace('s', secs); console.log(output); } stop() { clearInterval(this.timer); } start() { this.render(); this.timer = setInterval(() => this.render(), 1000); } } ================================================ FILE: 1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html ================================================ ================================================ FILE: 1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md ================================================ importance: 5 --- # Horloge étendue Nous avons une classe `Clock`. À partir de maintenant, il affiche l'heure à chaque seconde. [js src="source.view/clock.js"] Créez une nouvelle classe `ExtendedClock` qui hérite de `Clock` et ajoute le paramètre `precision` - le nombre de `ms` entre "ticks". Devrait être `1000` (1 seconde) par défaut. - Votre code devrait être dans le fichier `extended-clock.js` - Ne modifiez pas le fichier `clock.js` d'origine. Étendez-le. ================================================ FILE: 1-js/09-classes/02-class-inheritance/article.md ================================================ # Héritage de classe L'héritage de classe est un moyen pour une classe d'étendre une autre classe. Nous pouvons donc créer de nouvelles fonctionnalités à partir de celles qui existent déjà. ## Le mot-clé "extends" Supposons que nous ayons la classe `Animal` : ```js class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stands still.`); } } let animal = new Animal("My animal"); ``` Voici comment nous pouvons représenter graphiquement l'objet `animal` et la classe `Animal` : ![](rabbit-animal-independent-animal.svg) ... Et nous aimerions créer une autre `class Rabbit`. Comme les lapins sont des animaux, la classe `Rabbit` devrait être basé sur `Animal`, avoir accès à des méthodes animales, de sorte que les lapins puissent faire ce que les animaux "génériques" peuvent faire. La syntaxe pour étendre une autre classe est la suivante : `class Child extends Parent`. Créons `class Rabbit` qui hérite de `Animal` : ```js *!* class Rabbit extends Animal { */!* hide() { alert(`${this.name} hides!`); } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit court à la vitesse 5. rabbit.hide(); // White Rabbit se cache! ``` L’objet de la classe `Rabbit` a accès à la fois aux méthodes `Rabbit`, telles que `rabbit.hide()`, et aux méthodes `Animal`, telles que `rabbit.run()`. En interne, le mot clé `extended` fonctionne en utilisant le bon vieux prototype. Il établit `Rabbit.prototype.[[Prototype]]` vers `Animal.prototype`. Donc, si une méthode n'est pas trouvée dans `Rabbit.prototype`, JavaScript le prend de `Animal.prototype`. ![](animal-rabbit-extends.svg) Par exemple, pour trouver la méthode `rabbit.run`, le moteur vérifie (de bas en haut sur l'image) : 1. L'objet `rabbit` (n'a pas de `run`). 2. Son prototype, c'est-à-dire `Rabbit.prototype` (a `hide`, mais pas `run`). 3. Son prototype, c'est-à-dire (en raison de `extends`) `Animal.prototype`, qui a finalement la méthode `run`. Comme nous pouvons nous en rappeler dans le chapitre , JavaScript lui-même utilise l'héritage prototypale pour les objets intégrés. Exemple : `Date.prototype.[[Prototype]]` est `Object.prototype`. C'est pourquoi les dates ont accès aux méthodes d'objet génériques. ````smart header="Toute expression est autorisée après `extends`" La syntaxe de classe permet de spécifier non seulement une classe, mais toute expression après `extends`. Par exemple, un appel de fonction qui génère la classe parent : ```js run function f(phrase) { return class { sayHi() { alert(phrase); } }; } *!* class User extends f("Hello") {} */!* new User().sayHi(); // Hello ``` Ici la classe `user` hérite du résultat de `f("Hello")`. Cela peut être utile pour les modèles de programmation avancés lorsque nous utilisons des fonctions pour générer des classes en fonction de nombreuses conditions et que nous pouvons en hériter. ```` ## Remplacer une méthode Maintenant, avançons et substituons une méthode. Par défaut, toutes les méthodes qui ne sont pas spécifiées dans `class Rabbit` sont prises directement "telles quelles" dans `class Animal`. Mais si nous spécifions notre propre méthode dans `Rabbit`, telle que `stop()`, elle sera utilisée à la place : ```js class Rabbit extends Animal { stop() { // ...maintenant ceci sera utilisé pour rabbit.stop() // au lieu de stop() de la classe Animal } } ``` Mais en général, nous ne voulons pas remplacer totalement une méthode parente, mais plutôt construire dessus, modifier ou étendre ses fonctionnalités. Nous faisons quelque chose dans notre méthode, mais appelons la méthode parente avant / après ou dans le processus. Les classes fournissent le mot clé `"super"` pour cela. - `super.method(...)` pour appeler une méthode parente. - `super(...)` pour appeler un constructeur parent (dans notre constructeur uniquement). Par exemple, laissons rabbit se cacher automatiquement à l’arrêt : ```js run class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stands still.`); } } class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } *!* stop() { super.stop(); // appeler stop du parent this.hide(); // puis hide } */!* } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit court à la vitesse 5. rabbit.stop(); // White Rabbit reste immobile. White Rabbit se cache! ``` Maintenant, `Rabbit` a la méthode `stop` qui appelle le `super.stop()` du parent dans le processus. ````smart header="Les fonctions fléchées n'ont pas de `super`" Comme mentionné dans le chapitre , les fonctions fléchées n'ont pas `super`. Si on y accède, c'est tiré de la fonction externe. Par exemple : ```js class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // appel stop du parent après 1sec } } ``` Le `super` dans la fonction fléchée est le même que dans `stop()`, donc cela fonctionne comme prévu. Si nous spécifions ici une fonction "régulière", il y aurait une erreur : ```js // Super inattendu setTimeout(function() { super.stop() }, 1000); ``` ```` ## Remplacement du constructeur Avec les constructeurs, cela devient un peu délicat. Jusqu'à maintenant, `Rabbit` n'avait pas son propre `constructor`. Selon la [spécification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), si une classe étend une autre classe et n'a pas de `constructor`, alors le `constructor` "vide" suivant est généré : ```js class Rabbit extends Animal { // généré pour l'extension de classes sans constructeur propre *!* constructor(...args) { super(...args); } */!* } ``` Comme nous pouvons le constater, il appelle essentiellement le `constructor` du parent en lui passant tous les arguments. Cela se produit si nous n'écrivons pas notre propre constructeur. Ajoutons maintenant un constructeur personnalisé à `Rabbit`. Il spécifiera le `earLength` en plus de `name` : ```js run class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { *!* constructor(name, earLength) { this.speed = 0; this.name = name; this.earLength = earLength; } */!* // ... } *!* // Ça ne marche pas! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. */!* ``` Oups ! Nous avons une erreur. Maintenant, nous ne pouvons pas créer de lapins. Qu'est-ce qui s'est passé ? La réponse courte est : - **les constructeurs dans les classes qui héritent doivent appeler `super(...)`, et (!) le faire avant d'utiliser `this`.** ...Mais pourquoi ? Que se passe t-il ici ? En effet, l'exigence semble étrange. Bien sûr, il y a une explication. Entrons dans les détails pour que vous compreniez vraiment ce qui se passe. En JavaScript, il existe une distinction entre une fonction constructeur d'une classe héritante (appelée "constructeur dérivé") et d'autres fonctions. Un constructeur dérivé a une propriété interne spéciale `[[ConstructorKind]] : "derived"`. C'est un label interne spécial. Ce label affecte son comportement avec `new`. - Lorsqu'une fonction normale est exécutée avec `new`, elle crée un objet vide et l'assigne à `this`. - Mais lorsqu'un constructeur dérivé s'exécute, il ne le fait pas. Il s'attend à ce que le constructeur parent fasse ce travail. Ainsi, un constructeur dérivé doit appeler `super` pour exécuter son constructeur parent (de base), sinon l'objet pour `this` ne sera pas créé. Et nous aurons une erreur. Pour que le constructeur `Rabbit` fonctionne, il doit appeler `super()` avant d'utiliser `this`, comme ici : ```js run class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { constructor(name, earLength) { *!* super(name); */!* this.earLength = earLength; } // ... } *!* // maintenant ça marche let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10 */!* ``` ### Remplacer les champs de classe : une note délicate ```warn header="Note avancée" Cette note suppose que vous avez une certaine expérience avec les classes, peut-être dans d'autres langages de programmation. Cela donne un meilleur aperçu du langage et explique également le comportement qui pourrait être une source de bogues (mais pas très souvent). Si vous avez du mal à comprendre, continuez, poursuivez votre lecture et revenez-y un peu plus tard. ``` Nous pouvons remplacer non seulement les méthodes, mais également les champs de classe. Cependant, il existe un comportement délicat lorsque nous accédons à un champ surchargé dans le constructeur parent, assez différent de la plupart des autres langages de programmation. Prenons cet exemple : ```js run class Animal { name = 'animal'; constructor() { alert(this.name); // (*) } } class Rabbit extends Animal { name = 'rabbit'; } new Animal(); // animal *!* new Rabbit(); // animal */!* ``` Ici, la classe `Rabbit` étend `Animal` et remplace le champ `name` par sa propre valeur. Il n'y a pas de constructeur propre dans `Rabbit`, donc le constructeur `Animal` est appelé. Ce qui est intéressant, c'est que dans les deux cas : `new Animal()` et `new Rabbit()`, l'`alert` dans la ligne `(*)` montre `animal`. **En d'autres termes, le constructeur parent utilise toujours sa propre valeur de champ, pas celle remplacée.** Qu'est-ce qui est étrange à ce sujet ? Si ce n'est pas encore clair, veuillez comparer avec les méthodes. Voici le même code, mais au lieu du champ `this.name`, nous appelons la méthode `this.showName()` : ```js run class Animal { showName() { // au lieu de this.name = 'animal' alert('animal'); } constructor() { this.showName(); // au lieu de alert(this.name); } } class Rabbit extends Animal { showName() { alert('rabbit'); } } new Animal(); // animal *!* new Rabbit(); // rabbit */!* ``` Remarque : maintenant la sortie est différente. Et c'est ce à quoi nous nous attendons naturellement. Lorsque le constructeur parent est appelé dans la classe dérivée, il utilise la méthode substituée. ... Mais ce n'est pas le cas pour les champs de classe. Comme nous l'avons dit, le constructeur parent utilise toujours le champ parent. Pourquoi y a-t-il une différence ? Eh bien, la raison est dans l'ordre d'initialisation du champ. Le champ de classe est initialisé : - Avant le constructeur de la classe de base (qui n'étend rien), - Immédiatement après `super()` pour la classe dérivée. Dans notre cas, `Rabbit` est la classe dérivée. Il n'y a pas de `constructor()` dedans. Comme dit précédemment, c'est la même chose que s'il y avait un constructeur vide avec seulement `super(...args)`. Ainsi, `new Rabbit()` appelle `super()`, exécutant ainsi le constructeur parent, et (selon la règle pour les classes dérivées) seulement après cela ses champs de classe sont initialisés. Au moment de l'exécution du constructeur parent, il n'y a pas encore de champs de classe `Rabbit`, c'est pourquoi les champs `Animal` sont utilisés. Cette subtile différence entre les champs et les méthodes est propre à JavaScript. Heureusement, ce comportement ne se révèle que si un champ surchargé est utilisé dans le constructeur parent. Ensuite, il peut être difficile de comprendre ce qui se passe, alors nous l'expliquons ici. Si cela devient un problème, on peut le résoudre en utilisant des méthodes ou des getters / setters au lieu de champs. ## Super: les internes, [[HomeObject]] ```warn header="Informations avancées" Si vous lisez le tutoriel pour la première fois - cette section peut être ignorée. Il concerne les mécanismes internes de l'héritage et du "super". ``` Poussons un peu plus loin sous le capot de `super`. Nous y verrons des choses intéressantes. Tout d'abord, d'après tout ce que nous avons appris jusqu'à présent, `super` ne devrait pas fonctionner du tout ! Oui, en effet, demandons-nous comment cela devrait fonctionner techniquement ? Lorsqu'une méthode d'objet est exécutée, l'objet actuel est remplacé par `this`. Si nous appelons `super.method()`, le moteur doit obtenir la `method` à partir du prototype de l'objet actuel. Mais comment ? La tâche peut sembler simple, mais elle ne l’est pas. Le moteur connaît l'objet en cours `this`, de sorte qu'il pourrait obtenir la `method` parent sous la forme `this.__proto__.Method`. Malheureusement, une telle solution "naïve" ne fonctionnera pas. Montrons le problème. Sans les classes, en utilisant des objets simples pour des raisons de simplicité. Si vous ne voulez pas connaître les détails, vous pouvez sauter cette partie et aller en bas à la sous-section `[[HomeObject]]`. Cela ne fera pas de mal. Ou lisez si vous souhaitez comprendre les choses en profondeur. Dans l'exemple ci-dessous, `rabbit.__proto__=animal`. Essayons maintenant : dans `rabbit.eat()` nous appellerons `animal.eat()`, en utilisant `this.__proto__` : ```js run let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { *!* // c'est ainsi que super.eat() pourrait fonctionner this.__proto__.eat.call(this); // (*) */!* } }; rabbit.eat(); // Rabbit eats. ``` À la ligne `(*)` nous prenons `eat` du prototype (`animal`) et l'appelons dans le contexte de l'objet actuel. Veuillez noter que `.call(this)` est important ici, car un simple `this.__proto__.eat()` exécuterait le `eat` du parent dans le contexte du prototype et non de l'objet actuel. Et dans le code ci-dessus, cela fonctionne réellement comme prévu : nous avons la bonne `alert`. Ajoutons maintenant un objet de plus à la chaîne. Nous verrons comment les choses se cassent : ```js run let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, eat() { // ...rebondir et appeler la méthode du parent (animal) this.__proto__.eat.call(this); // (*) } }; let longEar = { __proto__: rabbit, eat() { // ...faire quelque chose puis appeler la méthode du parent (rabbit) this.__proto__.eat.call(this); // (**) } }; *!* longEar.eat(); // Error: Maximum call stack size exceeded */!* ``` Le code ne fonctionne plus ! Nous pouvons voir l'erreur en essayant d'appeler `longEar.eat()`. Ce n'est peut-être pas si évident, mais si nous suivons l'appel de `longEar.eat()`, nous pouvons voir pourquoi. Dans les deux lignes `(*)` et `(**)`, la valeur de `this` est l'objet actuel (`longEar`). C'est essentiel : toutes les méthodes d'objet obtiennent l'objet actuel sous la forme `this`, pas un prototype ou quelque chose d'autre. Ainsi, dans les deux lignes `(*)` et `(**)`, la valeur de `this.__proto__` est exactement la même : `rabbit`. Ils appellent tous deux `rabbit.eat` sans remonter la chaîne, dans une boucle infinie. Voici l'image de ce qui se passe : ![](this-super-loop.svg) 1. Dans `longEar.eat()`, la ligne `(**)` appelle `rabbit.eat` et lui fournit `this = longEar`. ```js // dans longEar.eat() nous avons this = longEar this.__proto__.eat.call(this) // (**) // devient longEar.__proto__.eat.call(this) // à savoir rabbit.eat.call(this); ``` 2. Ensuite, dans la ligne `(*)` de `rabbit.eat`, nous aimerions passer l'appel encore plus haut dans la chaîne, mais `this = longEar`, donc `this.__proto__.eat` est encore `rabbit.eat` ! ```js // dans rabbit.eat() nous avons aussi this = longEar this.__proto__.eat.call(this) // (*) // devient longEar.__proto__.eat.call(this) // ou (encore) rabbit.eat.call(this); ``` 3. ...Donc `rabbit.eat` s’appelle lui-même dans une boucle infinie, car il ne peut pas monter plus loin. Le problème ne peut pas être résolu en utilisant seulement `this`. ### `[[HomeObject]]` Pour fournir la solution, JavaScript ajoute une propriété interne spéciale supplémentaire pour les fonctions : `[[HomeObject]]`. Lorsqu'une fonction est spécifiée en tant que méthode de classe ou d'objet, sa propriété `[[HomeObject]]` devient cet objet. Ensuite, `super` l'utilise pour résoudre le prototype parent et ses méthodes. Voyons comment cela fonctionne, d'abord avec les objets simples : ```js run let animal = { name: "Animal", eat() { // animal.eat.[[HomeObject]] == animal alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { // rabbit.eat.[[HomeObject]] == rabbit super.eat(); } }; let longEar = { __proto__: rabbit, name: "Long Ear", eat() { // longEar.eat.[[HomeObject]] == longEar super.eat(); } }; *!* // fonctionne correctement longEar.eat(); // Long Ear eats. */!* ``` Cela fonctionne comme prévu, en raison de la mécanique `[[HomeObject]]`. Une méthode, telle que `longEar.eat`, connaît son `[[HomeObject]]` et prend la méthode parente de son prototype. Sans aucune utilisation de `this`. ### Les méthodes ne sont pas "libres" Comme nous l'avons vu précédemment, les fonctions sont généralement "libres" et ne sont pas liées à des objets en JavaScript. Ils peuvent donc être copiés entre des objets et appelés avec un autre `this`. L'existence même de `[[HomeObject]]` viole ce principe, car les méthodes se souviennent de leurs objets. `[[HomeObject]]` ne peut pas être changé, ce lien est donc permanent. Le seul endroit dans le langage où `[[HomeObject]]` est utilisé est `super`. Donc, si une méthode n'utilise pas `super`, on peut toujours la considérer comme libre et la copier entre les objets. Mais avec `super`, les choses peuvent mal tourner. Voici la démo d'un mauvais résultat de `super` après la copie : ```js run let animal = { sayHi() { alert(`I'm an animal`); } }; // rabbit hérite de animal let rabbit = { __proto__: animal, sayHi() { super.sayHi(); } }; let plant = { sayHi() { alert("I'm a plant"); } }; // tree hérite de plant let tree = { __proto__: plant, *!* sayHi: rabbit.sayHi // (*) */!* }; *!* tree.sayHi(); // I'm an animal (?!?) */!* ``` Un appel à `tree.sayHi()` indique "I'm an animal". Certainement faux. La raison est simple : - Dans la ligne `(*)`, la méthode `tree.sayHi` a été copiée à partir de `rabbit`. Peut-être que nous voulions simplement éviter la duplication de code ? - Son `[[[HomeObject]]` est `rabbit`, comme il a été créé dans `rabbit`. Il n'y a aucun moyen de changer `[[HomeObject]]`. - Le code de `tree.sayHi()` a `super.sayHi()` à l'intérieur. Il monte de `rabbit` et prend la méthode de `animal`. Voici le schéma de ce qui se passe : ![](super-homeobject-wrong.svg) ### Méthodes, pas des propriétés de fonction `[[HomeObject]]` est défini pour les méthodes à la fois dans les classes et dans les objets simples. Mais pour les objets, les méthodes doivent être spécifiées exactement comme `method()`, pas comme `"method: function()"`. La différence peut être non essentielle pour nous, mais c'est important pour JavaScript. Dans l'exemple ci-dessous, une syntaxe non-méthode est utilisée pour la comparaison. La propriété `[[HomeObject]]` n'est pas définie et l'héritage ne fonctionne pas : ```js run let animal = { eat: function() { // écrire intentionnellement comme ceci au lieu de eat() {... // ... } }; let rabbit = { __proto__: animal, eat: function() { super.eat(); } }; *!* rabbit.eat(); // Error calling super (parce qu'il n'y a pas de [[HomeObject]]) */!* ``` ## Résumé 1. Pour étendre une classe : `class Child extends Parent` : - Cela signifie que `Child.prototype.__proto__` sera `Parent.prototype`, donc les méthodes sont héritées. 2. Lors du remplacement d'un constructeur : - Nous devons appeler le constructeur parent en tant que `super()` dans le constructeur `Child` avant d'utiliser `this`. 3. Lors du remplacement d'une autre méthode : - Nous pouvons utiliser `super.method()` dans une méthode `Child` pour appeler la méthode `Parent`. 4. Internes : - Les méthodes se souviennent de leur classe/objet dans la propriété interne `[[HomeObject]]`. C'est ainsi que `super` résout les méthodes parent. - Il n'est donc pas prudent de copier une méthode avec `super` d'un objet à un autre. Également : - Les fonctions fléchées n'ont pas leurs propre `this` ou `super`, elles s'adaptent donc de manière transparente au contexte environnant. ================================================ FILE: 1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md ================================================ Voyons d'abord pourquoi ce dernier code ne fonctionne pas. La raison devient évidente si nous essayons de l'exécuter. Un constructeur de classe héritant doit appeler `super()`. Sinon `"this"` ne sera pas "défini". Alors, voici la solution : ```js run class Rabbit extends Object { constructor(name) { *!* super(); // besoin d'appeler le constructeur parent lors de l'héritage */!* this.name = name; } } let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // true ``` Mais ce n'est pas tout. Même après le correctif, il existe toujours une différence importante entre `"class Rabbit extends Object"` et `class Rabbit`. Comme on le sait, la syntaxe "extends" configure deux prototypes : 1. Entre le `"prototype"` des fonctions du constructeur (pour les méthodes). 2. Entre les fonctions constructeur elles-mêmes (pour les méthodes statiques). Dans notre cas, pour `class Rabbit extends Object`, cela signifie : ```js run class Rabbit extends Object {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true ``` Donc `Rabbit` donne maintenant accès aux méthodes statiques de `Object` via `Rabbit`, comme ceci : ```js run class Rabbit extends Object {} *!* // normalement nous appelons Object.getOwnPropertyNames alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b */!* ``` Mais si nous n’avons pas `extends Object`, alors `Rabbit.__proto__` n'est pas défini sur `Object`. Voici la démo : ```js run class Rabbit {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) alert( Rabbit.__proto__ === Function.prototype ); // comme toute fonction par défaut *!* // error, no such function in Rabbit alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error */!* ``` Donc, `Rabbit` ne donne pas accès aux méthodes statiques de `Object` dans ce cas. En passant, `Function.prototype` a des méthodes de fonction "génériques", comme `call`, `bind`, etc. Elles sont finalement disponibles dans les deux cas, car pour le constructeur `Object` intégré, `Object.__proto__ === Function.prototype`. Voici l'image : ![](rabbit-extends-object.svg) Donc, pour faire court, il y a deux différences : | class Rabbit | class Rabbit extends Object | |--------------|------------------------------| | -- | doit appeler `super()` dans le constructeur | | `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` | ================================================ FILE: 1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md ================================================ importance: 3 --- # Class extends Object ? Comme nous le savons, tous les objets héritent normalement de `Object.prototype` et ont accès à des méthodes d'objet "génériques" comme `hasOwnProperty` etc. Par exemple : ```js run class Rabbit { constructor(name) { this.name = name; } } let rabbit = new Rabbit("Rab"); *!* // la méthode hasOwnProperty provient de Object.prototype alert( rabbit.hasOwnProperty('name') ); // true */!* ``` Mais si nous l’épelons explicitement comme suit : `"class Rabbit extends Object"`, le résultat serait alors différent d´un simple `"class Rabbit"` ? Quelle est la différence ? Voici un exemple d'un tel code (cela ne fonctionne pas - pourquoi ? Réparez le ?) : ```js class Rabbit extends Object { constructor(name) { this.name = name; } } let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // Error ``` ================================================ FILE: 1-js/09-classes/03-static-properties-methods/article.md ================================================ # Propriétés et méthodes statiques Nous pouvons aussi assigner une méthode à la fonction de classe elle-même, pas à son `"prototype"`. De telles méthodes sont appelées *statique*. Dans une classe, elles sont précédées du mot clé `static`, comme ceci : ```js run class User { *!* static staticMethod() { */!* alert(this === User); } } User.staticMethod(); // true ``` Cela revient en fait à l'affecter directement à une propriété : ```js run class User { } User.staticMethod = function() { alert(this === User); }; User.staticMethod(); // true ``` La valeur de `this` dans l'appel `User.staticMethod()` est le constructeur de la classe `User` lui-même (la règle "objet avant le point"). Généralement, les méthodes statiques sont utilisées pour implémenter des fonctions appartenant à la classe, mais pas à un objet particulier de celle-ci. Par exemple, nous avons des objets `Article` et avons besoin d'une fonction pour les comparer. Une solution naturelle serait d’ajouter la méthode `Article.compare`, comme ceci : ```js run class Article { constructor(title, date) { this.title = title; this.date = date; } *!* static compare(articleA, articleB) { return articleA.date - articleB.date; } */!* } // usage let articles = [ new Article("HTML", new Date(2019, 1, 1)), new Article("CSS", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1)) ]; *!* articles.sort(Article.compare); */!* alert( articles[0].title ); // CSS ``` Ici, `Article.compare` est "au dessus" des articles, comme un moyen de les comparer. Ce n'est pas une méthode d'article, mais plutôt de toute la classe. Un autre exemple serait une méthode dite "d'usine". Disons que nous avons besoin de plusieurs façons de créer un article : 1. Créez avec des paramètres donnés (`title`, `date`, etc.). 2. Créez un article vide avec la date du jour. 3. ... ou d'une certaine manière. Le premier moyen peut être implémenté par le constructeur. Et pour le second, nous pouvons créer une méthode statique de la classe. Comme `Article.createTodays()` ici : ```js run class Article { constructor(title, date) { this.title = title; this.date = date; } *!* static createTodays() { // rappelez vous, this = Article return new this("Today's digest", new Date()); } */!* } let article = Article.createTodays(); alert( article.title ); // Today's digest ``` Maintenant, chaque fois que nous avons besoin de créer le résumé d'aujourd'hui, nous pouvons appeler `Article.createTodays()`. Encore une fois, ce n'est pas une méthode d'article, mais une méthode de toute la classe. Les méthodes statiques sont également utilisées dans les classes liées à la base de données pour rechercher/enregistrer/supprimer des entrées dans la base de données, comme ceci : ```js // en supposant que Article est une classe spéciale pour la gestion d'articles // méthode statique pour supprimer l'article : Article.remove({id: 12345}); ``` ````warn header="Les méthodes statiques ne sont pas disponibles pour les objets individuels" Les méthodes statiques peuvent être appelées sur des classes, pas sur des objets individuels. Par exemple, ce genre de code ne fonctionnera pas : ```js // ... article.createTodays(); // Error: article.createTodays is not a function ``` ```` ## Propriétés statiques [recent browser=Chrome] Les propriétés statiques sont également possibles, elles ressemblent aux propriétés de classe ordinaires, mais précédées de `static` : ```js run class Article { static publisher = "Ilya Kantor"; } alert( Article.publisher ); // Ilya Kantor ``` C’est la même chose qu’une assignation directe à `Article` : ```js Article.publisher = "Ilya Kantor"; ``` ## Héritage de méthodes et de propriétés statiques [#heritage-de-methodes-et-proprietes-statiques] Les propriétés et méthodes statiques sont héritées. Par exemple, `Animal.compare` et `Animal.planet` dans le code ci-dessous sont hérités et accessibles par `Rabbit.compare` et `Rabbit.planet` : ```js run class Animal { static planet = "Earth"; constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } *!* static compare(animalA, animalB) { return animalA.speed - animalB.speed; } */!* } // Hérite de Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5) ]; *!* rabbits.sort(Rabbit.compare); */!* rabbits[0].run(); // Black Rabbit runs with speed 5. alert(Rabbit.planet); // Earth ``` Maintenant, lorsque nous appellerons `Rabbit.compare`, le `Animal.compare` hérité sera appelé. Comment cela fonctionne-t-il ? Encore une fois, en utilisant des prototypes. Comme vous l'avez peut-être déjà deviné, `extends` donne à `Rabbit` la référence de `[[Prototype]]` à `Animal`. ![](animal-rabbit-static.svg) Ainsi, `Rabbit extends Animal` crée deux références `[[Prototype]]` : 1. La fonction `Rabbit` hérite de façon prototypique de la fonction `Animal`. 2. `Rabbit.prototype` hérite de façon prototypique de `Animal.prototype`. En conséquence, l'héritage fonctionne à la fois pour les méthodes régulières et statiques. Vérifions cela par code : ```js run class Animal {} class Rabbit extends Animal {} // pour les méthodes statiques alert(Rabbit.__proto__ === Animal); // true // pour les méthodes régulières alert(Rabbit.prototype.__proto__ === Animal.prototype); // true ``` ## Résumé Les méthodes statiques sont utilisées pour les fonctionnalités appartenant à la classe "dans son ensemble". Cela ne concerne pas une instance de classe concrète. Par exemple, une méthode de comparaison `Article.compare(article1, article2)` ou une méthode d'usine `Article.createTodays()`. Ils sont étiquetés par le mot `static` dans la déclaration de classe. Les propriétés statiques sont utilisées lorsque nous souhaitons stocker des données au niveau de la classe, également non liées à une instance. La syntaxe est la suivante : ```js class MyClass { static property = ...; static method() { ... } } ``` Techniquement, la déclaration statique revient à assigner à la classe elle-même : ```js MyClass.property = ... MyClass.method = ... ``` Les propriétés et méthodes statiques sont héritées. Pour `class B extends A`, le prototype de la classe `B` pointe lui-même à `A` : `B.[[Prototype]] = A`. Donc, si un champ n'est pas trouvé dans `B`, la recherche continue dans `A`. ================================================ FILE: 1-js/09-classes/04-private-protected-properties-methods/article.md ================================================ # Propriétés et méthodes privées et protégées L'un des principes les plus importants de la programmation orientée objet - délimiter l'interface interne de l'interface externe. C’est une pratique «incontournable» dans le développement de quelque chose de plus complexe que l’application «hello world». Pour comprendre cela, écartons nous du développement et tournons nos yeux dans le monde réel. Habituellement, les appareils que nous utilisons sont assez complexes. Mais délimiter l'interface interne de l'interface externe permet de les utiliser sans problèmes. ## Un exemple concret Par exemple, une machine à café. Simple de l'extérieur : un bouton, un écran, quelques trous... Et le résultat : du bon café ! :) ![](coffee.jpg) Mais à l'intérieur... (une image du manuel de réparation) ![](coffee-inside.jpg) Beaucoup de détails. Mais on peut l'utiliser sans rien savoir. Les machines à café sont assez fiables, n'est-ce pas ? Nous pouvons en utiliser une pendant des années, et seulement en cas de problème, la faire réparer. Le secret de la fiabilité et de la simplicité d'une machine à café - tous les détails sont bien réglés et cachés à l'intérieur. Si nous retirons le capot de protection de la machine à café, son utilisation sera beaucoup plus complexe (où appuyer ?) et dangereuse (elle peut électrocuter). Comme nous le verrons, les objets de programmation ressemblent à des machines à café. Mais pour masquer les détails intérieurs, nous n'utiliserons pas une couverture de protection, mais une syntaxe spéciale du langage et des conventions. ## Interface interne et externe En programmation orientée objet, les propriétés et les méthodes sont divisées en deux groupes : - *Interface interne* - méthodes et propriétés, accessibles à partir d'autres méthodes de la classe, mais pas de l'extérieur. - *Interface externe* - méthodes et propriétés, accessibles aussi de l'extérieur de la classe. Si nous continuons l'analogie avec la machine à café - ce qui est caché à l'intérieur : un tube de chaudière, un élément chauffant, etc. -, c'est son interface interne. Une interface interne est utilisée pour que l’objet fonctionne, ses détails s’utilisent les uns les autres. Par exemple, un tube de chaudière est attaché à l'élément chauffant. Mais de l'extérieur, une machine à café est fermée par le capot de protection, de sorte que personne ne puisse y accéder. Les détails sont cachés et inaccessibles. Nous pouvons utiliser ses fonctionnalités via l'interface externe. Il suffit donc de connaître son interface externe pour utiliser un objet. Nous ne savons peut-être pas comment cela fonctionne à l'intérieur, et c'est très bien. C'était une introduction générale. En JavaScript, il existe deux types de champs d’objet (propriétés et méthodes) : - Publique : accessible de n'importe où. Ils comprennent l'interface externe. Jusqu'à présent, nous utilisions uniquement des propriétés et méthodes publiques. - Privée : accessible uniquement de l'intérieur de la classe. Ce sont pour l'interface interne. Dans de nombreux autres langages, il existe également des champs "protégés" : accessibles uniquement de l'intérieur de la classe et de ceux qui en héritent (comme privé, mais avec accès des classes héritées). Ils sont également utiles pour l'interface interne. En un sens, elles sont plus répandues que les méthodes privées, car nous souhaitons généralement que les classes héritées puissent y accéder. Les champs protégés ne sont pas implémentés en JavaScript au niveau du langage, mais dans la pratique, ils sont très pratiques, ils sont donc imités. Nous allons maintenant créer une machine à café en JavaScript avec tous ces types de propriétés. Une machine à café a beaucoup de détails, nous ne les modéliserons pas pour rester simples (bien que nous puissions). ## Protection de "waterAmount" Faisons d'abord une classe de machine à café simple : ```js run class CoffeeMachine { waterAmount = 0; // la quantité d'eau à l'intérieur constructor(power) { this.power = power; alert( `Created a coffee-machine, power: ${power}` ); } } // créer la machine à café let coffeeMachine = new CoffeeMachine(100); // ajoutez de l'eau coffeeMachine.waterAmount = 200; ``` À l'heure actuelle, les propriétés `waterAmount` et `power` sont publiques. Nous pouvons facilement les accéder/muter de l’extérieur à n’importe quelle valeur. Changeons la propriété `waterAmount` en protégée pour avoir plus de contrôle sur celle-ci. Par exemple, nous ne voulons pas que quiconque la règle en dessous de zéro. **Les propriétés protégées sont généralement précédées d'un trait de soulignement `_`.** Cela n'est pas appliqué au niveau du langage, mais il existe une convention bien connue entre les programmeurs selon laquelle ces propriétés et méthodes ne doivent pas être accessibles de l'extérieur. Donc notre propriété s'appellera `_waterAmount` : ```js run class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { if (value < 0) { value = 0; } this._waterAmount = value; } get waterAmount() { return this._waterAmount; } constructor(power) { this._power = power; } } // créer la machine à café let coffeeMachine = new CoffeeMachine(100); // ajoutez de l'eau coffeeMachine.waterAmount = -10; // _waterAmount va devenir 0, pas -10 ``` Maintenant, l'accès est sous contrôle, donc le réglage de l'eau en dessous de zéro échoue. ## "power" en lecture seule Pour la propriété `power`, rendons-la en lecture seule. Il arrive parfois qu'une propriété doive être définie au moment de la création, puis ne jamais être modifiée. C'est exactement le cas pour une machine à café : la puissance ne change jamais. Pour ce faire, il suffit de définir l'accesseur, mais pas le mutateur : ```js run class CoffeeMachine { // ... constructor(power) { this._power = power; } get power() { return this._power; } } // créer la machine à café let coffeeMachine = new CoffeeMachine(100); alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W coffeeMachine.power = 25; // Error (no setter) ``` ````smart header="Fonctions Accesseur/Mutateur" Ici, nous avons utilisé la syntaxe accesseur/mutateur. Mais la plupart du temps, les fonctions `get...` / `set...` sont préférées, comme ceci : ```js class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { if (value < 0) value = 0; this._waterAmount = value; } *!*getWaterAmount()*/!* { return this._waterAmount; } } new CoffeeMachine().setWaterAmount(100); ``` Cela semble un peu plus long, mais les fonctions sont plus flexibles. Elles peuvent accepter plusieurs arguments (même si nous n'en avons pas besoin maintenant). D'un autre côté, la syntaxe accesseur/mutateur est plus courte, donc il n'y a pas de règle stricte, c'est à vous de décider. ```` ```smart header="Les champs protégés sont hérités" Si nous héritons de `classe MegaMachine extends CoffeeMachine`, rien ne nous empêche d'accéder à `this._waterAmount` ou `this._power` à partir des méthodes de la nouvelle classe. Les champs protégés sont donc naturellement héritables. Contrairement aux champs privés que nous verrons ci-dessous. ``` ## "#waterLimit" privée [recent browser=none] Il existe une proposition JavaScript finie, presque dans la norme, qui fournit une prise en charge au niveau du langage pour les propriétés et méthodes privées. Les propriétés privées devraient commencer par `#`. Elles ne sont accessibles que de l'intérieur de la classe. Par exemple, voici une propriété privée `#waterLimit` et la méthode privée de vérification du niveau de l'eau `#fixWaterAmount` : ```js run class CoffeeMachine { *!* #waterLimit = 200; */!* *!* #fixWaterAmount(value) { if (value < 0) return 0; if (value > this.#waterLimit) return this.#waterLimit; } */!* setWaterAmount(value) { this.#waterLimit = this.#fixWaterAmount(value); } } let coffeeMachine = new CoffeeMachine(); *!* // ne peut pas accéder aux propriétés privées de l'extérieur de la classe coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error */!* ``` Au niveau du langage, `#` est un signe spécial spécifiant que le champ est privé. Nous ne pouvons pas y accéder de l'extérieur ou des classes héritées. Les champs privés n'entrent pas en conflit avec les champs publics. Nous pouvons avoir les champs privés `#waterAmount` et publics `waterAmount` en même temps. Pour l'exemple, faisons de `waterAmount` un accesseur pour `#waterAmount` : ```js run class CoffeeMachine { #waterAmount = 0; get waterAmount() { return this.#waterAmount; } set waterAmount(value) { if (value < 0) value = 0; this.#waterAmount = value; } } let machine = new CoffeeMachine(); machine.waterAmount = 100; alert(machine.#waterAmount); // Error ``` Contrairement aux champs protégés, les champs privés sont imposés par le langage lui-même. C'est une bonne chose. Mais si nous héritons de `CoffeeMachine`, nous n’aurons aucun accès direct à `#waterAmount`. Nous aurons besoin de compter sur l'accesseur/mutateur `waterAmount` : ```js class MegaCoffeeMachine extends CoffeeMachine { method() { *!* alert( this.#waterAmount ); // Error: can only access from CoffeeMachine */!* } } ``` Dans de nombreux scénarios, une telle limitation est trop sévère. Si nous étendons une `CoffeeMachine`, nous pouvons avoir une raison légitime d’accéder à ses composants internes. C'est pourquoi les champs protégés sont utilisés plus souvent, même s'ils ne sont pas pris en charge par la syntaxe du langage. ````warn header="Les champs privés ne sont pas disponibles par this[nom]" Les champs privés sont spéciaux. Comme nous le savons, nous pouvons généralement accéder aux champs en utilisant `this[name]` : ```js class User { ... sayHi() { let fieldName = "name"; alert(`Hello, ${*!*this[fieldName]*/!*}`); } } ``` C'est impossible avec les champs privés : `this['#name']` ne fonctionne pas. C'est une limitation de syntaxe pour assurer la confidentialité. ```` ## Résumé En termes de POO, la délimitation de l'interface interne de l'interface externe est appelée [encapsulation]("https://fr.wikipedia.org/wiki/Encapsulation_(programmation)"). Cela offre les avantages suivants : Protection des utilisateurs pour qu'ils ne se tirent pas une balle dans le pied : Imaginez, il y a une équipe de développeurs utilisant une machine à café. Elle a été fabriquée par la société "Best CoffeeMachine" et fonctionne parfaitement, mais une coque de protection a été retirée. Donc, l'interface interne est exposée. Tous les développeurs sont civilisés - ils utilisent la machine à café comme prévu. Mais l'un d'entre eux, John, a décidé qu'il était le plus intelligent et a apporté quelques modifications aux éléments internes de la machine à café. La machine à café a donc échoué deux jours plus tard. Ce n’est sûrement pas la faute de John, mais bien de la personne qui a enlevé le capot de protection et laissé John manipuler. La même chose en programmation. Si un utilisateur d'une classe va changer des choses qui ne sont pas destinées à être modifiées de l'extérieur, les conséquences sont imprévisibles. Maintenable : La programmation est plus complexe qu’une machine à café réelle, car nous ne l’achetons pas une seule fois. Le code est en constante évolution et amélioration. **Si nous délimitons strictement l'interface interne, le développeur de la classe peut modifier librement ses propriétés et méthodes internes, même sans en informer les utilisateurs.** Si vous êtes développeur d'une telle classe, il est bon de savoir que les méthodes privées peuvent être renommées en toute sécurité, que leurs paramètres peuvent être modifiés, voire supprimés, car aucun code externe ne dépend d'eux. Pour les utilisateurs, lorsqu'une nouvelle version est disponible, il peut s'agir d'une refonte totale en interne, mais reste simple à mettre à niveau si l'interface externe est la même. Cacher la complexité : Les gens adorent utiliser des choses simples. Au moins de l'extérieur. Ce qui est à l'intérieur est une chose différente. Les programmeurs ne sont pas une exception. **C'est toujours pratique lorsque les détails de l'implémentation sont cachés et qu'une interface externe simple et bien documentée est disponible.** Pour masquer l'interface interne, nous utilisons des propriétés protégées ou privées : - Les champs protégés commencent par `_`. C'est une convention bien connue, non appliquée au niveau du langage. Les programmeurs doivent uniquement accéder à un champ commençant par `_` depuis sa classe et les classes qui en héritent. - Les champs privés commencent par `#`. JavaScript garantit que nous ne pouvons accéder à ceux la que de l'intérieur de la classe. Pour le moment, les champs privés ne sont pas bien supportés par les navigateurs, mais peuvent être polyfilled. ================================================ FILE: 1-js/09-classes/05-extend-natives/article.md ================================================ # Extension des classes intégrées Les classes intégrées telles que Array, Map et autres sont également extensibles. Par exemple, ici, `PowerArray` hérite du `Array` natif : ```js run // ajoutez-y une méthode supplémentaire class PowerArray extends Array { isEmpty() { return this.length === 0; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false let filteredArr = arr.filter(item => item >= 10); alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false ``` Notez une chose très intéressante. Les méthodes intégrées telles que `filter`, `map` et autres renvoient des nouveaux objets exactement du type hérité `PowerArray`. Leur implémentation interne utilise la propriété d'objet `constructor` pour cela. Dans l'exemple ci-dessus, ```js arr.constructor === PowerArray ``` Lorsque `arr.filter()` est appelé, elle crée en interne le nouveau tableau de résultats en utilisant exactement `arr.constructor`, et non pas `Array`. C'est en fait très intéressant, car nous pouvons continuer à utiliser les méthodes `PowerArray` sur le résultat. Encore plus, nous pouvons personnaliser ce comportement. Nous pouvons ajouter un accésseur statique spécial `Symbol.species` à la classe. S'il existe, il devrait renvoyer le constructeur que JavaScript utilisera en interne pour créer de nouvelles entités dans `map`, `filter`, etc. Si nous souhaitons que des méthodes intégrées comme `map` ou `filter` renvoient des tableaux classiques, nous pouvons retourner `Array` dans `Symbol.species`, comme ici : ```js run class PowerArray extends Array { isEmpty() { return this.length === 0; } *!* // les méthodes intégrées l'utiliseront comme constructeur static get [Symbol.species]() { return Array; } */!* } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false // filter crée un nouveau tableau en utilisant arr.constructor [Symbol.species] comme constructeur let filteredArr = arr.filter(item => item >= 10); *!* // filteredArr n'est pas un PowerArray, mais un Array */!* alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function ``` Comme vous pouvez le constater, maintenant, `.filter` renvoie `Array`. La fonctionnalité étendue n'est donc plus transmise. ```smart header="D'autres collections fonctionnent de la même manière" D'autres collections, telles que `Map` et `Set`, fonctionnent de la même manière. Ils utilisent également `Symbol.species`. ``` ## Pas d'héritage statique dans les éléments intégrés Les objets intégrés ont leurs propres méthodes statiques, par exemple `Object.keys`, `Array.isArray`, etc. Comme nous le savons déjà, les classes natives s'étendent les uns des autres. Par exemple, `Array` étend (hérite de) `Object`. Normalement, lorsqu'une classe en étend une autre, les méthodes statiques et non statiques sont héritées. Cela a été expliqué en détail dans le chapitre [](info:static-properties-methods#statics-and-inheritance). Mais les classes intégrées sont une exception. Elles n'héritent pas des méthodes statiques les unes des autres. Par exemple, `Array` et `Date` héritent de `Object`, de sorte que leurs instances ont des méthodes issues de `Object.prototype`. Mais `Array.[[Prototype]]` ne fait pas référence à `Object`, il n'y a donc pas, par exemple, de méthode statique `Array.keys()` (ou `Date.keys()`). Voici le schéma la structure pour `Date` et `Object` : ![](object-date-inheritance.svg) Comme vous pouvez le constater, il n'y a pas de lien entre `Date` et `Object`. Ils sont indépendants, seul `Date.prototype` hérite de `Object.prototype`. C'est une différence d'héritage importante entre les objets intégrés par rapport à ce que nous obtenons avec `extends`. ================================================ FILE: 1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md ================================================ Ouais, ça a l'air étrange. Mais `instanceof` ne se soucie pas de la fonction, mais plutôt de son `prototype`, qui correspond à la chaîne de prototypes. Et ici `a.__ proto__ == B.prototype`, ainsi `instanceof` renvoie `true`. Ainsi, par la logique de `instanceof`, le `prototype` définit en fait le type, pas la fonction constructeur. ================================================ FILE: 1-js/09-classes/06-instanceof/1-strange-instanceof/task.md ================================================ importance: 5 --- # instanceof étrange Dans le code ci-dessous, pourquoi `instanceof` renvoie `true` ? Nous pouvons facilement voir que `a` n'est pas créé par `B()`. ```js run function A() {} function B() {} A.prototype = B.prototype = {}; let a = new A(); *!* alert( a instanceof B ); // true */!* ``` ================================================ FILE: 1-js/09-classes/06-instanceof/article.md ================================================ # Vérification de classe : "instanceof" L'opérateur `instanceof` permet de vérifier si un objet appartient à une certaine classe. Il prend également en compte l'héritage. Une telle vérification peut être nécessaire dans de nombreux cas. Nous l'utilisons ici pour construire une fonction *polymorphique*, celle qui traite les arguments différemment en fonction de leur type. ## L'opérateur instanceof [#ref-instanceof] La syntaxe est la suivante : ```js obj instanceof Class ``` Cela renvoie `true` si `obj` appartient à la `Class` ou à une classe qui en hérite. Par exemple : ```js run class Rabbit {} let rabbit = new Rabbit(); // est-ce un objet de la classe Rabbit ? *!* alert( rabbit instanceof Rabbit ); // true */!* ``` Cela fonctionne aussi avec les fonctions constructeur : ```js run *!* // au lieu de classe function Rabbit() {} */!* alert( new Rabbit() instanceof Rabbit ); // true ``` ...Et avec des classes intégrées comme `Array` : ```js run let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true ``` Veuillez noter que `arr` appartient également à la classe `Object`. C'est parce que `Array` hérite de manière prototypale de `Object`. Normalement, l’opérateur `instanceof` examine la chaîne prototypale pour la vérification. Nous pouvons également définir une logique personnalisée dans la méthode statique `Symbol.hasInstance`. L'algorithme de `obj instanceof Class` fonctionne à peu près comme suit : 1. S'il existe une méthode statique `Symbol.hasInstance`, appelez-la simplement : `Class[Symbol.hasInstance](obj)`. Cela devrait renvoyer `true` ou `false`, et nous avons terminé. C'est ainsi que nous pouvons personnaliser le comportement de `instanceof`.      Par exemple : ```js run // configuration du contrôle de instanceof qui suppose que // tout ce qui a la propriété canEat est un animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) est appelée ``` 2. La plupart des classes n'ont pas `Symbol.hasInstance`. Dans ce cas, la logique standard est utilisée : `obj instanceof Class` vérifie si `Class.prototype` est égale à l'un des prototypes de la chaîne prototypale `obj`. En d'autres termes, on compare l'un après l'autre : ```js obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // si une réponse est vraie, renvoie true // sinon, si nous arrivons au bout de la chaîne, renvoie false ``` Dans l'exemple ci-dessus, `rabbit.__ proto__ === Rabbit.prototype`, donne donc la réponse immédiatement. Dans le cas d'un héritage, la correspondance se fera à la deuxième étape : ```js run class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); *!* alert(rabbit instanceof Animal); // true */!* // rabbit.__proto__ === Animal.prototype (pas de correspondance) *!* // rabbit.__proto__.__proto__ === Animal.prototype (ça correspond!) */!* ``` Voici l'illustration de ce que `rabbit instanceof Animal` compare avec `Animal.prototype` : ![](instanceof.svg) À propos, il y a aussi une méthode [objA.isPrototypeOf(objB)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/isPrototypeOf), qui renvoie `true` si `objA` se trouve quelque part dans la chaîne de prototypes pour `objB`. Ainsi, le test de `obj instanceof Class` peut être reformulé comme suit : `Class.prototype.isPrototypeOf(obj)`. C'est drôle, mais le constructeur `Class` lui-même ne participe pas au contrôle ! Seule la chaîne de prototypes et `Class.prototype` compte. Cela peut avoir des conséquences intéressantes lorsqu'une propriété `prototype` est modifiée après la création de l'objet. Comme ici : ```js run function Rabbit() {} let rabbit = new Rabbit(); // le prototype est changé Rabbit.prototype = {}; // ...plus un rabbit ! *!* alert( rabbit instanceof Rabbit ); // false */!* ``` ## Bonus : Object.prototype.toString pour le type Nous savons déjà que les objets simples sont convertis en chaîne sous la forme `[objet Objet]` : ```js run let obj = {}; alert(obj); // [object Object] alert(obj.toString()); // la même chose ``` C'est leur implémentation de `toString`. Mais il existe une fonctionnalité cachée qui rend `toString` beaucoup plus puissant que cela. Nous pouvons l'utiliser comme un `typeof` étendu et une alternative pour `instanceof`. Cela semble étrange ? Effectivement. Démystifions. Par [spécification](https://tc39.github.io/ecma262/#sec-object.prototype.tostring), le `toString` intégré peut être extrait de l'objet et exécuté dans le contexte de toute autre valeur. Et son résultat dépend de cette valeur. - Pour un nombre, ce sera `[object Number]` - Pour un booléen, ce sera `[object Boolean]` - Pour `null` : `[objet Null]` - Pour `undefined` : `[objet Undefined]` - Pour les tableaux : `[objet Array]` - ... etc. (personnalisable). Montrons cela : ```js run // copier la méthode toString dans une variable pour plus d'utilité let objectToString = Object.prototype.toString; // quel type est-ce ? let arr = []; alert( objectToString.call(arr) ); // [object *!*Array*/!*] ``` Ici nous avons utilisé [call](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/call) comme décrit dans le chapitre [](info:call-apply-decorators) pour exécuter la fonction `objectToString` dans le contexte `this=arr`. En interne, l'algorithme `toString` examine `this` et renvoie le résultat correspondant. Plus d'exemples : ```js run let s = Object.prototype.toString; alert( s.call(123) ); // [object Number] alert( s.call(null) ); // [object Null] alert( s.call(alert) ); // [object Function] ``` ### Symbol.toStringTag Le comportement de Object `toString` peut être personnalisé à l'aide d'une propriété d'objet spéciale `Symbol.toStringTag`. Par exemple : ```js run let user = { [Symbol.toStringTag]: "User" }; alert( {}.toString.call(user) ); // [object User] ``` Pour la plupart des objets spécifiques à l'environnement, il existe une telle propriété. Voici quelques exemples spécifiques à votre navigateur : ```js run // toStringTag pour l'objet et la classe spécifiques à l'environnement : alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest] ``` Comme vous pouvez le constater, le résultat est exactement `Symbol.toStringTag` (s'il existe), encapsulé dans `[objet ...]`. Au final, nous avons un "typeof sous stéroïdes" qui fonctionne non seulement pour les types de données primitifs, mais aussi pour les objets intégrés et qui peut même être personnalisé. Nous pouvons utiliser `{}.toString.call` au lieu de `instanceof` pour les objets intégrés lorsque nous voulons obtenir le type sous forme de chaîne de caractères plutôt que pour simplement vérifier. ## Résumé Résumons les méthodes de vérification de type que nous connaissons : | | fonctionne pour | renvoie | |---------------|---------------------------------------------------------------|------------| | `typeof` | primitives | string | | `{}.toString` | primitives, objets intégrés, objets avec `Symbol.toStringTag` | string | | `instanceof` | objects | true/false | Comme on peut le constater, `{}.toString` est techniquement un `typeof` "plus avancé". Et l'opérateur `instanceof` excelle lorsque nous travaillons avec une hiérarchie de classes et voulons vérifier si la classe prend en compte l'héritage. ================================================ FILE: 1-js/09-classes/07-mixins/article.md ================================================ # Les mixins En JavaScript, nous ne pouvons hériter que d'un seul objet. Il ne peut y avoir qu'un `[[Prototype]]` pour un objet. Et une classe peut étendre qu'une seule autre classe. Mais parfois, cela semble limitant. Par exemple, nous avons une classe `StreetSweeper` et une classe `Bicycle`, et nous voulons faire leur mélange : un `StreetSweepingBicycle`. Ou nous avons une classe `User` et une classe `EventEmitter` qui implémente la génération d'événements, et nous aimerions ajouter la fonctionnalité de `EventEmitter` à `User` afin que nos utilisateurs puissent émettre des événements. Il existe un concept qui peut aider ici, appelé "mixins". Comme défini dans Wikipedia, un [mixin](https://fr.wikipedia.org/wiki/Mixin) est une classe contenant des méthodes qui peuvent être utilisées par d'autres classes sans avoir à en hériter. En d'autres termes, un *mixin* fournit des méthodes qui implémentent un certain comportement, mais nous ne l'utilisons pas seul, nous l'utilisons pour ajouter le comportement à d'autres classes. ## Un exemple de mixin Le moyen le plus simple d'implémenter un mixin en JavaScript est de créer un objet avec des méthodes utiles, de sorte que nous puissions facilement les fusionner dans un prototype de n'importe quelle classe. Par exemple ici, le mixin `sayHiMixin` est utilisé pour ajouter un peu de "discours" à `User` : ```js run *!* // mixin */!* let sayHiMixin = { sayHi() { alert(`Hello ${this.name}`); }, sayBye() { alert(`Bye ${this.name}`); } }; *!* // usage: */!* class User { constructor(name) { this.name = name; } } // copier les méthodes Object.assign(User.prototype, sayHiMixin); // maintenant User peut dire bonjour new User("Dude").sayHi(); // Hello Dude! ``` Il n'y a pas d'héritage, mais une simple copie de méthode. Ainsi, `User` peut hériter d'une autre classe et inclure le mixin pour ajouter les méthodes supplémentaires, comme ceci : ```js class User extends Person { // ... } Object.assign(User.prototype, sayHiMixin); ``` Les mixins peuvent utiliser l'héritage à l'intérieur d'eux-mêmes. Par exemple, ici `sayHiMixin` hérite de `sayMixin` : ```js run let sayMixin = { say(phrase) { alert(phrase); } }; let sayHiMixin = { __proto__: sayMixin, // (ou nous pourrions utiliser Object.setPrototypeOf pour définir le prototype ici) sayHi() { *!* // appeler la méthode du parent */!* super.say(`Hello ${this.name}`); // (*) }, sayBye() { super.say(`Bye ${this.name}`); // (*) } }; class User { constructor(name) { this.name = name; } } // copier les méthodes Object.assign(User.prototype, sayHiMixin); // maintenant User peut dire bonjour new User("Dude").sayHi(); // Hello Dude! ``` Veuillez noter que l’appel à la méthode du parent `super.say()` à partir de `sayHiMixin` (aux lignes étiquetées avec `(*)`) recherche la méthode dans le prototype de ce mixin, pas la classe. Voici le schéma (voir la partie droite) : ![](mixin-inheritance.svg) C'est parce que les méthodes `sayHi` et `sayBye` ont été initialement créées dans `sayHiMixin`. Ainsi, même si elles ont été copiées, leur propriété interne `[[HomeObject]]` fait référence à `sayHiMixin`, comme indiqué sur l’image ci-dessus. Comme `super` cherche des méthodes du parent dans `[[HomeObject]].[[Prototype]]`, cela signifie qu'il cherche `sayHiMixin.[[Prototype]]`. ## EventMixin Faisons maintenant un mixin concret. Une caractéristique importante de nombreux objets de navigateur (par exemple) est qu'ils peuvent générer des événements. Les événements sont un excellent moyen de "diffuser des informations" à tous ceux qui le souhaitent. Faisons donc un mixin qui permet d’ajouter facilement des fonctions relatives aux événements à n’importe quelle classe/objet. - Le mixin fournira une méthode `.trigger(name, [... data])` pour "générer un événement" quand quelque chose d'important lui arrive. L'argument `name` est un nom de l'événement, éventuellement suivi d'arguments supplémentaires avec les données d'événement. - Également la méthode `.on(name, handler)` qui ajoute la fonction `handler` en tant qu'écouteur aux événements portant le nom donné. Il sera appelé lorsqu’un événement avec le `name` donné se déclenche, et récupérera les arguments de l’appel `.trigger`. - ... Et la méthode `.off(name, handler)` qui supprime le programme d'écoute `handler`. Après avoir ajouté le mixin, un objet `user` sera capable de générer un événement `"login"` lorsque le visiteur se connectera. Un autre objet, par exemple, `calendar` peut vouloir écouter de tels événements pour charger le calendrier de la personne connectée. Ou bien, un `menu` peut générer l'événement `"select"` lorsqu'un élément de menu est sélectionné, et d'autres objets peuvent affecter des gestionnaires pour réagir à cet événement. Etc. Voici le code : ```js run let eventMixin = { /** * Souscrire à l'événement, usage : * menu.on('select', function(item) { ... } */ on(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { this._eventHandlers[eventName] = []; } this._eventHandlers[eventName].push(handler); }, /** * Annuler la souscription, usage : * menu.off('select', handler) */ off(eventName, handler) { let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for (let i = 0; i < handlers.length; i++) { if (handlers[i] === handler) { handlers.splice(i--, 1); } } }, /** * Générer un événement avec le nom et les données donnés * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers?.[eventName]) { return; // aucun gestionnaire pour ce nom d'événement } // appeler les gestionnaires this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; ``` - `.on(eventName, handler)` - assigne la fonction `handler` à exécuter lorsque l'événement portant ce nom se produit. Techniquement, il existe une propriété `_eventHandlers`, qui stocke un tableau de gestionnaires pour chaque nom d'événement, et il l'ajoute simplement à la liste. - `.off(eventName, handler)` - supprime la fonction de la liste des gestionnaires. - `.trigger(eventName, ... args)` - génère l'événement : tous les gestionnaires de `_eventHandlers[eventName]` sont appelés, avec une liste d'arguments `...args`. Usage : ```js run // Créez une classe class Menu { choose(value) { this.trigger("select", value); } } // Ajouter le mixin avec les méthodes liées aux événements Object.assign(Menu.prototype, eventMixin); let menu = new Menu(); // ajouter un gestionnaire, à appeler lors de la sélection : *!* menu.on("select", value => alert(`Value selected: ${value}`)); */!* // déclenche l'événement => le gestionnaire ci-dessus s'exécute et affiche : // Value selected: 123 menu.choose("123"); ``` Maintenant, si nous souhaitons que le code réagisse lors de la sélection du menu, nous pouvons l'écouter avec `menu.on(...)`. Et le mixin `eventMixin` facilite l'ajout d'un tel comportement à autant de classes que nous le voudrions, sans interférer avec la chaîne d'héritage. ## Résumé *Mixin* est un terme générique de programmation orienté objet : une classe contenant des méthodes pour d’autres classes. D'autres langages autorisent l'héritage multiple. JavaScript ne prend pas en charge l'héritage multiple, mais les mixins peuvent être implémentés en copiant les méthodes dans le prototype. Nous pouvons utiliser les mixins comme moyen d'ajouter à une classe plusieurs comportements, comme la gestion d'événements, comme nous l'avons vu ci-dessus. Les mixins peuvent devenir un point de conflit s'ils écrasent accidentellement les méthodes de classe existantes. En règle générale, il convient de bien réfléchir aux méthodes de nommage d’un mixin, afin de minimiser la probabilité que cela se produise. ================================================ FILE: 1-js/09-classes/07-mixins/head.html ================================================ ================================================ FILE: 1-js/09-classes/index.md ================================================ # Classes ================================================ FILE: 1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md ================================================ La différence devient évidente quand on regarde le code dans une fonction. Le comportement est différent s'il y a un "saut en dehors" de `try..catch`. Par exemple, quand il y a un `return` dans `try..catch`. La clause `finally` fonctionne en cas de *toute* sortie de `try..catch`, même via l'instruction `return` : juste après la fin de `try..catch`, mais avant que le code appelant obtienne le contrôle. ```js run function f() { try { alert('start'); *!* return "result"; */!* } catch (err) { // ... } finally { alert('cleanup!'); } } f(); // cleanup! ``` ...Ou quand il y a un `throw`, comme ici : ```js run function f() { try { alert('start'); throw new Error("an error"); } catch (err) { // ... if("can't handle the error") { *!* throw err; */!* } } finally { alert('cleanup!') } } f(); // cleanup! ``` C'est `finally` qui garantit le nettoyage ici. Si nous mettons simplement le code à la fin de `f`, il ne fonctionnerait pas dans ces situations. ================================================ FILE: 1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md ================================================ importance: 5 --- # Finally ou juste le code ? Comparez les deux fragments de code. 1. Le premier utilise `finally` pour exécuter le code après `try..catch` : ```js try { work work } catch (err) { handle errors } finally { *!* cleanup the working space */!* } ``` 2. Le deuxième fragment fait le nettoyage juste après `try..catch` : ```js try { work work } catch (err) { handle errors } *!* cleanup the working space */!* ``` Nous avons absolument besoin du nettoyage après le travail, peu importe qu'il y ait une erreur ou non. Y at-il un avantage ici à utiliser `finally` ou les deux fragments de code sont égaux ? Si un tel avantage existe, donnez un exemple lorsque cela compte. ================================================ FILE: 1-js/10-error-handling/1-try-catch/article.md ================================================ # Gestion des erreurs, "try...catch" Peu importe notre niveau en programmation, nos scripts comportent parfois des erreurs. Elles peuvent être dues à nos erreurs, à une entrée utilisateur imprévue, à une réponse erronée du serveur et à mille autres raisons. Généralement, un script "meurt" (s'arrête immédiatement) en cas d'erreur, en l'affichant dans la console. Mais il existe une structure de syntaxe `try...catch` qui permet au script "d'attraper" les erreurs et, au lieu de mourir en cas de problème, de faire quelque chose de plus raisonnable. ## La syntaxe "try...catch" La structure `try...catch` a deux blocs principaux : `try`, puis `catch` : ```js try { // code... } catch (err) { // Gestion des erreurs } ``` Cela fonctionne comme ceci : 1. Tout d'abord, le code dans `try {...}` est exécuté. 2. S'il n'y a pas eu d'erreur, alors `catch(err)` est ignoré : l'exécution arrive à la fin de `try` et continue en ignorant `catch`. 3. Si une erreur survient, alors l'exécution de `try` est arrêtée et le contrôle se place au début de `catch(err)`. La variable `err` (qui peut utiliser n'importe quel nom) contient un objet d'erreur avec des détails sur ce qui s'est passé. ![](try-catch-flow.svg) Donc, une erreur dans le bloc `try {...}` ne tue pas le script -- nous avons une opportunité de la gérer dans `catch`. Voyons des exemples. - Un exemple sans erreur : affiche `alert` `(1)` et `(2)` : ```js run try { alert('Start of try runs'); // *!*(1) <--*/!* // ...pas d'erreur alert('End of try runs'); // *!*(2) <--*/!* } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) } ``` - Un exemple avec une erreur : montre `(1)` et `(3)` : ```js run try { alert('Start of try runs'); // *!*(1) <--*/!* *!* lalala; // error, variable is not defined! */!* alert('End of try (never reached)'); // (2) } catch (err) { alert(`Error has occurred!`); // *!*(3) <--*/!* } ``` ````warn header="`try...catch` ne fonctionne que pour les erreurs d'exécution" Pour que `try...catch` fonctionne, le code doit être exécutable. En d'autres termes, le code doit être du JavaScript valide. Cela ne fonctionnera pas si le code est syntaxiquement incorrect, par exemple, il a des accolades inégalées : ```js run try { {{{{{{{{{{{{ } catch (err) { alert("The engine can't understand this code, it's invalid"); } ``` Le moteur JavaScript lit d'abord le code, puis l'exécute. Les erreurs qui se produisent lors de la phase de lecture sont appelées erreurs "d'analyse" et sont irrécupérables (de l'intérieur de ce code). C'est parce que le moteur ne peut pas comprendre le code. Ainsi, `try...catch` ne peut gérer que les erreurs qui se produisent dans du code valide. De telles erreurs sont appelées "erreurs d'exécution" ou, parfois, "exceptions". ```` ````warn header="`try...catch` fonctionne de manière synchrone" Si une exception se produit dans le code "planifié", comme dans `setTimeout`, `try...catch` ne l'attrapera pas : ```js run try { setTimeout(function() { noSuchVariable; // le script mourra ici }, 1000); } catch (err) { alert( "won't work" ); } ``` C’est parce que la fonction elle-même est exécutée ultérieurement, lorsque le moteur a déjà quitté la structure `try...catch`. Pour capturer une exception dans une fonction planifiée, `try...catch` doit être à l'intérieur de cette fonction : ```js run setTimeout(function() { try { noSuchVariable; // try...catch gère l'erreur ! } catch { alert( "error is caught here!" ); } }, 1000); ``` ```` ## Objet d'erreur En cas d'erreur, JavaScript génère un objet contenant les détails à son sujet. L'objet est ensuite passé en argument à `catch` : ```js try { // ... } catch(err) { // <-- "l'objet d'erreur", pourrait utiliser un autre mot au lieu de err // ... } ``` Pour toutes les erreurs intégrées, l'objet d'erreur a deux propriétés principales : `name` : Nom de l'erreur. Par exemple, pour une variable non définie, il s'agit de `"ReferenceError"`. `message` : Message textuel sur les détails de l'erreur. Il existe d'autres propriétés non standard disponibles dans la plupart des environnements. L'un des plus largement utilisés et supportés est : `stack` : Pile d'exécution en cours : chaîne contenant des informations sur la séquence d'appels imbriqués ayant entraîné l'erreur. Utilisé à des fins de débogage. Par exemple : ```js run untrusted try { *!* lalala; // error, variable is not defined! */!* } catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) // Peut aussi montrer une erreur dans son ensemble // L'erreur est convertie en chaîne comme "name: message" alert(err); // ReferenceError: lalala is not defined } ``` ## Omission facultative pour "catch" [recent browser=new] Si nous n'avons pas besoin de détails d'erreur, `catch` peut l'omettre : ```js try { // ... } catch { // <-- sans (err) // ... } ``` ## L'utilisation de "try..catch" Explorons un cas d'utilisation réel de `try...catch`. Comme nous le savons déjà, JavaScript prend en charge la méthode [JSON.parse(str)](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/JSON/parse) pour lire les valeurs encodées en JSON. Il est généralement utilisé pour décoder les données reçues sur le réseau, par le serveur ou par une autre source. Nous le recevons et appelons `JSON.parse` comme ceci : ```js run let json = '{"name":"John", "age": 30}'; // données du serveur *!* let user = JSON.parse(json); // convertir la représentation textuelle en objet JS */!* // maintenant, user est un objet avec les propriétés de la chaîne alert( user.name ); // John alert( user.age ); // 30 ``` Vous trouverez des informations plus détaillées sur JSON dans le chapitre . **Si `json` est malformé, `JSON.parse` génère une erreur, de sorte que le script "meurt".** Devrions-nous en être satisfaits ? Bien sûr que non ! De cette façon, si quelque chose ne va pas avec les données, le visiteur ne le saura jamais (à moins d'ouvrir la console du développeur). Et les gens n'aiment vraiment pas quand quelque chose "meurt" sans aucun message d'erreur. Utilisons `try...catch` pour gérer l'erreur : ```js run let json = "{ bad json }"; try { *!* let user = JSON.parse(json); // <-- quand une erreur se produit... */!* alert( user.name ); // ne fonctionne pas } catch (err) { *!* // ...l'exécution saute ici alert( "Our apologies, the data has errors, we'll try to request it one more time." ); alert( err.name ); alert( err.message ); */!* } ``` Ici, nous utilisons le bloc `catch` uniquement pour afficher le message, mais nous pouvons faire beaucoup plus : envoyer une nouvelle requête réseau, suggérer une alternative au visiteur, envoyer des informations sur l'erreur à un système de journalisation, ... Bien mieux que de juste mourir. ## Lever nos propres exceptions Que se passe-t-il si `json` est correct du point de vue syntaxique, mais qu'il n'a pas de propriété requise `name` ? Comme ceci : ```js run let json = '{ "age": 30 }'; // données incomplètes try { let user = JSON.parse(json); // <-- pas d'erreurs *!* alert( user.name ); // pas de "name" ! */!* } catch (err) { alert( "doesn't execute" ); } ``` Ici, `JSON.parse` fonctionne normalement, mais l'absence de `name` est en réalité une erreur pour nous. Pour unifier le traitement des erreurs, nous allons utiliser l'opérateur `throw`. ### L'instruction "throw" L'instruction `throw` génère une erreur. La syntaxe est la suivante : ```js throw ``` Techniquement, on peut utiliser n'importe quoi comme objet d'erreur. Cela peut même être une primitive, comme un nombre ou une chaîne, mais il est préférable d’utiliser des objets, de préférence avec les propriétés `name` et `message` (pour rester quelque peu compatibles avec les erreurs intégrées). JavaScript comporte de nombreux constructeurs intégrés pour les erreurs standards : `Error`, `SyntaxError`, `ReferenceError`, `TypeError` et autres. Nous pouvons également les utiliser pour créer des objets d'erreur. Leur syntaxe est la suivante : ```js let error = new Error(message); // ou let error = new SyntaxError(message); let error = new ReferenceError(message); // ... ``` Pour les erreurs intégrées (pas pour les objets, mais pour les erreurs), la propriété `name` est exactement le nom du constructeur. Et `message` est tiré de l'argument. Par exemple : ```js run let error = new Error("Things happen o_O"); alert(error.name); // Error alert(error.message); // Things happen o_O ``` Voyons quel type d'erreur `JSON.parse` génère : ```js run try { JSON.parse("{ bad json o_O }"); } catch (err) { *!* alert(err.name); // SyntaxError */!* alert(err.message); // Unexpected token b in JSON at position 2 } ``` Comme on peut le constater, c’est une `SyntaxError`. Et dans notre cas, l’absence de `name` est une erreur, car les utilisateurs doivent avoir un `name`. Alors utilisons `throw` : ```js run let json = '{ "age": 30 }'; // données incomplètes try { let user = JSON.parse(json); // <-- pas d'erreurs if (!user.name) { *!* throw new SyntaxError("Incomplete data: no name"); // (*) */!* } alert( user.name ); } catch (err) { alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name } ``` À la ligne `(*)`, l'instruction `throw` génère une `SyntaxError` avec le `message` donné, de la même manière que JavaScript le générerait lui-même. L'exécution de `try` s'arrête immédiatement et le flux de contrôle saute dans `catch`. Maintenant, `catch` est devenu un emplacement unique pour toutes les erreurs de traitement : à la fois pour `JSON.parse` et d'autres cas. ## Propager une exception Dans l'exemple ci-dessus, nous utilisons `try...catch` pour gérer des données incorrectes. Mais est-il possible qu’*une autre erreur inattendue* se produise dans le bloc `try {...}` ? Comme une erreur de programmation (variable is not defined) ou quelque chose d'autre, pas seulement cette "donnée incorrecte". Par exemple : ```js run let json = '{ "age": 30 }'; // données incomplètes try { user = JSON.parse(json); // <-- oublié de mettre "let" avant user // ... } catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (aucune erreur JSON) } ``` Bien sûr, tout est possible ! Les programmeurs font des erreurs. Même dans les utilitaires à code source ouvert utilisés par des millions de personnes pendant des décennies, on découvre soudainement un bug qui conduit à de terribles piratages. Dans notre cas, `try...catch` est destiné à intercepter les erreurs "incorrect data". Mais par sa nature, `catch` obtient *toutes* les erreurs de `try`. Ici, une erreur inattendue se produit, mais le même message `"JSON Error"` est toujours affiché. C'est faux et rend également le code plus difficile à déboguer. Pour éviter de tels problèmes, nous pouvons utiliser la technique du "rethrowing" (re-lancement). La règle est simple : **Catch ne doit traiter que les erreurs qu'il connaît et "renvoyer" toutes les autres.** La technique "rethrowing" peut être expliqué plus en détail comme : 1. Catch obtient toutes les erreurs. 2. Dans le bloc `catch (err) {...}` nous analysons l'objet d'erreur `err`. 3. Si nous ne savons pas comment le gérer, nous faisons `throw err`. Habituellement, nous pouvons vérifier le type d'erreur en utilisant l'opérateur `instanceof` : ```js run try { user = { /*...*/ }; } catch (err) { *!* if (err instanceof ReferenceError) { */!* alert('ReferenceError'); // "ReferenceError" pour avoir accédé à une variable non définie } } ``` Nous pouvons également obtenir le nom de la classe d'erreur à partir de la propriété `err.name`. Toutes les erreurs natives l'ont. Une autre option est de lire `err.constructor.name`. Dans le code ci-dessous, nous utilisons la technique de "propagation" pour que `catch` ne traite que `SyntaxError` : ```js run let json = '{ "age": 30 }'; // données incomplètes try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("Incomplete data: no name"); } *!* blabla(); // erreur inattendue */!* alert( user.name ); } catch (err) { *!* if (err instanceof SyntaxError) { alert( "JSON Error: " + err.message ); } else { throw err; // propager (*) } */!* } ``` L’erreur de l’instruction `throw` à la ligne `(*)` de l’intérieur du bloc `catch` "sort" de `try...catch` et peut être soit capturée par une structure `try...catch` externe (si elle existe), soit elle arrête le script. Ainsi, le bloc `catch` ne traite que les erreurs qu’il sait gérer et "ignore" toutes les autres. L'exemple ci-dessous montre comment de telles erreurs peuvent être capturées par un niveau supplémentaire de `try...catch` : ```js run function readData() { let json = '{ "age": 30 }'; try { // ... *!* blabla(); // error! */!* } catch (err) { // ... if (!(err instanceof SyntaxError)) { *!* throw err; // propager l'erreur (ne sachant pas comment la gérer) */!* } } } try { readData(); } catch (err) { *!* alert( "External catch got: " + err ); // attrapé ! */!* } ``` Ici, `readData` ne sait que gérer `SyntaxError`, alors que le `try...catch` extérieur sait comment tout gérer. ## try...catch...finally Mais ce n'est pas tout. La structure `try...catch` peut avoir un bloc de code supplémentaire : `finally`. S'il existe, il s'exécute dans tous les cas : - après `try`, s'il n'y a pas eu d'erreur, - après `catch`, s'il y a eu des erreurs. La syntaxe étendue ressemble à ceci : ```js *!*try*/!* { ... try to execute the code ... } *!*catch*/!* (err) { ... handle errors ... } *!*finally*/!* { ... execute always ... } ``` Essayez d'exécuter ce code : ```js run try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); } catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); } ``` Ce code a deux manières de s'exécuter : 1. Si vous répondez "Yes" à "Make an error?", Alors `try -> catch -> finally`. 2. Si vous dites "No", alors `try -> finally`. La clause `finally` est souvent utilisée lorsque nous commençons à faire quelque chose et que nous voulons le finaliser dans tous les cas de figure. Par exemple, nous voulons mesurer le temps que prend une fonction de nombre de Fibonacci `fib(n)`. Naturellement, nous pouvons commencer à mesurer avant l'exécution et finir ensuite. Mais que se passe-t-il s'il y a une erreur lors de l'appel de la fonction ? En particulier, la mise en oeuvre de `fib(n)` dans le code ci-dessous renvoie une erreur pour les nombres négatifs ou non entiers. La clause `finally` est un bon endroit pour finir les mesures, quoi qu’il arrive. Ici, `finally` garantit que le temps sera correctement mesuré dans les deux situations - en cas d’exécution réussie de `fib` et en cas d’erreur : ```js run let num = +prompt("Enter a positive integer number.", 35) let diff, result; function fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("Must not be negative, and also an integer."); } return n <= 1 ? n : fib(n - 1) + fib(n - 2); } let start = Date.now(); try { result = fib(num); } catch (err) { result = 0; *!* } finally { diff = Date.now() - start; } */!* alert(result || "error occurred"); alert( `execution took ${diff}ms` ); ``` Vous pouvez vérifier en exécutant le code en entrant `35` dans `prompt` - il s'exécute normalement, `finally` après `try`. Puis entrez `-1` -- il y aura une erreur immédiate, puis l'exécution prendra `0ms`. Les deux mesures sont effectuées correctement. En d'autres termes, la fonction peut finir par `return` ou `throw`, cela n'a pas d'importance. La clause `finally` s'exécute dans les deux cas. ```smart header="Les variables sont locales à l'intérieur de `try...catch...finally`" Veuillez noter que les variables `result` et `diff` dans le code ci-dessus sont déclarées *avant* `try...catch`. Sinon, si nous déclarions `let` dans le bloc `try`, il ne serait visible qu'à l'intérieur de celui-ci. ``` ````smart header="`finally` et `return`" La clause `finally` fonctionne pour *toute* sortie de `try...catch`. Cela inclut un `return` explicite. Dans l'exemple ci-dessous, il y a `return` dans `try`. Dans ce cas, `finally` est exécuté juste avant que le contrôle ne retourne au code externe. ```js run function func() { try { *!* return 1; */!* } catch (err) { /* ... */ } finally { *!* alert( 'finally' ); */!* } } alert( func() ); // en premier l'alert du `finally`, puis celle-ci (`1`) ``` ```` ````smart header="`try...finally`" La structure `try...finally`, sans la clause `catch`, est également utile. Nous l'appliquons lorsque nous ne voulons pas gérer les erreurs ici (les laisser passer), mais nous voulons être sûrs que les processus que nous avons démarrés sont finalisés. ```js function func() { // commencer à faire quelque chose qui doit être complété (comme des mesures) try { // ... } finally { // compléter cette chose, même si tout meurt } } ``` Dans le code ci-dessus, une erreur à l'intérieur de `try` ressort toujours, car il n'y a pas de `catch`. Mais `finally` fonctionne avant que le flux d’exécution ne quitte la fonction. ```` ## Catch global ```warn header="Spécifique à l'environnement" Les informations de cette section ne font pas partie du JavaScript de base. ``` Imaginons que nous ayons une erreur fatale en dehors de `try...catch` et que le script soit mort. Comme une erreur de programmation ou une autre chose terrible. Y a-t-il un moyen de réagir à de tels événements ? Nous pouvons vouloir enregistrer l'erreur, montrer quelque chose à l'utilisateur (normalement, ils ne voient pas les messages d'erreur), etc. Il n'y en a pas dans la spécification, mais les environnements le fournissent généralement, car c'est vraiment utile. Par exemple, Node.js a [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) pour ça. Et dans le navigateur, nous pouvons attribuer une fonction à la propriété [window.onerror](https://developer.mozilla.org/fr/docs/Web/API/GlobalEventHandlers/onerror), qui fonctionnera en cas d'erreur non interceptée. La syntaxe : ```js window.onerror = function(message, url, line, col, error) { // ... }; ``` `message` : Message d'erreur. `url` : URL du script où l'erreur s'est produite. `line`, `col` : Numéros de ligne et de colonne où une erreur s'est produite. `error` : Objet d'erreur. Par exemple : ```html run untrusted refresh height=1 ``` Le rôle du gestionnaire global `window.onerror` est généralement de ne pas récupérer l'exécution du script - c'est probablement impossible en cas d'erreur de programmation, mais d'envoyer le message d'erreur aux développeurs. Il existe également des services Web fournissant un journal des erreurs pour de tels cas, comme ou . Ils fonctionnent comme ceci : 1. Nous nous inscrivons au service et obtenons un morceau de JS (ou une URL de script) à insérer sur des pages. 2. Ce script JS définit une fonction `window.onerror` personnalisée. 3. Lorsqu'une erreur se produit, une demande réseau à ce sujet est envoyée au service. 4. Nous pouvons nous connecter à l'interface Web du service et voir les erreurs. ## Résumé La structure `try...catch` permet de gérer les erreurs d'exécution. Cela permet littéralement "d'essayer" (`try`) d'exécuter le code et "d’attraper" (`catch`) les erreurs qui peuvent s'y produire. La syntaxe est la suivante : ```js try { // exécuter ce code } catch(err) { // si une erreur s'est produite, alors saute ici // err est l'objet d'erreur } finally { // faire dans tous les cas après try / catch } ``` Il peut ne pas y avoir de section `catch` ou de `finally`, donc les structures plus courtes `try...catch` et `try...finally` sont également valides. Les objets d'erreur ont les propriétés suivantes : - `message` - le message d'erreur. - `name` - la chaîne avec le nom d'erreur (nom du constructeur de l'erreur). - `stack` (non standard, mais bien supporté) - la pile d'exécution au moment de la création de l'erreur. Si un objet d'erreur n'est pas nécessaire, nous pouvons l'omettre en utilisant `catch {...}` au lieu de `catch(err) {...}`. Nous pouvons également générer nos propres erreurs en utilisant l'opérateur `throw`. Techniquement, l'argument de `throw` peut être n'importe quoi, mais il s'agit généralement d'un objet d'erreur héritant de la classe `Error` intégrée. Plus d'informations sur l'extension des erreurs dans le chapitre suivant. La technique de *propagation* est un modèle très important de gestion des erreurs : un bloc `catch` s'attend généralement à un type d'erreur particulier et sait comment le gérer, il doit donc "propager" (renvoyer) les erreurs qu'il ne connaît pas. Même si nous n'avons pas `try...catch`, la plupart des environnements permettent de configurer un gestionnaire d'erreurs "globales" pour intercepter les erreurs qui "tombent". Dans le navigateur, c'est `window.onerror`. ================================================ FILE: 1-js/10-error-handling/2-custom-errors/1-format-error/solution.md ================================================ ```js run untrusted class FormatError extends SyntaxError { constructor(message) { super(message); this.name = this.constructor.name; } } let err = new FormatError("formatting error"); alert( err.message ); // formatting error alert( err.name ); // FormatError alert( err.stack ); // stack alert( err instanceof SyntaxError ); // true ``` ================================================ FILE: 1-js/10-error-handling/2-custom-errors/1-format-error/task.md ================================================ importance: 5 --- # Hériter de SyntaxError Créez une classe `FormatError` qui hérite de la classe `SyntaxError` intégrée. Il devrait supporter les propriétés `message`, `name` et `stack`. Exemple d'utilisation : ```js let err = new FormatError("formatting error"); alert( err.message ); // formatting error alert( err.name ); // FormatError alert( err.stack ); // stack alert( err instanceof FormatError ); // true alert( err instanceof SyntaxError ); // true (hérite de SyntaxError) ``` ================================================ FILE: 1-js/10-error-handling/2-custom-errors/article.md ================================================ # Les erreurs personnalisées, extension de Error Lorsque nous développons quelque chose, nous avons souvent besoin de nos propres classes d'erreur pour refléter des problèmes spécifiques qui peuvent mal tourner dans nos tâches. Pour les erreurs dans les opérations réseau, nous aurons peut-être besoin de `HttpError`, pour les opérations de base de données `DbError`, pour les opérations de recherche `NotFoundError`, etc. Nos erreurs devraient prendre en charge des propriétés d'erreur de base telles que `message`, `name` et, de préférence, `stack`. Mais elles peuvent aussi avoir d’autres propriétés propres, par exemple les objets `HttpError` peuvent avoir une propriété `statusCode` avec une valeur telle que `404` ou `403` ou `500`. JavaScript permet d'utiliser `throw` avec n'importe quel argument. Par conséquent, techniquement, nos classes d'erreur personnalisées n'ont pas besoin d'hériter de `Error`. Mais si nous héritons, il devient alors possible d'utiliser `obj instanceof Error` pour identifier les objets d'erreur. Il vaut donc mieux en hériter. Au fur et à mesure que l'application grandit, nos propres erreurs forment naturellement une hiérarchie. Par exemple, `HttpTimeoutError` peut hériter de `HttpError`, etc. ## Étendre Error A titre d'exemple, considérons une fonction `readUser(json)` qui devrait lire JSON avec des données utilisateur. Voici un exemple de l'apparence d'un `json` valide : ```js let json = `{ "name": "John", "age": 30 }`; ``` En interne, nous utiliserons `JSON.parse`. S'il reçoit un `json` malformé, il renvoie `SyntaxError`. Mais même si `json` est syntaxiquement correct, cela ne signifie pas que c'est un utilisateur valide, non ? Il peut manquer les données nécessaires. Par exemple, il peut ne pas avoir les propriétés `name` et `age` qui sont essentielles pour nos utilisateurs. Notre fonction `readUser(json)` va non seulement lire JSON, mais aussi vérifier ("valider") les données. S'il n'y a pas de champs obligatoires ou si le format est incorrect, c'est une erreur. Et ce n’est pas une `SyntaxError`, car les données sont syntaxiquement correctes, mais un autre type d’erreur. Nous l'appellerons `ValidationError` et créerons une classe pour cela. Une erreur de ce type devrait également comporter des informations sur le champ fautif. Notre classe `ValidationError` devrait hériter de la classe `Error`. La class `Error` est une classe intégrée, voici le code approximatif pour que nous comprenions ce que nous étendons : ```js // Le "pseudocode" pour la classe d'erreur intégrée définie par JavaScript lui-même class Error { constructor(message) { this.message = message; this.name = "Error"; // (noms différents pour différentes classes d'erreur intégrées) this.stack = ; // non standard, mais la plupart des environnements le supportent } } ``` Maintenant, héritons de `ValidationError` et mettons-le en action : ```js run *!* class ValidationError extends Error { */!* constructor(message) { super(message); // (1) this.name = "ValidationError"; // (2) } } function test() { throw new ValidationError("Whoops!"); } try { test(); } catch(err) { alert(err.message); // Whoops! alert(err.name); // ValidationError alert(err.stack); // une liste des appels imbriqués avec le numéro de ligne pour chacun d'entre eux } ``` Remarque : à la ligne `(1)`, nous appelons le constructeur parent. JavaScript exige que nous appelions `super` dans le constructeur de l'enfant, donc c'est obligatoire. Le constructeur parent définit la propriété `message`. Le constructeur parent définit également la propriété `name` sur `"Error"`, donc à la ligne `(2)` nous la réinitialisons à la valeur correcte. Essayons de l'utiliser dans `readUser(json)` : ```js run class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } // Usage function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new ValidationError("No field: age"); } if (!user.name) { throw new ValidationError("No field: name"); } return user; } // un example avec try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* alert("Invalid data: " + err.message); // Invalid data: No field: name */!* } else if (err instanceof SyntaxError) { // (*) alert("JSON Syntax Error: " + err.message); } else { throw err; // erreur inconnue, on la propage (**) } } ``` Le bloc `try..catch` dans le code ci-dessus gère à la fois notre `ValidationError` et le `SyntaxError` intégré de `JSON.parse`. Veuillez regarder comment nous utilisons `instanceof` pour vérifier le type d'erreur spécifique à la ligne `(*)`. Nous pourrions aussi regarder `err.name`, comme ceci : ```js // ... // au lieu de (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ... ``` La version `instanceof` est bien meilleure, car dans le futur nous allons étendre `ValidationError`, en créer des sous-types, comme `PropertyRequiredError`. Et `instanceof` continuera à fonctionner pour les nouvelles classes héritées. Donc, c'est à l'épreuve du futur. Il est également important que si `catch` rencontre une erreur inconnue, il la renvoie à la ligne `(**)`. Le bloc `catch` ne sait gérer que les erreurs de validation et de syntaxe, les autres types (causés par une faute de frappe dans le code ou d'autres raisons inconnues) devraient êtres propagés. ## Héritage complémentaire La classe `ValidationError` est très générique. Beaucoup de choses peuvent mal se passer. La propriété peut être absente ou dans un format incorrect (comme une valeur de chaîne de caractères pour `age` au lieu d’un nombre). Faisons une classe plus concrète `PropertyRequiredError`, exactement pour les propriétés absentes. Elle contiendra des informations supplémentaires sur la propriété qui manque. ```js run class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } *!* class PropertyRequiredError extends ValidationError { constructor(property) { super("No property: " + property); this.name = "PropertyRequiredError"; this.property = property; } } */!* // Usage function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } return user; } // example avec try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* alert("Invalid data: " + err.message); // Invalid data: No property: name alert(err.name); // PropertyRequiredError alert(err.property); // name */!* } else if (err instanceof SyntaxError) { alert("JSON Syntax Error: " + err.message); } else { throw err; // erreur inconnue, on la propage } } ``` La nouvelle classe `PropertyRequiredError` est facile à utiliser : il suffit de passer le nom de la propriété : `new PropertyRequiredError(property)`. Le `message` est généré par le constructeur. Veuillez noter que `this.name` dans le constructeur `PropertyRequiredError` est à nouveau attribué manuellement. Cela peut devenir un peu fastidieux -- d'assigner `this.name = ` dans chaque classe d'erreur personnalisée. Nous pouvons l'éviter en créant notre propre classe "d'erreur de base" qui assigne `this.name = this.constructor.name`. Puis nous en ferons hériter toutes nos classes d'erreur personnalisées. Appelons cela `MyError`. Voici le code avec `MyError` et d'autres classes d'erreur personnalisées, simplifié : ```js run class MyError extends Error { constructor(message) { super(message); *!* this.name = this.constructor.name; */!* } } class ValidationError extends MyError { } class PropertyRequiredError extends ValidationError { constructor(property) { super("No property: " + property); this.property = property; } } // le nom est correcte alert( new PropertyRequiredError("field").name ); // PropertyRequiredError ``` Maintenant, les erreurs personnalisées sont beaucoup plus courtes, en particulier `ValidationError`, car nous nous sommes débarrassés de la ligne `"this.name = ..."` dans le constructeur. ## Le wrapping des exceptions Le but de la fonction `readUser` dans le code ci-dessus est "de lire les données de l'utilisateur". Il peut y avoir différents types d’erreurs dans le processus. À l'heure actuelle, nous avons `SyntaxError` et `ValidationError`, mais à l'avenir, la fonction `readUser` pourrait croître et générer probablement d'autres types d'erreurs. Le code qui appelle `readUser` devrait gérer ces erreurs. À l'heure actuelle, il utilise plusieurs if dans le bloc `catch`, qui vérifient la classe et gèrent les erreurs connues et rejettent les inconnues. Le schéma est le suivant : ```js try { ... readUser() // la source d'erreur potentielle ... } catch (err) { if (err instanceof ValidationError) { // handle validation errors } else if (err instanceof SyntaxError) { // handle syntax errors } else { throw err; // erreur inconnue, la relancer } } ``` Dans le code ci-dessus, nous pouvons voir deux types d'erreurs, mais il peut y en avoir plus. Si la fonction `readUser` génère plusieurs types d'erreurs, alors nous devrions nous demander : voulons-nous vraiment vérifier tous les types d'erreur un par un à chaque fois ? Souvent, la réponse est "non", nous aimerions être "un niveau au-dessus de tout cela". Nous voulons simplement savoir s'il y a eu une "erreur de lecture des données" -- pourquoi exactement cela s'est produit est souvent hors de propos (le message d'erreur le décrit). Ou, encore mieux, nous aimerions avoir un moyen d'obtenir les détails de l'erreur, mais seulement si nous en avons besoin. La technique que nous décrivons ici est appelée "encapsulation d'exceptions". 1. Nous allons créer une nouvelle classe `ReadError` pour représenter une erreur générique de "lecture des données". 2. La fonction `readUser` interceptera les erreurs de lecture de données qui se produisent à l'intérieur, telles que `ValidationError` et `SyntaxError`, et générera à la place une `ReadError`. 3. L'objet `ReadError` conservera la référence à l'erreur d'origine dans sa propriété `cause`. Ensuite, le code qui appelle `readUser` n'aura qu'à vérifier `ReadError`, pas pour tous les types d'erreurs de lecture de données. Et s'il a besoin de plus de détails sur une erreur, il peut vérifier sa propriété `cause`. Voici le code qui définit `ReadError` et illustre son utilisation dans `readUser` et `try..catch` : ```js run class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'ReadError'; } } class ValidationError extends Error { /*...*/ } class PropertyRequiredError extends ValidationError { /* ... */ } function validateUser(user) { if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } } function readUser(json) { let user; try { user = JSON.parse(json); } catch (err) { *!* if (err instanceof SyntaxError) { throw new ReadError("Syntax Error", err); } else { throw err; } */!* } try { validateUser(user); } catch (err) { *!* if (err instanceof ValidationError) { throw new ReadError("Validation Error", err); } else { throw err; } */!* } } try { readUser('{bad json}'); } catch (e) { if (e instanceof ReadError) { *!* alert(e); // Original error: SyntaxError: Unexpected token b in JSON at position 1 alert("Original error: " + e.cause); */!* } else { throw e; } } ``` Dans le code ci-dessus, `readUser` fonctionne exactement comme décrit - il intercepte les erreurs de syntaxe et de validation et propage des erreurs `ReadError` (les erreurs inconnues sont propagées comme d'habitude). Donc, le code externe vérifie `instanceof ReadError` et c'est tout. Pas besoin de lister tous les types d'erreur possibles. L'approche est appelée "encapsulation d'exceptions", car nous prenons les exceptions "de bas niveau" et les "encapsulons" dans `ReadError` qui est plus abstrait. Il est largement utilisé dans la programmation orientée objet. ## Résumé - Nous pouvons hériter de `Error` et d'autres classes d'erreurs intégrées normalement. Nous devons juste nous occuper de la propriété `name` et ne pas oublier d'appeler `super`. - Nous pouvons utiliser `instanceof` pour vérifier des erreurs particulières. Cela fonctionne aussi avec l'héritage. Mais parfois, nous avons un objet d'erreur provenant d'une bibliothèque tierce et il n'y a pas de moyen facile d'obtenir la classe. Dans ce cas, la propriété `name` peut être utilisée pour de telles vérifications. - Le wrapping des exceptions est une technique répandue : une fonction gère les exceptions de bas niveau et crée des erreurs de niveau supérieur au lieu de diverses erreurs de bas niveau. Les exceptions de bas niveau deviennent parfois des propriétés de cet objet comme `err.cause` dans les exemples ci-dessus, mais ce n'est pas strictement requis. ================================================ FILE: 1-js/10-error-handling/index.md ================================================ # La gestion des erreurs ================================================ FILE: 1-js/11-async/01-callbacks/article.md ================================================ # Introduction: callbacks ```warn header="Nous utilisons ici des méthodes du navigateur dans les exemples" Pour démontrer l'utilisation des callbacks, des promesses et d'autres concepts abstraits, nous utiliserons certaines méthodes du navigateur : plus précisément, nous chargerons des scripts et effectuerons des manipulations simples de documents. Si vous n'êtes pas familier avec ces méthodes, et que leur utilisation dans les exemples est confuse, vous pouvez lire quelques chapitres de la [partie suivante](/document) du tutoriel. Mais nous allons quand même essayer de rendre les choses claires. Il n'y aura rien de vraiment complexe au niveau du navigateur. ``` De nombreuses fonctions sont fournies par les environnements hôtes JavaScript qui vous permettent de planifier des actions *asynchrones*. En d'autres termes, des actions que nous lançons maintenant, mais qui se terminent plus tard. Par exemple, une de ces fonctions est la fonction `setTimeout`. Il existe d'autres exemples concrets d'actions asynchrones, par exemple le chargement de scripts et de modules (nous les aborderons dans les chapitres suivants). Regardez la fonction `loadScript(src)`, qui charge un script avec le `src` donné: ```js function loadScript(src) { // crée une balise ================================================ FILE: 1-js/11-async/02-promise-basics/03-animate-circle-promise/task.md ================================================ # Animer un cercle avec une promesse Ré-écrivez la fonction `showCircle` dans la solution de la tâche pour qu'elle renvoie une promesse au lieu d'une fonction de retour. La nouvelle utilisation : ```js showCircle(150, 150, 100).then(div => { div.classList.add('message-ball'); div.append("Hello, world!"); }); ``` Prenez la solution de la tâche comme base. ================================================ FILE: 1-js/11-async/02-promise-basics/article.md ================================================ # Promesse (promise) Imaginez que vous êtes un grand chanteur et les fans vous demandent jour et nuit votre prochaine chanson. Pour avoir un peu de paix, vous promettez de leur envoyer dès que celle-ci est publiée. Vous donnez à vos fans une liste d'abonnement. Ils peuvent y ajouter leur adresse mail, comme cela, quand le single est sorti, tous les emails reçoivent votre single. Et même si quelque chose arrive, comme un feu dans le studio, et que vous ne pouvez pas sortir le single, ils en seront aussi notifiés. Tout le monde est content : vous, puisque l'on vous laisse plus tranquille, et vos fans parce qu'ils savent qu'ils ne rateront pas la chanson. C'est une analogie réelle à un problème courant de programmation : 1. Un "producteur de code" qui réalise quelque chose mais nécessite du temps. Par exemple, un code qui charge des données à travers un réseau. C'est le "chanteur". 2. Un "consommateur de code" qui attend un résultat du "producteur de code" quand il est prêt. Beaucoup de fonctions peuvent avoir besoin de ce résultat. Ces fonctions sont les "fans". 3. Une *promesse* (promise) est un objet spécial en JavaScript qui lie le "producteur de code" et le "consommateur de code" ensemble. En comparant à notre analogie c'est la "liste d'abonnement". Le "producteur de code" prend le temps nécessaire pour produire le résultat promis, et la "promesse" donne le résultat disponible pour le code abonné quand c'est prêt. L'analogie n'est pas la plus correcte, car les promesses en JavaScript sont un peu plus complexes qu'une simple liste d'abonnement : elles ont d'autres possibilités mais aussi certaines limitations. Toutefois c'est suffisant pour débuter. La syntaxe du constructeur pour une promesse est : ```js let promise = new Promise(function(resolve, reject) { // L'exécuteur (le code produit, le "chanteur") }); ``` La fonction passée à `new Promise` est appelée l'*exécuteur*. Quand `new Promise` est créée, elle est lancée automatiquement. Elle contient le producteur de code, qui doit produire un résulat final. Dans l'analogie ci-dessus : l'exécuteur est le "chanteur". Ses arguments `resolve` (tenir) et `reject` (rompre) sont les fonctions de retour directement fournies par JavaScript. Notre code est inclus seulement dans l'exécuteur. Quand l'exécuteur obtient un résultat, qu'il soit rapide ou pas, cela n'a pas d'importance, il appellera une des deux fonctions de retour : - `resolve(value)` -  si la tâche s'est terminée avec succès, avec le résultat `value`. - `reject(error)` - si une erreur est survenue, `error` est l'objet erreur. Donc, pour résumer : l'exécuteur s'exécute automatiquement et tente d’effectuer un travail. Ensuite, il devrait appeler `resolve` s'il a réussi ou `reject` s'il y avait une erreur. L'objet `promise` retourné par le constructeur `new Promise` a des propriétés internes : - `state` (état) - initialement à `"pending"` (en attente), se change soit en `"fulfilled"` (tenue) lorsque `resolve` est appelé ou `"rejected"` (rompue) si `reject` est appelé. - `result` - initialement à `undefined` se change en `value` quand `resolve(value)` est appelé ou en `error` quand `reject(error)` est appelé. Ainsi l'exécuteur changera la promesse à un de ces états : ![](promise-resolve-reject.svg) Plus tard nous verrons comment les "fans" peuvent s'abonner à ces changements. Voici un exemple d'un constructeur d'une promesse et d'une fonction exécutrice simple avec un "code produit" qui prend du temps (utilisant `setTimeout`) : ```js run let promise = new Promise(function(resolve, reject) { // la fonction est exécutée automatiquement quand la promesse est construite // On signale au bout d'une seconde que la tâche est terminée avec le résultat "done" setTimeout(() => *!*resolve("done")*/!*, 1000); }); ``` On peut voir deux choses en lançant le code ci-dessus : 1. L'exécuteur est appelé automatiquement et immédiatement (avec `new Promise`). 2. L'exécuteur reçoit deux arguments : `resolve` et `reject` - ces deux fonctions sont pré-définies par le moteur JavaScript, ainsi nous n'avons pas besoin de les créer. Nous devons seulement appeler l'une ou l'autre quand le résultat est prêt. Après une seconde de "traitement" l'exécuteur appelle `resolve("done")` pour produire le résultat. Cela change l'état de l'objet `promise` : ![](promise-resolve-1.svg) Nous avons vu un exemple d'une tâche terminée avec succès, une promesse "tenue". Voyons maintenant un exemple d'un exécuteur rompant la promesse avec une erreur : ```js let promise = new Promise(function(resolve, reject) { // On signale après 1 seconde que la tâche est terminée avec une erreur setTimeout(() => *!*reject(new Error("Whoops!"))*/!*, 1000); }); ``` L'appel a `reject(...)` change l'object promesse à l'état `"rejected"` : ![](promise-reject-1.svg) Pour résumer, l'exécuteur devrait réaliser une tâche (normalement quelque chose qui prend du temps) puis appelle `resolve` ou `reject` pour changer l'état de l'objet promesse correspondant. Une promesse qui est soit tenue soit rejetée est appelée "settled" (acquitttée) par opposition à une promesse initialisée à "en attente". ````smart header="Il ne peut y avoir qu'un seul résultat ou une erreur" L'exécuteur devrait appeler seulement une fois `resolve` ou `reject`. N'importe quel changement d'état est définitif. Les appels supplémentaires à `resolve` et `reject` sont ignorés : ```js let promise = new Promise(function(resolve, reject) { *!* resolve("done"); */!* reject(new Error("…")); // ignoré setTimeout(() => resolve("…")); // ignoré }); ``` L'idée est que la tâche exécutée par un exécuteur ne peut avoir qu'un seul résultat ou une erreur. De plus, `resolve`/`reject` n'attend qu'un seul argument (ou aucun) et ignorera les arguments suivants. ```` ```smart header="Rompre avec l'objet `Error`" Dans le cas ou quelque chose se passe mal, l'exécuteur doit appeler `reject`. Cela est possible avec n'importe type d'argument (comme pour `resolve`). Mais il est plutôt recommandé d'utiliser l'objet `Error` (ou les objets en héritant). La raison va vous paraître évidente dans un instant. ``` ````smart header="Appel de `resolve`/`reject` immédiat" En pratique, un exécuteur réalise normalement une opération asynchrone et appelle `resolve`/`reject` après un certain temps, mais il n'est pas obligatoire d'être asynchrone. On peut aussi appeler immédiatement `resolve` ou `reject`, comme cela : ```js let promise = new Promise(function(resolve, reject) { // La tâche ne prend pas de temps resolve(123); // rend immédiatement le résultat : 123 }); ``` Par exemple, cela peut arriver quand nous commençons une tâche mais nous voyons que la tâche est déja réalisée et en cache. Pas de soucis. Nous acquittons immédiatement la promesse. ```` ```smart header="Le `state` et `result` est interne" Les propriétés `state` et `result` de l'objet `Promise` sont internes. Nous ne pouvons directement accéder à celles-ci. Nous pouvons utiliser `.then`/`.catch`/`.finally` pour cela. Elles sont décrites ci-dessous. ``` ## Les consommateurs : then, catch Un objet promesse permet le lien entre l'exécuteur (le "code produit" ou "chanteur") et les fonctions consommatrices (les "fans"), lesquels recevront un résultat ou une erreur. Ces fonctions consommatrices peuvent s'abonner (subscribed) en utilisant les méthodes `.then`, `.catch`. ### then (alors) Le plus important, le plus crucial est `.then`. La syntaxe est : ```js promise.then( function(result) { *!*/* gère un résultat correct */*/!* }, function(error) { *!*/* gère une erreur */*/!* } ); ``` Le premier argument de `.then` est une fonction qui se lance si la promesse est tenue, et reçoit le résultat. Le deuxième argument de `.then` est une fonction qui se lance si la promesse est rompue, et reçoit l'erreur. Par exemple, voyons la réponse à une requête correctement tenue : ```js run let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done!"), 1000); }); // resolve lance la première fonction dans .then promise.then( *!* result => alert(result), // affiche "done!" après 1 seconde */!* error => alert(error) // ne se lance pas ); ``` La première fonction s'est exécutée. Et dans le cas d'un rejet -- la deuxième seulement s'exécute : ```js run let promise = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("Whoops!")), 1000); }); // reject lance la seconde fonction dans .then promise.then( result => alert(result), // ne se lance pas *!* error => alert(error) // affiche "Error: Whoops!" après 1 seconde */!* ); ``` Si nous sommes seulement intéressés par les promesses tenues, nous pouvons alors seulement fournir une fonction en argument à `.then` : ```js run let promise = new Promise(resolve => { setTimeout(() => resolve("done!"), 1000); }); *!* promise.then(alert); // affiche "done!" après 1 seconde */!* ``` ### catch Si nous sommes seulement intéressés par les erreurs, alors nous pouvons mettre `null` comme premier argument : `.then(null, fonctionGerantLErreur)`. Ou nous pouvons utiliser `.catch(fonctionGerantLErreur)`, qui revient au même : ```js run let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Whoops!")), 1000); }); *!* // .catch(f) est similaire à promise.then(null, f) promise.catch(alert); // affiche "Error: Whoops!" après 1 seconde */!* ``` L'appel à `.catch(f)` est complètement analogue à `.then(null, f)`, c'est juste un raccourci. ## Cleanup: finally Comme il y a un terme `finally` dans un `try {...} catch {...}`, il y a des `finally` dans les promesses. L'appel à `.finally(f)` est similaire à `.then(f, f)` dans le sens où `f` se lance toujours quand la promesse est aquittée : qu'elle soit tenue ou rompue. L'idée de `finally` est de configurer un gestionnaire pour effectuer le nettoyage/la finalisation une fois les opérations précédentes terminées. Par exemple l'arrêt des voyants de charge, la fermeture des connexions devenues inutiles, etc. Considérez-le comme un nettoyeur de fête. Peu importe qu'une fête soit bonne ou mauvaise, combien d'amis y participaient, nous devons toujours (ou du moins devrions) faire un nettoyage après. Le code peut ressembler à ceci : ```js new Promise((resolve, reject) => { /* faire quelque chose qui prend du temps, puis appeler resolve ou peut-être reject */ }) *!* // se lance quand la promesse est acquittée, peu importe si celle-ci est tenue ou rompue .finally(() => stop loading indicator) // donc l'indicateur de chargement est toujours arrêté avant de continuer */!* .then(result => show result, err => show error) ``` Veuillez noter que `finally(f)` n'est pas exactement un alias de `then(f,f)`. Il existe des différences importantes : 1. Un gestionnaire `finally` n'a pas d'arguments. Dans `finally` nous ne savons pas si la promesse est réussie ou non. Ce n'est pas grave, car notre tâche consiste généralement à effectuer des procédures de finalisation "générales". Veuillez jeter un coup d'œil à l'exemple ci-dessus : comme vous pouvez le voir, le gestionnaire "finally" n'a pas d'arguments et le résultat de la promesse est géré par le gestionnaire suivant. 2. Un gestionnaire "finally" "transmet" le résultat ou l'erreur au prochain gestionnaire approprié. Par exemple, ici, le résultat est passé de `finally` à `then` : ```js run new Promise((resolve, reject) => { setTimeout(() => resolve("value"), 2000); }) .finally(() => alert("Promise ready")) // triggers first .then(result => alert(result)); // <-- .then shows "value" ``` Comme vous pouvez le voir, la `value` renvoyée par la première promesse est transmise par `finally` au prochain `then`. C'est très pratique, car `finally` n'est pas destiné à traiter un résultat de promesse. Comme déjà dit, c'est un endroit pour faire un nettoyage générique, quel que soit le résultat. Et voici un exemple d'erreur, pour que nous puissions voir comment elle est passée de `finally` à `catch` : ```js run new Promise((resolve, reject) => { throw new Error("error"); }) .finally(() => alert("Promise ready")) // triggers first .catch(err => alert(err)); // <-- .catch shows the error ``` 3. Un gestionnaire `finally` ne devrait pas non plus renvoyer quoi que ce soit. Si c'est le cas, la valeur renvoyée est silencieusement ignorée. La seule exception à cette règle est lorsqu'un gestionnaire `finally` génère une erreur. Ensuite, cette erreur passe au gestionnaire suivant, à la place de tout résultat précédent. Pour résumer : - Un gestionnaire `finally` n'obtient pas le résultat du gestionnaire précédent (il n'a pas d'arguments). Ce résultat est transmis à la place au prochain gestionnaire approprié. - Si un gestionnaire `finally` renvoie quelque chose, il est ignoré. - Lorsque `finally` génère une erreur, l'exécution passe au gestionnaire d'erreurs le plus proche. Ces fonctionnalités sont utiles et permettent aux choses de fonctionner correctement si nous utilisons `finally` comme elles sont censées être utilisées : pour les procédures de nettoyage génériques. ````smart header="Nous pouvons attacher des gestionnaires aux promesses réglées" Si une promesse est en attente, les gestionnaires `.then/catch/finally` attendent son résultat. Parfois, il se peut qu'une promesse soit déjà réglée lorsque nous y ajoutons un gestionnaire. Dans ce cas, ces gestionnaires s'exécutent immédiatement : ```js run // la prommesse est acquittée immédiatement à la création let promise = new Promise(resolve => resolve("done!")); promise.then(alert); // done! (s'affiche immédiatement) ``` Notez que cela rend les promesses plus puissantes que le scénario réel de "liste d'abonnement". Si le chanteur a déjà sorti sa chanson et qu'une personne s'inscrit sur la liste d'abonnement, elle ne recevra probablement pas cette chanson. Les abonnements dans la vraie vie doivent être effectués avant l'événement. Les promesses sont plus flexibles. Nous pouvons ajouter des gestionnaires à tout moment : si le résultat est déjà là, ils s'exécutent simplement. ```` ## Example: loadScript [#loadscript] Ensuite, voyons des exemples plus pratiques pour lesquels les promesses nous aident à écrire du code asynchrone. Nous avons la fonction `loadScript` pour charger un script du chapitre précédent. Pour rappel voyons la solution avec des fonctions de retour : ```js function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } ``` Re-écrivons-la avec une promesse. La nouvelle fonction `loadScript` ne nécessite aucune fonction de retour. À la place, elle va créer et retournera une promesse qui s'acquittera lorque le chargement sera complet. Le code externe peut ajouter des gestionnaires (fonction s'abonnant) à celle-ci en utilisant `.then`. ```js run function loadScript(src) { return new Promise(function(resolve, reject) { let script = document.createElement('script'); script.src = src; script.onload = () => resolve(script); script.onerror = () => reject(new Error(`Script load error for ${src}`)); document.head.append(script); }); } ``` Utilisation: ```js run let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`), error => alert(`Error: ${error.message}`) ); promise.then(script => alert('Another handler...')); ``` On peut remarquer immédiatement quelques avantages par rapport aux fonctions de retour : | Promesses | Fonctions de retour | |-----------|----------------------| | Les promesses nous permettent de faire des choses dans un ordre naturel. D'abord, nous lançons `loadScript(script)`, puis avec `.then` nous codons quoi faire avec le résultat. | Nous devons avoir une fonction de retour à notre disposition quand nous appelons `loadScript(script, callback)`. En d'autres termes, nous devons savoir quoi faire du résultat *avant* que `loadScript` soit appelé. | | Nous pouvons appeler `.then` sur une promesse autant de fois que nécessaire. À chaque fois, que nous ajoutons un nouveau "fan", une nouvelle fonction s'abonne à la "liste d'abonnés". Nous en verrons plus à ce sujet dans le prochain chapitre : [](info:promise-chaining). | Il ne peut y avoir qu'une seule fonction de retour. | Les promesses nous permettent donc d'avoir plus de sens et une meilleure flexibilité. Mais il y a plus. Nous allons voir cela dans les chapitres suivants. ================================================ FILE: 1-js/11-async/02-promise-basics/head.html ================================================ ================================================ FILE: 1-js/11-async/03-promise-chaining/01-then-vs-catch/solution.md ================================================ La réponse courte est : **non, ils ne sont pas égaux** : La différence est que si une erreur survient dans `f1`, elle est gérée par `.catch` ici : ```js run promise .then(f1) .catch(f2); ``` ...Mais pas ici: ```js run promise .then(f1, f2); ``` En effet, une erreur est transmise dans la chaîne et, dans le second code, il n'y a pas de chaîne à la suite de `f1`. En d'autres termes, `.then` transmet les résultats/erreurs au prochain `.then/catch`. Donc, dans le premier exemple, il y a un `catch` en dessous, et dans le second - il n'y en a pas, donc l'erreur n'est pas gérée. ================================================ FILE: 1-js/11-async/03-promise-chaining/01-then-vs-catch/task.md ================================================ # Promesse: then contre catch Ces fragments de code sont-ils égaux? En d'autres termes, se comportent-ils de la même manière en toutes circonstances, pour toutes les fonctions gestionnaires? ```js promise.then(f1).catch(f2); ``` Contre: ```js promise.then(f1, f2); ``` ================================================ FILE: 1-js/11-async/03-promise-chaining/article.md ================================================ # Chaînage des promesses Revenons au problème mentionné dans le chapitre : nous avons une séquence de tâches asynchrones à effectuer l'une après l'autre. Par exemple, charger des scripts. Comment pouvons-nous bien le coder ? Les promesses fournissent quelques options pour le faire. Dans ce chapitre, nous traitons de l'enchaînement des promesses. Cela ressemble à ceci : ```js run new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // (*) }).then(function(result) { // (**) alert(result); // 1 return result * 2; }).then(function(result) { // (***) alert(result); // 2 return result * 2; }).then(function(result) { alert(result); // 4 return result * 2; }); ``` L'idée est que le résultat est transmis à travers la chaîne de gestionnaires `.then`. Ici, le flux est : 1. La promesse initiale est résolue en 1 seconde `(*)`, 2. Ensuite, le gestionnaire `.then` est appelé `(**)`, qui à son tour crée une nouvelle promesse (résolue avec la valeur `2`). 3. Le `then` suivant `(***)` obtient le résultat du précédent, le traite (double) et le passe au gestionnaire suivant. 4. ...et ainsi de suite. Lorsque le résultat est transmis le long de la chaîne de gestionnaires, nous pouvons voir une séquence d'appels `alert`: `1` -> `2` -> `4`. ![](promise-then-chain.svg) Le tout fonctionne, parce qu’un appel à `.then` renvoie une nouvelle promesse, de sorte que nous puissions appeler le prochain `.then` dessus. Lorsqu'un gestionnaire renvoie une valeur, cela devient le résultat de cette promesse. Le prochain `.then` est appelé avec. **Une erreur classique pour les débutants: techniquement, nous pouvons également ajouter plusieurs `.then` à une seule promesse. Ceci n'est pas le chaînage des promesses.** Par example : ```js run let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); ``` Ce que nous avons fait ici n’est qu'ajouter plusieurs gestionnaires pour une promesse. Ils ne se transmettent pas le résultat, ils la traitent de manière indépendante. Voici la representation (comparez-la avec l'enchaînement ci-dessus): ![](promise-then-many.svg) Tous les `.then` sur la même promesse obtiennent le même résultat - le résultat de cette promesse. Donc, dans le code ci-dessus, les `alert` montre la même chose : `1`. En pratique, nous avons rarement besoin de plusieurs gestionnaires pour une même promesse. Le chaînage est utilisé beaucoup plus souvent. ## Renvoie de promesses Un gestionnaire, utilisé dans `.then(handler)` peut créer et renvoyer une promesse. Dans ce cas, les autres gestionnaires attendent que le problème soit réglé, puis le résultat est obtenu. Par exemple: ```js run new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }).then(function(result) { alert(result); // 1 *!* return new Promise((resolve, reject) => { // (*) setTimeout(() => resolve(result * 2), 1000); }); */!* }).then(function(result) { // (**) alert(result); // 2 return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }).then(function(result) { alert(result); // 4 }); ``` Ici, le premier `.then` affiche `1` et renvoie `new Promise(…)` à la ligne `(*)`. Au bout d'une seconde c'est résolu et le résultat (l'argument de `resolve`, ici, `result * 2`) est transmis au gestionnaire du deuxième `.then`. Ce gestionnaire est à la ligne `(**)`, il affiche `2` et fait la même chose. Le résultat est donc le même que dans l'exemple précédent: 1 -> 2 -> 4, mais maintenant avec un délai d'une seconde entre les appels `alert`. Le renvoie des promesses nous permet de construire des chaînes d’actions asynchrones. ## Exemple: loadScript Utilisons cette fonctionnalité avec le `loadScript` promisifié, défini dans le [chapitre précédent](info:promise-basics#loadscript), afin de charger les scripts un à un, dans l'ordre: ```js run loadScript("/article/promise-chaining/one.js") .then(function(script) { return loadScript("/article/promise-chaining/two.js"); }) .then(function(script) { return loadScript("/article/promise-chaining/three.js"); }) .then(function(script) { // utiliser les fonctions déclarées dans les scripts // pour montrer qu'ils ont effectivement chargé one(); two(); three(); }); ``` Ce code peut être un peu plus court avec les fonctions fléchées: ```js run loadScript("/article/promise-chaining/one.js") .then(script => loadScript("/article/promise-chaining/two.js")) .then(script => loadScript("/article/promise-chaining/three.js")) .then(script => { // les scripts sont chargés, on peut utiliser les fonctions qui y sont déclarées one(); two(); three(); }); ``` Ici, chaque appel à `loadScript` renvoie une promesse et le prochain `.then` s'exécute lorsqu'il est résolu. Ensuite, il lance le chargement du script suivant. Les scripts sont donc chargés les uns après les autres. Nous pouvons ajouter plus d'actions asynchrones à la chaîne. Noter que le code est toujours "plat", il grandit verticallement, pas vers la droite. Il n'y a aucun signe de "pyramid of doom". Techniquement, nous pourrions ajouter `.then` directement à chaque `loadScript`, comme ceci: ```js run loadScript("/article/promise-chaining/one.js").then(script1 => { loadScript("/article/promise-chaining/two.js").then(script2 => { loadScript("/article/promise-chaining/three.js").then(script3 => { // cette fonction a accès aux variables script1, script2 et script3 one(); two(); three(); }); }); }); ``` Ce code fait la même chose: charge 3 scripts en séquence. Mais il "pousse vers la droite". Nous avons donc le même problème qu'avec les callbacks. Les gens qui commencent à utiliser leurs promesses ne savent parfois pas comment enchaîner, alors ils l'écrivent de cette façon. Généralement, le chaînage est préféré. Parfois, il est correct d'écrire directement `.then`, car la fonction imbriquée a accès à la portée externe. Dans l'exemple ci-dessus, le rappel le plus imbriqué a accès à toutes les variables `script1`, `script2`, `script3`. Mais c'est une exception plutôt qu'une règle. ````smart header="Thenables" Pour être précis, un gestionnaire peut renvoyer pas exactement une promesse, mais un soi-disant objet "thenable" - un objet arbitraire doté de la méthode `.then`. Il sera traité de la même manière q'une promesse. L'idée est que les bibliothèques tierces peuvent implémenter leurs propres objets "compatibles avec les promesses". Elles peuvent avoir un ensemble étendu de méthodes, mais aussi être compatibles avec les promesses natives, car ils implémentent `.then`. Voici un exemple d'un objet "thenable": ```js run class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // promesse tenu avec this.num * 2 après 1 seconde setTimeout(() => resolve(this.num * 2), 1000); // (**) } } new Promise(resolve => resolve(1)) .then(result => { *!* return new Thenable(result); // (*) */!* }) .then(alert); // shows 2 after 1000ms ``` JavaScript vérifie l'objet retourné par le gestionnaire `.then` à la ligne `(*)` : s il a une méthode appelable nommé `then`, il appelle cette méthode fournissant les fonctions natives `resolve` et `reject` comme arguments (semblable à un executeur) et attend que l’un d’eux soit appelé. Dans l'exemple ci-dessus, `resolve(2)` est appelé après 1 seconde `(**)`. Ensuite, le résultat est transmis plus loin dans la chaîne. Cette fonctionnalité nous permet d'intégrer des objets personnalisés avec des chaînes de promesse sans avoir à hériter de `Promise`. ```` ## Un plus grand exemple: fetch Dans la programmation du front-end, les promesses sont souvent utilisées pour les requêtes réseau. Voyons donc un exemple étendu de cela. Nous allons utiliser la méthode [fetch](info:fetch) pour charger les informations sur l'utilisateur à partir du serveur distant. Il a beaucoup de paramètres optionnels couverts dans [des chapitres séparés](info:fetch), mais la syntaxe de base est assez simple: ```js let promise = fetch(url); ``` Cela fait une requête réseau à la `url` et renvoie une promesse. La promesse se résout avec un objet `response` lorsque le serveur distant répond avec des en-têtes, mais *avant le téléchargement complet de la réponse*. Pour lire la réponse complète, nous devons appeler la méthode `response.text()` : elle renvoie une promesse qui résout le téléchargement du texte intégral à partir du serveur distant, avec ce texte en tant que résultat. Le code ci-dessous envoie une requête à `user.json` et charge son texte depuis le serveur: ```js run fetch('/article/promise-chaining/user.json') // .then ci-dessous s'exécute lorsque le serveur distant répond .then(function(response) { // response.text() renvoie une nouvelle promesse qui résout avec le texte de réponse complet // quand ça charge return response.text(); }) .then(function(text) { // ...et voici le contenu du fichier distant alert(text); // {"name": "iliakan", "isAdmin": true} }); ``` L'objet `response` renvoyé par `fetch` comprend également la méthode `response.json()` qui lit les données distantes et les analyse en JSON. Dans notre cas, c'est encore plus pratique, alors passons-y. Nous allons également utiliser les fonctions fléchées pour la brièveté: ```js run // comme ci-dessus, mais response.json() analyse le contenu distant en tant que JSON fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => alert(user.name)); // iliakan, nom d'utilisateur obtenu ``` Maintenant faisons quelque chose avec l'utilisateur chargé. Par exemple, nous pouvons faire une demande supplémentaire à GitHub, charger le profil de l'utilisateur et afficher l'avatar: ```js run // Faire une demande pour user.json fetch('/article/promise-chaining/user.json') // Charger en tant que json .then(response => response.json()) // Faire une demande à GitHub .then(user => fetch(`https://api.github.com/users/${user.name}`)) // Charger la réponse en tant que json .then(response => response.json()) // Afficher l'image de l'avatar (githubUser.avatar_url) pendant 3 secondes (peut-être l'animer) .then(githubUser => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => img.remove(), 3000); // (*) }); ``` Le code fonctionne ; voir les commentaires à propos des détails. Pourtant, il y a un problème potentiel, une erreur typique de ceux qui commencent à utiliser les promesses. Regardez la ligne `(*)`: comment pouvons-nous faire quelque chose *après* l'avatar a fini d'afficher et d'être supprimé? Par exemple, nous aimerions montrer un formulaire pour éditer cet utilisateur ou autre chose. Pour l'instant, il n'y a pas moyen. Pour rendre la chaîne extensible, nous devons retourner une promesse qui sera résolue une fois que l'avatar aura fini de s'afficher. Comme ceci: ```js run fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) *!* .then(githubUser => new Promise(function(resolve, reject) { // (*) */!* let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); *!* resolve(githubUser); // (**) */!* }, 3000); })) // se déclenche après 3 secondes .then(githubUser => alert(`Finished showing ${githubUser.name}`)); ``` En d’autres termes, le gestionnaire `.then` à la ligne `(*)` renvoie `new Promise`, qui ne sera réglé qu’après l’appel de `resolve(githubUser)` dans `setTimeout` `(**)`. Le prochain `.then` dans la chaîne attendra cela. Comme bonne pratique, une action asynchrone doit toujours renvoyer une promesse. Cela permet de planifier des actions après. Même si nous n'avons pas l'intention d'étendre la chaîne maintenant, nous en aurons peut-être besoin plus tard. Enfin, nous pouvons scinder le code en fonctions réutilisables: ```js run function loadJson(url) { return fetch(url) .then(response => response.json()); } function loadGithubUser(name) { return loadJson(`https://api.github.com/users/${name}`); } function showAvatar(githubUser) { return new Promise(function(resolve, reject) { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); }); } // Utilise les: loadJson('/article/promise-chaining/user.json') .then(user => loadGithubUser(user.name)) .then(showAvatar) .then(githubUser => alert(`Finished showing ${githubUser.name}`)); // ... ``` ## Résumé Si un gestionnaire `.then` (ou `catch/finally`, peu importe) renvoie une promesse, le reste de la chaîne attend jusqu'à ce qu'elle se règle. Quand cela se produit, son résultat (ou son erreur) est passé plus loin. Voici une image complète: ![](promise-handler-variants.svg) ================================================ FILE: 1-js/11-async/03-promise-chaining/getMessage.js ================================================ function getMessage() { return "Hello, world!"; } ================================================ FILE: 1-js/11-async/03-promise-chaining/head.html ================================================ ================================================ FILE: 1-js/11-async/03-promise-chaining/one.js ================================================ function one() { alert(1); } ================================================ FILE: 1-js/11-async/03-promise-chaining/three.js ================================================ function three() { alert(3); } ================================================ FILE: 1-js/11-async/03-promise-chaining/two.js ================================================ function two() { alert(2); } ================================================ FILE: 1-js/11-async/03-promise-chaining/user.json ================================================ { "name": "iliakan", "isAdmin": true } ================================================ FILE: 1-js/11-async/04-promise-error-handling/01-error-async/solution.md ================================================ La réponse est: **Non, cela n'arrivera pas:**: ```js run new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert); ``` Comme décrit dans le chapitre, il y a un "`try..catch` implicite" autour du code de la fonction. Toutes les erreurs synchrones sont donc traitées. Mais ici, l'erreur n'est pas générée pendant l'exécution de l'exécuteur, mais plus tard. Donc la promesse ne peut pas tenir. ================================================ FILE: 1-js/11-async/04-promise-error-handling/01-error-async/task.md ================================================ # Erreur dans setTimeout Qu'en pensez-vous ? Est-ce que le `.catch` va se déclencher ? Expliquez votre réponse. ```js new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert); ``` ================================================ FILE: 1-js/11-async/04-promise-error-handling/article.md ================================================ # Gestion des erreurs avec des promesses Les chaînes de promesses sont excellentes pour la gestion des erreurs. Lorsqu'une promesse est rejetée, le contrôle saute au gestionnaire de rejet le plus proche. C'est très pratique en pratique. Par exemple, dans le code en dessous de l'URL de `fetch` est faux (aucun site de ce type) et `.catch` gère l'erreur : ```js run *!* fetch('https://no-such-server.blabla') // rejets */!* .then(response => response.json()) .catch(err => alert(err)) // TypeError: failed to fetch (le texte peut varier) ``` Comme vous pouvez le voir, le `.catch` n'a pas besoin d'être immédiat. Il peut apparaître après un ou peut-être plusieurs `.then`. Ou, peut-être, que tout va bien avec le site, mais la réponse JSON n'est pas valide. La façon la plus simple d'attraper toutes les erreurs est d'ajouter `.catch` à la fin de la chaîne : ```js run fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) .then(githubUser => new Promise((resolve, reject) => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); })) *!* .catch(error => alert(error.message)); */!* ``` Normalement, un tel `.catch` ne se déclenche pas du tout. Mais si l'une des promesses ci-dessus rejette (un problème de réseau, un json invalide ou autre), alors il l'attraperait. ## try..catch implicite Le code d'un exécuteur de promesses et d'un gestionnaire de promesses est entouré d'un "`try...catch` invisible". Si une exception se produit, elle est prise en compte et traitée comme un rejet. Par exemple, ce code: ```js run new Promise((resolve, reject) => { *!* throw new Error("Whoops!"); */!* }).catch(alert); // Error: Whoops! ``` ...Fonctionne exactement de la même façon que ceci: ```js run new Promise((resolve, reject) => { *!* reject(new Error("Whoops!")); */!* }).catch(alert); // Error: Whoops! ``` Le "`try..catch` invisible" autour de l'exécuteur attrape automatiquement l'erreur et la transforme en promesse rejetée. Cela se produit non seulement dans la fonction exécuteur, mais aussi dans ses gestionnaires. Si nous utilisons `throw` à l'intérieur d'un gestionnaire `.then', cela signifie une promesse rejetée, donc le contrôle saute au gestionnaire d'erreur le plus proche. En voici un exemple: ```js run new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { *!* throw new Error("Whoops!"); // rejette la promesse */!* }).catch(alert); // Error: Whoops! ``` Cela se produit pour toutes les erreurs, pas seulement celles causées par l'état `throw`. Par exemple, une erreur de programmation : ```js run new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { *!* blabla(); // aucune fonction de ce type */!* }).catch(alert); // ReferenceError: blabla is not defined ``` Le `.catch` final n'attrape pas seulement les rejets explicites, mais aussi les erreurs occasionnelles dans les gestionnaires ci-dessus. ## Renouvellement Comme nous l'avons déjà remarqué, `.catch` à la fin de la chaîne est similaire à `try...catch`. Nous pouvons avoir autant de gestionnaires `.then` que nous le voulons, puis utiliser un seul `.catch` à la fin pour gérer les erreurs dans chacun d'eux. Dans un `try...catch` classique nous pouvons analyser l'erreur et peut-être la relancer si nous ne pouvons pas la gérer. La même chose est possible pour les promesses. Si nous utilisons `throw` dans `.catch`, alors le contrôle passe au gestionnaire d'erreur suivant qui est plus proche. Et si nous gérons l'erreur et finissons normalement, alors elle continue jusqu'au gestionnaire `.then` le plus proche. In the example below the `.catch` successfully handles the error: ```js run // l'exécution: catch -> then new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(function(error) { alert("The error is handled, continue normally"); }).then(() => alert("Next successful handler runs")); ``` Ici, le bloc `.catch` se termine normalement. Le prochain gestionnaire `.then` réussi est donc appelé. Dans l'exemple ci-dessous nous voyons l'autre situation avec `.catch`. Le gestionnaire `(*)` attrape l'erreur et ne peut tout simplement pas la gérer (par ex: il sait seulement comment gérer `URIError`), donc il la relance: ```js run // l'exécution: catch -> catch new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(function(error) { // (*) if (error instanceof URIError) { // handle it } else { alert("Can't handle such error"); *!* throw error; // lancer cette erreur ou une autre saute au prochain catch. */!* } }).then(function() { /* ne s'exécute pas ici */ }).catch(error => { // (**) alert(`The unknown error has occurred: ${error}`); // ne retourne rien => l'exécution se déroule normalement }); ``` The execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain. ## Rejets non traités Que se passe-t-il lorsqu'une erreur n'est pas traitée ? Par exemple, nous avons oublié d'ajouter `.catch` à la fin de la chaîne, comme ici : ```js untrusted run refresh new Promise(function() { noSuchFunction(); // Erreur ici (aucune fonction de ce type) }) .then(() => { // gestionnaires de promesses réussit, une ou plus }); // sans .catch à la fin! ``` En cas d'erreur, la promesse est rejetée et l'exécution doit passer au gestionnaire de rejet le plus proche. Mais il n'y en a pas. L'erreur est donc "coincée". Il n'y a pas de code pour le gérer. En pratique, tout comme pour les erreurs régulières qui sont non gérées dans le code, cela signifie que quelque chose a très mal tourné. Que se passe-t-il lorsqu'une erreur régulière se produit et n'est pas détectée par `try...catch` ? Le script meurt avec un message dans la console. Il se produit la même chose lors du rejet de promesses non tenues. Le moteur JavaScript suit ces rejets et génère une erreur globale dans ce cas. Vous pouvez le voir dans la console si vous exécutez l'exemple ci-dessus. Dans le navigateur, nous pouvons détecter de telles erreurs en utilisant l'événement `unhandledrejection`: ```js run *!* window.addEventListener('unhandledrejection', function(event) { // l'objet event possède deux propriétés spéciales: alert(event.promise); // [object Promise] - la promesse qui a généré l'erreur alert(event.reason); // Error: Whoops! - l'objet d'erreur non géré }); */!* new Promise(function() { throw new Error("Whoops!"); }); // no catch to handle the error ``` L'événement fait partie des [standards HTML](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections) *(en anglais)*. Si une erreur se produit, et qu'il n'y a pas de `.catch`, le gestionnaire `unhandledrejection` se déclenche, et reçoit l'objet `event` avec les informations sur l'erreur, donc nous pouvons faire quelque chose. Habituellement, de telles erreurs sont irrécupérables, donc notre meilleure solution est d'informer l'utilisateur à propos du problème et probablement de signaler l'incident au serveur. Dans les environnements sans navigateur comme Node.js, il existe d'autres moyens de suivre les erreurs non gérées. ## Résumé - `.catch` gère les erreurs dans les promesses de toutes sortes : qu'il s'agisse d'un appel `reject()`, ou d'une erreur lancée dans un gestionnaire. - `.then` intercepte également les erreurs de la même manière, si on lui donne le deuxième argument (qui est le gestionnaire d'erreurs). - Nous devrions placer `.catch` exactement aux endroits où nous voulons traiter les erreurs et savoir comment les traiter. Le gestionnaire doit analyser les erreurs (les classes d'erreurs personnalisées aident) et relancer les erreurs inconnues (ce sont peut-être des erreurs de programmation). - C'est acceptable de ne pas utiliser `.catch` du tout, s'il n'y a aucun moyen de récupérer d'une erreur. - Dans tous les cas, nous devrions avoir le gestionnaire d'événements `unhandledrejection` (pour les navigateurs, et les analogues pour les autres environnements), pour suivre les erreurs non gérées et informer l'utilisateur (et probablement notre serveur) à leur sujet, afin que notre application ne "meurt jamais". ================================================ FILE: 1-js/11-async/04-promise-error-handling/getMessage.js ================================================ function getMessage() { return "Hello, world!"; } ================================================ FILE: 1-js/11-async/04-promise-error-handling/head.html ================================================ ================================================ FILE: 1-js/11-async/04-promise-error-handling/one.js ================================================ function one() { alert(1); } ================================================ FILE: 1-js/11-async/04-promise-error-handling/three.js ================================================ function three() { alert(3); } ================================================ FILE: 1-js/11-async/04-promise-error-handling/two.js ================================================ function two() { alert(2); } ================================================ FILE: 1-js/11-async/04-promise-error-handling/user.json ================================================ { "name": "iliakan", "isAdmin": true } ================================================ FILE: 1-js/11-async/05-promise-api/article.md ================================================ # Promesse API Il y a 6 méthodes statiques dans la classe `Promise`. Nous allons rapidement couvrir leurs usages ici. ## Promise.all Disons que nous voulons exécuter de nombreuses promesses en parallèle, et attendre qu'elles soient toutes prêtes. Par exemple, téléchargez plusieurs URLs en parallèle et traitez le contenu lorsque tout est terminé. C'est à cela que sert `Promise.all`. La syntaxe est: ```js let promise = Promise.all(iterable); ``` `Promise.all` prend un itérable (généralement un tableau de promesses) et renvoie une nouvelle promesse. La nouvelle promesse est résolue lorsque toutes les promesses énumérées sont résolues et que le tableau de leurs résultats devient son résultat. Par exemple, le `Promise.all` ci-dessous se règle après 3 secondes, et ensuite son résultat est un tableau `[1, 2, 3]`: ```js run Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 ]).then(alert); // 1,2,3 quand les promesses sont prêtes : chaque promesse apporte un élément du tableau ``` Veuillez noter que l'ordre des éléments du tableau résultant est le même que celui des promesses sources. Même si la première promesse prend le plus de temps à se résoudre, elle est toujours la première dans le tableau des résultats. Une astuce courante consiste à mapper un tableau de données de tâches dans un tableau de promesses, puis à l'intégrer dans `Promise.all`. Par exemple, si nous avons un tableau d'URLs, nous pouvons tous les récupérer comme ceci: ```js run let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://api.github.com/users/jeresig' ]; // mappe chaque url à la promesse du fetch let requests = urls.map(url => fetch(url)); // Promise.all attend jusqu'à ce que toutes les tâches soient résolues Promise.all(requests) .then(responses => responses.forEach( response => alert(`${response.url}: ${response.status}`) )); ``` Voici un plus gros exemple avec la récupération des informations des utilisateurs GitHub dans un tableau, par leurs noms (nous pourrions récupérer un tableau d'informations par leurs identifiants, la logique est la même) : ```js run let names = ['iliakan', 'remy', 'jeresig']; let requests = names.map(name => fetch(`https://api.github.com/users/${name}`)); Promise.all(requests) .then(responses => { // toutes les réponses sont résolues avec succès for(let response of responses) { alert(`${response.url}: ${response.status}`); // affiche 200 pour chaque url } return responses; }) // mappe le tableau de "responses" dans le tableau "response.json()" pour lire leurs contenus .then(responses => Promise.all(responses.map(r => r.json()))) // toutes les réponses JSON sont analysées : "users" est leur tableau .then(users => users.forEach(user => alert(user.name))); ``` **Si l'une des promesses est rejetée, la promesse retournée par `Promise.all` est rejetée immédiatement avec cette erreur.** Par exemple: ```js run Promise.all([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), *!* new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), */!* new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).catch(alert); // Error: Whoops! ``` Ici, la deuxième promesse est rejetée en deux secondes. Cela conduit au rejet immédiat de `Promise.all`, donc `.catch` s'exécute : l'erreur de rejet devient le résultat de l'ensemble `Promise.all`. ```warn header="En cas d'erreur, les autres promesses sont ignorées." Si une promesse est rejetée, `Promise.all` est immédiatement rejetée, oubliant complètement les autres dans la liste. Leurs résultats sont ignorés. Par exemple, s'il y a plusieurs appels `fetch, comme dans l'exemple ci-dessus, et que l'un d'eux échoue, les autres continueront à s'exécuter, mais `Promise.all` ne les considérera plus. Ils vont probablement se résoudre, mais leurs résultats sera ignoré. `Promise.all` ne fait rien pour les annuler, car il n'y a pas de concept "d'annulation" dans les promesses. Dans[un autre chapitre](info:fetch-abort) nous couvrirons `AbortController` qui peut vous aider avec cela, mais ce n'est pas une partie de l'API Promise. ``` ````smart header="`Promise.all(iterable)` autorise toutes les valeurs \"régulières\" qui ne sont pas une promesse dans `iterable`" Normallement, `Promise.all(...)` accepte un itérable (dans la plupart des cas, un tableau) de promesses. Mais si l'un de ces objets n'est pas une promesse, il est transmis au tableau résultant "tel quel". Par exemple, ici les résultats sont les suivants `[1, 2, 3]`: ```js run Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), 2, 3 ]).then(alert); // 1, 2, 3 ``` Ainsi, nous sommes en mesure de passer des valeurs disponibles à `Promise.all` où cela nous convient. ```` ## Promise.allSettled [recent browser="new"] `Promise.all` rejette dans son ensemble si une quelconque promesse est rejetée. Cela est bon pour les cas "tout ou rien", quand on a besoin de *tous* les résultats pour continuer : ```js Promise.all([ fetch('/template.html'), fetch('/style.css'), fetch('/data.json') ]).then(render); // la méthode "render" a besoin des résultats de tous les "fetchs" ``` `Promise.allSettled` attend juste que toutes les promesses se résolvent, quel que soit le résultat. Le tableau résultant a : - `{status:"fulfilled", value:result}` pour les réponses réussies, - `{status:"rejected", reason:error}` pour les erreurs. Par exemple, nous aimerions récupérer l'information sur les utilisateurs multiples. Même si une demande échoue, les autres nous intéressent. Utilisons `Promise.allSettled`: ```js run let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://no-such-url' ]; Promise.allSettled(urls.map(url => fetch(url))) .then(results => { // (*) results.forEach((result, num) => { if (result.status == "fulfilled") { alert(`${urls[num]}: ${result.value.status}`); } if (result.status == "rejected") { alert(`${urls[num]}: ${result.reason}`); } }); }); ``` Les `résultats` dans la ligne `(*)` ci-dessus seront: ```js [ {status: 'fulfilled', value: ...response...}, {status: 'fulfilled', value: ...response...}, {status: 'rejected', reason: ...error object...} ] ``` Ainsi, pour chaque promesse, nous obtenons son statut et `value/error`. ### Polyfill Si le navigateur ne prend pas en charge `Promise.allSettled`, il est facile de le polyfill: ```js if (!Promise.allSettled) { const rejectHandler = reason => ({ status: 'rejected', reason }); const resolveHandler = value => ({ status: 'fulfilled', value }); Promise.allSettled = function (promises) { const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler)); return Promise.all(convertedPromises); }; } ``` Dans ce code, `promises.map` prend les valeurs d'entrée, les transforme en promesses (juste au cas où autre chose qu'une promesse serait transmis) avec `p => Promise.resolve(p)`, puis ajoute le gestionnaire `.then` à chacun. Ce gestionnaire transforme un résultat réussi `value` en `{state:'fulfilled', value}`, et une erreur `reason` en `{state:'rejected', reason}`. C'est exactement le format de `Promise.allSettled`. Dorénavant, nous pouvons utiliser `Promise.allSettled` pour obtenir les résultats ou toutes les promesses données, même si certaines d'entre elles sont rejetées. ## Promise.race Similaire à `Promise.all`, mais n'attend que la première promesse soit résolue, et obtient son résultat (ou erreur). La syntaxe est : ```js let promise = Promise.race(iterable); ``` Par exemple, ici, le résultat sera `1` : ```js run Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1 ``` La première promesse a été la plus rapide, donc, elle est devenue le résultat. Après la première promesse faite " vainqueur de la course ", tous les autres résultats/erreurs sont ignorés. ## Promise.any Similar to `Promise.race`, but waits only for the first fulfilled promise and gets its result. If all of the given promises are rejected, then the returned promise is rejected with [`AggregateError`](mdn:js/AggregateError) - a special error object that stores all promise errors in its `errors` property. The syntax is: ```js let promise = Promise.any(iterable); ``` For instance, here the result will be `1`: ```js run Promise.any([ new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)), new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1 ``` The first promise here was fastest, but it was rejected, so the second promise became the result. After the first fulfilled promise "wins the race", all further results are ignored. Here's an example when all promises fail: ```js run Promise.any([ new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000)) ]).catch(error => { console.log(error.constructor.name); // AggregateError console.log(error.errors[0]); // Error: Ouch! console.log(error.errors[1]); // Error: Error! }); ``` As you can see, error objects for failed promises are available in the `errors` property of the `AggregateError` object. ## Promise.resolve/reject Les méthodes `Promise.resolve` et `Promise.reject` sont rarement nécessaires dans le code moderne, parce que la syntaxe `async/await` (nous les couvrirons dans [un peu plus tard](info:async-await)) les rend en quelque sorte obsolètes. Nous les couvrons ici par souci de clarté, et pour ceux qui ne peuvent pas utiliser `async/await` pour une quelconque raison. ### Promise.resolve - `Promise.resolve(value)` crée une promesse résolue avec le résultat `value`. Comme pour: ```js let promise = new Promise(resolve => resolve(value)); ``` La méthode est utilisée pour la compatibilité, lorsqu'une fonction est censée renvoyer une promesse. Par exemple, la fonction `loadCached` ci-dessous récupère l'URL et mémorise (met en cache) son contenu. Pour les appels futurs avec la même URL, elle récupère immédiatement le contenu précédent du cache, mais utilise `Promise.resolve` pour en faire une promesse, de sorte que la valeur retournée soit toujours une promesse : ```js let cache = new Map(); function loadCached(url) { if (cache.has(url)) { *!* return Promise.resolve(cache.get(url)); // (*) */!* } return fetch(url) .then(response => response.text()) .then(text => { cache.set(url,text); return text; }); } ``` Nous pouvons écrire `loadCached(url).then(...)`, car la fonction est garantie de renvoyer une promesse. Nous pouvons toujours utiliser `.then` après `loadCached`. C'est le but de `Promise.resolve` dans la ligne `(*)`. ### Promise.reject - `Promise.reject(error)` crée une promesse rejetée avec `error`. Comme pour: ```js let promise = new Promise((resolve, reject) => reject(error)); ``` En pratique, cette méthode n'est presque jamais utilisée. ## Résumé Il y a 6 méthodes statiques de la classe `Promise`: 1. `Promise.all(promises)` -- attend que toutes les promesses se résolvent et retourne un tableau de leurs résultats. Si l'une des promesses données est rejetée, alors elle devient l'erreur de `Promise.all`, et tous les autres résultats sont ignorés. 2. `Promise.allSettled(promises)` (méthode récemment ajoutée) -- attend que toutes les promesses se règlent et retourne leurs résultats sous forme de tableau d'objets avec: - `state`: `"fulfilled"` ou `"rejected"` - `value` (si rempli) ou `reason` (en cas de rejet). 3. `Promise.race(promises)` -- attend que la première promesse soit réglée, et son résultat/erreur devient le résultat. 4. `Promise.any(promises)` (méthode récemment ajoutée) -- attend que la première promesse se réalise, et son résultat devient le résultat. Si toutes les promesses données sont rejetées, [`AggregateError`](mdn:js/AggregateError) devient l'erreur de `Promise.any`. 5. `Promise.resolve(value)` -- fait une promesse résolue avec la valeur donnée. 6. `Promise.reject(error)` -- fait une promesse rejetée avec l'erreur donnée. De tous ceux-ci, `Promise.all` est probablement le plus courant dans la pratique. ================================================ FILE: 1-js/11-async/05-promise-api/head.html ================================================ ================================================ FILE: 1-js/11-async/05-promise-api/iliakan.json ================================================ { "name": "iliakan", "isAdmin": true } ================================================ FILE: 1-js/11-async/05-promise-api/one.js ================================================ function one() { alert(1); } ================================================ FILE: 1-js/11-async/05-promise-api/two.js ================================================ function two() { alert(2); } ================================================ FILE: 1-js/11-async/06-promisify/article.md ================================================ # Promisification "Promisification" est un long mot pour une simple transformation. Il s'agit de la conversion d'une fonction qui accepte une fonction de rappel ("callback") en une fonction renvoyant une promesse. De telles transformations sont souvent nécessaires dans la vie réelle, car de nombreuses fonctions et bibliothèques sont basées sur des callback. Mais les promesses sont plus pratiques. Il est donc logique de les transformer. Pour une meilleure compréhension, voyons un exemple. Par exemple, nous avons `loadScript(src, callback)` du chapitre . ```js run function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } // usage: // loadScript('path/script.js', (err, script) => {...}) ``` La fonction charge un script avec le `src` donné, puis appelle `callback(err)` en cas d'erreur, ou `callback (null, script) `en cas de chargement réussi. C'est un accord répandu pour l'utilisation des rappels, nous l'avons vu auparavant. Promisifions le. Nous allons créer une nouvelle fonction `loadScriptPromise(src)`, qui fait la même chose (charge le script), mais retourne une promesse au lieu d'utiliser des rappels. En d'autres termes, nous le transmettons uniquement `src` (pas de `callback`) et obtenons une promesse en retour, qui se résout avec `script` lorsque le chargement est réussi, et sinon rejette avec l'erreur. Here it is: ```js let loadScriptPromise = function(src) { return new Promise((resolve, reject) => { loadScript(src, (err, script) => { if (err) reject(err); else resolve(script); }); }); }; // usage: // loadScriptPromise('path/script.js').then(...) ``` Comme nous pouvons le voir, la nouvelle fonction est un wrapper autour de la fonction originale `loadScript`. Il l'appelle en fournissant son propre rappel qui se traduit par la promesse de `resolve/reject`. Dorénavant `loadScriptPromise` s'intègre bien dans le code basé sur la promesse. Si nous aimons les promesses plus que les rappels (et bientôt nous verrons plus de raisons à cela), alors nous les utiliserons à la place. Dans la pratique, nous pouvons avoir besoin de promettre plus d'une fonction, il est donc logique d'utiliser un assistant. Nous l'appellerons `promisify (f)` : il accepte une fonction à promettre `f` et renvoie une fonction wrapper. ```js function promisify(f) { return function (...args) { // return a wrapper-function (*) return new Promise((resolve, reject) => { function callback(err, result) { // our custom callback for f (**) if (err) { reject(err); } else { resolve(result); } } args.push(callback); // ajoute notre rappel personnalisé à la fin des arguments de f f.call(this, ...args); // appeler la fonction d'origine }); }; } // usage: let loadScriptPromise = promisify(loadScript); loadScriptPromise(...).then(...); ``` Le code peut sembler un peu complexe, mais c'est essentiellement le même que celui que nous avons écrit ci-dessus, tout en promettant la fonction `loadScript`. Un appel à `promisify(f)` retourne un wrapper autour de `f` `(*)`. Ce wrapper renvoie une promesse et transmet l'appel au `f` d'origine, en suivant le résultat dans le rappel personnalisé `(**)`. Ici, `promisify` suppose que la fonction d'origine attend un rappel avec exactement deux arguments `(err, result) `. C'est ce que nous rencontrons le plus souvent. Ensuite, notre rappel personnalisé est exactement dans le bon format, et `promisify` fonctionne très bien dans un tel cas. Mais que se passe-t-il si le `f` original attend un rappel avec plus d'arguments `callback(err, res1, res2, ...)`? Nous pouvons améliorer notre helper. Faisons une version plus avancée de `promisify`. - Lorsqu'il est appelé en tant que `promisify(f)`, il devrait fonctionner de la même manière que la version ci-dessus. - Lorsqu'il est appelé en tant que `promisify(f, true)`, il doit retourner la promesse qui se résout avec le tableau des résultats de rappel. C'est exactement pour les rappels avec de nombreux arguments. ```js // promisify(f, true) pour obtenir un tableau de résultats function promisify(f, manyArgs = false) { return function (...args) { return new Promise((resolve, reject) => { function *!*callback(err, ...results*/!*) { // notre rappel personnalisé pour f if (err) { reject(err); } else { // résoudre avec tous les résultats de rappel si manyArgs est spécifié *!*resolve(manyArgs ? results : results[0]);*/!* } } args.push(callback); f.call(this, ...args); }); }; } // usage: f = promisify(f, true); f(...).then(arrayOfResults => ..., err => ...); ``` Comme vous pouvez le voir, c'est essentiellement la même chose que ci-dessus, mais `resolve` est appelé avec un seul ou tous les arguments selon que `manyArgs` est vrai. Pour des formats de rappel plus exotiques, comme ceux sans `err` : `callback(result)`, nous pouvons promettre de telles fonctions manuellement sans utiliser l'assistant. Il existe également des modules avec des fonctions de promisification un peu plus flexibles, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). Dans Node.js, il existe une fonction intégrée `util.promisify` pour cela. ```smart La promisification est une excellente approche, en particulier lorsque vous utilisez `async/await` (traité plus loin dans le chapitre ), mais ne remplace pas totalement les callbacks. N'oubliez pas qu'une promesse peut avoir un seul résultat, mais un rappel peut techniquement être appelé plusieurs fois. La promisification ne concerne donc que les fonctions qui appellent le rappel une fois. D'autres appels seront ignorés. ``` ================================================ FILE: 1-js/11-async/07-microtask-queue/article.md ================================================ # Les micro-tâches Les gestionnaires de promesses `.then`/`.catch`/`.finally` sont toujours asynchrones. Même lorsqu'une promesse est immédiatement résolue, le code sur les lignes situées *ci-dessous* `.then`/`.catch`/`.finally` sera toujours exécuté avant ces gestionnaires. Voici la démo: ```js run let promise = Promise.resolve(); promise.then(() => alert("promise done!")); alert("code finished"); // cette alerte s'affiche d'abord ``` Si vous exécutez, vous voyez `code finished` d'abord, puis `promise done!`. C'est étrange, car la promesse est certainement résolue depuis le début. Pourquoi le `.then` se déclenche par la suite? Que se passe-t-il? ## File d'attente pour micro-tâches Les tâches asynchrones nécessitent une gestion appropriée. Pour cela, la norme ECMA spécifie une file d'attente interne `PromiseJobs`, plus souvent appelée "microtask queue" en anglais (terme V8). Comme indiqué dans la [spécification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues): - La file d'attente est premier entré, premier sorti: les tâches mises en file d'attente en premier sont exécutées en premier. - L'exécution d'une tâche est lancée uniquement lorsque rien d'autre n'est en cours d'exécution. Ou, simplement, lorsqu'une promesse est prête, ses gestionnaires `.then/catch/finally` sont mis en file d'attente ; ils ne sont pas encore exécutés. Lorsque le moteur JavaScript est libéré du code actuel, il extrait une tâche de la file d'attente et l'exécute. C'est pourquoi "code finished" dans l'exemple ci-dessus s'affiche en premier. ![](promiseQueue.svg) Les gestionnaires de promesses passent toujours par cette file d'attente interne. S'il existe une chaîne avec plusieurs `.then/catch/finally`, chacun d'entre eux est exécuté de manière asynchrone. C'est-à-dire qu'il est d'abord mis en file d'attente et exécuté lorsque le code actuel est terminé et que les gestionnaires précédemment placés en file d'attente sont terminés. **Et si l'ordre importait pour nous ? Comment pouvons-nous faire en sorte que `code finished` apparaisse après `promise done` ?** Facile, il suffit de le mettre dans la file d'attente avec `.then`: ```js run Promise.resolve() .then(() => alert("promise done!")) .then(() => alert("code finished")); ``` Maintenant, l'ordre est comme prévu. ## Rejet non traité Souvenez-vous de l'événement `unhandledrejection` du chapitre ? Maintenant, nous pouvons voir exactement comment JavaScript découvre qu'il y a eu un rejet non géré **Un "rejet non traité" se produit lorsqu'une erreur de promesse n'est pas traitée à la fin de la file d'attente des micro-tâches.** Normalement, si nous nous attendons à une erreur, nous ajoutons `.catch` dans la chaîne de promesse pour la gérer: ```js run let promise = Promise.reject(new Error("Promise Failed!")); *!* promise.catch(err => alert('caught')); */!* // n'exécute pas: erreur gérée window.addEventListener('unhandledrejection', event => alert(event.reason)); ``` … Mais si nous oublions d’ajouter `.catch`, dans ce cas le moteur déclenche l’événement une fois que la file d’attente de micro-tâches est vide : ```js run let promise = Promise.reject(new Error("Promise Failed!")); // Promise Failed! window.addEventListener('unhandledrejection', event => alert(event.reason)); ``` Et si nous gérons l'erreur plus tard? Comme ceci: ```js run let promise = Promise.reject(new Error("Promise Failed!")); *!* setTimeout(() => promise.catch(err => alert('caught')), 1000); */!* // Error: Promise Failed! window.addEventListener('unhandledrejection', event => alert(event.reason)); ``` Maintenant, si vous l'exécutez, nous verrons d'abord le message `Promise Failed!`, Puis `caught`. Si nous ne connaissions pas la file d'attente de micro-tâches, nous pourrions nous demander : "Pourquoi le gestionnaire `unhandledrejection` a-t-il été exécuté ? Nous avons capturé et géré l'erreur !". Mais nous comprenons maintenant que `unhandledrejection` est généré à la fin de la file d'attente des micro-tâches : le moteur examine les promesses et, si l'une d'entre elles est à l'état "rejected", l'événement se déclenche. Dans l'exemple ci-dessus, `.catch` ajouté par `setTimeout` se déclenche également, mais plus tard, après que `unhandledrejection` se soit déjà produit, mais cela ne change rien. ## Résumé Le traitement des promesses est toujours asynchrone, car toutes les actions de promesse passent par la file d'attente interne "promise jobs", également appelée "microtask queue" (terme V8). Ainsi, les gestionnaires `.then/catch/finally` sont toujours appelés une fois le code actuel terminé. Si nous devons garantir qu'un morceau de code est exécuté après `.then/catch/finally`, nous pouvons l'ajouter à un appel `.then` enchaîné. Dans la plupart des moteurs JavaScript, y compris les navigateurs et Node.js, le concept de micro-tâches est étroitement lié à la "boucle d'événement" et aux "macrotaches". Comme elles n’ont pas de relation directe avec les promesses, elles sont décrites dans une autre partie du didacticiel, au chapitre . ================================================ FILE: 1-js/11-async/08-async-await/01-rewrite-async/solution.md ================================================ Les notes sont en dessous du code: ```js run async function loadJson(url) { // (1) let response = await fetch(url); // (2) if (response.status == 200) { let json = await response.json(); // (3) return json; } throw new Error(response.status); } loadJson('https://javascript.info/no-such-user.json') .catch(alert); // Error: 404 (4) ``` Notes: 1. La fonction `loadJson` devient `async`. 2. Tous les `.then` intérieurs sont remplacés par `await`.. 3. Nous pouvons `return response.json()` au lieu de l'attendre, comme ceci: ```js if (response.status == 200) { return response.json(); // (3) } ``` Ensuite, le code externe devra "attendre" la résolution de cette promesse. Dans notre cas, cela n'a pas d'importance. 4. L'erreur émise par `loadJson` est gérée par `.catch`. Nous ne pouvons pas utiliser `await loadJson(...)` ici, car nous ne sommes pas dans une fonction `async`.. ================================================ FILE: 1-js/11-async/08-async-await/01-rewrite-async/task.md ================================================ # Réécriture avec async/await Réécrire cet exemple de code du chapitre en utilisant `async/await` au lieu de `.then/catch`: ```js run function loadJson(url) { return fetch(url) .then(response => { if (response.status == 200) { return response.json(); } else { throw new Error(response.status); } }); } loadJson('https://javascript.info/no-such-user.json') .catch(alert); // Error: 404 ``` ================================================ FILE: 1-js/11-async/08-async-await/02-rewrite-async-2/solution.md ================================================ Il n'y a pas d'astuces ici. Remplacez simplement `.catch` par `try..catch` dans `demoGithubUser` et ajoutez `async/await` là où c'est nécessaire: ```js run class HttpError extends Error { constructor(response) { super(`${response.status} for ${response.url}`); this.name = 'HttpError'; this.response = response; } } async function loadJson(url) { let response = await fetch(url); if (response.status == 200) { return response.json(); } else { throw new HttpError(response); } } // demander un nom d'utilisateur jusqu'à ce que github renvoie un utilisateur valide async function demoGithubUser() { let user; while(true) { let name = prompt("Enter a name?", "iliakan"); try { user = await loadJson(`https://api.github.com/users/${name}`); break; // pas d'erreur, sortie de la boucle } catch(err) { if (err instanceof HttpError && err.response.status == 404) { // la boucle continue après l'alerte alert("No such user, please reenter."); } else { // erreur inconnue, rejeter throw err; } } } alert(`Full name: ${user.name}.`); return user; } demoGithubUser(); ``` ================================================ FILE: 1-js/11-async/08-async-await/02-rewrite-async-2/task.md ================================================ # Réécriture de "rethrow" avec async/await Vous trouverez ci-dessous l'exemple "rethrow". Réécrivez-le en utilisant `async/await` au lieu de `.then/catch`. Et débarrassez-vous de la récursion en faveur d'une boucle dans `demoGithubUser` : avec `async/await`, cela devient facile à faire. ```js run class HttpError extends Error { constructor(response) { super(`${response.status} for ${response.url}`); this.name = 'HttpError'; this.response = response; } } function loadJson(url) { return fetch(url) .then(response => { if (response.status == 200) { return response.json(); } else { throw new HttpError(response); } }); } // demander un nom d'utilisateur jusqu'à ce que github renvoie un utilisateur valide function demoGithubUser() { let name = prompt("Enter a name?", "iliakan"); return loadJson(`https://api.github.com/users/${name}`) .then(user => { alert(`Full name: ${user.name}.`); return user; }) .catch(err => { if (err instanceof HttpError && err.response.status == 404) { alert("No such user, please reenter."); return demoGithubUser(); } else { throw err; } }); } demoGithubUser(); ``` ================================================ FILE: 1-js/11-async/08-async-await/03-async-from-regular/solution.md ================================================ C'est le cas quand il est utile de savoir comment ça marche à l'intérieur. Il suffit de traiter l'appel `async` comme une promesse et d'y attacher `.then`: ```js run async function wait() { await new Promise(resolve => setTimeout(resolve, 1000)); return 10; } function f() { // affiche 10 après 1 seconde *!* wait().then(result => alert(result)); */!* } f(); ``` ================================================ FILE: 1-js/11-async/08-async-await/03-async-from-regular/task.md ================================================ # Appeler l'asynchrone à partir du non-asynchrone Nous avons une fonction "normale" appelée `f`. Comment pouvez-vous appeler la fonction `async` `wait()` et utiliser son résultat à l'intérieur de `f` ? ```js async function wait() { await new Promise(resolve => setTimeout(resolve, 1000)); return 10; } function f() { // ...que devez-vous écrire ici? // nous devons appeler async wait() et attendre pour obtenir 10 // Souvenez-vous, on ne peut pas utiliser "await". } ``` P.S. La tâche est techniquement très simple, mais la question est assez courante pour les développeurs novices en matière d'async/await. ================================================ FILE: 1-js/11-async/08-async-await/article.md ================================================ # Async/await Il existe une syntaxe spéciale pour travailler avec les promesses d'une manière plus confortable, appelée "async/await". Elle est étonnamment facile à comprendre et à utiliser. ## Fonctions asynchrones Commençons par le mot-clé `async`. Il peut être placé avant une fonction, comme ceci: ```js async function f() { return 1; } ``` Le mot "async" devant une fonction signifie une chose simple : une fonction renvoie toujours une promesse. Les autres valeurs sont enveloppées dans une promesse résolue automatiquement. Par exemple, cette fonction renvoie une promesse résolue avec le résultat `1` ; testons-la: ```js run async function f() { return 1; } f().then(alert); // 1 ``` ...Nous pourrions explicitement renvoyer une promesse, ce qui reviendrait au même: ```js run async function f() { return Promise.resolve(1); } f().then(alert); // 1 ``` Ainsi, `async` s'assure que la fonction renvoie une promesse, et enveloppe les non-promesses dans celle-ci. Assez simple, non ? Mais pas seulement. Il y a un autre mot-clé, `await`, qui ne fonctionne qu'à l'intérieur des fonctions `async`, et c'est plutôt cool. ## Await La syntaxe: ```js // ne fonctionne que dans les fonctions asynchrones let value = await promise; ``` Le mot-clé `await` fait en sorte que JavaScript attende que cette promesse se réalise et renvoie son résultat. Voici un exemple avec une promesse qui se résout en 1 seconde: ```js run async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) }); *!* let result = await promise; // attendre que la promesse soit résolue (*) */!* alert(result); // "done!" } f(); ``` L'exécution de la fonction fait une "pause" à la ligne `(*)` et reprend lorsque la promesse s'installe, `result` devenant son résultat. Ainsi le code ci-dessus affiche "done!" en une seconde. Soulignons-le : `await` suspend littéralement l'exécution de la fonction jusqu'à ce que la promesse soit réglée, puis la reprend avec le résultat de la promesse. Cela ne coûte pas de ressources CPU, car le moteur JavaScript peut faire d'autres travaux pendant ce temps : exécuter d'autres scripts, gérer des événements, etc. C'est juste une syntaxe plus élégante pour obtenir le résultat de la promesse que `promise.then`. Et c'est plus facile à lire et à écrire. ````warn header="On ne peut pas utiliser `await` dans les fonctions régulières" Si nous essayons d'utiliser `await` dans une fonction non-async, il y aurait une erreur de syntaxe: ```js run function f() { let promise = Promise.resolve(1); *!* let result = await promise; // Syntax error */!* } ``` Nous pouvons obtenir cette erreur si nous oublions de mettre `async` avant une fonction. Comme indiqué précédemment, `await` ne fonctionne qu'à l'intérieur d'une fonction `async`. ```` Prenons l'exemple `showAvatar()` du chapitre et réécrivons-le en utilisant `async/await`: 1. Nous devons remplacer les appels `.then` par `await`. 2. Aussi, nous devrions faire la fonction `async` pour qu'ils fonctionnent. ```js run async function showAvatar() { // lire notre JSON let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); // lire l'utilisateur de github let githubResponse = await fetch(`https://api.github.com/users/${user.name}`); let githubUser = await githubResponse.json(); // montrer l'avatar let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); // attendre 3 secondes await new Promise((resolve, reject) => setTimeout(resolve, 3000)); img.remove(); return githubUser; } showAvatar(); ``` Plutôt propre et facile à lire, non ? Bien mieux qu'avant. ````smart header="Les navigateurs modernes permettent l'utilisation de `await` au niveau supérieur dans les modules" Dans les navigateurs modernes, `await` au niveau supérieur fonctionne très bien, lorsque nous sommes dans un module. Nous couvrirons les modules dans l'article . Par exemple: ```js run module // nous supposons que ce code s'exécute au niveau supérieur, dans un module let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); console.log(user); ``` Si nous n'utilisons pas de modules, ou [des navigateurs plus anciens](https://caniuse.com/mdn-javascript_operators_await_top_level) doivent être supportés, il y a une recette universelle: enveloppement dans une fonction asynchrone anonyme. Comme ceci : ```js (async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })(); ``` ```` ````smart header="`await` accepte \"thenables\"" Comme `promise.then`, `await` nous permet d'utiliser des objets "thenables" (ceux qui ont une méthode `then` appelable). L'idée est qu'un objet tiers peut ne pas être une promesse, mais être compatible avec les promesses : s'il supporte `.then`, c'est suffisant pour l'utiliser avec `await`.. Voici une classe `Thenable` de démonstration ; le `await` ci-dessous accepte ses instances: ```js run class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // résous this.num*2 après 1000ms setTimeout(() => resolve(this.num * 2), 1000); // (*) } } async function f() { // attend pendant 1 seconde, puis le résultat devient 2 let result = await new Thenable(1); alert(result); } f(); ``` Si `await` reçoit un objet non-promis avec `.then`, il appelle cette méthode en fournissant les fonctions intégrées `resolve` et `reject` comme arguments (comme pour un exécuteur `Promise` normal). Ensuite, `await` attend que l'une d'entre elles soit appelée (dans l'exemple ci-dessus, cela se produit à la ligne `(*)`) et procède ensuite avec le résultat. ```` ````smart header="Méthodes de classe asynchrones" Pour déclarer une méthode de classe asynchrone, il suffit de la faire précéder de `async`: ```js run class Waiter { *!* async wait() { */!* return await Promise.resolve(1); } } new Waiter() .wait() .then(alert); // 1 (c'est la même chose que (result => alert(result))) ``` La signification est la même : elle assure que la valeur retournée est une promesse et active `await`. ```` ## Gestion des erreurs Si une promesse se résout normalement, alors `await promise` renvoie le résultat. Mais dans le cas d'un rejet, il jette l'erreur, comme s'il y avait une instruction `throw` à cette ligne. Ce code: ```js async function f() { *!* await Promise.reject(new Error("Whoops!")); */!* } ``` ...est le même que celui-ci: ```js async function f() { *!* throw new Error("Whoops!"); */!* } ``` Dans des situations réelles, la promesse peut prendre un certain temps avant de rejeter. Dans ce cas, il y aura un délai avant que `await` ne lance une erreur. Nous pouvons rattraper cette erreur en utilisant `try..catch`, de la même manière qu'un `throw` normal: ```js run async function f() { try { let response = await fetch('http://no-such-url'); } catch(err) { *!* alert(err); // TypeError: failed to fetch */!* } } f(); ``` En cas d'erreur, le contrôle saute au bloc `catch`. Nous pouvons également envelopper plusieurs lignes: ```js run async function f() { try { let response = await fetch('/no-user-here'); let user = await response.json(); } catch(err) { // attrape les erreurs à la fois dans fetch et response.json alert(err); } } f(); ``` Si nous n'avons pas `try..catch`, alors la promesse générée par l'appel de la fonction asynchrone `f()` sera rejetée. Nous pouvons ajouter `.catch` pour le gérer: ```js run async function f() { let response = await fetch('http://no-such-url'); } // f() devient une promesse rejetée *!* f().catch(alert); // TypeError: failed to fetch // (*) */!* ``` Si nous oublions d'ajouter `.catch` à cet endroit, nous obtenons une erreur de promesse non gérée (visible dans la console). Nous pouvons attraper de telles erreurs en utilisant un gestionnaire d'événement global `unhandledrejection` comme décrit dans le chapitre . ```smart header="`async/await` et `promise.then/catch`" Lorsque nous utilisons `async/await`, nous avons rarement besoin de `.then`, car `await` gère l'attente pour nous. Et nous pouvons utiliser un `try..catch` normal au lieu de `.catch`. C'est généralement (mais pas toujours) plus pratique. Mais au niveau supérieur du code, lorsque nous sommes en dehors de toute fonction `async`, nous sommes syntaxiquement incapables d'utiliser `await`, donc c'est une pratique normale d'ajouter `.then/catch` pour gérer le résultat final ou l'erreur de chute, comme dans la ligne `(*)` de l'exemple ci-dessus. ``` ````smart header="`async/await` fonctionne bien avec `Promise.all`" Lorsque nous devons attendre plusieurs promesses, nous pouvons les envelopper dans `Promise.all` et ensuite `await`: ```js // attendre le tableau de résultats let results = await Promise.all([ fetch(url1), fetch(url2), ... ]); ``` Dans le cas d'une erreur, elle se propage comme d'habitude, de la promesse échouée à `Promise.all`, et devient alors une exception que nous pouvons attraper en utilisant `try..catch` autour de l'appel. ```` ## Résumé Le mot-clé `async` devant une fonction a deux effets: 1. Fait en sorte qu'elle retourne toujours une promesse. 2. Permet l'utilisation de `await` dans celle-ci. Le mot-clé `await` devant une promesse fait en sorte que JavaScript attende jusqu'à ce que cette promesse se règle, puis: 1. Si c'est une erreur, l'exception est générée - comme si `throw error` était appelé à cet endroit précis. 2. Sinon, il renvoie le résultat. Ensemble, ils fournissent un cadre idéal pour écrire du code asynchrone facile à lire et à écrire. Avec `async/await`, nous avons rarement besoin d'écrire `promise.then/catch`, mais nous ne devons pas oublier qu'ils sont basés sur des promesses, parce que parfois (par exemple dans le scope le plus externe) nous devons utiliser ces méthodes. De plus, `Promise.all` est très utile lorsque l'on attend plusieurs tâches simultanément. ================================================ FILE: 1-js/11-async/08-async-await/head.html ================================================ ================================================ FILE: 1-js/11-async/index.md ================================================ # Promesses, async/await ================================================ FILE: 1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/_js.view/solution.js ================================================ function* pseudoRandom(seed) { let value = seed; while(true) { value = value * 16807 % 2147483647 yield value; } }; ================================================ FILE: 1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/_js.view/test.js ================================================ describe("pseudoRandom", function() { it("follows the formula", function() { let generator = pseudoRandom(1); assert.equal(generator.next().value, 16807); assert.equal(generator.next().value, 282475249); assert.equal(generator.next().value, 1622650073); }); it("returns same value for the same seed", function() { let generator1 = pseudoRandom(123); let generator2 = pseudoRandom(123); assert.deepEqual(generator1.next(), generator2.next()); assert.deepEqual(generator1.next(), generator2.next()); assert.deepEqual(generator1.next(), generator2.next()); }); }); ================================================ FILE: 1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md ================================================ ```js run demo function* pseudoRandom(seed) { let value = seed; while(true) { value = value * 16807 % 2147483647; yield value; } }; let generator = pseudoRandom(1); alert(generator.next().value); // 16807 alert(generator.next().value); // 282475249 alert(generator.next().value); // 1622650073 ``` Veuillez noter que la même chose peut être faite avec une fonction régulière, comme ceci: ```js run function pseudoRandom(seed) { let value = seed; return function() { value = value * 16807 % 2147483647; return value; } } let generator = pseudoRandom(1); alert(generator()); // 16807 alert(generator()); // 282475249 alert(generator()); // 1622650073 ``` Cela fonctionne aussi. Mais alors nous perdons la capacité à itérer avec `for..of` et d'utiliser une composition de générateur, qui pourrait être utile ailleurs. ================================================ FILE: 1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/task.md ================================================ # Pseudo-random generator Il y a de nombreux cas où nous avons besoin de données aléatoires. L'un d'eux est le test. Nous aurons peut-être besoin de données aléatoires: texte, chiffres, etc., pour bien tester les choses. En JavaScript, nous pourrions utiliser `Math.random()`. Mais si quelque chose ne va pas, nous aimerions pouvoir répéter le test en utilisant exactement les mêmes données. Pour cela, on utilise des "générateurs pseudo-aléatoires"(seeded pseudo-random generators). Ils prennent une "graine", la première valeur, puis génèrent les suivantes à l'aide d'une formule. De sorte que la même graine donne la même séquence, de sorte que tout le flux est facilement reproductible. Il suffit de rappeler la graine pour la répéter. Un exemple d'une telle formule, qui génère des valeurs distribuées de manière assez uniforme: ``` next = previous * 16807 % 2147483647 ``` Si on utilise `1` comme graine, les valeurs seront: 1. `16807` 2. `282475249` 3. `1622650073` 4. ...etc... La tâche ici est de créer une fonction de générateur `pseudoRandom(seed)` qui prend une `seed`(graine) et crée le générateur avec cette formule. Exemple d'utilisation: ```js let generator = pseudoRandom(1); alert(generator.next().value); // 16807 alert(generator.next().value); // 282475249 alert(generator.next().value); // 1622650073 ``` ================================================ FILE: 1-js/12-generators-iterators/1-generators/article.md ================================================ # Générateurs Les fonctions régulières ne renvoient qu'une seule valeur (ou rien). Les générateurs peuvent renvoyer ("rendement") plusieurs valeurs, l'une après l'autre, à la demande. Ils fonctionnent très bien avec les [iterables](info:iterable), permettant de créer des flux de données en toute simplicité. ## Fonctions de générateur Pour créer un générateur, nous avons besoin d'une construction syntaxique spéciale: `fonction*`, appelée "fonction générateur". Cela ressemble à ça: ```js function* generateSequence() { yield 1; yield 2; return 3; } ``` Les fonctions du générateur se comportent différemment des fonctions normales. Lorsqu'une telle fonction est appelée, elle n'exécute pas son code. Au lieu de cela, elle renvoie un objet spécial, appelé "objet générateur", pour gérer l'exécution. Jetez un oeil ici: ```js run function* generateSequence() { yield 1; yield 2; return 3; } // "fonction générateur" crée "objet générateur" let generator = generateSequence(); *!* alert(generator); // [object Generator] */!* ``` L'exécution du code de la fonction n'a pas encore commencé: ![](generateSequence-1.svg) La principale méthode d'un générateur est `next()`. Lorsqu'il est appelé, il est exécuté jusqu'à la déclaration de ` yield` la plus proche (la `valeur` peut être omise, alors il est `undefined`). Ensuite, l'exécution de la fonction s'interrompt et la `valeur` yield est renvoyée au code externe. Le résultat de `next()` est toujours un objet avec deux propriétés: - `value`: la valeur yielded. - `done`: `true` si le code de la fonction est terminé, sinon `false`. Par exemple, ici nous créons le générateur et obtenons sa première valeur yield: ```js run function* generateSequence() { yield 1; yield 2; return 3; } let generator = generateSequence(); *!* let one = generator.next(); */!* alert(JSON.stringify(one)); // {value: 1, done: false} ``` Pour l'instant, nous n'avons obtenu que la première valeur, et l'exécution de la fonction est sur la deuxième ligne: ![](generateSequence-2.svg) Appelons `generator.next()` encore une fois. Il reprend l'exécution du code et retourne le `yield` suivant: ```js let two = generator.next(); alert(JSON.stringify(two)); // {value: 2, done: false} ``` ![](generateSequence-3.svg) Et, si nous l'appelons une troisième fois, l'exécution atteint l'instruction `return` qui termine la fonction: ```js let three = generator.next(); alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*} ``` ![](generateSequence-4.svg) Le générateur est maintenant terminé. Nous devrions voir avec `done:true` et traiter `value:3` comme résultat final. De nouveaux appels de `generator.next()` n'ont plus de sens maintenant. Si nous les faisons quand même, ils retournent le même objet: `{done: true}`. ```smart header="`function* f(…)` or `function *f(…)`?" Les deux syntaxes sont correctes. Mais généralement, la première syntaxe est préférée, car l'étoile `*` indique qu'il s'agit d'une fonction de générateur, elle décrit le type, pas le nom, elle doit donc rester avec le mot clé `function`. ``` ## Les générateurs sont itérables Comme vous l'avez probablement déjà deviné en regardant la méthode `next()`, les générateurs sont [iterable](info:iterable). Nous pouvons parcourir leurs valeurs en utilisant `for..of` : ```js run function* generateSequence() { yield 1; yield 2; return 3; } let generator = generateSequence(); for(let value of generator) { alert(value); // 1, then 2 } ``` Cela semble beaucoup plus agréable que d'appeler `.next().value`, non? ...Mais veuillez noter: l'exemple ci-dessus montre `1`, puis `2`, et c'est tout. le `3` n'est pas montré! C'est parce que l'iteration `for..of` ignore la dernière `value`, quand`done: true`. Donc, si nous voulons que tous les résultats soient affichés par `for..of`, nous devons les retourner avec `yield`: ```js run function* generateSequence() { yield 1; yield 2; *!* yield 3; */!* } let generator = generateSequence(); for(let value of generator) { alert(value); // 1, then 2, then 3 } ``` Comme les générateurs sont itérables, nous pouvons appeler toutes les fonctionnalités associées, par exemple la syntaxe spread `...` : ```js run function* generateSequence() { yield 1; yield 2; yield 3; } let sequence = [0, ...generateSequence()]; alert(sequence); // 0, 1, 2, 3 ``` Dans le code ci-dessus, `...generateSequence()` transforme l'objet générateur itérable en tableau d'éléments (Essayer d'en savoir plus sur la syntaxe spread dans le chapitre [](info:rest-parameters-spread-operator#spread-operator)) ## Utilisation de générateurs pour les itérables Il y a quelque temps, dans le chapitre [](info:iterable) nous avons créé un objet `range` qui retourne les valeurs `from..to`. Ici, rappelons-nous ce code: ```js run let range = { from: 1, to: 5, // for..of range appelle cette méthode une fois au tout début [Symbol.iterator]() { // ...il renvoie l'objet itérateur: // en avant, for..of ne fonctionne qu'avec cet objet, lui demandant les valeurs suivantes return { current: this.from, last: this.to, // next() est appelé à chaque itération par la boucle for..of next() { // il doit renvoyer la valeur en tant qu'objet {done:.., value :...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; // l'itération sur la plage renvoie des nombres de range.from à range.to alert([...range]); // 1,2,3,4,5 ``` Nous pouvons utiliser une fonction de générateur pour l'itération en la fournissant comme pour `Symbol.iterator`. Voici la même `range`, mais beaucoup plus compact: ```js run let range = { from: 1, to: 5, *[Symbol.iterator]() { // un raccourci pour [Symbol.iterator]: function*() for(let value = this.from; value <= this.to; value++) { yield value; } } }; alert( [...range] ); // 1,2,3,4,5 ``` Cela fonctionne, car `range[Symbol.iterator]()` renvoie maintenant un générateur, et les méthodes de générateur sont exactement ce que `for..of` attend: - il a la méthode `.next()` - qui renvoie des valeurs sous la forme `{value: ..., done: true/false}` Ce n'est pas une coïncidence, bien sûr. Des générateurs ont été ajoutés au langage JavaScript en pensant aux itérateurs, pour les implémenter plus facilement. La variante avec générateur est beaucoup plus concise que le code itérable original de `range`, et garde la même fonctionnalité. ```smart header="Les générateurs peuvent générer des valeurs pour toujours" Dans les exemples ci-dessus, nous avons généré des séquences finies, mais nous pouvons également créer un générateur qui donne des valeurs pour toujours. Par exemple, une séquence sans fin de nombres pseudo-aléatoires. Cela nécessiterait sûrement un `break` (ou un `return`) dans `for..of` sur un tel générateur, sinon la boucle se répéterait pour toujours et se bloquerait. ``` ## Composition du générateur La composition des générateurs est une caractéristique spéciale des générateurs qui permet "d'incorporer" les générateurs de manière transparente les uns dans les autres. Par exemple, nous avons une fonction qui génère une séquence de nombres: ```js function* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; } ``` Maintenant, nous aimerions le réutiliser pour une séquence plus complexe: - d'abord, les chiffres `0..9` (avec des codes de caractères 48..57), - suivi de lettres de l'alphabet en majuscules `A..Z` (codes de caractères 65..90) - suivi de lettres de l'alphabet en minuscules `a..z` (codes de caractères 97..122) Nous pouvons utiliser cette séquence, par exemple pour créer des mots de passe en sélectionnant des caractères (pourrait également ajouter des caractères de syntaxe), mais générons-le d'abord. Dans une fonction régulière, pour combiner les résultats de plusieurs autres fonctions, nous les appelons, stockons les résultats, puis les rejoignons à la fin. Pour les générateurs, il existe une syntaxe spéciale `yield*` pour "incorporer" (composer) un générateur dans un autre. Le générateur composé: ```js run function* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; } function* generatePasswordCodes() { *!* // 0..9 yield* generateSequence(48, 57); // A..Z yield* generateSequence(65, 90); // a..z yield* generateSequence(97, 122); */!* } let str = ''; for(let code of generatePasswordCodes()) { str += String.fromCharCode(code); } alert(str); // 0..9A..Za..z ``` La directive `yield*` *délègue* l'exécution à un autre générateur. Ce terme signifie que `yield* gen` itère sur le générateur `gen` et transmet de manière transparente ses yiels à l'extérieur. Comme si les valeurs étaient fournies par le générateur extérieur. Le résultat est le même que si nous insérions le code des générateurs imbriqués: ```js run function* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; } function* generateAlphaNum() { *!* // yield* generateSequence(48, 57); for (let i = 48; i <= 57; i++) yield i; // yield* generateSequence(65, 90); for (let i = 65; i <= 90; i++) yield i; // yield* generateSequence(97, 122); for (let i = 97; i <= 122; i++) yield i; */!* } let str = ''; for(let code of generateAlphaNum()) { str += String.fromCharCode(code); } alert(str); // 0..9A..Za..z ``` Une composition de générateur est un moyen naturel d'insérer le flux d'un générateur dans un autre. Il n'utilise pas de mémoire supplémentaire pour stocker les résultats intermédiaires. ## "yield" est une route à double sens Jusqu'à présent, les générateurs étaient similaires aux objets itérables, avec une syntaxe spéciale pour générer des valeurs. Mais en fait, ils sont beaucoup plus puissants et flexibles. C'est parce que `yield` est une route à double sens : il renvoie non seulement le résultat à l'extérieur, mais peut également transmettre la valeur à l'intérieur du générateur. Pour ce faire, nous devons appeler `generator.next(arg)`, avec un argument. Cet argument devient le résultat de `yield`. Voyons un exemple: ```js run function* gen() { *!* // Passe une question au code externe et attend une réponse let result = yield "2 + 2 = ?"; // (*) */!* alert(result); } let generator = gen(); let question = generator.next().value; // <-- yield retournes une valeur generator.next(4); // --> passe le résultat dans le générateur ``` ![](genYield2.svg) 1. Le premier appel `generator.next()` est toujours sans argument. Il démarre l'exécution et renvoie le résultat du premier `yield "2+2=?"`. À ce stade, le générateur suspend l'exécution (toujours sur cette ligne). 2. Ensuite, comme le montre l'image ci-dessus, le résultat de `yield` entre dans la variable `question` du code appelant. 3. Sur `generator.next(4)`, le générateur reprend et `4` entre comme résultat: `let result = 4`. Veuillez noter que le code externe n'a pas à appeler immédiatement `next(4)`. Cela peut prendre du temps. Ce n'est pas un problème : le générateur attendra. Par exemple: ```js // reprend le générateur après un certain temps setTimeout(() => generator.next(4), 1000); ``` Comme nous pouvons le voir, contrairement aux fonctions régulières, un générateur et le code appelant peuvent échanger les résultats en passant des valeurs dans `next/yield`. Pour rendre les choses plus évidentes, voici un autre exemple, avec plus d'appels: ```js run function* gen() { let ask1 = yield "2 + 2 = ?"; alert(ask1); // 4 let ask2 = yield "3 * 3 = ?" alert(ask2); // 9 } let generator = gen(); alert( generator.next().value ); // "2 + 2 = ?" alert( generator.next(4).value ); // "3 * 3 = ?" alert( generator.next(9).done ); // true ``` L'image d'exécution: ![](genYield2-2.svg) 1. Le premier `.next()` démarre l'exécution... Il atteint le premier `yield`. 2. Le résultat est renvoyé au code externe. 3. Le second `.next(4)` retourne `4` au générateur à la suite du premier `yield`, et reprend l'exécution. 4. ...Il atteint le deuxième `yield`, qui devient le résultat de l'appel du générateur. 5. Le troisième `next(9)` passe `9` dans le générateur à la suite du deuxième `yield` et reprend l'exécution qui atteint la fin de la fonction, donc `done: true`. C'est comme un jeu de "ping-pong". Chaque `next(value)` (à l'exclusion du premier) passe une valeur dans le générateur, qui devient le résultat du `yield` actuel, puis récupère le résultat du prochain `yield`. ## generator.throw Comme nous l'avons observé dans les exemples ci-dessus, le code externe peut transmettre une valeur au générateur, à la suite de `yield`. ...Mais il peut aussi y initier (lancer) une erreur. C'est naturel, car une erreur est une sorte de résultat. Pour passer une erreur dans un `yield`, nous devons appeler `generator.throw(err)`. Dans ce cas, le `err` est jeté dans la ligne avec ce `yield`. Par exemple, ici le yield de `"2 + 2 = ?"` conduit à une erreur: ```js run function* gen() { try { let result = yield "2 + 2 = ?"; // (1) alert("L'exécution n'atteint pas ici, car l'exception est levée juste au-dessus"); } catch(e) { alert(e); // montre l'erreur } } let generator = gen(); let question = generator.next().value; *!* generator.throw(new Error("La réponse est introuvable dans ma base de données")); // (2) */!* ``` L'erreur, jetée dans le générateur sur la ligne `(2)` conduit à une exception dans la ligne `(1)` avec `yield`. Dans l'exemple ci-dessus, `try..catch` l'attrape et s'affiche. Si nous ne l'attrapons pas, alors comme toute exception, il fait "retomber" le générateur dans le code appelant. La ligne actuelle du code appelant est la ligne avec `generator.throw`, étiquetée comme `(2)`. Nous pouvons donc l'attraper ici, comme ceci: ```js run function* generate() { let result = yield "2 + 2 = ?"; // Error in this line } let generator = generate(); let question = generator.next().value; *!* try { generator.throw(new Error("La réponse est introuvable dans ma base de données")); } catch(e) { alert(e); // shows the error } */!* ``` Si nous n'attrapons pas l'erreur là, alors, comme d'habitude, elle passe au code d'appel externe (le cas échéant) et, s'il n'est pas détecté, tue le script. ## generator.return `generator.return(value)` termine l'exécution du générateur et renvoie la `value` donnée. ```js function* gen() { yield 1; yield 2; yield 3; } const g = gen(); g.next(); // { value: 1, done: false } g.return('foo'); // { value: "foo", done: true } g.next(); // { value: undefined, done: true } ``` Si nous utilisons à nouveau `generator.return()` dans un générateur terminé, il renverra à nouveau cette valeur ([MDN](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Generator/return)). Souvent, nous ne l'utilisons pas, car la plupart du temps, nous voulons obtenir toutes les valeurs de retour, mais cela peut être utile lorsque nous voulons arrêter le générateur dans une condition spécifique. ## Résumé - Les générateurs sont créés par des fonctions de générateur `function* f(…) {…}`. - À l'intérieur des générateurs (uniquement), il existe un operateur `yield`. - Le code externe et le générateur peuvent échanger les résultats via les appels `next/yield`. Dans le JavaScript moderne, les générateurs sont rarement utilisés. Mais parfois, ils sont utiles, car la capacité d'une fonction à échanger des données avec le code appelant pendant l'exécution est tout à fait unique. Et, certainement, ils sont parfaits pour fabriquer des objets itérables. De plus, dans le chapitre suivant, nous apprendrons les générateurs asynchrones, qui sont utilisés pour lire des flux de données générées de manière asynchrone (par exemple, des récupérations paginées sur un réseau) dans la boucle `for wait ... of`. Dans la programmation Web, nous travaillons souvent avec des données en streaming, c'est donc un autre cas d'utilisation très important. ================================================ FILE: 1-js/12-generators-iterators/2-async-iterators-generators/article.md ================================================ # Itérateurs et générateurs asynchrones Les itérateurs asynchrones permettent d'itérer sur des données qui arrivent de manière asynchrone, à la demande. Par exemple, quand nous téléchargeons quelque chose morceau par morceau sur un réseau. Les générateurs asynchrones rendent cela encore plus pratique. ## Rappeler les itérables Rappelons le sujet des itérables. L'idée est que nous avons un objet, tel que `range` ici : ```js let range = { from: 1, to: 5 }; ``` ...Et nous aimerions utiliser la boucle `for..of` dessus, comme `for(value of range)`, pour obtenir des valeurs de `1` à `5`. En d'autres termes, nous voulons ajouter une *capacité d'itération* à l'objet. Cela peut être implémenté en utilisant une méthode spéciale avec le nom `Symbol.iterator` : - Cette méthode est appelée par la construction `for..of` lorsque la boucle est lancée, et elle doit renvoyer un objet avec la méthode `next`. - Pour chaque itération, la méthode `next()` est invoquée pour la valeur suivante. - Le `next()` doit retourner une valeur sous la forme `{done: true/false, value:}`, où `done:true` signifie la fin de la boucle. Voici une implémentation pour l'itérable `range` : ```js run let range = { from: 1, to: 5, *!* [Symbol.iterator]() { // appelé une fois, au début de for..of */!* return { current: this.from, last: this.to, *!* next() { // appelé à chaque itération, pour obtenir la valeur suivante */!* if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; for(let value of range) { alert(value); // 1 puis 2, puis 3, puis 4, puis 5 } ``` Si quelque chose n'est pas clair, veuillez consulter le chapitre [](info:iterable), il donne tous les détails sur les itérables réguliers. ## Itérables asynchrones Une itération asynchrone est nécessaire lorsque les valeurs arrivent de manière asynchrone: après `setTimeout` ou un autre type de retard. Le cas le plus courant est que l'objet doit faire une requête réseau pour fournir la valeur suivante, nous en verrons un exemple réel un peu plus tard. Pour rendre un objet itérable de manière asynchrone : 1. Utiliser `Symbol.asyncIterator` au lieu de `Symbol.iterator`. 2. La méthode `next()` devrait retourner une promesse (à remplir avec la valeur suivante). - Le mot-clé `async` le gère, nous pouvons simplement faire `async next()`. 3. Pour itérer sur un tel objet, nous devrions utiliser une boucle `for await (let item of iterable)`. - Notez le mot `await`. Comme exemple de départ, créons un objet `range` itérable, similaire à celui d'avant, mais maintenant il retournera des valeurs de manière asynchrone, une par seconde. Tout ce que nous devons faire est d'effectuer quelques remplacements dans le code ci-dessus : ```js run let range = { from: 1, to: 5, *!* [Symbol.asyncIterator]() { // (1) */!* return { current: this.from, last: this.to, *!* async next() { // (2) */!* *!* // note: nous pouvons utiliser "await" dans l'async suivant : await new Promise(resolve => setTimeout(resolve, 1000)); // (3) */!* if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; (async () => { *!* for await (let value of range) { // (4) alert(value); // 1,2,3,4,5 } */!* })() ``` Nous pouvons observer que la structure est similaire aux itérateurs réguliers : 1. Pour rendre un objet itérable, asynchrone, il doit avoir une méthode `Symbol.asyncIterator` `(1)`. 2. Cette méthode doit retourner l'objet avec la méthode `next()` retournant une promesse `(2)`. 3. La méthode `next()` n'a pas besoin d'être `async`, elle peut être une méthode normale retournant une promesse, mais `async` permet d'utiliser `await`, donc c'est pratique. Ici, nous ne faisons qu'attendre une seconde `(3)`. 4. Pour itérer, nous utilisons `for await(let value of range)` `(4)`, c'est-à-dire que nous ajoutons "await" après "for". Il appelle `range[Symbol.asyncIterator]()` une fois, et ensuite son `next()` pour chaque valeur. Voici un petit tableau avec les différences : | | itérateurs | itérateurs asynchrones | |-------|------------|------------------------| | Méthode de l'objet qui fournit un itérateur | `Symbol.iterator` | `Symbol.asyncIterator` | | valeur de retour de la fonction `next()` | peu importe | `Promise` | | pour boucler, utilisez | `for..of` | `for await..of` | ````warn header="la 'spread syntax' `...` ne fonctionne pas de manière asynchrone" Les fonctionnalités qui nécessitent des itérateurs réguliers et synchrones ne fonctionnent pas avec les asynchrones. Par exemple, la 'spread syntax' ne fonctionnera pas : ```js alert( [...range] ); // Erreur, pas de Symbol.iterator ``` C'est naturel, comme il s'attend à trouver `Symbol.iterator`, pas `Symbol.asyncIterator`. C'est aussi le cas pour `for..of` : la syntaxe sans `await` a besoin de `Symbol.iterator`. ```` ## Rappeler les générateurs Rappelons maintenant les générateurs, car ils permettent de raccourcir le code d'itération. La plupart du temps, lorsque nous souhaitons créer un itérable, nous utiliserons des générateurs. Par soucis de simplicité, nous omettons certaines choses importantes, ce sont des "fonctions qui génèrent (produisent) des valeurs". Elles sont expliquées en détails dans le chapitre [](info:generators). Les générateurs sont étiquetés avec `function*` (notez l'étoile) et utilisent `yield` pour générer une valeur, puis nous pouvons utiliser `for..of` pour boucler par dessus. Cet exemple génère une séquence de valeurs de `start` à `end` : ```js run function* generateSequence(start, end) { for (let i = start; i <= end; i++) { yield i; } } for(let value of generateSequence(1, 5)) { alert(value); // 1, puis 2, puis 3, puis 4, puis 5 } ``` Comme nous le savons déjà, pour rendre un objet itérable, nous devons lui ajouter `Symbol.iterator`. ```js let range = { from: 1, to: 5, *!* [Symbol.iterator]() { return } */!* } ``` Une pratique courante pour `Symbol.iterator` est de renvoyer un générateur, cela raccourcit le code, comme vous pouvez le voir : ```js run let range = { from: 1, to: 5, *[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*() for(let value = this.from; value <= this.to; value++) { yield value; } } }; for(let value of range) { alert(value); // 1, ensuite 2, ensuite 3, ensuite 4, ensuite 5 } ``` Veuillez consulter le chapitre [](info:generators) si vous souhaitez plus de détails. Dans les générateurs standards, nous ne pouvons pas utiliser `await`. Toutes les valeurs doivent être synchronisées, comme l'exige la construction `for..of`. Et si nous souhaitons générer des valeurs de manière asynchrone ? À partir de requêtes réseau, par exemple. Passons aux générateurs asynchrones pour rendre cela possible. ## Générateurs asynchrones (finally) Pour la plupart des applications pratiques, lorsque nous souhaitons créer un objet qui génère de manière asynchrone une séquence de valeurs, nous pouvons utiliser un générateur asynchrone. La syntaxe est simple : ajoutez `function*` à `async`. Cela rend le générateur asynchrone. Et puis utilisez `for await (...)` pour itérer dessus, comme ceci : ```js run *!*async*/!* function* generateSequence(start, end) { for (let i = start; i <= end; i++) { *!* // Wow, on peut utiliser await! await new Promise(resolve => setTimeout(resolve, 1000)); */!* yield i; } } (async () => { let generator = generateSequence(1, 5); for *!*await*/!* (let value of generator) { alert(value); // 1, puis 2, puis 3, puis 4, puis 5 (avec un délai entre) } })(); ``` Comme le générateur est asynchrone, nous pouvons utiliser `await` à l'intérieur, nous fier aux promesses, effectuer des requêtes réseau et ainsi de suite. ````smart header="Différence sous le capot" Techniquement, si vous êtes un lecteur avancé qui se souvient des détails sur les générateurs, il y a une différence interne. Pour les générateurs asynchrones, la méthode `generator.next()` est asynchrone, elle renvoie des promesses. Dans un générateur classique, nous utiliserions `result = generator.next()` pour obtenir des valeurs. Alors que, dans un générateur asynchrone, nous devrions ajouter `await`, comme ceci : ```js result = await generator.next(); // result = {value: ..., done: true/false} ``` C'est pourquoi les générateurs asynchrones fonctionnent avec `for await...of`. ```` ### Plage itérative asynchrone Les générateurs réguliers peuvent être utilisés comme `Symbol.iterator` pour raccourcir le code d'itération. Similaire à cela, les générateurs asynchrones peuvent être utilisés comme `Symbol.asyncIterator` pour implémenter l'itération asynchrone. Par exemple, nous pouvons faire en sorte que l'objet `range` génère des valeurs de manière asynchrone, une fois par seconde, en remplaçant `Symbol.iterator` synchrone par `Symbol.asyncIterator` asynchrone : ```js run let range = { from: 1, to: 5, // cette ligne est la même que [Symbol.asyncIterator]: async function*() { *!* async *[Symbol.asyncIterator]() { */!* for(let value = this.from; value <= this.to; value++) { // faire une pause entre les valeurs, attendre quelque chose await new Promise(resolve => setTimeout(resolve, 1000)); yield value; } } }; (async () => { for *!*await*/!* (let value of range) { alert(value); // 1, puis 2, puis 3, puis 4, puis 5 } })(); ``` Désormais, les valeurs sont accompagnées d'un délai de 1 seconde entre elles. ```smart Techniquement, nous pouvons ajouter à la fois `Symbol.iterator` et `Symbol.asyncIterator` à l'objet, donc il est à la fois itérable de manière synchrone (`for..of`) et asynchrone (`for await..of`). En pratique cependant, ce serait une chose étrange à faire. ``` ## Exemple réel : données paginées Jusqu'à présent, nous avons vu des exemples de base pour mieux comprendre. Passons maintenant en revue un cas d'utilisation réel. Ce modèle est très courant. Il ne s'agit pas d'utilisateurs, mais de n'importe quoi. Par exemple, GitHub nous permet de récupérer les commits de la même manière paginée : - Nous devrions faire une demande avec `fetch` sous la forme `https://api.github.com/repos//commits`. - Il répond avec un JSON de 30 commits et fournit également un lien vers la page suivante dans l'en-tête `Link`. - Ensuite, nous pouvons utiliser ce lien pour la prochaine demande, pour obtenir plus de commits, etc. Pour notre code, nous aimerions avoir un moyen plus simple d'obtenir des commits. Faisons une fonction `fetchCommits(repo)` qui obtient des commits pour nous, faisant des requêtes chaque fois que nécessaire. Et laissez-le se soucier de tous les trucs de pagination. Pour nous, ce sera une simple itération asynchrone `for await..of`. Donc, l'utilisation sera comme ceci : ```js for await (let commit of fetchCommits("username/repository")) { // process commit } ``` Voici une telle fonction, implémentée en tant que générateur asynchrone : ```js async function* fetchCommits(repo) { let url = `https://api.github.com/repos/${repo}/commits`; while (url) { const response = await fetch(url, { // (1) headers: {'User-Agent': 'Our script'}, // Github a besoin de l'en-tête user-agent }); const body = await response.json(); // (2) la réponse est un JSON (tableau de commits) // (3) l'URL de la page suivante est dans le header, il faut l'extraire let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/); nextPage = nextPage?.[1]; url = nextPage; for(let commit of body) { // (4) génère les commits un par un, jusqu'à la fin de la page yield commit; } } } ``` Plus d'explications sur son fonctionnement : 1. Nous utilisons la méthode du navigateur [fetch](info:fetch) pour télécharger les commits. - L'URL initiale est `https://api.github.com/repos//commits`, et la page suivante sera dans l'en-tête `Link` de la réponse. - La méthode `fetch` nous permet de fournir une autorisation et d'autres en-têtes si nécessaire - ici GitHub nécessite `User-Agent`. 2. Les commits sont renvoyés au format JSON. 3. Nous devrions obtenir l'URL de la page suivante à partir de l'en-tête `Link` de la réponse. Il a un format spécial, nous utilisons donc une [expression régulière](info:regular-expressions)). pour cela. - L'URL de la page suivante peut ressembler à `https://api.github.com/repositories/93253246/commits?page=2`. Elle est générée par GitHub lui-même. 4. Ensuite, nous donnons les commits reçus un par un, et quand ils se terminent, la prochaine itération `while(url)` se déclenchera, faisant une demande de plus. Un exemple d'utilisation (montrant les auteurs de chaque commit en console) : ```js run (async () => { let count = 0; for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) { console.log(commit.author.login); if (++count == 100) { // Arrêtons-nous à 100 commits break; } } })(); // Note: If you are running this in an external sandbox, you'll need to paste here the function fetchCommits described above ``` C'est exactement ce que nous voulions. La mécanique interne des pages est invisible de l'extérieur. Pour nous, c'est juste un générateur asynchrone qui retourne chacun des commits. ## Résumé Les itérateurs et générateurs normaux fonctionnent bien avec les données dont la génération est rapide. Lorsque nous nous attendons à ce que les données arrivent de manière asynchrone, puisque leur génération est possiblement chronophage, les équivalents asynchrones ainsi que " for await..of " au lieu de " for..of " peuvent être utilisés. Différences de syntaxe entre les itérateurs asynchrones et synchrones : | | itérateurs | itérateurs asynchrones | |-------|------------|------------------------| | Méthode de l'objet qui fournit un itérateur | `Symbol.iterator` | `Symbol.asyncIterator` | | valeur de retour de la fonction `next()` | `{value:…, done: true/false}` | Promesse qui se resout en `{value:…, done: true/false}` | Différences de syntaxe entre les générateurs asynchrones et synchrones : | | Générateurs | Générateurs asynchrones | |-------|-------------|-------------------------| | Déclaration | `function*` | `async function*` | | valeur de retour de la fonction `next()` | `{value:…, done: true/false}` | Promesse qui se résout en `{value:…, done: true/false}` | Dans le développement Web, nous rencontrons souvent des flux de données, circulant morceau par morceau. Par exemple, dans le téléchargement ou l'envoi de gros fichiers. Nous pouvons utiliser des générateurs asynchrones pour traiter ce genre de données. Il est également intéressant de noter que dans certains environnements, comme les navigateurs, il existe une autre API appelée Streams, qui fournit des interfaces spéciales pour travailler avec de tels flux, pour transformer les données et pour les faire passer d'un flux à l'autre (par exemple, télécharger à partir d'un endroit et envoyer immédiatement ailleurs). ================================================ FILE: 1-js/12-generators-iterators/2-async-iterators-generators/head.html ================================================ ================================================ FILE: 1-js/12-generators-iterators/index.md ================================================ # Générateurs, itération avancée ================================================ FILE: 1-js/13-modules/01-modules-intro/article.md ================================================ # Modules, introduction Au fur et à mesure que notre application grandit, nous souhaitons la scinder en plusieurs fichiers, appelés "modules". Un module contient généralement une classe ou une bibliothèque de fonctions pour une tâche précise. Pendant longtemps, JavaScript n'avait pas de module. Ce n’était pas un problème, car au départ les scripts étaient petits et simples, il n’était donc pas nécessaire. Mais les scripts sont devenus de plus en plus complexes et la communauté a donc inventé diverses méthodes pour organiser le code en modules, des bibliothèques spéciales pour charger des modules à la demande. Pour en nommer quelques-uns (pour des raisons historiques) : - [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- un des systèmes de modules les plus anciens, initialement mis en œuvre par la bibliothèque [require.js](https://requirejs.org/). - [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- le système de module créé pour Node.js - [UMD](https://github.com/umdjs/umd) -- un système de module supplémentaire, proposé comme universel, compatible avec AMD et CommonJS Maintenant, tous ces éléments deviennent lentement du passé, mais nous pouvons toujours les trouver dans d’anciens scripts. Le système de modules au niveau du langage est apparu dans la norme en 2015, a progressivement évolué depuis, et est désormais pris en charge par tous les principaux navigateurs et dans Node.js. Nous allons donc étudier les modules JavaScript modernes à partir de maintenant. ## Qu'est-ce qu'un module? Un module n'est qu'un fichier. Un script est un module. Aussi simple que cela. Les modules peuvent se charger mutuellement et utiliser des directives spéciales, `export` et `import`, pour échanger des fonctionnalités, appeler les fonctions d’un module dans un autre: - Le mot-clé `export` labelise les variables et les fonctions qui doivent être accessibles depuis l'extérieur du module actuel. - `import` permet l'importation de fonctionnalités à partir d'autres modules. Par exemple, si nous avons un fichier `sayHi.js` exportant une fonction ```js // 📁 sayHi.js export function sayHi(user) { alert(`Hello, ${user}!`); } ``` ...Un autre fichier peut l'importer et l'utiliser: ```js // 📁 main.js import {sayHi} from './sayHi.js'; alert(sayHi); // function... sayHi('John'); // Hello, John! ``` La directive `import` charge le module qui a pour chemin `./sayHi.js` par rapport au fichier actuel et affecte la fonction exportée `sayHi` à la variable correspondante. Lançons l’exemple dans le navigateur. Comme les modules prennent en charge des mots-clés et des fonctionnalités spéciales, nous devons indiquer au navigateur qu'un script doit être traité comme un module, en utilisant l'attribut ` ``` ### Portée au niveau du module Chaque module a sa propre portée globale. En d'autres termes, les variables et les fonctions globales d'un module ne sont pas visibles dans les autres scripts. Dans l'exemple ci-dessous, deux scripts sont importés et `hello.js` essaie d'utiliser la variable `user` déclarée dans `user.js`. Il échoue, car il s'agit d'un module distinct (vous verrez l'erreur dans la console) : [codetabs src="scopes" height="140" current="index.html"] Les modules doivent `export` ce qu'ils veulent être accessible de l'extérieur et `import` ce dont ils ont besoin. - `user.js` devrait exporter la variable `user`. - `hello.js` devrait l'importer depuis le module `user.js`. En d'autres termes, avec les modules, nous utilisons l'import/export au lieu de nous appuyer sur des variables globales. Ceci est la bonne variante : [codetabs src="scopes-working" height="140" current="hello.js"] Dans le navigateur, si nous parlons de pages HTML, une portée de niveau supérieur indépendante existe également pour chaque ` ``` ```smart Dans le navigateur, nous pouvons rendre une variable globale au niveau de la fenêtre en l'affectant explicitement à une propriété `window`, par exemple `window.user = "John"`. Ensuite, tous les scripts la verront, à la fois avec `type="module"` et sans. Cela dit, faire de telles variables globales est mal vu. Veuillez essayer de les éviter. ``` ### Un code de module est chargé la première fois lorsqu'il est importé Si le même module est importé dans plusieurs autres modules, son code n'est exécuté qu'une seule fois, lors de la première importation. Ensuite, ses exportations sont données à tous les autres importateurs. L'évaluation ponctuelle a des conséquences importantes, dont nous devons être conscients. Voyons quelques exemples. Premièrement, si exécuter un code de module entraîne des effets secondaires, comme afficher un message, l'importer plusieurs fois ne le déclenchera qu'une seule fois - la première fois: ```js // 📁 alert.js alert("Module is evaluated!"); ``` ```js // Importer le même module à partir de fichiers différents // 📁 1.js import `./alert.js`; // le module est chargé // 📁 2.js import `./alert.js`; // (n'affiche rien) ``` La deuxième importation ne montre rien, car le module a déjà été évalué. Il y a une règle : le code du module de niveau supérieur doit être utilisé pour l'initialisation, la création de structures de données internes spécifiques au module. Si nous devons rendre quelque chose appelable plusieurs fois, nous devons l'exporter en tant que fonction, comme nous l'avons fait avec `sayHi` ci-dessus. Maintenant, considérons un exemple plus profond. Disons qu'un module exporte un objet: ```js // 📁 admin.js export let admin = { name: "John" }; ``` Si ce module est importé à partir de plusieurs fichiers, il n'est chargé que la première fois, un objet `admin` est créé, puis transmis à tous les autres importateurs. Tous les importateurs obtiennent exactement le seul et unique objet `admin`: ```js // 📁 1.js import {admin} from './admin.js'; admin.name = "Pete"; // 📁 2.js import {admin} from './admin.js'; alert(admin.name); // Pete *!* // 1.js et 2.js font référence au même objet admin // Les modifications apportées dans 1.js sont visibles dans 2.js */!* ``` Comme vous pouvez le voir, lorsque `1.js` modifie la propriété `name` dans le `admin` importé, alors `2.js` peut voir le nouveau `admin.name`. C'est précisément parce que le module n'est exécuté qu'une seule fois. Les exportations sont générées, puis partagées entre les importateurs, donc si quelque chose change l'objet `admin`, les autres modules le verront. **Un tel comportement est en fait très pratique, car il nous permet de *configurer* des modules.** En d'autres termes, un module peut fournir une fonctionnalité générique qui nécessite une configuration. Par exemple. l'authentification a besoin d'informations d'identification. Ensuite, il peut exporter un objet de configuration en attendant que le code externe lui soit affecté. Voici le modèle classique : 1. Un module exporte certains moyens de configuration, par exemple un objet de configuration. 2. Lors de la première importation, nous l'initialisons, écrivons dans ses propriétés. Le script d'application de niveau supérieur peut le faire. 3. D'autres importations utilisent le module. Par exemple, le module `admin.js` peut fournir certaines fonctionnalités (par exemple, l'authentification), mais s'attend à ce que les informations d'identification entrent dans l'objet `config` de l'extérieur : ```js // 📁 admin.js export let config = { }; export function sayHi() { alert(`Ready to serve, ${config.user}!`); } ``` Ici, `admin.js` exporte l'objet `config` (initialement vide, mais peut également avoir des propriétés par défaut). Ensuite, dans `init.js`, le premier script de notre application, nous en importons `config` et définissons `config.user` : ```js // 📁 init.js import {config} from './admin.js'; config.user = "Pete"; ``` ...Maintenant, le module `admin.js` est configuré. Further importers can call it, and it correctly shows the current user: ```js // 📁 another.js import {sayHi} from './admin.js'; sayHi(); // Prêt à être utilisé, *!*Pete*/!*! ``` ### import.meta L'objet `import.meta` contient les informations sur le module actuel. Son contenu dépend de l'environnement. Dans le navigateur, il contient l'URL du script ou une URL de page Web actuelle si elle est en HTML: ```html run height=0 ``` ### Dans un module, "this" n'est pas défini C’est un peu une caractéristique mineure, mais pour être complet, nous devrions le mentionner. Dans un module, l'objet global `this` est indéfini. Comparez-le à des scripts sans module, là où il est un object global: ```html run height=0 ``` ## Fonctionnalités spécifiques au navigateur Il existe également plusieurs différences de scripts spécifiques au navigateur avec `type="module"` par rapport aux scripts classiques. Vous devriez peut-être ignorer cette section pour l'instant si vous lisez pour la première fois ou si vous n'utilisez pas JavaScript dans un navigateur. ### Les modules sont différés Les modules sont *toujours* différés, avec le même effet que l'attribut `defer` (décrit dans le chapitre [](info:script-async-defer)), pour les scripts externes et intégrés. En d'autres termes: - télécharger des modules externe ` Comparez au script habituel ci-dessous: ``` Remarque : le deuxième script fonctionne avant le premier ! Nous verrons donc d'abord `undefined`, puis `object`. C’est parce que les modules sont différés, nous attendons donc que le document soit traité. Les scripts réguliers s'exécutent immédiatement, nous avons donc vu son resultat en premier. Lorsque nous utilisons des modules, nous devons savoir que la page HTML apparaît lors de son chargement et que les modules JavaScript s'exécutent par la suite, afin que l'utilisateur puisse voir la page avant que l'application JavaScript soit prête. Certaines fonctionnalités peuvent ne pas encore fonctionner. Nous devons définir des "indicateurs de chargement" ou veiller à ce que le visiteur ne soit pas confus par cela. ### Async fonctionne sur les scripts en ligne Pour les scripts non modulaires, l'attribut `async` ne fonctionne que sur les scripts externes. Les scripts asynchrones s'exécutent immédiatement lorsqu'ils sont prêts, indépendamment des autres scripts ou du document HTML. Pour les modules, cela fonctionne sur tous les scripts. Par exemple, le script ci-dessous est `async` et n’attend donc personne. Il effectue l'importation (récupère `./analytics.js`) et s'exécute lorsqu'il est prêt, même si le document HTML n'est pas encore terminé ou si d'autres scripts sont toujours en attente. C’est bon pour une fonctionnalité qui ne dépend de rien, comme des compteurs, des annonces, des écouteurs d’événements au niveau du document. ```html ``` ### Scripts externes Les scripts externes de `type="module"` se distinguent sous deux aspects: 1. Les scripts externes avec le même `src` ne s'exécutent qu'une fois: ```html ``` 2. Les scripts externes extraits d’une autre origine (par exemple, un autre site) nécessitent [CORS](https://developer.mozilla.org/fr/docs/Web/HTTP/CORS) en-têtes, comme décrit dans le chapitre . En d'autres termes, si un module est extrait d'une autre origine, le serveur distant doit fournir un en-tête `Access-Control-Allow-Origin` permettant l'extraction. ```html ``` Cela garantit une meilleure sécurité par défaut. ### Aucun module "nu" autorisé Dans le navigateur, `import` doit avoir une URL relative ou absolue. Les modules sans chemin sont appelés modules "nus". De tels modules ne sont pas autorisés lors de l'importation. Par exemple, cette `import` n'est pas valide: ```js import {sayHi} from 'sayHi'; // Error, "bare" module // le module doit avoir un chemin, par exemple './sayHi.js' ``` Certains environnements, tels que Node.js ou les outils de bundle autorisent les modules nus, sans chemin d'accès, car ils disposent de moyens propres de recherche de modules trouver des modules et des hooks pour les ajuster. Mais les navigateurs ne supportent pas encore les modules nus. ### Compatibilité, “nomodule” Les anciens navigateurs ne comprennent pas `type="module"`. Les scripts de type inconnu sont simplement ignorés. Pour eux, il est possible de fournir une solution de secours en utilisant l’attribut `nomodule` : ```html run ``` ## Construire des outils Dans la vie réelle, les modules de navigateur sont rarement utilisés sous leur forme "brute". Généralement, nous les regroupons avec un bundle tel que [Webpack](https://webpack.js.org/) et les déployons sur le serveur de production. L'un des avantages de l'utilisation des bundles est -- qu'ils permettent de mieux contrôler la façon dont les modules sont résolus, permettant ainsi des modules nus et bien plus encore, comme les modules CSS / HTML. Les outils de construction font ce qui suit: 1. Prenons un module "principal", celui qui est destiné à être placé dans ` ``` Cela dit, les modules natifs sont également utilisables. Nous n’utilisons donc pas Webpack ici: vous pourrez le configurer plus tard. ## Résumé Pour résumer, les concepts de base sont les suivants: 1. Un module est un fichier. Pour que `import/export` fonctionne, les navigateurs ont besoin de ` ================================================ FILE: 1-js/13-modules/01-modules-intro/say.view/say.js ================================================ export function sayHi(user) { return `Hello, ${user}!`; } ================================================ FILE: 1-js/13-modules/01-modules-intro/scopes-working.view/hello.js ================================================ import {user} from './user.js'; document.body.innerHTML = user; // John ================================================ FILE: 1-js/13-modules/01-modules-intro/scopes-working.view/index.html ================================================ ================================================ FILE: 1-js/13-modules/01-modules-intro/scopes-working.view/user.js ================================================ export let user = "John"; ================================================ FILE: 1-js/13-modules/01-modules-intro/scopes.view/hello.js ================================================ alert(user); // pas de variable user (chaque module a des variables indépendantes) ================================================ FILE: 1-js/13-modules/01-modules-intro/scopes.view/index.html ================================================ ================================================ FILE: 1-js/13-modules/01-modules-intro/scopes.view/user.js ================================================ let user = "John"; ================================================ FILE: 1-js/13-modules/02-import-export/article.md ================================================ # Exporter et importer Les directives d'exportation et d'importation ont plusieurs variantes de syntaxe. Dans l'article précédent, nous avons vu une utilisation simple, explorons maintenant plus d'exemples. ## Exporter avant les déclarations Nous pouvons étiqueter n'importe quelle déclaration comme exportée en plaçant `export` devant elle, que ce soit une variable, une fonction ou une classe. Par exemple, ici toutes les exportations sont valides: ```js // exporter un tableau *!*export*/!* let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // exporter une constante *!*export*/!* const MODULES_BECAME_STANDARD_YEAR = 2015; // exporter une classe *!*export*/!* class User { constructor(name) { this.name = name; } } ``` ````smart header="Pas de point-virgule après la classe/fonction d'exportation" Veuillez noter que l'`export` avant une classe ou une fonction n'en fait pas une [function expression](info:function-expressions). C’est toujours une fonction déclaration, bien qu’elle soit exportée. La plupart des guides de bonnes pratiques JavaScript ne recommandent pas les points-virgules après les déclarations de fonctions et de classes. C’est pourquoi il n’est pas nécessaire d’utiliser un point-virgule à la fin de `export class` et de `export function`: ```js export function sayHi(user) { alert(`Hello, ${user}!`); } *!* // pas de ; à la fin */!* ``` ```` ## Exporter en dehors des déclarations En outre, nous pouvons mettre l'`export` séparément. Ici, nous déclarons d'abord, puis exportons: ```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); } function sayBye(user) { alert(`Bye, ${user}!`); } *!* export {sayHi, sayBye}; // une liste de variables exportées */!* ``` … Ou, techniquement, nous pourrions également définir les fonctions d'`export` au-dessus des fonctions. ## Import * Habituellement, nous mettons une liste de ce qu'il faut importer entre accolades `import {...}`, comme ceci: ```js // 📁 main.js *!* import {sayHi, sayBye} from './say.js'; */!* sayHi('John'); // Hello, John! sayBye('John'); // Bye, John! ``` Mais s’il y a beaucoup à importer, nous pouvons tout importer en tant qu’objet en utilisant `import * as `, par exemple: ```js // 📁 main.js *!* import * as say from './say.js'; */!* say.sayHi('John'); say.sayBye('John'); ``` À première vue, "importer tout" semble être une chose tellement cool, simple a écrire, pourquoi devrions-nous explicitement énumérer ce que nous devons importer? Eh bien, il y a quelques raisons. 1. Lister explicitement ce qu'il faut importer donne des noms plus courts : `sayHi()` au lieu de `say.sayHi()`. 2. La liste explicite des importations donne un meilleur aperçu de la structure du code : ce qui est utilisé et où. Cela facilite la prise en charge du code et la refactorisation. ```smart header="N'ayez pas peur d'importer trop" Les outils de construction modernes, tels que [webpack](https://webpack.js.org/) et d'autres, regroupent les modules et les optimisent pour accélérer le chargement. Ils ont également supprimé les importations inutilisées. Par exemple, si vous importer `import * as library` à partir d'une énorme bibliothèque de codes, puis n'utilisez que quelques méthodes, celles qui ne sont pas utilisées [ne seront pas incluses] (https://github.com/webpack/webpack/tree/main/ examples/harmony-unused#examplejs) dans le bundle optimisé. ``` ## Import "as" Nous pouvons également utiliser `as` pour importer sous différents noms. Par exemple, importons `sayHi` dans la variable locale `hi` par souci de concision, et importons `sayBye` en `bye`: ```js // 📁 main.js *!* import {sayHi as hi, sayBye as bye} from './say.js'; */!* hi('John'); // Hello, John! bye('John'); // Bye, John! ``` ## Export "as" La syntaxe similaire existe pour l'`export`. Exportons les fonctions en tant que `hi` et `bye`: ```js // 📁 say.js ... export {sayHi as hi, sayBye as bye}; ``` Maintenant, `hi` et `bye` sont les noms à utiliser dans les importations: ```js // 📁 main.js import * as say from './say.js'; say.*!*hi*/!*('John'); // Hello, John! say.*!*bye*/!*('John'); // Bye, John! ``` ## Export default En pratique, il existe principalement deux types de modules. 1. Les modules qui contiennent une bibliothèque, un pack de fonctions, comme `say.js` ci-dessus. 2. Les modules qui déclarent une seule entité, par exemple un module `user.js` qui exporte uniquement la `class User`. La deuxième approche est généralement privilégiée, de sorte que chaque "chose" réside dans son propre module. Naturellement, cela nécessite beaucoup de fichiers, car toute chose veut son propre module, mais ce n’est pas un problème du tout. En fait, la navigation dans le code devient plus facile si les fichiers sont bien nommés et structurés en dossiers. Les modules fournissent une syntaxe spéciale `export default` ("l'exportation par défaut") afin d'améliorer l'aspect "une chose par module". Placez `export default` avant l'entité à exporter: ```js // 📁 user.js export *!*default*/!* class User { // ajouter juste "default" constructor(name) { this.name = name; } } ``` Il ne peut y avoir qu'un seul `export default` par fichier. … Et ensuite importez-le sans accolades: ```js // 📁 main.js import *!*User*/!* from './user.js'; // pas {User}, juste User new User('John'); ``` Les importations sans accolades sont plus agréables. Une erreur courante lorsque vous commencez à utiliser des modules est d’oublier les accolades. Par conséquent, rappelez-vous que l’`import` nécessite des accolades pour les exportations nommées et ne les utilise pas pour celle par défaut. | Export nommé | Export par défaut | |--------------|-------------------| | `export class User {...}` | `export default class User {...}` | | `import {User} from ...` | `import User from ...`| Techniquement, nous pouvons avoir à la fois des exportations par défaut et des exportations nommées dans un seul module, mais dans la pratique, les gens ne les mélangent généralement pas. Un module a soit, des exports nommés, soit celui par défaut. Comme il peut y avoir au plus une exportation par défaut par fichier, l'entité exportée peut ne pas avoir de nom. Par exemple, ce sont toutes des exportations par défaut parfaitement valides: ```js export default class { // pas de nom de classe constructor() { ... } } ``` ```js export default function(user) { // pas de nom de fonction alert(`Hello, ${user}!`); } ``` ```js // exporter une seule valeur sans créer de variable export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; ``` Ne pas donner de nom, c'est bien, car l'`export default` est unique par fichier. Par conséquent, l'importation sans accolades sait ce qu'il faut importer. Sans `defaut`, une telle exportation donnerait une erreur: ```js export class { // Erreur! (un export autre que par défaut nécessite un nom) constructor() {} } ``` ### Le nom par "default" Dans certaines situations, le mot clé `default` est utilisé pour référencer l'exportation par défaut. Par exemple, pour exporter une fonction séparément de sa définition: ```js function sayHi(user) { alert(`Hello, ${user}!`); } // comme si nous avions ajouté "export default" avant la fonction export {sayHi as default}; ``` Ou, dans un autre cas, supposons qu'un module `user.js` exporte un élément principal par "défaut" et quelques éléments nommés (rarement le cas, mais ça arrive): ```js // 📁 user.js export default class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); } ``` Voici comment importer l'exportation par défaut avec celle nommée: ```js // 📁 main.js import {*!*default as User*/!*, sayHi} from './user.js'; new User('John'); ``` Et, enfin, si vous importez tout `*` comme objet, la propriété `default` est exactement l'exportation par défaut: ```js // 📁 main.js import * as user from './user.js'; let User = user.default; // l'exportation par défaut new User('John'); ``` ### Un mot contre les exportations par défaut Les exportations nommées sont explicites. Ils nomment exactement ce qu’ils importent, nous avons donc ces informations, c’est une bonne chose. Les exportations nommées nous obligent à utiliser exactement le bon nom pour importer : ```js import {User} from './user.js'; // importer {MyUser} ne fonctionnera pas, le nom devrait être {User} ``` ...Alors que pour une exportation par défaut, nous choisissons toujours le nom lors de l'importation: ```js import User from './user.js'; // fonctionne import MyUser from './user.js'; // fonctionne aussi // n'importe quoi pourrait être importé ..., cela continuera de fonctionner ``` Les membres de l'équipe peuvent donc utiliser des noms différents pour importer la même chose, et ce n'est pas bien. Habituellement, pour éviter cela et garder le code cohérent, il existe une règle voulant que les variables importées correspondent aux noms de fichier, par exemple: ```js import User from './user.js'; import LoginForm from './loginForm.js'; import func from '/path/to/func.js'; ... ``` Néanmoins, certaines équipes considèrent qu'il s'agit d'un grave inconvénient des exportations par défaut. Ils préfèrent donc toujours utiliser des exportations nommées. Même si une seule chose est exportée, elle est toujours exportée sous un nom, sans `default`. Cela facilite également la réexportation (voir ci-dessous). ## Réexportation La syntaxe "re-export" `export ... from ...` permet d'importer et d'exporter immédiatement des éléments (éventuellement sous un autre nom), comme ceci: ```js export {sayHi} from './say.js'; // réexportez sayHi export {default as User} from './user.js'; // réexportez default ``` Pourquoi cela peut être nécessaire ? Voyons un cas d'utilisation pratique. Imaginez, nous écrivons un "package": un dossier avec beaucoup de modules, avec une partie des fonctionnalités exportées à l'extérieur (des outils comme NPM nous permettent de publier et de distribuer de tels packages, mais nous n'avons pas à les utiliser), et de nombreux modules ne sont que des "helpers", destinés à une utilisation interne dans d'autres modules de package. La structure de fichier pourrait être comme ceci : ``` auth/ index.js user.js helpers.js tests/ login.js providers/ github.js facebook.js ... ``` Nous aimerions exposer les fonctionnalités du paquet via un seul point d’entrée. En d'autres termes, une personne souhaitant utiliser notre package ne doit importer que depuis le "fichier principal" `auth/index.js`. Comme ceci : ```js import {login, logout} from 'auth/index.js' ``` Le "fichier principal", `auth / index.js` exporte toutes les fonctionnalités que nous aimerions fournir dans notre package. L'idée est que les tiers, les développeurs qui utilisent notre package, ne doivent pas se mêler de sa structure interne, rechercher des fichiers dans notre dossier de packages. Nous n'exportons que ce qui est nécessaire dans `auth / index.js` et gardons le reste caché des regards indiscrets. La fonctionnalité exportée étant dispersée dans le package, nous pouvons l'importer dans `auth / index.js` et l'exporter: ```js // 📁 auth/index.js // importer les login / logout et les exporter immédiatement import {login, logout} from './helpers.js'; export {login, logout}; // importer par défaut en tant qu'utilisateur et l'exporter import User from './user.js'; export {User}; ... ``` Maintenant, les utilisateurs de notre paquet peuvent `import {login} from "auth/index.js"`. La syntaxe `export ... from ...` est juste une notation plus courte pour importer et exporter directement: ```js // 📁 auth/index.js // re-export login/logout export {login, logout} from './helpers.js'; // re-export l'exportation par défaut en tant qu'User export {default as User} from './user.js'; ... ``` La différence notable entre `export ... from` et `import/export` est que les modules réexportés ne sont pas disponibles dans le fichier actuel. Donc, dans l'exemple ci-dessus de `auth/index.js`, nous ne pouvons pas utiliser les fonctions `login/logout` réexportées. ### Ré-exportation de l'exportation par défaut L'exportation par défaut nécessite un traitement séparé lors de la réexportation. Supposons que nous ayons `user.js` avec le `export default class User` et que nous souhaitons le réexporter : ```js // 📁 user.js export default class User { // ... } ``` On peut y rencontrer deux problèmes : 1. `export User from './user.js'` çe ne fonctionnera pas... Cela conduirait à une erreur de syntaxe. Pour réexporter l'exportation par défaut, nous devrions écrire `export {default as User}`, comme dans l'exemple ci-dessus. 2. `export * from './user.js'` ne réexporte que les exportations nommées, et ignore celle par défaut. Si nous souhaitons réexporter l'export nommé et l'export par défaut, deux instructions sont nécessaires: ```js export * from './user.js'; // réexporter les exportations nommées export {default} from './user.js'; // réexporter l'exportation par défaut ``` Ces bizarreries de réexporter une exportation par défaut sont l'une des raisons pour lesquelles certains développeurs n'aiment pas les exportations par défaut et préfèrent les exportations nommées. ## Résumé Voici tous les types d'`export` que nous avons abordés dans ce chapitre et dans les chapitres précédents. Vous pouvez vérifier vous-même en les lisant et en vous rappelant leur signification: - Avant la déclaration d'une classe / fonction / ..: - `export [default] class/function/variable ...` - Exportation autonome: - `export {x [as y], ...}`. - Réexportation: - `export {x [as y], ...} from "module"` - `export * from "module"` (ne ré-exporte pas par défaut). - `export {default [as y]} from "module"` (ré-export par défaut). Import: - Importations d’exports nommés : - `import {x [as y], ...} from "module"` - Importation de l’export par défaut : - `import x from "module"` - `import {default as x} from "module"` - Tout importer : - `import * as obj from "module"` - Importer le module (son code s'exécute), mais ne l'affecte pas à une variable : - `import "module"` Nous pouvons mettre des déclarations `import/export` en haut ou en bas d'un script, cela n'a pas d'importance. Donc, techniquement, ce code est correct: ```js sayHi(); // ... import {sayHi} from './say.js'; // importer à la fin du fichier ``` En pratique, les importations se font généralement au début du fichier, mais ce n'est que pour des raisons de commodité. **Veuillez noter que les instructions import/export ne fonctionnent pas si elles sont à l'intérieur `{...}`.** Une importation conditionnelle, comme celle-ci, ne fonctionnera pas: ```js if (something) { import {sayHi} from "./say.js"; // Erreur: l'importation doit être au plus haut niveau } ``` ...Mais que se passe-t-il si nous devons vraiment importer quelque chose de manière conditionnelle? Ou au bon moment? Aimez-vous, charger un module sur demande, quand c'est vraiment nécessaire? Nous verrons les importations dynamiques dans le chapitre suivant. ================================================ FILE: 1-js/13-modules/03-modules-dynamic-imports/article.md ================================================ # Importations dynamiques Les déclarations d'exportation et d'importation décrites dans les chapitres précédents sont appelées "statiques". La syntaxe est très simple et stricte. Premièrement, nous ne pouvons générer dynamiquement aucun paramètre d'`import`. Le chemin du module doit être une chaîne de caractères, il ne peut pas être un appel de fonction. Exemple, cela ne fonctionnera pas: ```js import ... from *!*getModuleName()*/!*; // Erreur, seulement une chaîne de caractères est autorisé ``` Deuxièmement, nous ne pouvons pas importer de manière conditionnelle ou au moment de l’exécution: ```js if(...) { import ...; // Erreur, pas autorisé! } { import ...; // Erreur, nous ne pouvons pas importer dans un bloc } ``` C’est parce que `import`/`export` vise à fournir une structure de base à la structure du code. C’est une bonne chose, car la structure du code peut être analysée, les modules peuvent être rassemblés et regroupés dans un fichier à l’aide d’outils spéciaux, les exportations inutilisées peuvent être supprimées ("tree-shaken"). Cela n’est possible que parce que la structure des importations / exportations est simple et fixe. Mais comment importer un module de manière dynamique, à la demande? ## L'expression import() L'expression `import(module)` charge le module et renvoie une promesse résolue en un objet de module contenant toutes ses exportations. Il peut être appelé de n’importe quel endroit du code. Nous pouvons l’utiliser dynamiquement à n’importe quel endroit du code, par exemple: ```js let modulePath = prompt("Which module to load?"); import(modulePath) .then(obj => ) .catch(err => ) ``` Ou bien, nous pourrions utiliser `let module = await import(modulePath)` s'il se trouve dans une fonction asynchrone. Par exemple, si nous avons le module suivant, `say.js`: ```js // 📁 say.js export function hi() { alert(`Hello`); } export function bye() { alert(`Bye`); } ``` ...Alors l'importation dynamique peut être comme ça: ```js let {hi, bye} = await import('./say.js'); hi(); bye(); ``` Ou, si `say.js` a l'exportation par défaut: ```js // 📁 say.js export default function() { alert("Module loaded (export default)!"); } ``` ...Ensuite, pour y accéder, nous pouvons utiliser la propriété `default` de l'objet module: ```js let obj = await import('./say.js'); let say = obj.default; // ou en une ligne: let {default: say} = await import('./say.js'); say(); ``` Voici l’exemple complet: [codetabs src="say" current="index.html"] ```smart Les importations dynamiques fonctionnent dans des scripts standard, elles n’exigent pas de `script type="module"`. ``` ```smart Bien que `import()` ressemble à un appel de fonction, il s’agit d’une syntaxe spéciale qui utilise des parenthèses (similaire à `super()`). Nous ne pouvons donc pas copier `import` dans une variable ni utiliser `call/apply` avec elle. Ce n'est pas une fonction. ``` ================================================ FILE: 1-js/13-modules/03-modules-dynamic-imports/say.view/index.html ================================================ ================================================ FILE: 1-js/13-modules/03-modules-dynamic-imports/say.view/say.js ================================================ export function hi() { alert(`Hello`); } export function bye() { alert(`Bye`); } export default function() { alert("Module loaded (export default)!"); } ================================================ FILE: 1-js/13-modules/index.md ================================================ # Modules ================================================ FILE: 1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md ================================================ ```js run let user = { name: "John" }; function wrap(target) { return new Proxy(target, { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } else { throw new ReferenceError(`Property doesn't exist: "${prop}"`) } } }); } user = wrap(user); alert(user.name); // John alert(user.age); // ReferenceError: Property doesn't exist: "age" ``` ================================================ FILE: 1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md ================================================ # Erreur lors de la lecture d'une propriété inexistante Habituellement, une tentative de lecture d'une propriété inexistante renvoie `undefined`. Créez à la place un proxy qui génère une erreur pour une tentative de lecture d'une propriété inexistante. Cela peut aider à détecter précocement les erreurs de programmation. Écrivez une fonction `wrap(target)` qui prend un objet `target` et retourne un proxy qui ajoute cet aspect fonctionnel. Voilà comment cela devrait fonctionner: ```js let user = { name: "John" }; function wrap(target) { return new Proxy(target, { *!* /* your code */ */!* }); } user = wrap(user); alert(user.name); // John *!* alert(user.age); // ReferenceError: la propriété n'existe pas : "age" */!* ``` ================================================ FILE: 1-js/99-js-misc/01-proxy/02-array-negative/solution.md ================================================ ```js run let array = [1, 2, 3]; array = new Proxy(array, { get(target, prop, receiver) { if (prop < 0) { // même si on y accède comme arr[1] // prop est une chaîne, il faut donc la convertir en nombre prop = +prop + target.length; } return Reflect.get(target, prop, receiver); } }); alert(array[-1]); // 3 alert(array[-2]); // 2 ``` ================================================ FILE: 1-js/99-js-misc/01-proxy/02-array-negative/task.md ================================================ # Accès au tableau [-1] Dans certains langages de programmation, nous pouvons accéder aux éléments du tableau à l'aide d'index négatifs, comptés à partir de la fin. comme ça: ```js let array = [1, 2, 3]; array[-1]; // 3, le premier élément en partant de la fin array[-2]; // 2, le second élément en partant de la fin array[-3]; // 1, le troisième élément en partant de la fin ``` En d'autres termes, `array[-N]` est identique à `array[array.length - N]`. Créez un proxy pour implémenter ce comportement. Voilà comment cela devrait fonctionner: ```js let array = [1, 2, 3]; array = new Proxy(array, { /* your code */ }); alert( array[-1] ); // 3 alert( array[-2] ); // 2 // Les autres fonctionnalités de array doivent être conservées ``` ================================================ FILE: 1-js/99-js-misc/01-proxy/03-observable/solution.md ================================================ La solution se compose de deux parties: 1. Chaque fois que `.observe(handler)` est appelé, nous devons nous souvenir du handler quelque part, pour pouvoir l'appeler plus tard. Nous pouvons stocker des handler directement dans l'objet, en utilisant notre symbole comme clé de propriété 2. Nous avons besoin d'un proxy avec le piège `set` pour appeler les handler en cas de changement ```js run let handlers = Symbol('handlers'); function makeObservable(target) { // 1. initialiser le stockage de l'handler target[handlers] = []; // Stocker la fonction de l'handler dans un tableau pour les appels futurs target.observe = function(handler) { this[handlers].push(handler); }; // 2. Créer un proxy pour gérer les modifications return new Proxy(target, { set(target, property, value, receiver) { let success = Reflect.set(...arguments); // transmettre l'opération à l'objet if (success) { // s'il n'y a pas eu d'erreur lors de la définition de la propriété // appeler tous les handler target[handlers].forEach(handler => handler(property, value)); } return success; } }); } let user = {}; user = makeObservable(user); user.observe((key, value) => { alert(`SET ${key}=${value}`); }); user.name = "John"; ``` ================================================ FILE: 1-js/99-js-misc/01-proxy/03-observable/task.md ================================================ # Observable Créez une fonction `makeObservable(target)` qui "rend l'objet observable" en renvoyant un proxy. Voici comment cela devrait fonctionner: ```js run function makeObservable(target) { /* your code */ } let user = {}; user = makeObservable(user); user.observe((key, value) => { alert(`SET ${key}=${value}`); }); user.name = "John"; // alerts: SET name=John ``` En d'autres termes, un objet retourné par `makeObservable` est exactement comme celui d'origine, mais possède également la méthode `observe(handler)` qui définit la fonction de `handler` à appeler lors de tout changement de propriété. Chaque fois qu'une propriété change, le `handler(key, value)` est appelé avec le nom et la valeur de la propriété. P.S. Dans cette tâche, veillez uniquement à écrire sur une propriété. D'autres opérations peuvent être implémentées de manière similaire. ================================================ FILE: 1-js/99-js-misc/01-proxy/article.md ================================================ # Proxy et Reflect Un objet `Proxy` encapsule un autre objet et intercepte des opérations, comme la lecture / écriture de propriétés et d'autres, éventuellement en les manipulant de lui-même ou en permettant à l'objet de les gérer de manière transparente. Les proxys sont utilisés dans de nombreuses bibliothèques et certains frameworks de navigateur. Nous verrons de nombreux cas pratiques dans cet article. ## Proxy La syntaxe: ```js let proxy = new Proxy(target, handler) ``` - `target` (cible) -- est un objet à envelopper, cela peut être n'importe quoi, y compris des fonctions. - `handler` -- configuration du proxy: un objet avec des "pièges" qui interceptent les opérations. - par exemple. `get` pour lire une propriété de `target`, `set` pour écrire une propriété dans `target`, etc. Pour les opérations sur le `proxy`, s'il existe un piège correspondant dans le `handler`, il s'exécute et le proxy a une chance de le gérer, sinon l'opération est effectuée sur `target`. Comme exemple de départ, créons un proxy sans aucun piège: ```js run let target = {}; let proxy = new Proxy(target, {}); // handler vide proxy.test = 5; // écrire dans proxy (1) alert(target.test); // 5, la propriété est apparue dans target! alert(proxy.test); // 5, nous pouvons aussi la lire à partir du proxy (2) for(let key in proxy) alert(key); // test, les itérations fonctionne (3) ``` Comme il n'y a pas de pièges, toutes les opérations sur le `proxy` sont transmises à `target`. 1. Une opération d'écriture `proxy.test =` définit la valeur sur `target`. 2. Une opération de lecture `proxy.test` renvoie la valeur de `target`. 3. L'itération sur le `proxy` renvoie les valeurs de `target`. Comme nous pouvons le voir, sans aucun piège, le `proxy` est un "wrapper transparent" autour de `target`. ![](proxy.svg) Le `proxy` est un "objet exotique" spécial. Il n'a pas de propriétés propres. Avec un `handler` vide, il transfère de manière transparente les opérations vers `target`. Pour activer plus de fonctionnalités, ajoutons des pièges. Que pouvons-nous intercepter avec eux? Pour la plupart des opérations sur les objets, il existe une soi-disant "méthode interne" dans la spécification JavaScript qui décrit comment cela fonctionne au plus bas niveau. Par exemple `[[Get]]`, la méthode interne pour lire une propriété, `[[Set]]`, la méthode interne pour écrire une propriété, etc. Ces méthodes ne sont utilisées que dans la spécification, nous ne pouvons pas les appeler directement par leur nom. Les pièges proxy interceptent les invocations de ces méthodes. Ils sont répertoriés dans le [Spécification du proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots) et dans le tableau ci-dessous Pour chaque méthode interne, il y a un piège dans ce tableau: le nom de la méthode que nous pouvons ajouter au `handler` du `new Proxy` pour intercepter l'opération: | Méthode interne | Méthode d'handler | Se déclenche lorsque... | |-----------------|----------------|-------------| | `[[Get]]` | `get` | lit une propriété | | `[[Set]]` | `set` | écrit une propriété | | `[[HasProperty]]` | `has` | utilise l'opérateur `in` | | `[[Delete]]` | `deleteProperty` | utilise l'opérateur `delete` | | `[[Call]]` | `apply` | appel une fonction | | `[[Construct]]` | `construct` | utilise l'opérateur `new` | | `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | | `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | | `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](mdn:/JavaScript/Reference/Global_Objects/Object/isExtensible) | | `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) | | `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) | | `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | | `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` | ```warn header="Invariants" JavaScript applique certains invariants -- conditions qui doivent être remplies par des méthodes et des pièges internes. La plupart d'entre eux sont destinés aux valeurs de retour: - `[[Set]]` doit retourner `true` si la valeur a été écrite avec succès, sinon `false`. - `[[Delete]]` doit retourner `true` si la valeur a été supprimée avec succès, sinon `false`. - ...et ainsi de suite, nous en verrons plus dans les exemples ci-dessous. Il y a d'autres invariants, comme: - `[[GetPrototypeOf]]`, appliqué à l'objet proxy doit renvoyer la même valeur que `[[GetPrototypeOf]]` appliquée à l'objet cible de l'objet proxy. En d'autres termes, la lecture du prototype d'un proxy doit toujours renvoyer le prototype de l'objet cible. Les pièges peuvent intercepter ces opérations, mais ils doivent suivre ces règles. Les invariants garantissent un comportement correct et cohérent des fonctionnalités du langage. La liste complète des invariants est dans [la spécification](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). Vous ne les violerez probablement pas si vous ne faites pas quelque chose de bizarre. ``` Voyons comment cela fonctionne dans des cas pratiques. ## Valeur par défaut avec le piège "get" Les pièges les plus courants concernent les propriétés de lecture / écriture. Pour intercepter la lecture, l'`handler` doit avoir une méthode `get (target, property, receiver)`. Il se déclenche lorsqu'une propriété est lue, avec les arguments suivants: - `target` -- est l'objet cible, celui passé comme premier argument au `new proxy`, - `property` -- nom de la propriété, - `receiver` -- si la propriété cible est un getter, le `receiver` est l'objet qui sera utilisé comme `this` dans son appel. Habituellement, c'est l'objet `proxy` lui-même (ou un objet qui en hérite, si nous héritons du proxy). Pour l'instant, nous n'avons pas besoin de cet argument, il sera donc expliqué plus en détail plus tard. Utilisons `get` pour implémenter les valeurs par défaut d'un objet. Nous allons créer un tableau numérique qui renvoie `0` pour les valeurs inexistantes. Habituellement, quand on essaie d'obtenir un élément de tableau non existant, il est `undefined`, mais nous encapsulerons un tableau normal dans le proxy qui interceptera la lecture et retournera `0` s'il n'y a pas une telle propriété: ```js run let numbers = [0, 1, 2]; numbers = new Proxy(numbers, { get(target, prop) { if (prop in target) { return target[prop]; } else { return 0; // valeur par défaut } } }); *!* alert( numbers[1] ); // 1 alert( numbers[123] ); // 0 (élément inexistant) */!* ``` Comme nous pouvons le voir, c'est assez facile à faire avec un piège `get`. Nous pouvons utiliser `Proxy` pour implémenter n'importe quelle logique pour les valeurs "par défaut". Imaginez que nous ayons un dictionnaire, avec des phrases et leurs traductions: ```js run let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome'] ); // undefined ``` À l'heure actuelle, s'il n'y a pas de phrase, la lecture de `dictionary` renvoie `undefined`. Mais en pratique, laisser une phrase non traduite est généralement mieux que `undefined`. Faisons donc renvoyer une phrase non traduite dans ce cas au lieu de `undefined`. Pour y parvenir, nous allons envelopper le `dictionary` dans un proxy qui intercepte les opérations de lecture: ```js run let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; dictionary = new Proxy(dictionary, { *!* get(target, phrase) { // intercepter la lecture d'une propriété du dictionnaire */!* if (phrase in target) { // si nous l'avons dans le dictionnaire return target[phrase]; // retourne la traduction } else { // sinon, retourne la phrase non traduite return phrase; } } }); // Rechercher des phrases arbitraires dans le dictionnaire! // Au pire, ils ne sont pas traduits alert( dictionary['Hello'] ); // Hola *!* alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (pas de traduction) */!* ``` ````smart Veuillez noter comment le proxy écrase la variable: ```js dictionary = new Proxy(dictionary, ...); ``` Le proxy doit remplacer totalement l'objet cible partout. Personne ne devrait jamais référencer l'objet cible après qu'il a été utilisé comme target du proxy. ```` ## Validation avec le piège "set" Disons que nous voulons un tableau exclusivement pour les nombres. Si une valeur d'un autre type est ajoutée, il devrait y avoir une erreur. Le piège `set` se déclenche lorsqu'une propriété est écrite. `set(target, property, value, receiver)`: - `target` -- est l'objet cible, celui passé comme premier argument au `new proxy`, - `property` -- nom de la propriété, - `value` -- valeur de la propriété, - `receiver` -- similaire au piège `get`, ne concerne que les propriétés du setter. Le piège `set` doit retourner `true` si le réglage est réussi et `false` dans le cas contraire (déclenche `TypeError`). Utilisons-le pour valider de nouvelles valeurs: ```js run let numbers = []; numbers = new Proxy(numbers, { // (*) *!* set(target, prop, val) { // intercepter l'écriture de propriété */!* if (typeof val == 'number') { target[prop] = val; return true; } else { return false; } } }); numbers.push(1); // ajouté avec succès numbers.push(2); // ajouté avec succès alert("Length is: " + numbers.length); // 2 *!* numbers.push("test"); // TypeError ('set' sur proxy retourne false) */!* alert("This line is never reached (error in the line above)"); ``` Note: la fonctionnalité intégrée des tableaux fonctionne toujours! Les valeurs sont ajoutées par `push`. La propriété `length` augmente automatiquement lorsque des valeurs sont ajoutées. Notre proxy ne casse rien. Nous n'avons pas à remplacer les méthodes de tableau à valeur ajoutée comme `push` et `unshift`, etc., pour y ajouter des vérifications, car en interne, elles utilisent l'opération `[[Set]]` interceptée par le proxy. Le code est donc propre et concis. ```warn header="N'oubliez pas de retouner `true`" Comme indiqué ci-dessus, il y a des invariants à tenir Pour `set`, il doit retourner `true` pour une écriture réussie. Si nous oublions de le faire ou retournons une valeur fausse, l'opération déclenche `TypeError`. ``` ## Itération avec "ownKeys" et "getOwnPropertyDescriptor" La boucle `Object.keys`, `for..in` et la plupart des autres méthodes qui itèrent sur les propriétés des objets utilisent la méthode interne `[[OwnPropertyKeys]]` (interceptée par le piège `ownKeys`) pour obtenir une liste des propriétés. Ces méthodes diffèrent dans les détails: - `Object.getOwnPropertyNames(obj)` renvoie des clés non symboliques. - `Object.getOwnPropertySymbols(obj)` renvoie des clés symboliques. - `Object.keys/values()` renvoie les clés / valeurs non symboliques avec l'indicateur `enumerable` (les indicateurs de propriété ont été expliqués dans l'article ). - `for..in` boucle sur les clés non symboliques avec le drapeau `enumerable`, ainsi que sur les clés prototypes. ... Mais tous commencent par cette liste. Dans l'exemple ci-dessous, nous utilisons le piège `ownKeys` pour faire une boucle `for..in` sur `user`, ainsi que `Object.keys` et `Object.values`, pour ignorer les propriétés commençant par un trait de soulignement`_` : ```js run let user = { name: "John", age: 30, _password: "***" }; user = new Proxy(user, { *!* ownKeys(target) { */!* return Object.keys(target).filter(key => !key.startsWith('_')); } }); // "ownKeys" filtre _password for(let key in user) alert(key); // name, après: age // même effet sur ces méthodes: alert( Object.keys(user) ); // name,age alert( Object.values(user) ); // John,30 ``` Jusqu'à présent, cela fonctionne. Bien que, si nous renvoyons une clé qui n'existe pas dans l'objet, `Object.keys` ne la répertoriera pas: ```js run let user = { }; user = new Proxy(user, { *!* ownKeys(target) { */!* return ['a', 'b', 'c']; } }); alert( Object.keys(user) ); // ``` Pourquoi? La raison est simple: `Object.keys` renvoie uniquement les propriétés avec l'indicateur `enumerable`. Pour le vérifier, il appelle la méthode interne `[[GetOwnProperty]]` pour chaque propriété à obtenir [son descripteur](info:property-descriptors). Et ici, comme il n'y a pas de propriété, son descripteur est vide, pas d'indicateur `enumerable`, il est donc ignoré. Pour que `Object.keys` renvoie une propriété, nous avons besoin qu'elle existe dans l'objet, avec l'indicateur `enumerable`, ou nous pouvons intercepter les appels à `[[GetOwnProperty]]` (le piège `getOwnPropertyDescriptor` le fait), et renvoyer un descripteur avec `enumerable: true`. Voici un exemple: ```js run let user = { }; user = new Proxy(user, { ownKeys(target) { // appelé une fois pour obtenir une liste de propriétés return ['a', 'b', 'c']; }, getOwnPropertyDescriptor(target, prop) { // appelé pour chaque propriété return { enumerable: true, configurable: true /* ...other flags, probable "value:..." */ }; } }); alert( Object.keys(user) ); // a, b, c ``` Notons encore une fois: nous n'avons besoin d'intercepter `[[GetOwnProperty]]` que si la propriété est absente dans l'objet. ## Propriétés protégées avec "deleteProperty" et autres pièges Il existe une convention répandue selon laquelle les propriétés et les méthodes précédées d'un trait de soulignement `_` sont internes. Ils ne doivent pas être accessibles depuis l'extérieur de l'objet. Techniquement, c'est possible: ```js run let user = { name: "John", _password: "secret" }; alert(user._password); // secret ``` Utilisons des proxys pour empêcher tout accès aux propriétés commençant par `_`. Nous aurons besoin des pièges: - `get` lancer une erreur lors de la lecture d'une telle propriété, - `set` lancer une erreur lors de l'écriture, - `deleteProperty` lancer une erreur lors de la suppression, - `ownKeys` pour exclure les propriétés commençant par `_` de `for..in` et les méthodes comme `Object.keys`. Voici le code: ```js run let user = { name: "John", _password: "***" }; user = new Proxy(user, { *!* get(target, prop) { */!* if (prop.startsWith('_')) { throw new Error("Access denied"); } let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (*) }, *!* set(target, prop, val) { // intercepter l'écriture de propriété */!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { target[prop] = val; return true; } }, *!* deleteProperty(target, prop) { // pour intercepter la suppression de propriété */!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { delete target[prop]; return true; } }, *!* ownKeys(target) { // intercepter la liste des propriétés */!* return Object.keys(target).filter(key => !key.startsWith('_')); } }); // "get" ne permet pas de lire _password try { alert(user._password); // Erreur: accès refusé } catch(e) { alert(e.message); } // "set" ne permet pas d'écrire _password try { user._password = "test"; // Erreur: accès refusé } catch(e) { alert(e.message); } // "deleteProperty" ne permet pas de supprimer _password try { delete user._password; // Erreur: accès refusé } catch(e) { alert(e.message); } // "ownKeys" filtre _password for(let key in user) alert(key); // name ``` Veuillez noter les détails importants dans le piège `get`, dans la ligne `(*)`: ```js get(target, prop) { // ... let value = target[prop]; *!* return (typeof value === 'function') ? value.bind(target) : value; // (*) */!* } ``` Pourquoi avons-nous besoin d'une fonction pour appeler `value.bind(target)` ? La raison est que les méthodes d'objet, telles que `user.checkPassword()`, doivent pouvoir accéder à `_password`: ```js user = { // ... checkPassword(value) { // la méthode objet doit pouvoir lire _password return value === this._password; } } ``` L'appel `user.checkPassword()` obtient l'`user` proxy comme `this` (l'objet avant le point devient `this`), donc quand il essaie d'accéder à `this._password`, le piège `get` s'active (il se déclenche sur n'importe quelle propriété lue) et génère une erreur. Nous lions donc le contexte des méthodes objet à l'objet d'origine, `target`, dans la ligne `(*)`. Ensuite, leurs futurs appels utiliseront `target` comme `this`, sans aucun piège. Cette solution fonctionne généralement, mais n'est pas idéale, car une méthode peut faire passer l'objet non sollicité ailleurs. En outre, un objet peut être proxy plusieurs fois (plusieurs procurations peuvent ajouter différents "réglages" à l'objet), et si nous transmettons un objet non enveloppé à une méthode, il peut y avoir des conséquences inattendues. Donc, un tel proxy ne devrait pas être utilisé partout. ```smart header="Propriétés privées d'une classe" Les moteurs JavaScript modernes prennent en charge nativement les propriétés privées dans les classes, préfixées par `#`. Ils sont décrits dans l'article . Aucun proxy requis. Ces propriétés ont cependant leurs propres problèmes. En particulier, ils ne sont pas hérités. ``` ## "In range" avec le piège "has" Voyons plus d'exemples. Nous avons un objet `range`: ```js let range = { start: 1, end: 10 }; ``` Nous aimerions utiliser l'opérateur `in` pour vérifier qu'un nombre est `in range` (à portée). Le piège `has` intercepte l'opérateur `in`. `has(target, property)` - `target` -- est l'objet cible, passé comme premier argument à `new Proxy`, - `property` -- nom de la propriété Voici la démo: ```js run let range = { start: 1, end: 10 }; range = new Proxy(range, { *!* has(target, prop) { */!* return prop >= target.start && prop <= target.end; } }); *!* alert(5 in range); // true alert(50 in range); // false */!* ``` bon sucre syntaxique, non? Et très simple à mettre en œuvre. ## Wrapping functions: "apply" [#proxy-apply] Nous pouvons également envelopper un proxy autour d'une fonction. Le piège `apply(target, thisArg, args)` gère l'appel d'un proxy en tant que fonction: - `target` est l'objet cible (la fonction est un objet en JavaScript), - `thisArg` est la valeur de `this`. - `args` est une liste d'arguments. Par exemple, rappelons le décorateur `delay(f, ms)`, que nous avons fait dans l'article . Dans cet article, nous l'avons fait sans proxy. Un appel à `delay(f, ms)` a renvoyé une fonction qui transfère tous les appels à `f` après `ms` millisecondes. Voici l'implémentation précédente basée sur les fonctions: ```js run function delay(f, ms) { // retourner un wrapper qui passe l'appel à f après le délai d'expiration return function() { // (*) setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } // après ce wrapping, les appels à sayHi seront retardés de 3 secondes sayHi = delay(sayHi, 3000); sayHi("John"); // Hello, John! (après 3 secondes) ``` Comme nous l'avons déjà vu, cela fonctionne souvent. La fonction wrapper `(*)` effectue l'appel après le délai d'expiration. Mais une fonction wrapper ne transmet pas les opérations de lecture / écriture de propriété ni rien d'autre. Après le wrapping, l'accès est perdu pour les propriétés des fonctions d'origine, telles que le `name`, `length` et autres: ```js run function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } *!* alert(sayHi.length); // 1 (la longueur de la fonction est le nombre d'arguments dans sa déclaration) */!* sayHi = delay(sayHi, 3000); *!* alert(sayHi.length); // 0 (dans la déclaration wrapper, il n'y a aucun argument) */!* ``` Le `proxy` est beaucoup plus puissant, car il transmet tout à l'objet cible. Utilisons `Proxy` au lieu d'une fonction de "wrapping": ```js run function delay(f, ms) { return new Proxy(f, { apply(target, thisArg, args) { setTimeout(() => target.apply(thisArg, args), ms); } }); } function sayHi(user) { alert(`Hello, ${user}!`); } sayHi = delay(sayHi, 3000); *!* alert(sayHi.length); // 1 (*) le proxy transmet l'opération "get length" à la cible */!* sayHi("John"); // Hello, John! (après 3 secondes) ``` Le résultat est le même, mais maintenant non seulement les appels, mais toutes les opérations sur le proxy sont transférés vers la fonction d'origine. Donc, `sayHi.length` est renvoyé correctement après le retour à la ligne `(*)`. Nous avons un wrapper "plus riche". D'autres pièges existent: la liste complète se trouve au début de cet article. Leur modèle d'utilisation est similaire à ce qui précède. ## Reflect `Reflect` est un objet intégré qui simplifie la création de `Proxy`. Il a été dit précédemment que les méthodes internes, telles que `[[Get]]`, `[[Set]]` et d'autres ne sont que des spécifications, elles ne peuvent pas être appelées directement. L'objet `Reflect` rend cela possible. Ses méthodes sont des wrapper minimales autour des méthodes internes. Voici des exemples d'opérations et d'appels `Reflect` identiques: | Opération | Appel `Reflect` | Méthode interne | |-----------------|----------------|-------------| | `obj[prop]` | `Reflect.get(obj, prop)` | `[[Get]]` | | `obj[prop] = value` | `Reflect.set(obj, prop, value)` | `[[Set]]` | | `delete obj[prop]` | `Reflect.deleteProperty(obj, prop)` | `[[Delete]]` | | `new F(value)` | `Reflect.construct(F, value)` | `[[Construct]]` | | ... | ... | ... | Par exemple: ```js run let user = {}; Reflect.set(user, 'name', 'John'); alert(user.name); // John ``` `Reflect` nous permet d'appeler des opérateurs (`new`, `delete` ...) en tant que fonctions (`Reflect.construct`, `Reflect.deleteProperty`, ...). C'est une capacité intéressante, mais ici, une autre chose est importante. **Pour chaque méthode interne, piégeable par `Proxy`, il existe une méthode correspondante dans `Reflect`, avec le même nom et les mêmes arguments que le piège dans `Proxy`.** Nous pouvons donc utiliser `Reflect` pour transmettre une opération à l'objet d'origine. Dans cet exemple, les deux pièges `get` et `set` de manière transparente (comme si elles n'existaient pas) transmettent les opérations de lecture / écriture à l'objet, affichant un message ```js run let user = { name: "John", }; user = new Proxy(user, { get(target, prop, receiver) { alert(`GET ${prop}`); *!* return Reflect.get(target, prop, receiver); // (1) */!* }, set(target, prop, val, receiver) { alert(`SET ${prop}=${val}`); *!* return Reflect.set(target, prop, val, receiver); // (2) */!* } }); let name = user.name; // affiche "GET name" user.name = "Pete"; // affiche "SET name=Pete" ``` Ici: - `Reflect.get` lit une propriété d'objet. - `Reflect.set` écrit une propriété d'objet et renvoie `true` en cas de succès, `false` dans le cas contraire Autrement dit, tout est simple: si un piège veut renvoyer l'appel à l'objet, il suffit d'appeler `Reflect.` avec les mêmes arguments. Dans la plupart des cas, nous pouvons faire de même sans `Reflect`, par exemple, la lecture d'une propriété `Reflect.get(target, prop, receiver)` peut être remplacée par `target[prop]`. Il y a cependant des nuances importantes. ### Proxying a getter Voyons un exemple qui montre pourquoi `Reflect.get` est meilleur. Et nous verrons également pourquoi `get/set` a le troisième argument `receiver`, que nous n'avions pas utilisé auparavant. Nous avons un objet `user` avec la propriété `_name` et un getter pour cela. Voici un proxy autour de lui: ```js run let user = { _name: "Guest", get name() { return this._name; } }; *!* let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; } }); */!* alert(userProxy.name); // Guest ``` Le piège `get` est "transparent" ici, il renvoie la propriété d'origine et ne fait rien d'autre. Cela suffit pour notre exemple. Tout semble aller bien. Mais rendons l'exemple un peu plus complexe. Après avoir hérité d'un autre objet `admin` de l'`user`, nous pouvons observer le comportement incorrect: ```js run let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; // (*) target = user } }); *!* let admin = { __proto__: userProxy, _name: "Admin" }; // Attendu: Admin alert(admin.name); // retourne: Guest (?!?) */!* ``` La lecture de `admin.name` devrait renvoyer `"Admin"`, pas `"Guest"`! Quel est le problème? Peut-être que nous avons fait quelque chose de mal avec l'héritage? Mais si nous supprimons le proxy, tout fonctionnera comme prévu. Le problème est en fait dans le proxy, dans la ligne `(*)`. 1. Lorsque nous lisons `admin.name`, comme l'objet `admin` n'a pas une telle propriété, la recherche va à son prototype. 2. Le prototype est `userProxy`. 3. Lors de la lecture de la propriété `name` du proxy, son piège `get` se déclenche et la renvoie à partir de l'objet d'origine en tant que `target[prop]` dans la ligne `(*)`. Un appel à `target[prop]`, lorsque `prop` est un getter, exécute son code dans le contexte `this=target`. Le résultat est donc `this._name` de l'objet `target` d'origine , c'est-à-dire de l'`user`. Pour résoudre de telles situations, nous avons besoin de `receiver`, le troisième argument du piège `get`. Il garde le bon `this` à transmettre à un getter. Dans notre cas, c'est `admin`. Comment passer le contexte pour un getter? Pour une fonction régulière, nous pourrions utiliser `call/apply`, mais c'est un getter, ce n'est pas "appelé", juste accessible. `Reflect.get` peut faire ça. Tout fonctionnera bien si nous l'utilisons. Voici la variante corrigée: ```js run let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { // receiver = admin *!* return Reflect.get(target, prop, receiver); // (*) */!* } }); let admin = { __proto__: userProxy, _name: "Admin" }; *!* alert(admin.name); // Admin */!* ``` Maintenant, `receiver` garde une référence à `this` correct (c'est-à-dire `admin`), est transmis au getter en utilisant `Reflect.get` dans la ligne `(*)`. On peut réécrire le piège encore plus court: ```js get(target, prop, receiver) { return Reflect.get(*!*...arguments*/!*); } ``` Les appels `Reflect` sont nommés exactement de la même manière que les pièges et acceptent les mêmes arguments. Ils ont été spécialement conçus de cette façon. Donc, `return Reflect...` fournit un moyen sûr et simple de faire avancer l'opération et assure qu'on oubliera rien. ## Limitations du proxy Les proxys offrent un moyen unique de modifier ou d'améliorer le comportement des objets existants au niveau le plus bas. Pourtant, ce n'est pas parfait. Il y a des limites. ### Objets intégrés: emplacements internes De nombreux objets intégrés, par exemple `Map`, `Set`, `Date`, `Promise` et d'autres utilisent des «emplacements internes». Ce sont des propriétés similaires, mais réservées à des fins internes uniquement. Par exemple, `Map` stocke les éléments dans l'emplacement interne `[[MapData]]`. Les méthodes intégrées y accèdent directement, pas via les méthodes internes `[[Get]]/[[Set]]`. Donc, `Proxy` ne peut pas intercepter cela. Pourquoi s'en soucier? Ils sont internes de toute façon! Eh bien, voici le problème. Une fois qu'un objet intégré comme celui-ci a été proxy, le proxy n'a pas ces emplacements internes, les méthodes intégrées échoueront donc. Par exemple: ```js run let map = new Map(); let proxy = new Proxy(map, {}); *!* proxy.set('test', 1); // Erreur */!* ``` En interne, un `Map` stocke toutes les données dans son emplacement interne `[[MapData]]`. Le proxy n'a pas un tel emplacement. La [méthode intégrée `Map.prototype.set`](https://tc39.es/ecma262/#sec-map.prototype.set) essaie d'accéder à la propriété interne `this.[[MapData]]`, mais parce que `this=proxy`, elle ne peut pas la trouver dans le proxy et échoue. Heureusement, il existe un moyen de le corriger: ```js run let map = new Map(); let proxy = new Proxy(map, { get(target, prop, receiver) { let value = Reflect.get(...arguments); *!* return typeof value == 'function' ? value.bind(target) : value; */!* } }); proxy.set('test', 1); alert(proxy.get('test')); // 1 (ça fonctionne!) ``` Maintenant, cela fonctionne très bien, car le piège `get` lie les propriétés de la fonction, telles que `map.set`, à l'objet cible (`map`) lui-même. Contrairement à l'exemple précédent, la valeur de `this` dans `proxy.set(...)` ne sera pas `proxy`, mais le `map` d'origine. Ainsi, lorsque l'implémentation interne de `set` essaie d'accéder à l'emplacement interne `this.[[MapData]]`, il réussit. ```smart header="`Array` n'a pas d'emplacements internes" Une exception notable: `Array` n'utilise pas d'emplacement internes. Pour des raisons historiques. Il n'y a donc pas de problème de ce type lors de l'utilisation d'un proxy. ``` ### Champs privés La même chose se produit avec les champs de classe privés. Par exemple, la méthode `getName()` accède à la propriété privée `#name` et s'arrête après le proxy: ```js run class User { #name = "Guest"; getName() { return this.#name; } } let user = new User(); user = new Proxy(user, {}); *!* alert(user.getName()); // Erreur */!* ``` La raison est que les champs privés sont implémentés à l'aide d'emplacement internes. JavaScript n'utilise pas `[[Get]]/[[Set]]` pour y accéder. Dans l'appel `getName()`, la valeur de `this` est l'`user` proxy, et il n'a pas l'emplacement avec des champs privés. Encore une fois, la solution avec la liaison de la méthode fonctionne: ```js run class User { #name = "Guest"; getName() { return this.#name; } } let user = new User(); user = new Proxy(user, { get(target, prop, receiver) { let value = Reflect.get(...arguments); return typeof value == 'function' ? value.bind(target) : value; } }); alert(user.getName()); // Guest ``` Cela dit, la solution présente des inconvénients, comme expliqué précédemment: elle expose l'objet d'origine à la méthode, ce qui peut potentiellement le faire passer plus loin et briser d'autres fonctionnalités proxy. ### Proxy != target Le proxy et l'objet d'origine sont des objets différents. C'est normal, non? Donc, si nous utilisons l'objet d'origine comme clé, puis le proxy, le proxy ne peut pas être trouvé: ```js run let allUsers = new Set(); class User { constructor(name) { this.name = name; allUsers.add(this); } } let user = new User("John"); alert(allUsers.has(user)); // true user = new Proxy(user, {}); *!* alert(allUsers.has(user)); // false */!* ``` Comme nous pouvons le voir, après le proxy, nous ne pouvons pas trouver d'`user` dans l'ensemble `allUsers`, car le proxy est un objet différent. ```warn header=""Les proxy ne peuvent pas intercepter un test d'égalité strict `===`" Les proxys peuvent intercepter de nombreux opérateurs, tels que `new` (avec `construct`), `in` (avec `has`), `delete` (avec `deleteProperty`), etc. Mais il n'y a aucun moyen d'intercepter un test d'égalité strict pour les objets. Un objet est strictement égal à lui-même uniquement, et aucune autre valeur. Ainsi, toutes les opérations et les classes intégrées qui comparent les objets pour l'égalité feront la différence entre l'objet et le proxy. Pas de remplacement transparent ici. ``` ## Proxies révocables Un proxy *révocable* est un proxy qui peut être désactivé. Disons que nous avons une ressource et que nous aimerions en fermer l'accès à tout moment. Ce que nous pouvons faire, c'est de l'envelopper dans un proxy révocable, sans aucun piège. Un tel proxy transmettra les opérations à l'objet, et nous pouvons le désactiver à tout moment. La syntaxe est: ```js let {proxy, revoke} = Proxy.revocable(target, handler) ``` L'appel renvoie un objet avec la fonction `proxy` et `revoke` pour le désactiver. Voici un exemple: ```js run let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); // passer le proxy quelque part au lieu de l'objet... alert(proxy.data); // Valuable data // plus tard dans le code revoke(); // le proxy ne fonctionne plus (révoqué) alert(proxy.data); // Erreur ``` Un appel à `revoke()` supprime toutes les références internes à l'objet cible du proxy, de sorte qu'ils ne sont plus connectés. Initialement, `revoke` est séparé de `proxy`, de sorte que nous pouvons passer `proxy` tout en laissant `revoke` dans la portée actuelle. Nous pouvons également lier la méthode `revoke` au proxy en définissant `proxy.revoke = revoke`. Une autre option est de créer une `WeakMap` qui a `proxy` comme clé et la valeur `revoke` correspondante, qui permet de trouver facilement `revoke` pour un proxy : ```js run *!* let revokes = new WeakMap(); */!* let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); // ..plus tard dans notre code.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Erreur (révoqué) ``` Nous utilisons ici `WeakMap` au lieu de `Map` car cela ne bloquera pas le "garbage collection". Si un objet proxy devient "inaccessible" (par exemple si plus aucune variable ne le référence), `WeakMap` permet de l'effacer de la mémoire en même temps que `revoke` dont nous n'aurons plus besoin. ## Références - spécification: [Proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). - MDN: [Proxy](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy). ## Résumé Le `proxy` est un wrapper autour d'un objet, qui transfère des opérations sur celui-ci à l'objet, éventuellement en piégeant certains d'entre eux. Il peut envelopper n'importe quel type d'objet, y compris les classes et les fonctions. La syntaxe est: ```js let proxy = new Proxy(target, { /* traps */ }); ``` ... Ensuite, nous devrions utiliser le `proxy` partout au lieu de `target`. Un proxy n'a pas ses propres propriétés ou méthodes. Il intercepte une opération si l'interruption est fournie, sinon la transmet à `target`. Nous pouvons piéger : - Lecture (`get`), écriture (`set`), suppression (`deleteProperty`) d'une propriété (même inexistante). - Appeler une fonction (piège `apply`). - L'opérateur `new` (piège `construct`). - De nombreuses autres opérations (la liste complète se trouve au début de l'article et dans la [documentation](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy)). Cela nous permet de créer des propriétés et des méthodes "virtuelles", d'implémenter des valeurs par défaut, des objets observables, des décorateurs de fonctions et bien plus encore. Nous pouvons également envelopper un objet plusieurs fois dans différents proxys, en le décorant avec divers aspects de la fonctionnalité. L'API de [Reflect](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Reflect) est conçu pour compléter [Proxy](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy). Pour tout piège `proxy`, il existe un appel `Reflect` avec les mêmes arguments. Nous devons les utiliser pour transférer des appels vers des objets cibles Les proxy ont certaines limites: - Les objets intégrés ont des "emplacements internes", l'accès à ceux-ci ne peut pas être proxy. Voir la solution de contournement ci-dessus. - Il en va de même pour les champs de classe privés, car ils sont implémentés en interne à l'aide de slots. Les appels de méthode proxy doivent donc avoir l'objet cible comme `this` pour y accéder - Les tests d'égalité strics `===` ne peuvent pas être interceptés - Performances: les benchmarks dépendent d'un moteur, mais généralement accéder à une propriété à l'aide d'un proxy simple prend un peu plus de temps. En pratique, cela n'a d'importance que pour certains objets "bottleneck". ================================================ FILE: 1-js/99-js-misc/02-eval/1-eval-calculator/solution.md ================================================ Utilisons `eval` pour calculer l'expression mathématique : ```js demo run let expr = prompt("Type an arithmetic expression?", '2*3+2'); alert( eval(expr) ); ``` L'utilisateur peut envoyer n'importe quel texte ou code cependant. Pour rendre les choses sûres, et les limiter à de l'arithmétique, nous pouvons vérifier `expr` en utilisant une [expression régulière](info:regular-expressions), pour qu'elle ne contienne que des nombres et des opérateurs. ================================================ FILE: 1-js/99-js-misc/02-eval/1-eval-calculator/task.md ================================================ importance: 4 --- # Calculatrice-eval Créez une calculatrice qui demande une expression arithmétique et retourne son résultat. Il n'y a pas besoin de vérifier l'exactitude de l'expression. Évaluez simplement la et retournez le résultat. [demo] ================================================ FILE: 1-js/99-js-misc/02-eval/article.md ================================================ # Eval : exécution d'un texte code La fonction intégrée `eval` permet d'exécuter une chaîne de caractère comprenant du code. La syntaxe est : ```js let result = eval(code); ``` Par exemple: ```js run let code = 'alert("Hello")'; eval(code); // Hello ``` Une chaîne de code peut être long, contenir des sauts à la ligne, des déclarations de fonctions, de variables et autres. Le résultat de `eval` est le résultat de la dernière instruction. For example: ```js run let value = eval('1+1'); alert(value); // 2 ``` ```js run let value = eval('let i = 0; ++i'); alert(value); // 1 ``` Le code évalué est exécuté dans l'environnement lexical actuel, il peut donc voir les variables extérieures : ```js run no-beautify let a = 1; function f() { let a = 2; *!* eval('alert(a)'); // 2 */!* } f(); ``` Il peut également changer les variables extérieures : ```js untrusted refresh run let x = 5; eval("x = 10"); alert(x); // 10, valeur modifiée ``` En mode strict, `eval` a son propre environnement lexical. Donc les fonctions et variables, déclarées au sein de l'eval, ne sont pas visibles en dehors : ```js untrusted refresh run // rappel : 'use strict' est actif par défaut dans les exemples exécutables eval("let x = 5; function f() {}"); alert(typeof x); // undefined (pas de telle variable) // la fonction f n'est pas visible non plus ``` Sans `use strict`, `eval` n'a pas d'environnement lexical propre, donc nous verrions `x` et `f` en dehors. ## Utiliser "eval" Dans la programmation moderne, `eval` est utilisé avec beaucoup de parcimonie. Il est souvent dit que "eval est le mal". La raison est simple : il y a très, très longtemps, JavaScript était un langage beaucoup plus faible, beaucoup de choses ne pouvaient être faites que par l'intermédiaire de `eval`. Mais ce temps est passé depuis une décennie. À l'heure actuelle, il n'y a presque aucune raison `eval`. Si quelqu'un l'utilise, il y a de fortes chances pour qu'il/elle puisse le remplacer par une construction plus moderne ou un [Module JavaScript](info:modules). Veuillez noter que son accessibilité aux variables extérieures a des effets secondaires. Les minifacteurs de code (outils utilisés avant que JS parte en production, pour le compresser) renomment les variables locales avec des noms plus courts (comme `a`, `b` etc) pour rendre le code plus court. C'est souvent sûr, mais pas si `eval` est utilisé, étant donné que les variables locales peuvent être accessibles par le code évalué. Donc les minifacteurs ne font pas ce travail pour toutes les variables potentiellement visibles par le `eval`. Cela affecte négativement le ratio de compression. Utiliser les variables locales extérieures dans un `eval` est de plus considéré comme une mauvaise pratique, étant donné que cela réduit la maintenabilité du code. Il y a deux moyens pour être entièrement sûrs d'éviter de tels problèmes. **Si le code évalué n'a pas besoin des variables extérieures, appelez `eval` via `window.eval(...)`:** De ce fait, le code est exécuté dans la portée globale: ```js untrusted refresh run let x = 1; { let x = 5; window.eval('alert(x)'); // 1 (variable globale) } ``` **Si le code évalué a besoin des variables extérieures, changez `eval` par `new Function` et passez-le comme argument :** ```js run let f = new Function('a', 'alert(a)'); f(5); // 5 ``` La construction `new Function` est expliquée dans le chapitre . Elle crée une fonction à partir d'une chaîne de caractères dans la portée globale. Elle ne peut donc pas voir les variables locales. Mais c'est beaucoup plus clair de les passer explicitement comme arguments, comme dans l'exemple ci-dessus. ## Résumé Un appel de `eval(code)` exécute la chaîne de code et retourne le résultat de la dernière instruction. - Rarement utilisé dans le JavaScript moderne, puisque souvent inutile. - Peut accéder aux variables locales extérieures. C'est considéré comme une mauvaise pratique. - À la place, pour `eval` le code dans la portée globale, utilisez `window.eval(code)`. - Sinon, si votre code a besoin de données de la portée extérieure, utilisez `new Function` et passez-le comme argument. ================================================ FILE: 1-js/99-js-misc/03-currying-partials/article.md ================================================ libs: - lodash --- # Curryfication La [curryfication](https://fr.wikipedia.org/wiki/Curryfication) est une technique avancée de travail avec les fonctions. Ce n'est pas seulement utilisé avec JavaScript, mais dans d'autres langages également. La curryfication est la transformation de fonction qui traduit une fonction de la forme `f(a, b, c)` en une fonction de la forme `f(a)(b)(c)`. La curryfication n'appelle pas une fonction ; elle la transforme simplement. Voyons d'abord un exemple pour mieux comprendre de quoi nous parlons, et mettons ensuite en pratique. Nous allons créer une fonction d'aide `curry(f)` qui curryfie une fonction `f` à deux arguments. En d'autres mots, `curry(f)` sur une fonction `f(a, b)` la traduit en une fonction qui s'appelle par `f(a)(b)` : ```js run *!* function curry(f) { // curry(f) fait la curryfication return function(a) { return function(b) { return f(a, b); }; }; } */!* // usage function sum(a, b) { return a + b; } let curriedSum = curry(sum); alert( curriedSum(1)(2) ); // 3 ``` Comme vous pouvez le voir, l'implémentation est simple : il ne s'agit que de deux enveloppes. - Le résultat de `curry(func)` est une enveloppe `function(a)`. - Lorsqu'il est appelé comme `curriedSum(1)`, l'argument est sauvegardé dans l'environnement lexical, et une nouvelle enveloppe `function(b)` est retournée. - Ensuite cette enveloppe est appelée avec `2` comme argument, et passe l'appel à la fonction originelle `sum`. Des implémentations plus avancées de la curryfication, comme [_.curry](https://lodash.com/docs#curry) de la bibliothèque lodash, retournent une enveloppe qui permet à une fonction d'être à la fois appelée normalement et partiellement : ```js run function sum(a, b) { return a + b; } let curriedSum = _.curry(sum); // usage de _.curry de la bibliothèque lodash alert( curriedSum(1, 2) ); // 3, toujours appelable normalement alert( curriedSum(1)(2) ); // 3, appelée partiellement ``` ## La curryfication ? Pour quoi faire ? Pour comprendre les bénéfices, nous avons besoin d'un exemple réel. Par exemple, nous avons la fonction de journalisation `log(date, importance, message)` qui formate et écrit l'information. Dans de réels projets de telles fonctions ont beaucoup de fonctionnalités utiles comme envoyer les journaux sur un réseau, ici nous allons juste utiliser `alert` : ```js function log(date, importance, message) { alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`); } ``` Curryfions-la ! ```js log = _.curry(log); ``` Après ça `log` fonctionne normalement: ```js log(new Date(), "DEBUG", "some debug"); // log(a, b, c) ``` ... mais aussi dans la forme curryfiée : ```js log(new Date())("DEBUG")("some debug"); // log(a)(b)(c) ``` Nous pouvons maintenant faire une fonction pratique pour la journalisation actuelle : ```js // logNow sera la partie partielle de log avec un premier argument fixe let logNow = log(new Date()); // utilisons-la logNow("INFO", "message"); // [HH:mm] INFO message ``` Maintenant `logNow` est `log` avec un premier argument fixe, en d'autres termes "fonction partiellement appliquée" ou "partielle" pour faire court. Nous pouvons aller plus loin et faire une fonction pratique pour le débogage actuel : ```js let debugNow = logNow("DEBUG"); debugNow("message"); // [HH:mm] DEBUG message ``` Donc : 1. Nous n'avons rien perdu après avoir curryfié : `log` est toujours appelable normalement. 2. Nous pouvons aisément créer des fonctions partielles comme pour la journalisation d'aujourd'hui. ## Implémentation avancée de la curryfication Au cas où vous souhaiteriez entrer dans les détails, voici l'implémentation "avancée" de la curryfication pour les fonctions à plusieurs arguments que nous avons pu utiliser plus haut. C'est plutôt court : ```js function curry(func) { return function curried(...args) { if (args.length >= func.length) { return func.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2)); } } }; } ``` Exemples d'usage : ```js function sum(a, b, c) { return a + b + c; } let curriedSum = curry(sum); alert( curriedSum(1, 2, 3) ); // 6, toujours appelable normalement alert( curriedSum(1)(2,3) ); // 6, curryfiée au premier argument alert( curriedSum(1)(2)(3) ); // 6, curryfiée totalement ``` La nouvelle `curry` semble être compliquée, mais est assez simple à comprendre. Le résultat de `curry(func)` est l'enveloppe `curried` qui ressemble à ça : ```js // func est la fonction à transformer function curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { return function(...args2) { // (2) return curried.apply(this, args.concat(args2)); } } }; ``` Quand on la lance, il y a deux branches `if` : 1. Si le nombre d'`args` passé est égal ou supérieur à celui de la fonction d'origine dans sa définition (`func.length`), alors on lui passe simplement l'appel en utilisant `func.apply`. 2. Sinon, on obtient un partiel : nous n'appelons pas encore `func`. Au lieu de cela, un autre wrapper est retourné, qui réappliquera `curried` en fournissant les arguments précédents avec les nouveaux. Ensuite, si nous l'appelons, encore une fois, nous obtiendrons soit un nouveau partiel (si pas assez d'arguments) ou, finalement, le résultat. ```smart header="Fonctions à nombre d'arguments fixe seulement" La curryfaction requiert que la fonction ait un nombre d'arguments fixe. Une fonction qui utilise un paramètre de reste, comme `f(...args)`, ne peut pas être curryfiée de cette façon. ``` ```smart header="Un peu plus que la curryfication" Par définition, la curryfication devrait convertir `sum(a, b, c)` en `sum(a)(b)(c)`. Mais la plupart des implémentations en JavaScript sont avancées, comme décrites : elles laissent la possibilité d'appeler la fonction via plusieurs arguments. ``` ## Résumé La *curryfication* est une transformation qui rend `f(a,b,c)` appelable comme `f(a)(b)(c)`. Les implémentations en JavaScript laissent généralement la possibilité d'appeler les fonctions normalement et de retourner une partielle si le nombre d'arguments n'est pas suffisant. La curryfication nous permet d'avoir aisément des partielles. Comme nous avons pu le voir dans l'exemple de journalisation, après avoir curryfié la fonction à trois arguments, `log(date, importance, message)` nous donne une partielle quand appelée avec un argument (comme `log(date)`) ou deux arguments (comme `log(date, importance)`). ================================================ FILE: 1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md ================================================ **Erreur**! Essayez le : ```js run let user = { name: "John", go: function() { alert(this.name) } } (user.go)() // error! ``` Le message d'erreur dans la plupart des navigateurs ne permet pas de comprendre ce qui s'est mal passé. **L'erreur apparaît parce qu'un point-virgule est manquant après `user = {...}`.** JavaScript n'insert pas automatiquement un point-virgule avant une accolade `(user.go)()`, il lit donc le code comme ceci : ```js no-beautify let user = { go:... }(user.go)() ``` Ensuite, nous pouvons également voir qu'une telle expression conjointe est syntaxiquement un appel de l'objet `{ go: ... }` en tant que fonction avec l'argument `(user.go)`. Et cela se produit également sur la même ligne avec `let user`, alors que l'objet `user` n'a même pas encore été défini, d'où l'erreur. Si nous insérons le point-virgule, tout va bien : ```js run let user = { name: "John", go: function() { alert(this.name) } }*!*;*/!* (user.go)() // John ``` Veuillez noter que les parenthèses autour de `(user.go)` ne font rien ici. Habituellement, elles configurent l'ordre des opérations, mais ici le point `.` fonctionne toujours en premier de toute façon, donc aucun effet. Seul le point-virgule compte. ================================================ FILE: 1-js/99-js-misc/04-reference-type/2-check-syntax/task.md ================================================ importance: 2 --- # Vérification de la syntaxe Quel est le résultat de ce code ? ```js no-beautify let user = { name: "John", go: function() { alert(this.name) } } (user.go)() ``` P.S. Il y a un piège :) ================================================ FILE: 1-js/99-js-misc/04-reference-type/3-why-this/solution.md ================================================ Voici les explications. 1. C’est un appel de méthode d’objet standard. 2. De même, les parenthèses ne changent pas l'ordre des opérations ici, le point est le premier quand même. 3. Nous avons ici un appel plus complexe `(expression)()`. L'appel fonctionne comme s'il était divisé en deux lignes : ```js no-beautify f = obj.go; // calculer l'expression f(); // appeler ce que nous avons ``` Ici, `f()` est exécuté en tant que fonction, sans `this`. 4. La chose similaire à `(3)`, à gauche des parenthèses `()`, nous avons une expression. Pour expliquer le comportement de `(3)` et `(4)`, nous devons rappeler que les accesseurs de propriétés (points ou crochets) renvoient une valeur du type de référence. Toute opération sur celle-ci, à l'exception d'un appel de méthode (comme affectation `=` ou `||`), la convertit en une valeur ordinaire, qui ne porte pas les informations permettant de définir `this`. ================================================ FILE: 1-js/99-js-misc/04-reference-type/3-why-this/task.md ================================================ importance: 3 --- # Expliquez la valeur de "this" Dans le code ci-dessous, nous avons l'intention d'appeler la méthode `obj.go()` 4 fois de suite. Mais les appels `(1)` et `(2)` fonctionnent différemment de `(3)` et `(4)`. Pourquoi ? ```js run no-beautify let obj, method; obj = { go: function() { alert(this); } }; obj.go(); // (1) [object Object] (obj.go)(); // (2) [object Object] (method = obj.go)(); // (3) undefined (obj.go || obj.stop)(); // (4) undefined ``` ================================================ FILE: 1-js/99-js-misc/04-reference-type/article.md ================================================ # Type référence ```warn header="Sujet avancé" Cet article couvre un sujet avancé pour mieux comprendre certains cas limites. Ce n'est pas important. De nombreux développeurs expérimentés vivent bien sans le savoir. Continuez à lire si vous voulez savoir comment les choses fonctionnent sous le capot. ``` Un appel de méthode évalué dynamiquement peut perdre `this`. Par exemple: ```js run let user = { name: "John", hi() { alert(this.name); }, bye() { alert("Bye"); } }; user.hi(); // fonctionne // essayons maintenant d'appeler user.hi ou user.by selon name *!* (user.name == "John" ? user.hi : user.bye)(); // Error ! */!* ``` Sur la dernière ligne il y a un opérateur conditionnel qui choisit entre `user.hi` ou `user.bye`. Ici le résultat est `user.hi`. Ensuite la méthode est immédiatement appelée avec les parenthèses `()`. Mais cela ne fonctionne pas ! Comme vous pouvez le voir, l'appel se résout avec une erreur car la valeur de `"this"` dans l'appel devient `undefined`. Cet appel fonctionne (syntaxe de notation par points): ```js user.hi(); ``` Celui-là non (méthode évaluée): ```js (user.name == "John" ? user.hi : user.bye)(); // Error ! ``` Pourquoi ? Si nous voulons comprendre pourquoi cela arrive, regardons comment l'appel de `obj.method()` fonctionne sous le capot. ## Le type référence expliqué En y regardant plus précisement, on peut remarquer 2 opérations dans la déclaration de `obj.method()`: 1. En premier, le point `'.'` récupère la propriété `obj.method`. 2. Puis les parenthèses `()` l'éxécute. Mais comment l'information du `this` est passée de la première opération à la deuxième ? Si on sépare ces opération sur 2 lignes, alors `this` sera perdu : ```js run let user = { name: "John", hi() { alert(this.name); } }; *!* // On sépare l'accès à la méthode et son appel en deux lignes let hi = user.hi; hi(); // Error, car this n'est pas définit */!* ``` Ici `hi = user.hi` assigne la fonction à la variable, ensuite sur la dernière ligne `this` est complétement autonome et donc il n'y a pas de `this`. **Pour faire que `user.hi()` fonctionne, JavaScript utilise une astuce -- le point `'.'` ne retourne pas une fonction, mais une valeur de [type référence](https://tc39.github.io/ecma262/#sec-reference-specification-type).** Le type référence n'est pas un "type de spécifiation". On ne peut l'utiliser explicitement, mais il est utilisé en interne par le langage. La valeur de Type Référence est une combinaison de 3 valeurs `(base, name, strict)`, où : - `base` est l'objet. - `name` est le nom de la propriété. - `strict` est vrai si `use strict` est en vigueur. Le résultat de l'accès à la propriété `user.hi` n'est pas une fonction, mais une valeur de Type Référence. Pour `user.hi` en mode strict cela est : ```js // Valeur de type référence (user, "hi", true) ``` Lorsque les parenthèses `()` sont appelées sur le type de référence, elles reçoivent les informations complètes sur l'objet et sa méthode, et peuvent définir le bon `this` (`user` dans ce cas). Le type référence est un type interne "intermédiaire", avec comme but de passer l'information du point `.` aux parenthèses `()`. N'importe quelle autre opération d'assignement comme `hi = user.hi` rejette le type référence, prends la valeur de `user.hi` (une fonction) et la passe. Ainsi n'importe quelle opération suivante "perd" `this`. Il en résulte que la valeur de `this` n'est passée correctement seulement lorsque la fonction est appelée directement en utilisant la notation par points `obj.method()` ou la notation par crochet `obj['method']()` (c'est la même chose). Il existe différentes manières de résoudre ce problème comme [func.bind()](/bind#solution-2-bind). ## Résumé Le type référence est un type interne au langage. En lisant une propriété, comme avec le point `.` dans `obj.method()`, qui ne retourne pas la valeur de la propriété mais la valeur spéciale de "type référence", qui garde le nom de la propriété et l'objet relié à la propriété. That's for the subsequent method call `()` to get the object and set `this` to it. Cela est fait pour que l'éxécution suivante, l'appel à la méthode `()`, reçoive l'objet et lui assigne `this`. Pour toutes les autres opérations, le type référence sera automatiquement la valeur de la propriété (une fonction dans notre cas). Le fonctionnement est caché de notre vision. Cela n'a d'importance que dans certains cas, comme lorsqu'une méthode est obtenue dynamiquement de l'object en utilisant une expression. ================================================ FILE: 1-js/99-js-misc/05-bigint/article.md ================================================ # BigInt [recent caniuse="bigint"] `BigInt` est un type numéral spécial qui fournit un support pour les entiers de taille arbitraire. Un bigint est créé en ajoutant `n` à la fin d'un entier littéral ou en appelant la fonction `BigInt` qui crée des bigints de chaînes de caractères, nombres, etc. ```js const bigint = 1234567890123456789012345678901234567890n; const sameBigint = BigInt("1234567890123456789012345678901234567890"); const bigintFromNumber = BigInt(10); // pareil que 10n ``` ## Opérateurs mathématiques `BigInt` peut la plupart du temps être utilisé comme un nombre ordinaire, par exemple : ```js run alert(1n + 2n); // 3 alert(5n / 2n); // 2 ``` Note : la division `5/2` retourne le résultat arrondi à zéro, sans la partie décimale. Toutes les opérations sur des bigints retourne des bigints. Nous ne pouvons pas mélanger des bigints et des nombres ordinaires : ```js run alert(1n + 2); // Error: Cannot mix BigInt and other types ``` Nous devrions explicitement les convertir si nécessaire en utilisant `BigInt()` ou `Number()`, comme ceci : ```js run let bigint = 1n; let number = 2; // nomber vers bigint alert(bigint + BigInt(number)); // 3 // bigint vers nombre alert(Number(bigint) + number); // 3 ``` Les opérations de conversion sont toujours silencieuses, ne donnent jamais d'erreur, mais si le bigint est trop grand et ne rentre pas dans le type number, alors les bits en trop seront retirés, donc nous devrions être prudents lorsque nous effectuons une telle conversion. ````smart header="Le plus unaire n'est pas supporté sur les bigints" Le plus unaire `+value` est un moyen bien connu pour convertir `value` en un nombre. Afin d'éviter toute confusion, il n'est pas pris en charge sur les bigints : ```js run let bigint = 1n; alert( +bigint ); // erreur ``` Donc nous devrions utiliser `Number()` pour convertir un bigint en un nombre. ```` ## Comparaisons Les comparaisons, telles `<`, `>` fonctionnent très bien avec les bigints et les nombres : ```js run alert( 2n > 1n ); // true alert( 2n > 1 ); // true ``` Veuillez cependant noter que puisque les nombres et les bigints sont deux types différents, ils peuvent être égaux `==`, mais pas strictement égaux `===`: ```js run alert( 1 == 1n ); // true alert( 1 === 1n ); // false ``` ## Opérations booléennes Lorsqu'à l'intérieur d'un `if` ou toutes autres opérations booléennes, les bigints se comportent comme les nombres. Par exemple, dans un `if`, un bigint `0n` est dit "falsy", les autres valeurs sont dites "truthy" : ```js run if (0n) { // ne s'exécutera jamais } ``` Les opérateurs booléens, tels `||`, `&&` et autres fonctionnent également avec les bigints, similairement aux nombres : ```js run alert( 1n || 2 ); // 1 (1n est considéré truthy) alert( 0n || 2 ); // 2 (0n est considéré falsy) ``` ## Polyfills Émuler des bigints est difficile. La raison est que beaucoup d'opérateurs de JavaScript, tels `+`, `-` et autres se comportent différemment avec les bigints comparé aux nombres ordinaires. Par exemple, la division de bigints retournera toujours un bigint (arrondi si nécessaire). Pour émuler un tel comportement, un polyfill devrait analyser le code et remplacer ces opérateurs par ses fonctions. Mais faire ainsi est encombrant et coûterait beaucoup en performances. Ainsi, il n'y a pas de bon polyfill connu. Cependant, l'une des solutions est proposée par les développeurs de la bibliothèque [JSBI](https://github.com/GoogleChromeLabs/jsbi). Cette bibliothèque implémente les nombres conséquents à l'aide de ses propres méthodes. Nous pouvons les utiliser à la place des bigints natifs : | Opération | `BigInt` natif | JSBI | |-----------|-----------------|------| | Création depuis un nombre | `a = BigInt(789)` | `a = JSBI.BigInt(789)` | | Addition | `c = a + b` | `c = JSBI.add(a, b)` | | Soustraction | `c = a - b` | `c = JSBI.subtract(a, b)` | | ... | ... | ... | ...Et ensuite utiliser le polyfill (plugin Babel) pour convertir les appels JSBI en bigints natifs pour les navigateurs les supportant. En d'autres termes, cette approche suggère d'écrire notre code avec JSBI à la place des bigints natifs. Mais JSBI travaille avec des nombres comme avec des bigints en interne, les émulant de près en suivant la spécification, le code sera ainsi compatible avec les bigints. Nous pouvons utiliser du code JSBI "tel quel" pour les moteurs ne supportant pas les bigints et pour ceux qui les supportent - le polyfill les convertira en bigints natifs. ## Références - [Documentation MDN sur le type BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt). - [Spécification](https://tc39.es/ecma262/#sec-bigint-objects). ================================================ FILE: 1-js/99-js-misc/06-unicode/article.md ================================================ # Unicode et fonctionnement des chaînes de caractères ```warn header="Connaissances avancées" Cette section approfondit les "String Internals", le fonctionnement interne des chaines de caractère. Des connaissances sur ces sujets vous seront utiles lorsque vous travaillerez avec les émojis, les caractères mathématiques ou les caractères hiéroglyphiques ou autres symboles rares. ``` Comme vous le savez, les chaines de caractères JavaScript sont basées sur le Unicode [Unicode](https://fr.wikipedia.org/wiki/Unicode): chaque caractère est représenté par une séquence d'octets de 1 à 4 octet. JavaScript permet d'injecter un caractère dans une chaîne en spécifiant son code hexadécimal sous une de ces trois formes: - `\xXX` `XX` doit être deux chiffres hexadécimaux ayant une valeur entre `00` et `FF`, alors `\xXX` est le caractère dont le code Unicode est `XX`. Parce que la notation `\xXX` ne supporte que deux chiffres hexadécimaux, elle peut être utilisée pour les 256 caractères Unicodes. Les 256 premiers caractères incluent l'alphabet latin, les caractères les plus basiques de syntaxe, et d'autres. Par exemple, `"\x7A"` correspond à `"z"` (Unicode `U+007A`). ```js run alert( "\x7A" ); // z alert( "\xA9" ); // ©, le symbole de Copyright ``` - `\uXXXX` `XXXX` doit obligatoirement être 4 chiffres hexadécimaux ayant une valeur entre `0000` et `FFFF`, alors `\uXXXX` est le caractère pour lequel le code Unicode est `XXXX`. Les caractères avec des codes Unicode supérieurs à `U+FFFF` peuvent également être représenté avec cette notation, mais dans ce cas, nous devons utiliser ce que l'on appelle une paire de substitution (nous reparlerons des paires de substitutions plus tard dans ce chapitre). ```js run alert( "\u00A9" ); // ©, le même que \xA9, en utilisant la notation hexadécimale à 4 chiffres. alert( "\u044F" ); // я, la lettre de l'alphabet cyrillique alert( "\u2191" ); // ↑, symbole de flèche vers le haut ``` - `\u{X…XXXXXX}` `X…XXXXXX` doit être une valeur hexadécimale de 1 à 6 octet entre `0` et `10FFFF` (le plus haut code défini par Unicode). Cette notation nous permet de représenter facilement tous les caractères Unicode existants. ```js run alert( "\u{20331}" ); // 佫, un caractère chinois rare (Unicode long) alert( "\u{1F60D}" ); // 😍, Le symbole d'un visage souriant (un autre Unicode long) ``` ## Les paires de substitution Tous les caractères fréquemment utilisés ont des codes de 2 octets (4 chiffres hexadécimaux). Les lettres dans les langages européens les plus courants, les nombres et les ensembles idéographiques unifiés de base (CJK -- provenant des systèmes d'écriture chinois, japonais et coréen), ont une représentation en 2 octets. A l'origine, le JavaScript est basé sur l'encodage UTF-16 qui ne permet que 2 octets par caractère. Mais 2 octets ne permettent que 65536 combinaisons ce qui n'est pas suffisant pour tous les symboles possibles de l'Unicode. Les symboles rares qui nécessitent plus de 2 octets sont encodés à l'aide d'une paire de caractères de 2 octets appelée "paire de substitution". Comme effet secondaire, la longueur de tels symboles est `2`: ```js run alert( '𝒳'.length ); // 2, Le script mathématique avec un X majuscule alert( '😂'.length ); // 2, un visage qui pleure de rire alert( '𩷶'.length ); // 2, un caractère chinois rare ``` C'est parce que les paires de substitution n'existaient pas aumoment de la création de JavaScript, et ne sont donc pas correctement traitées par le langage ! Nous avons en réalité un seul symbole dans chacune des paires ci-dessus, mais la propriété `length` affiche une longueur de `2`. Obtenir un symbole peut également être délicat, car la plupart des fonctionnalités du langage traitent les paires de substitution comme deux caractères. Par exemple, nous pouvons ici voir deux caractères impairs dans la sortie: ```js run alert( '𝒳'[0] ); // affiche des symboles étranges... alert( '𝒳'[1] ); // ...des parties de la paire de substitution ``` Les parties de la paire de substitution n'ont pas de sens l'une sans l'autre. Les alertes dans l'exemple ci-dessus affichent ainsi des caractères indésirables. Techniquement, les paires de substitution sont également détectables par leurs codes: Si un caractère possède le code dans l'intervalle `0xd800..0xdbff`, alors il sera dans la première partie de la paire de substitution. Le caractère suivant (la seconde partie) doit avoir un code dans l'intervalle `0xdc00..0xdfff`. Ces intervalles sont exclusivement réservés pour les paires de substitution d'après les standards. Les méthodes [String.fromCodePoint](https://developer.mozilla.org/fr-FR/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) et [str.codePointAt](https://developer.mozilla.org/fr-FR/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) ont été ajoutés à JavaScript afin de gérer les paires de substitution. Ils sont essentiellement les mêmes que [String.fromCharCode](mdn:js/String/fromCharCode) et [str.charCodeAt](mdn:js/String/charCodeAt), mais ils traitent les paires de substitution correctement. On peut voir la différence ici: ```js run // charCodeAt n'est pas conscient de la paire de substitution, donc il donne les codes pour la 1ère partie de 𝒳: alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 // codePointAt est conscient de la paire de substitution alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, lit les deux parties de la paire de substitution ``` Ceci dit, si nous prenons la position 1 (ce qui est plutôt incorrect ici), alors ils retournent tous les deux uniquement la 2ème partie de la paire: ```js run alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 // seconde moitié de la paire sans signification ``` Vous trouverez plusieurs moyens de gérer les paires de substitution plus tard dans ce chapitre . Il existe probablement des librairies spécialement conçues pour cela également, mais aucune n'est suffisamment connue pour vous la suggérer ici. ````warn header="A retenir: Diviser une chaîne de caractère sur un point arbitraire est dangereux" Nous ne pouvons pas simplement séparer une chaine de caractère sur un point arbitraire, par exemple, prenez `str.slice(0, 4)` et attendez-vous à ce que ce soit une chaîne de caractère valide, par exemple : ```js run alert( 'Salut 😂'.slice(0, 4) ); // Salut [?] ``` Ici, nous pouvons voir un caractère indésirable ( la première moitié de la paire de substitution du sourire) en sortie. Soyez simplement conscient de cela si vous avez l'intention de travailler de manière fiable avec des paires de substitution. Cela peut ne pas être un gros problème, mais vous devriez au moins comprendre ce qu'il se passe. ```` ## Marques diacritiques et normalisation Dans de nombreux langages, des symboles sont composés d'un caractère de base avec une marque au dessus ou en dessous. Par exemple, la lettre `a` peut être la base de ces caractères: `àáâäãåā`. Les caractères "composites" les plus communs ont leur propre code dans la table Unicode. Mais tous n'en ont pas en raison du trop grand nombre de possibilité de combinaison. Pour supporter les compositions arbitraires, le standard Unicode nous permet d'utiliser plusieurs caractères Unicode: le caractère de base suivi d'un ou plusieurs caractères de marques qui le "décorent". Par exemple, si nous avons `S` suivi par le caractère spécial "point au-dessus" (code `\u0307`), il est affiché comme Ṡ. ```js run alert( 'S\u0307' ); // Ṡ ``` Si nous avons besoin d'une marque supplémentaire au-dessus de la lettre (ou en dessous d'elle) -- pas de problème, il suffit simplement d'ajouter le caractère de marque nécessaire. Par exemple, si nous ajoutons un caractère "point en dessous" (code `\u0323`), nous aurons "S avec des points au-dessus et en dessous": `Ṩ`. Par exemple: ```js run alert( 'S\u0307\u0323' ); // Ṩ ``` Cela offre une grande flexibilité, mais aussi un problème intéressant: deux caractères peuvent visuellement se ressembler, mais être représenté par différentes compositions Unicode. Par exemple: ```js run let s1 = 'S\u0307\u0323'; // Ṩ, S + point au-dessus + point en dessous let s2 = 'S\u0323\u0307'; // Ṩ, S + point en dessous + point au-dessus alert( `s1: ${s1}, s2: ${s2}` ); alert( s1 == s2 ); // faux bien que les caractères semblent identiques (?!) ``` Pour résoudre ce problème, il existe une "normalisation Unicode", un algorithme qui convertit chaque chaîne vers sa forme "normale". Cet algorithme est implémenté par [str.normalize()](mdn:js/String/normalize). ```js run alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true ``` Il est intéressant de noter que dans notre situation `normalize()` rassemble en réalité une séquence de 3 caractères en un seul: `\u1e68` (S avec deux points). ```js run alert( "S\u0307\u0323".normalize().length ); // 1 alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true ``` En réalité, ce n'est pas toujours le cas. Cela est dû au fait que le symbole `Ṩ` est "assez commun", donc les créateurs de l'Unicode l'ont inclus dans la table principale et lui ont attribué un code. Si vous souhaitez en apprendre plus sur les règles de normalisation et ses variantes -- elles sont décrites dans l'appendix du standard Unicode: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), mais pour la plupart des besoins pratiques, les informations de cette section sont suffisantes. ================================================ FILE: 1-js/99-js-misc/index.md ================================================ # Divers ================================================ FILE: 1-js/index.md ================================================ # JavaScript le langage Dans ce guide nous allons apprendre JavaScript, en partant de zéro jusqu'à des concepts avancés comme la POO. Nous allons nous concentrer ici sur le langage lui même, avec le minimum de notes spécifiques aux environnements. ================================================ FILE: 2-ui/1-document/01-browser-environment/article.md ================================================ # L'environnement du navigateur, spécifications Le langage JavaScript a été initialement créé pour les navigateurs web. Dès lors, il a évolué et est devenu un langage aux multiples utilisations et plateformes. Une plate-forme peut être un navigateur, ou un serveur Web ou un autre *hôte*, ou même une machine à café "intelligente" si elle peut exécuter JavaScript. Chacun d'entre eux fournit des fonctionnalités spécifiques à la plate-forme. La spécification JavaScript appelle cela un *environnement hôte*. Un environnement hôte fournit ses propres objets et fonctions en plus du noyau du langage. Les navigateurs Web permettent de contrôler les pages Web. Node.js fournit des fonctionnalités côté serveur, etc. Voici une vue globale de ce que nous avons lorsque JavaScript s'exécute dans un navigateur Web : ![](windowObjects.svg) Il existe un objet "racine" appelé `window`. Il a 2 rôles : 1. Premièrement, c'est un objet global pour le code JavaScript, comme décrit dans le chapitre . 2. Deuxièmement, il représente la "fenêtre du navigateur" et fournit des méthodes pour la contrôler. Par exemple, nous l'utilisons ici comme un objet global : ```js run global function sayHi() { alert("Hello"); } // les fonctions globales sont des méthodes de l'objet global : window.sayHi(); ``` Et nous l'utilisons ici comme une fenêtre du navigateur pour voir la hauteur de la fenêtre : ```js run alert(window.innerHeight); // hauteur de la fenêtre intérieure ``` Il y a d'autres méthodes et propriétés spécifiques à la fenêtre, nous les étudierons plus tard. ## DOM (Document Object Model) Document Object Model, ou DOM en abrégé, représente tout le contenu de la page sous forme d'objets pouvant être modifiés. L'objet `document` est le "point d'entrée" principal de la page. Nous pouvons changer ou créer n'importe quoi sur la page en l'utilisant. Par exemple : ```js run // change la couleur de fond en rouge document.body.style.background = "red"; // réinitialisation après 1 seconde setTimeout(() => document.body.style.background = "", 1000); ``` ```smart header="DOM n'est pas seulement pour les navigateurs" La spécification DOM explique la structure d'un document et fournit des objets pour le manipuler. Il existe également des instruments autres que les navigateurs qui utilisent DOM. Par exemple, les scripts côté serveur qui téléchargent des pages HTML et les traitent peuvent également utiliser le DOM. Ils peuvent cependant ne supporter qu'une partie de la spécification. ``` ```smart header="CSSOM pour le style" Il existe également une spécification distincte, [Modèle d'objet CSS (CSSOM)](https://www.w3.org/TR/cssom-1/) pour les règles CSS et les feuilles de style, qui explique comment elles sont représentées en tant qu'objets et comment les lire et les écrire. CSSOM est utilisé avec DOM lorsque nous modifions les règles de style du document. En pratique cependant, CSSOM est rarement nécessaire, car nous avons rarement besoin de modifier les règles CSS à partir de JavaScript (généralement, nous ajoutons / supprimons simplement des classes CSS, pas de modifier leurs règles CSS), mais c'est également possible. ``` ## BOM (Browser Object Model) Le modèle d'objet du navigateur (BOM en anglais) contient des objets supplémentaires fournis par le navigateur (l'environnement hôte) pour travailler avec tout à l'exception du document. Par exemple : - L'objet [navigator](mdn:api/Window/navigator) fournit des informations contextuelles à propos du navigateur et du système d'exploitation. Il y a beaucoup de propriétés mais les deux plus connues sont : `navigator.userAgent` -- qui donne des informations sur le navigateur actuel, et `navigator.platform` sur la plateforme (peut permettre de faire la différence entre Windows/Linux/Mac etc). - L'objet [location](mdn:api/Window/location) nous permet de lire l'URL courante et peut rediriger le navigateur vers une nouvelle adresse. Voici comment l'on peut utiliser l'objet `location` : ```js run alert(location.href); // affiche l'URL actuelle if (confirm("Go to Wikipedia?")) { location.href = "https://wikipedia.org"; // rediriger le navigateur vers une autre URL } ``` Les fonctions `alert/confirm/prompt` font aussi partie du BOM : elles ne sont pas directement liées au document, mais représentent des méthodes du navigateur de communication pure avec l'utilisateur. ```smart header="Specifications" le BOM fait partie de la [spécification HTML](https://html.spec.whatwg.org) générale. Oui, vous avez bien entendu. La spécification HTML disponible à l'adresse ne parle pas seulement du "langage HTML" (balises, attributs), mais couvre également un tas d'objets, de méthodes et d'extensions DOM spécifiques au navigateur. C'est l'"HTML de manière générale". En outre, certaines parties ont des spécifications supplémentaires listées ici : . ``` ## Résumé Quand on parle de normes, nous avons : La spécification DOM : Décrit la structure du document, ses manipulations et événements, voir . La spécification CSSOM : Décrit les feuilles de style et les règles de style, les manipulations de style les impliquant et leur liaisons aux documents, voir . Spécification HTML : Décrit le langage HTML (c'est à dire les balises) mais également le BOM (modèle d'objet du navigateur) -- diverses fonctions du navigateur : `setTimeout`, `alert`, `location` etc, voir . Il récupère la spécification DOM et l'étend avec de nombreuses propriétés et méthodes additionnelles. De plus, certaines classes sont décrites séparément sur . Souvenez vous de ces liens, il y a tellement de choses à apprendre qu'il est impossible de tout couvrir et de se souvenir de tout. Lorsque vous souhaitez en apprendre plus sur une propriété ou une méthode, le manuel de Mozilla disponible sur est également une bonne ressource, mais la specification correspondante peut-être meilleure dans le sens qu'elle est plus complexe et longue à lire, mais rendra vos connaissances fondamentales saines et complètes. Pour trouver quelque chose, il est souvent pratique de faire une simple recherche de "WHATWG [terme]" ou "MDN [terme]", par exemple , . Nous allons maintenant nous pencher sur le DOM, car le document joue un rôle essentiel dans l'interface utilisateur (UI). ================================================ FILE: 2-ui/1-document/02-dom-nodes/article.md ================================================ libs: - d3 - domtree --- # L'arbre DOM L'épine dorsale d'un document HTML est constituée de balises. Selon le modèle d'objets de document (DOM), chaque balise HTML est un objet. Les balises imbriquées sont des "enfants" de celle qui les entoure. Le texte à l'intérieur d'une balise est également un objet. Tous ces objets sont accessibles via JavaScript, et nous pouvons les utiliser pour modifier la page. Par exemple, `document.body` est l'objet représentant la balise ``. L'exécution de ce code rendra le `` rouge pendant 3 secondes : ```js run document.body.style.background = 'red'; // make the background red setTimeout(() => document.body.style.background = '', 3000); // return back ``` Ici, nous avons utilisé `style.background` pour changer la couleur d'arrière-plan de `document.body`, mais il existe de nombreuses autres propriétés, telles que : - `innerHTML` -- Contenu HTML du nœud. - `offsetWidth` -- la largeur du nœud (en pixels) - … etc. Bientôt, nous apprendrons plus de façons de manipuler le DOM, mais nous devons d'abord connaître sa structure. ## Un exemple du DOM Commençons par le simple document suivant : ```html run no-beautify About elk The truth about elk. ``` Le DOM représente le HTML comme une structure arborescente de balises. Voici à quoi ça ressemble :
```online Sur l'image ci-dessus, vous pouvez cliquer sur les nœuds des éléments et leurs enfants s'ouvriront/se réduiront. ``` Chaque nœud de l'arbre est un objet. Les balises sont des *nœuds d'élément* (ou simplement des éléments) et forment la structure arborescente : `` est à la racine, puis `` et `` sont ses enfants, etc. Le texte à l'intérieur des éléments forme *des nœuds texte*, étiquetés comme `#text`. Un nœud texte ne contient qu'une chaîne de caractères. Il peut ne pas avoir d'enfants et est toujours une feuille de l'arbre. Par exemple, la balise `` a le texte `"About elk"`. Veuillez noter les caractères spéciaux dans les nœuds texte : - une nouvelle ligne : `↵` (en JavaScript appelé `\n`) - un espace : `␣` Les espaces et les nouvelles lignes sont des caractères totalement valides, comme les lettres et les chiffres. Ils forment des nœuds de texte et deviennent une partie du DOM. Ainsi, par exemple, dans l'exemple ci-dessus, la balise `<head>` contient des espaces avant `<title>`, et ce texte devient un nœud `#text` (il contient une nouvelle ligne et quelques espaces uniquement). Il n'y a que deux exclusions de haut niveau : 1. Les espaces et les nouvelles lignes avant `<head>` sont ignorés pour des raisons historiques. 2. Si nous mettons quelque chose après `</body>`, alors cela est automatiquement déplacé à l'intérieur du `body`, à la fin, car la spécification HTML exige que tout le contenu soit à l'intérieur de `<body>`. Il ne peut donc pas y avoir d'espace après `</body>`. Dans d'autres cas, tout est simple -- s'il y a des espaces (comme n'importe quel caractère) dans le document, alors ils deviennent des nœuds texte dans le DOM, et si nous les supprimons, il n'y en aura pas. Voici des nœud de texte sans espace : ```html no-beautify <!DOCTYPE HTML> <html><head><title>About elkThe truth about elk. ```
```smart header="Les espaces au début/à la fin de la chaîne de caractères et les nœuds texte uniquement composé d'espaces sont généralement masqués dans les outils" Les outils de navigation (qui seront bientôt traités) qui fonctionnent avec le DOM n'affichent généralement pas d'espaces au début/fin du texte et des nœuds texte vides (sauts de ligne) entre les balises. Les outils de développement permettent d'économiser de l'espace d'écran de cette façon. Sur d'autres images DOM, nous les omettons parfois lorsqu'elles ne sont pas pertinentes. Ces espaces n'affectent généralement pas la façon dont le document est affiché. ``` ## Auto-correction Si le navigateur rencontre du HTML mal formé, il le corrige automatiquement lors de la création du DOM. Par exemple, la balise la plus haute est toujours ``. Même si elle n'existe pas dans le document, elle existera dans le DOM, car le navigateur la créera. Il en va de même pour ``. Par exemple, si le fichier HTML est le seul mot `"Hello"`, le navigateur l'enroulera dans `` et ``, et ajoutera le `` requis, et le DOM sera :
Lors de la génération du DOM, les navigateurs traitent automatiquement les erreurs dans le document, ferment les balises, etc. Un document avec des balises non fermées : ```html no-beautify

Hello

  • Mom
  • and
  • Dad ``` … deviendra un DOM normal à mesure que le navigateur lit les balises et restaure les parties manquantes :
    ````warn header="Les tableaux ont toujours ``" Un "cas spécial" intéressant est celui des tableaux. Selon la spécification DOM, ils doivent avoir un ``, mais le texte HTML peut (officiellement) l'omettre. Ensuite, le navigateur crée automatiquement le `` dans le DOM. Pour le HTML : ```html no-beautify
    1
    ``` La structure du DOM sera :
    Vous voyez ? Le `` est sorti de nulle part. Vous devez garder cela à l'esprit lorsque vous travaillez avec des tableaux pour éviter les surprises. ```` ## Autres types de nœuds Il existe d'autres types de nœuds en plus des éléments et des nœuds de texte. Par exemple, les commentaires : ```html The truth about elk.
    1. An elk is a smart
    2. *!* */!*
    3. ...and cunning animal!
    ```
    Nous pouvons voir ici un nouveau type de nœud de l'arbre - *nœud commentaire*, étiqueté comme `#comment`, entre deux nœuds texte. Nous pouvons penser - pourquoi un commentaire est-il ajouté au DOM ? Cela n'affecte en rien la représentation visuelle. Mais il y a une règle -- si quelque chose est en HTML, alors il doit aussi être dans l'arborescence DOM. **Tout en HTML, même les commentaires, devient une partie du DOM.** Même la directive `` au tout début du html est également un noeud du dom. C'est dans l'arborescence du DOM juste avant ``. Peu de gens le savent. Nous n'allons pas toucher ce nœud, nous ne le dessinons même pas sur les diagrammes pour cette raison, mais il est là. L'objet `document` qui représente l'ensemble du document est également, formellement, un nœud dom. Il existe [12 types de nœuds](https://dom.spec.whatwg.org/#node). En pratique, nous travaillons généralement avec 4 d'entre eux : 1. le `document` -- le "point d'entrée" dans le dom. 2. les nœuds éléments -- les balises HTML, les blocs de construction de l'arborescence. 3. les nœuds texte -- contient du texte. 4. les commentaires -- parfois, nous pouvons y mettre des informations, elles ne seront pas affichées, mais js peut les lire depuis le dom. ## Voyez par vous-même Pour voir la structure dom en temps réel, essayez le [live DOM viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Tapez simplement le document, et il apparaîtra comme un dom en un instant. Une autre façon d'explorer le dom est d'utiliser les outils de développement du navigateur. en fait, c'est ce que nous utilisons lors du développement. Pour ce faire, ouvrez la page web [elk.html](elk.html), activez les outils de développement du navigateur et passez à l'onglet éléments. Cela devrait ressembler à ça : ![](elk.svg) Vous pouvez voir le dom, cliquer sur les éléments, voir leurs détails et ainsi de suite. Veuillez noter que la structure du dom dans les outils de développement est simplifiée. Les nœuds texte sont affichés comme du texte. Et il n'y a aucun nœud texte "vide" (espace uniquement). C'est très bien, car la plupart du temps nous nous intéressons aux nœuds éléments. En cliquant sur le bouton dans le coin supérieur gauche cela nous permet de choisir un nœud à partir de la page Web à l'aide d'une souris (ou d'autres périphériques de pointeur) et de "l'inspecter" (faites défiler jusqu'à l'onglet Éléments). cela fonctionne très bien lorsque nous avons une énorme page html (et un énorme dom correspondant) et que nous aimerions voir la place d'un élément particulier. Une autre façon de le faire serait simplement de cliquer avec le bouton droit sur une page Web et de sélectionner "inspecter" dans le menu contextuel. ![](inspect.svg) Dans la partie droite des outils se trouvent les sous-onglets suivants : - **Styles** -- nous pouvons voir le CSS appliqué à l'élément en cours, règle par règle, y compris les règles intégrées (gris). Presque tout peut être modifié sur place, y compris les dimensions/margins/paddings de la boîte ci-dessous. - **Computed** -- pour voir le CSS appliqué à l'élément par propriété : pour chaque propriété, nous pouvons voir une règle qui la lui donne (y compris l'héritage CSS et autres). - **Event Listeners** -- pour voir les écouteurs d'événements attachés aux éléments du DOM (nous les couvrirons dans la prochaine partie du tutoriel). - … etc. La meilleure façon de les étudier est de cliquer dessus. La plupart des valeurs sont modifiables sur place. ## Interaction avec la console Comme nous travaillons le DOM, nous pouvons également vouloir lui appliquer du JavaScript. Comme : obtenir un nœud et exécuter du code pour le modifier, pour voir le résultat. Voici quelques conseils pour voyager entre l'onglet Elements et la console. Pour commencer : 1. Sélectionnez le premier `
  • ` dans l'onglet Éléments. 2. Appuyez sur la touche `key:Esc` -- cela ouvrira la console juste en dessous de l'onglet Éléments. Maintenant, le dernier élément sélectionné est disponible en tant que `$0`, le précédent sélectionné est `$1`, etc. Nous pouvons exécuter des commandes sur eux. Par exemple, `$0.style.background = 'red'` rend l'élément de la liste sélectionné rouge, comme ceci : ![](domconsole0.svg) Voilà comment obtenir un nœud à partir d'Elements dans la console. Il y a aussi un chemin de retour. S'il y a une variable référençant un nœud DOM, alors nous pouvons utiliser la commande `inspect(node)` dans la console pour la voir dans le volet Éléments. Ou nous pouvons simplement sortir le nœud DOM dans la console et explorer "sur place", comme `document.body` ci-dessous : ![](domconsole1.svg) C'est à des fins de débogage bien sûr. À partir du chapitre suivant, nous accéderons et modifierons le DOM en utilisant JavaScript. Les outils de développement du navigateur sont d'une grande aide au développement : nous pouvons explorer le DOM, essayer des choses et voir ce qui ne va pas. ## Résumé Un document HTML/XML est représenté dans le navigateur sous forme d'arbre DOM. - Les balises deviennent des nœuds élément et forment la structure. - Le texte devient des nœuds texte. - … etc, tout en HTML a sa place dans le DOM, même les commentaires. Nous pouvons utiliser les outils de développement pour inspecter le DOM et le modifier manuellement. Ici, nous avons couvert les bases, les actions les plus utilisées et les plus importantes pour commencer. Il existe une documentation complète sur Chrome Developer Tools à l'adresse . La meilleure façon d'apprendre les outils est de cliquer ici et là, lire les menus : la plupart des options sont évidentes. Plus tard, lorsque vous les connaissez en général, lisez la documentation et découvrez le reste. Les nœuds DOM ont des propriétés et des méthodes qui nous permettent de voyager entre eux, de les modifier, de se déplacer dans la page, etc. Nous y reviendrons dans les prochains chapitres. ================================================ FILE: 2-ui/1-document/02-dom-nodes/elk.html ================================================ The truth about elk.
    1. An elk is a smart
    2. ...and cunning animal!
    ================================================ FILE: 2-ui/1-document/02-dom-nodes/head.html ================================================ ================================================ FILE: 2-ui/1-document/03-dom-navigation/1-dom-children/solution.md ================================================ Il existe de nombreuses façons, par exemple : Le noeud `
    ` du DOM : ```js document.body.firstElementChild // ou document.body.children[0] // ou (le premier nœud est l'espace, nous prenons donc le deuxième) document.body.childNodes[1] ``` Le nœud `