Repository: javascript-tutorial/id.javascript.info Branch: master Commit: e594d6760e9f Files: 1160 Total size: 3.7 MB Directory structure: gitextract_3kej2r86/ ├── .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 │ │ │ ├── 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 │ │ │ ├── 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 │ │ └── 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.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/ │ │ │ └── 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/ │ │ └── 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 ├── glossary.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 ================================================ FILE: 1-js/01-getting-started/1-intro/article.md ================================================ # Pengenalan JavaScript Mari kita lihat apa yang spesial dari JavaScript, apa saja yang bisa kita buat menggunakan JavaScript, dan teknologi apa yang cocok dengan JavaScript. ## Apa itu JavaScript? *Javascript" pada awalnya diciptakan untuk "membuat halaman web menjadi hidup". Program yang ada dalam bahasa ini disebut *script*. Script ini bisa ditulis langsung ke dalam kode HTML dari sebuah web dan berjalan otomatis saat halaman dimuat. Script tersedia dan dieksekusi sebagai sebuah teks biasa. Script tidak membutuhkan persiapan khusus atau kompilasi untuk dijalankan. Dalam hal ini, JavaScript sangat berbeda dari bahasa lain yang disebut [Java](https://en.wikipedia.org/wiki/Java_(programming_language)). ```smart header="Kenapa disebut JavaScript?" Saat JavaScript diciptakan, nama awalnya adalah "LiveScript". Namun saat itu Java sudah sangat terkenal, sehingga akhirnya diputuskanlah bahwa memposisikan bahasa baru menjadi "adik" Java dapat membantu. Seiring waktu, JavaScript tumbuh menjadi bahasa yang sepenuhnya bebas dengan memiliki spesifikasi [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), dan sekarang JavaScript tak punya hubungan apa-apa dengan Java. ``` Sekarang, JavaScript bisa berjalan tak hanya pada browser, tapi juga di server, atau di perangkat manapun yang memiliki program khusus [JavaScript engine](https://en.wikipedia.org/wiki/JavaScript_engine). Browser punya engine yang tertanam didalamnya yang disebut "JavaScript virtual machine". Tiap engine punya *codename*-nya sendiri. Misalnya: - [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- di Chrome dan Opera. - [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- di Firefox. - ...Ada juga codename lain seperti "Trident" dan "Chakra" untuk versi berbeda dari IE, "ChakraCore" untuk Microsoft Edge, "Nitro" dan "SquirrelFish" untuk Safari, dll. Istilah di atas sebaiknya diingat karena akan sering digunakan dalam artikel para developer di internet. Kita akan menggunakannya juga. Misalnya, jika "fitur X didukung V8", kemungkinan ia bisa jalan di Chrome dan Opera. ```smart header="Bagaimana engine bekerja?" Engine sangat rumit. Tapi basicnya mudah. 1. Engine (tertanam jika ia sebuah browser)bisa membaca ("memparsing") script. 2. Lalu ia mengkonversi ("mengkompilasi") script tersebut menjadi bahasa mesin. 3. Dan kemudian kode mesin berjalan, lumayan cepat. Engine melakukan optimisasi di setiap langkah proses. Dia bahkan memperhatikan script yang telah dikompilasi saat sedang berjalan, menganalisa data yang mengalir di dalam, dan melakukan optimisasi ke kode mesin berdasarkan pengetahuan itu. ``` ## Apa yang bisa dilakukan *in-browser JavaScript*? JavaScript modern merupakan bahasa pemrograman yang "aman". Ia tidak menyebabkan akses tingkat-rendah ke memory atau CPU, karena memang awalnya dibuat untuk browser, yang tentunya tidak membutuhkan hal tersebut. Kemampuan JavaScript sangat tergantung pada lingkungan tempat ia berjalan. Misalnya, [Node.js](https://wikipedia.org/wiki/Node.js) mendukung function yang memungkingkan JavaScript melakukan baca/tulis file apapun, melakukan permintaan jaringan, dsb. *In-browser JavaScript* bisa melakukan apapun terkait manipulasi halaman web, interaksi dengan pengguna, dan webserver. Misalnya, *in-browser JavaScript* mampu: - Menambah HTML baru ke sebuah halaman, mengganti isinya, memodifikasi gayanya. - Bereaksi terhadap aktifitas pengguna, berjalan saat mouse diklik, pointer digerakkan, tombol ditekan. - Mengirim permintaan jaringan ke remote server, mengunduh dan mengunggah file (disebut teknologi [AJAX](https://en.wikipedia.org/wiki/Ajax_(programming)) dan [COMET](https://en.wikipedia.org/wiki/Comet_(programming))). - Memperoleh and menset cookie, bertanya ke pengunjung, menampilkan pesan. - Menyimpan data pada client-side ("local storage"). ## Apa yang TIDAK BISA dilakukan *in-browser JavaScript*? Kemampuan JavaScript yang ada di dalam browser terbatas demi keamanan pengguna. Tujuannya supaya mencegah halaman web berbahya mengakses informasi pribadi atau merusak data pengguna. Contoh keterbatasan tersebut meliputi: - Javascript didalam sebuah halaman web seharusnya tidak dapat membaca/mengubah file didalam hardisk semaunya. Browser-browser modern memperbolehkan JavaScript mengakses file, tapi aksesnya dibatasi dan tersedia hanya jika pengguna melakukan hal tertentu, misalnya seperti "menjatuhkan" file ke dalam jendela browser atau memilih file via tag ``. Ada beberapa cara untuk berinteraksi dengan kamera/mikrofon dan perangkat-perangkat lainnya, namun mereka butuh ijin khusus pengguna. Jadi sebuah halaman yang memiliki JavaScript tidak bisa mengaktifkan web-camera, memantau sekeliling dan mengirim informasinya ke [NSA] secara diam-diam(https://en.wikipedia.org/wiki/National_Security_Agency). - Tab/jendela yang berbeda umumnya tidak ada hubungan sama sekali. Terkadang jendela yang berbeda bisa saling berhubungan juga, misalnya ketika satu jendela menggunakan JavaScript untuk membuka jendela lainnya. Tapi meski demikian, JavaScript dari suatu halaman tak boleh mengakses halaman lainnya jika mereka berasal dari situs yang berbeda (dari domain, protokol, atau port berbeda). Ini disebut "Same Origin Policy". Untuk melakukan hal tersebut, *kedua halaman* harus sepakat terhadap adanya pertukaran data dan memiliki kode JavaScript khusus yang melakukan hal tersebut. Kita akan membahasnya nanti dalam tutorial ini. Pembatasan ini pun demi keselamatan pengguna. Sebuah halaman dari `http://anysite.com` yang dibuka pengguna tidak akan bisa mengakses tab browser lainnya dengan URL `http://gmail.com` dan mencuri informasinya. - JavaScript bisa dengan mudah berinteraksi secara online ke server di mana halaman berasal. Tapi kemampuannya menerima data dari situs/domain lain dilumpuhkan. Meskipun mampu, ia butuh persetujuan explisit (yang diexpresikan dalam HTTP header) dari sisi remote. Sekali lagi, itu merupakan pembatasan keamanan. ![](limitations.svg) Pembatasan macam ini tidak ada jika JavaScript digunakan di luar browser, misalnya di server. Browser-browser modern juga memperbolehkan plugin/ekstensi yang membutuhkan ijin tambahan. ## Apa yang membuat JavaScript unik? Paling tidak ada *tiga* hal unik dari JavaScript: ```compare + Integrasi penuh dengan HTML/CSS. + Hal sederhana diselesaikan dengan sederhana. + Dukungan dari mayoritas web browser dan aktif secara default. ``` JavaScript merupakan satu-satunya teknologi browser yang mengkombinasikan ketiga poin di atas. Itulah yang membuat JavaScript unik. Itulah kenapa JavaScript menjadi alat yang paling sering untuk membuat antarmuka browser. Katanya, JavaScript juga bisa dipakai untuk membuat aplikasi server, mobile, dsb. ## Bahasa "di atas" JavaScript Sintaks JavaScript tidak memenuhi kebutuhan setiap orang. Masing-masing orang ingin fitur yang berbeda-beda. Itu wajar, karena proyek dan persyaratan tiap orang berbeda-beda. Akhir-akhir ini muncul banyak bahasa baru, yang *ditranspile* (dikonversi) ke JavaScript sebelum dijalankan di browser. Tools modern membuat transpilasi sangat cepat dan transparan, yang memungkinkan para developer menulis kodenya dalam bahasa lain dan mengautokonversi itu "di balik layar". Contoh bahasa yang dimaksud: - [CoffeeScript](http://coffeescript.org/) merupakan "syntactic sugar" dari JavaScript. Dia memperkenalkan syntax yang lebih pendek, memungkingkan kita menulis kode lebih bersih dan lebih presisi. Biasanya, Ruby devs menyukainya. - [TypeScript](http://www.typescriptlang.org/) berfokus pada penambahan "strict data typing" yang menyederhanakan pengembangan dan dukungan sistem yang komplex. Ia dikembangkan oleh Microsoft. - [Flow](http://flow.org/) juga menambahkan data typing, tapi dalam cara berbeda. Dikembangkan oleh Facebook. - [Dart](https://www.dartlang.org/) ialah bahasa mandiri yang punya engine sendiri yang berjalan di lingkungan non-peramban (seperti mobile apps), tapi bisa juga ditranspile ke JavaScript. Dikembangkan oleh Google. - [Brython](https://brython.info/) adalah transpiler python untuk Javascript yang memperbolehkan untuk menulis kode aplikasi didalam Python murni tanpa Javascript. - [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) adalah sebuah bahasa pemograman modern, ringkas dan aman yang dapat ditargetkan untuk browser atau Node. Masih banyak lagi. Tentunya, jika kita menggunakan salah satu bahasa yang ditranspile tersebut, kita sebaiknya juga paham JavaScript untuk mengerti apa yang mereka lakukan. ## Kesimpulan - JavaScript awalnya diciptakan sebagai bahasa khusus browser, namun sekarang banyak digunakan di lingkungan lain. - Sekarang, JavaScript mempunyai posisi unik sebagai bahasa browser paling banyak diadopsi dengan integrasi penuh dengan HTML/CSS. - Ada banyak bahasa yang "ditranspile" ke JavaScript dan menyediakan fitur tertentu. Disarankan untuk mempelajari mereka juga, minimal sebentar, setelah menguasai JavaScript. ================================================ FILE: 1-js/01-getting-started/2-manuals-specifications/article.md ================================================ # Manual dan spesifikasi Buku ini adalah _tutorial_. Tujuannya membantu kamu memahami bahasa ini (Javascript) pelan-pelan. Tapi sekali kamu akrab atau familiar dengan dasarnya, kamu juga membutuhkan dari sumber-sumber lain. ## Spesifikasi [Spesifikasi ECMA-262](https://www.ecma-international.org/publications/standards/Ecma-262.htm) berisi informasi formal, detil, and mendalam tentang JavaScript. Ia mendefisikan bahasa ini. Tapi karena menjadi formal, ia sulit dipahami di awal. Jadi jika kamu butuh sumber informasi terpercaya tentang detil bahasa, spesifikasi ini tempat yang tepat. Tapi ini bukan untuk penggunaan harian. Versi spesifikasi baru dirilis tiap tahun. Di antara rilis ini, draft spesifikasi terakhir ada di . Untuk membaca tentang fitur terkini, termasuk yang "hampir menjadi standar" (disebut "stage 3"), lihat proposalnya di . Juga, jika kamu dalam pengembangan untuk peramban, maka ada spek lain yang dibahas di [bagian kedua](info:browser-environment) di tutorial ini. ## Manual - **Referensi JavaScript MDN (Mozilla)** ialah manual dengan informasi dan contoh lain. Di sana bagus untuk mendapat informasi mendalam tentang metode, fungsi bahasa, dll. Kamu bisa cari di . Meski, sering lebih bagus menggunakan pencarian internet. Pakai "MDN [term]" di query, misal untuk mencari fungsi `parseInt`. - **MSDN** – Manual Microsoft dengan banyak informasi, termasuk JavaScript (sering dirujuk sebagai JScript). Jika kamu butuh sesuatu yang spesifik ke Internet Explorer, lebih baik pergi ke: . Juga, kamu bisa menggunakan pencarian internet dengan frasa seperti "RegExp MSDN" atau "RegExp MSDN jscript". ## Tabel kompatibilitas JavaScript merupakan bahasa berkembang, fitur baru ditambah secara reguler. Untuk melihat dukungan mereka pada engine berbasis peramban dan lainnya, lihat: - - tabel dukungan per-fitur, misal untuk melihat engine mana yang mendukung fungsi kryptografi modern: . - - tabel dengan fitur dan engine bahasa yang mendukung atau yang tidak mendukung. Semua sumber ini berguna di pengembangan nyata, karena mereka berisi informasi berharga tentang detil bahasa, dukungan mereka dll. Silakan ingat mereka (atau laman ini) saat kamu butuh informasi mendalam tentang fitur tertentu. ================================================ FILE: 1-js/01-getting-started/3-code-editors/article.md ================================================ # Editor kode Editor kode adalah tempat programmer menghabiskan usianya. Ada dua tipe utama editor kode: IDE dan editor ringan. Kebanyakan orang menggunakan satu tool saja dari setiap tipe. ## IDE Istilah [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) mengacu kepada editor mumpuni dengan banyak fitur yang biasanya beroperasi di atas "seluruh proyek." Dilihat dari namanya, ia bukan hanya sekedar editor biasa, tapi sebuah "lingkungan pengembangan" berskala besar. Satu IDE meload proyek (yang bisa berupa banyak file), memungkingkan navigasi antar file, menyediakan autocompletion berdasarkan seluruh proyek (tak hanya file terbuka), dan berintegrasi dengan sistem manajemen versi (seperti [git](https://git-scm.com/)), lingkungan pengujian, dan hal-hal "level proyek" lainnya. Jika kamu belum memilih satu IDE, pertimbangkan opsi-opsi berikut: - [Visual Studio Code](https://code.visualstudio.com/) (lintas-platform, gratis). - [WebStorm](http://www.jetbrains.com/webstorm/) (lintas-platform, berbayar). Untuk Windows, ada juga "Visual Studio", jangan dipusingkan dengan "Visual Studio Code." "Visual Studio" merupakan editor khusus Windows yang keren dan berbayar, sangat cocok untuk platform .NET. Ia bagus juga untuk JavaScript. Lalu ada juga versi gratisnya [Visual Studio Community](https://www.visualstudio.com/vs/community/). Banyak IDE berbayar, tapi punya masa percobaan. Biasanya harganya tak seberapa dibanding gaji pengembang berkualitas, jadi pilihlah yang terbaik untukmu. ## Editor ringan "Editor ringan" tak semumpuni IDE, tapi mereka cepat, elegan, dan simpel. Mereka digunakan terutama untuk membuka dan mengedit file secara instan. Perbedaan utama antara "editor ringan" dan "IDE" adalah IDE bekerja pada level proyek, jadi IDE meload banyak data di awal, menganalisa struktur proyek jika dibutuhkan dan lain sebagainya. Editor ringan jauh lebih cepat jika kita cuma membutuhkan hanya satu file. Pada praktiknya, editor ringan bisa punya banyak plugin termasuk syntax analyzers dan autocompleters level direktori, jadi tak ada batasan ketat antara editor ringan dan IDE. Opsi-opsi berikut patut anda perhatikan: - [Atom](https://atom.io/) (lintas-platform, gratis). - [Visual Studio Code](https://code.visualstudio.com/) (lintas-platform, gratis). - [Sublime Text](http://www.sublimetext.com) (lintas-platform, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, gratis). - [Vim](http://www.vim.org/) dan [Emacs](https://www.gnu.org/software/emacs/) sangat keren juga jika kamu tahu cara pakainya. ## Jangan berdebat Daftar editor di atas merupakan barang yang sudah biasa saya atau teman-teman saya para pengembang profesional gunakan selama ini dengan gembira. Ada banyak editor bagus lainnya di dunia kita yang besar ini. Silakan pilih satu yang paling kamu suka. Pilihan editor, sama seperti tool lainnya, bersifat individual dan tergantung pada proyek, kebiasaan, dan preferensi personal. ================================================ FILE: 1-js/01-getting-started/4-devtools/article.md ================================================ # Konsol pengembang Yang namanya kode selalu rentan error. Mudah sekali bagimu bikin error... Benar kan? Kamu *tidak akan pernah* luput dari error, itu karena kamu manusia, bukan [robot](https://en.wikipedia.org/wiki/Bender_(Futurama)). Tapi di dalam peramban, error tidak terlihat ke pengguna secara default. Jadi, kalau ada yang salah di script, itu tidak kelihatan mata kita dan kita sudah memperbaiki itu. Supaya bisa melihat error dan memperoleh informasi berfaedah lainnya dari script, "tools pengembang" ditanamkan di dalam peramban. Kebanyakan pengembang memakai Chrome atau Firefox untuk pengembangan karena tools pengembangan yang mereka punya paling mantap. Peramban lain punya juga koq, ada with special features, but are usually playing "catch-up" to Chrome or Firefox. So most developers have a "favorite" browser and switch to others if a problem is browser-specific. Tools pengembang mengandung banyak manfaat; mereka punya banyak fitur. Untuk memulainya, kita akan belajar cara membuka mereka, mencari error, dan menjalankan perintah JavaScript. ## Google Chrome Buka laman [bug.html](bug.html). Ada satu error di dalam kode JavaScript di situ. Ia tersembunyi dari mata pengunjung biasa, mari kita buka tools pengembang untuk melihatnya. Tekan `key:F12` atau, kalau kamu pakai Mac, tekan `key:Cmd+Opt+J`. Tools pengembang akan terbuka pada Console tab secara default. Nanti tampilannya seperti ini: ![chrome](chrome.png) Tampilan persisnya tools pengembang tergantung versi Chrome kamu. Ia berubah dari masa ke masa tapi tetap serupa. - Di sini kita bisa melihat pesan error berwarna merah. Di sini, scriptnya mengandung perintah asing "lalala". - Di kanan, ada link yang bisa diklik ke sumber `bug.html:12` dengan nomor baris di mana error itu muncul. Di bawah pesan error, ada simbol `>` berwarna biru. Ia menandakan "command line" di mana kita bisa mengetik perintah JavaScript. Tekan `key:Enter` untuk menjalankannya. Sekarang kita bisa melihat error, dan itu sudah cukup untuk permulaan. Kita nanti akan kembali ke tools pengembang dan mengcover debugging lebih dalam di bab . ```smart header="Multi-line input" Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. To insert multiple lines, press `key:Shift+Enter`. This way one can enter long fragments of JavaScript code. ``` ## Firefox, Edge, dan lainnya Banyak peramban lain memakai `key:F12` untuk membuka tools pengembang. Look & feel mereka hampir mirip. Sekali kamu tahu cara memakainya (kamu bisa mulai dengan Chrome), kamu bisa dengan mudah ganti dari satu ke yang lain. ## Safari Safari (peramban Mac, tidak didukung Windows/Linux) agak sedikit spesial di sini. Kita harus mengaktifkan "Develop menu" terlebih dulu. Buka Preferences dan pergi ke "Advanced" pane. Di sana ada checkbox di sebelah bawah: ![safari](safari.png) Sekarang `key:Cmd+Opt+C` bisa mentoggle konsol. Lalu, menu "Develop" muncul pada menu item di atas. Ia punya banyak perintah dan opsi. ## Kesimpulan - Tools pengembang memungkinkan kita melihat error, menjalankan perintah, memeriksa variabel, dan sebagainya. - Mereka bisa dibuka dengan `key:F12` untuk kebanyakan peramban di Windows. Chrome di Mac dengan `key:Cmd+Opt+J`, Safari `key:Cmd+Opt+C` (harus diaktifkan terlebih dulu). Kini kita sudah menyiapkan lingkungannya. Di seksi berikutnya, kita akan terjun ke 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 ================================================ # Pengenalan Tentang bahasa JavaScript dan lingkungan pengembangannya. ================================================ 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 ================================================ nilai penting: 5 --- # Tampilkan alert Buat laman yang menampilkan pesan "I'm JavaScript!". Lakukan dalam sandbox, atau di hard drive kamu, tak masalah, pastikan bisa berjalan. [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 ================================================ Kode HTML: [html src="index.html"] Untuk file `alert.js` di dalam folder yang sama: [js src="alert.js"] ================================================ FILE: 1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md ================================================ nilai penting: 5 --- # Tampilkan alert dengan script external Ambil solusi dari tugas sebelumnya . Modifikasi itu dengan mengextrak konten script ke file external `alert.js`, yang berada di dalam folder yang sama. Buka lamannya, pastikan alertnya bekerja. ================================================ FILE: 1-js/02-first-steps/01-hello-world/article.md ================================================ # Hello, world! Bagian dari tutorial ini ialah tentang inti JavaScript itu sendiri. Tapi kita butuh lingkungan kerja untuk menjalankan scripts kita dan, karena buku ini online, peramban adalah pilihan yang baik. Kita akan menjaga supaya jumlah perintah yang spesifik peramban (seperti `alert`) seminimum mungkin sehingga kamu tak boros waktu di situ jika kamu berencana untuk fokus ke lingkungan lain (seperti Node.js). Kita akan fokus ke JavaScript di peramban dalam [bagian selanjutnya](/ui) dari tutorial ini. Jadi pertama, kita lihat bagaimana kita menyisipkan script ke laman web. Untuk lingkungan (seperti Node.js), kamu bisa mengeksekusi script itu dengan perintah seperti `"node my.js"`. ## Tag "script" Program JavaScript bisa disisipkan ke dalam bagian mana saja dari dokumen HTML dengan bantuan tag ` */!*

...Kode ini ditulis setelah skrip.

``` ```online Kamu bisa menjalankan contohnya dengan mengklik tombol "Play" di sebelah ujung kanan-atas dari box di atas. ``` Tag ` ``` Trik ini tak lagi dipakai di JavaScript modern. Komen ini menyembunyikan kode JavaScript dari peramban tua yang tak tahu cara memproses tag ` ``` Di sini, `/path/to/script.js` adalah jalur absolut ke file script dari root sitius. Kamu juga bisa menyediakan jalur relatif dari laman ini. Misalnya, `src="script.js"` berarti file `"script.js"` dalam folder saat ini. Kamu bisa memasang URL penuh juga. Misalnya: ```html ``` Untuk menempelkan beberapa script, gunakan tag berlapis: ```html … ``` ```smart Aturannya, cuma script paling simpel yang ditaruh di dalam HTML. Script lebih rumit ditaruh di file terpisah. Keuntungan file terpisah ialah peramban akan mengunduhnya dan menyimpannya di [cache](https://en.wikipedia.org/wiki/Web_cache)-nya. Laman lain yang merujuk ke script yang sama akan mengambilnya dari cache ketimbang mengunduhnya, jadi sebenarnya file hanya diunduh sekali saja. Itu mengurangi trafik dan membuat laman jadi lebih cepat. ``` ````warn header="Jika `src` diset, konten script diabaikan." Tag ` ``` Kita harus memilih antara ` ``` ```` ## Kesimpulan - Kita bisa menggunakan tag ``. Masih banyak lagi yang harus dipelajari tentang script peramban dan interaksi mereka dengan laman web. Tapi harap diingat bahwa bagian ini dari tutorial ini dikhususkan hanya ke bahasa JavaScript, jadi kita tak akan membahas implementasi Javascript yang spesifik peramban. Kita akan menggunakan peramban hanya sebagai alat untuk menjalankan JavaScript, yang nyaman untuk bacaan online. ================================================ FILE: 1-js/02-first-steps/02-structure/article.md ================================================ # Struktur kode Hal pertama yang kita akan pelajari ialah membangun blok kode. ## Pernyataan Pernyataan ialah konsep dan perintah syntax yang mejalankan aksi. Kita sudah melihat satu pernyataan, `alert('Hello, world!')`, yang menampilkan pesan "Hello, world!". Kita bisa memiliki sebanyak apapun pernyataan dalam kode kita. Pernyataan bisa dipisah menggunakan titik koma. Misalnya, di sini kita memecah "Hello World" menjadi dua alert: ```js run no-beautify alert('Hello'); alert('World'); ``` Biasanya, pernyataan ditulis dalam baris terpisah supaya kode lebih mudah dibaca: ```js run no-beautify alert('Hello'); alert('World'); ``` ## Titik koma [#semicolon] Titik koma bisa dibuang dalam banyak kasus jika ada jeda baris. Ini juga akan berjalan: ```js run no-beautify alert('Hello') alert('World') ``` Di sini, JavaScript menginterpretasi jeda baris sebagai titik koma "implisit". Ini disebut [penyisipan titik koma otomatis](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion). **Dalam banyak kasus, sebuah garis baru mengimplikasikan titik koma. Tapi "dalam banyak kasus" tak "selalu" begitu!** Ada kasus ketika garis baru tidak berarti titik koma. Misalnya: ```js run no-beautify alert(3 + 1 + 2); ``` Output dari kode itu adalah `6` karena JavaScript tak menyisipkan titik koma di sini. Sudah jelas sekali bahwa barisnya selesai dengan tanda plus `"+"`, sehingga itu menjadi "expresi tak lengkap", jadi tak butuh titik koma. Dan dalam hal ini memang seperti itu. **Tapi ada situasi di mana JavaScript "gagal" mengasumsi titik koma di mana itu benar-benar dibutuhkan.** Galat yang muncul pada kasus ini agak sulit dicari dan dibetulkan. ````smart header="Contoh galat" Jika kamu penasaran untuk melihat contoh konkrit dari galat ini, cek kode ini: ```js run alert("Hello"); [1, 2].forEach(alert); ``` Untuk sekarang tak usah memikirkan makna kurung siku `[]` dan `forEach`. Kita akan mempelajari mereka nanti. Untuk sekarang, ingat hasil kode tersebut: yaitu `1` lalu `2`. Sekarang, ayo kita tambahkan `alert` sebelum kodenya *tanpa* diikuti titik koma: ```js run no-beautify alert("Hello") [1, 2].forEach(alert); ``` Sekarang jika kita menjalankan kodenya, hanya `alert` pertama yang tampil dan kemudian galat! Tapi semua akan baik-baik saja jika kita menambahkan titik koma setelah `alert`: ```js run alert("All fine now"); If we run this code, only the first `Hello` shows (and there's an error, you may need to open the console to see it). There are no numbers any more. Sekarang kita punya pesan "All fine now" diikuti dengan `1` dan `2`. Galat muncul pada varian tanpa titik koma karena JavaScript tak mengasumsikan titik koma sebelum kurung siku `[...]`. Jadi, karena titik koma tidak otomatis disisipkan, kode di contoh pertama diperlakukan sebagai pernyataan tunggal. Inilah cara engine melihatnya: ```js run no-beautify alert("Hello")[1, 2].forEach(alert); ``` Tapi itu harus jadi dua pernyataan terpisah, bukan satu. Penyatuan macam ini salah pada kasus ini, makanya galat. Ini bisa terjadi dalam situasi lain. ```` Kami sarankan menaruh titik koma di antara pernyataan meski mereka dipisahkan garis baru. Ini aturan yang diterima secara luas oleh komunitas. Harap diingat sekali lagi bahwa -- *bisa saja* menanggalkan titik koma di banyak kesempatan. Tapi akan lebih aman -- terutama untuk pemula -- untuk menggunakan mereka. ## Komentar [#komentar pada sebuah kode] Seiring waktu berjalan, program menjadi lebih rumit. Dan dibutuhkan *komen* yang menjelaskan kode apa itu dan kenapa. Komen bisa ditaruh di mana saja dari script. Dan tidak berpengaruh ke eksekusi karena engine mengabaikan mereka. **Satu-baris komen bermula dengan dua karakter slash `//`.** Sisa barisnya adalah komen. Ia bisa memenuhi satu baris sendiri atau mengikuti pernyataan. Seperti di sini: ```js run // Komen ini menghuni satu baris sendiri alert('Hello'); alert('World'); // Komen ini mengikuti pernyataan ``` **Komen multiline bermula dengan garis miring dan bintang /* dan berakhir dengan bintang dan garis miring */.** Seperti ini: ```js run /* Contoh dengan dua pesan. Ini komen multiline. */ alert('Hello'); alert('World'); ``` Konten komen diabaikan, jadi jika menaruh kode di dalam /* ... */, ia tidak akan dieksekusi. Kadang sangat berguna jika kita bisa menonaktifkan sementara sebagian kode: ```js run /* Mengkomen kode alert('Hello'); */ alert('World'); ``` ```smart header="Gunakan hotkey!" Di banyak editor, sebaris kode bisa dikomen dengan menekan hotkey `key:Ctrl+/` untuk komen baris-tunggal dan sesuatu macam `key:Ctrl+Shift+/` -- untuk komen multibaris (pilih sepotong kode dan tekan hotkeynya). Untuk Mac, coba `key:Cmd` ketimbang `key:Ctrl`. ``` ````warn header="Komen bersarang tidak didukung!" Tidak boleh ada `/*...*/` di dalam `/*...*/` yang lain. Kode begini akan berakhir galat: ```js run no-beautify /* /* komen bersarang ?!? */ */ alert( 'World' ); ``` ```` Silakan, jangan ragu mengkomen. Komen meningkatkan kode footprint garis besar, tapi itu bukan masalah sama sekali. Ada banyak tools yang meminifikasi kode sebelum dipublikasi ke production server. Mereka menghapus komen, jadi mereka tidak tampil di script yang berjalan. Selain itu, komen tidak punya efek negatif pada production sama sekali. Di akhir tutorial ini akan ada bab yang juga menerangkan cara menulis komen yang lebih baik. ================================================ FILE: 1-js/02-first-steps/03-strict-mode/article.md ================================================ # The modern mode, "use strict" Selama ini, JavaScript tumbuh tanpa isu kompatibilitas. Fitur baru ditambahkan tanpa mengubah fungsionalitas yang sudah ada. Keuntungannya adalah kode yang sudah ada tidak rusak. Tapi jeleknya adalah satu keputusan salah atau cacat yang dibuat oleh pembuat JavaScript akan menetap selamanya. Inilah yang terjadi hingga tahun 2009 ketika ECMAScript 5 (ES5) muncul. Fitur baru ditambah dan beberapa kode yang ada diubah. Supaya kode lama tetap berjalan, kebanyakan modifikasi seperti ini secara default mati. Kamu harus mengaktifkan mereka secara explisit menggunakan directive special: `"use strict"`. ## "use strict" Directive tersebut mirip string: `"use strict"` atau `'use strict'`. Jika itu diletakkan paling atas dari script, seluruh script akan bekerja secara "modern". Misalnya: ```js "use strict"; // this code works the modern way ... ``` Kita akan mempelajari fungsi (cara mengelompokkan perintah) segera. Melihat ke depan, ingatlah bahwa `"use strict"` bisa ditaruh di bagian awal fungsi. Itu membuat strict mode aktif hanya di dalam fungsi itu. Tapi biasanya, orang memakai itu untuk seluruh script. ````warn header="Yakinkan bahwa \"use strict\" berada paling atas" Pastikan `"use strict"` berada paling atas dari script kamu, kalau tidak strict mode tidak akan aktif. Strict mode tidak aktif di sini: ```js no-strict alert("some code"); // "use strict" di sini diabaikan--dia harus berada paling atas "use strict"; // strict mode tidak aktif ``` Hanya komen yang muncul di atas `"use strict"`. ```` ```warn header="Tidak ada cara untuk membatalkan `use strict`" Tak ada directive seperti `"no use strict"` yang merevert engine ke kelakuan lama. Sekali kita masuk strict mode, tak ada jalan kembali. ``` ## Konsol pengembang Ketika kamu menggunakan konsol pengembang [developer console](info:devtools) untuk menjalankan kode, perlu diingat pada dasarnya konsol pengembang tidak menggunakan `use strict`. Kadang, ketika menggunakan `use strict`, kamu akan mendapat hasil yang salah. Jadi, bagaimana seharusnya mengunakan `use strict` didalam konsol? Pertama, kamu bisa menekan tombol `key:Shift+Enter` untuk memasukan beberapa baris kode dan masukan `use strict` di paling atas, seperti ini: ```js 'use strict'; // ...Kodemu ``` Ini bekerja pada kebanyakan peramban, seperti Firefox dan Chrome. Jika tidak berjalan, seperti di browser lama, ada sebuah cara untuk memastikan penggunaan `use strict`. Masukan kodenya kedalam sebuah pembungkus: ```js (function() { 'use strict'; // ...Kode lainnya disini... })() ``` ## Haruskah kita menggunakan "use strict"? Pertanyaannya sudah cukup jelas, tapi ternyata tidak. Seseorang bisa saja merekomendasikan memulai skrip dengan menggunakan `"use strict"`...Tapi apakah kamu tahu apa keren? Javascript modern mendukung kelas atau *classes* dan modul atau *modules* - struktur bahasa tingkat tinggi (kita akan belajar nanti), yang dapat mengaktifkan `use strict` secara otomatis. Jadi kita tidak perlu untuk menambahkan instruksi `"use strict"`, jika kita menggunakannya. **Jadi, untuk sekarang `"use strict";` adalah hal yang perlu ada di awal dari skrip kamu. Nanti, ketika seluruh kodemu telah menggunakan kelas dan modul, kamu bisa menghilangkannya** Untuk sekarang, kita harus mengetahui tentang `use strict` secara dasar. Di bagian selanjutnya, selagi kita belajar tentang fitur jadi Javascript, kita akan melihat beberapa perbedaan diantara *strict mode* dan mode lama. Beruntungnya, tidak dapat terdapat banyak perbedaan dan sebenarnya keduanya sangat bermanfaat. Seluruh contoh di tutorial ini menggunakan *strict mode* kecuali (sangat jarang) dituliskan sebaliknya. ================================================ FILE: 1-js/02-first-steps/04-variables/1-hello-variables/solution.md ================================================ Dalam kode di bawah, tiap baris berkorespondensi ke item di dalam daftar tugas. ```js run let admin, name; // bisa mendeklarasikan dua variabel sekaligus name = "John"; admin = name; alert( admin ); // "John" ``` ================================================ FILE: 1-js/02-first-steps/04-variables/1-hello-variables/task.md ================================================ nilai penting: 2 --- # Bekerja dengan variabel 1. Deklarasikan dua variabel: `admin` and `name`. 2. Assign nilai `"John"` ke `name`. 3. Kopi nilai dari `name` ke `admin`. 4. Tampilkan nilai `admin` menggunakan `alert` (harus beroutput "John"). ================================================ FILE: 1-js/02-first-steps/04-variables/2-declare-variables/solution.md ================================================ ## Variabel untuk planet kita Itu simpel: ```js let ourPlanetName = "Earth"; ``` Ingat, kita bisa menggunakan nama lebih pendek `planet`, tapi itu tak akan jelas planet apa. Bagus jika lebih panjang dan deskriptif. Minimal hingga variabel isNotTooLong. ## Nama pengunjung saat ini ```js let currentUserName = "John"; ``` Lagi, kita bisa memperpendek jadi `userName` jika kita tahu pasti user saat ini. Editor modern dan autocomplete membuat nama variabel panjang mudah ditulis. Jangan pelit terhadap mereka. Nama yang mengandung 3 kata di dalamnya itu bagus. Dan jika editormu tidak punya autocompletion yang layak, carilah [editor yang baru](/code-editors). ================================================ FILE: 1-js/02-first-steps/04-variables/2-declare-variables/task.md ================================================ nilai penting: 3 --- # Memberikan nama yang tepat 1. Buat variabel dengan nama planet kita. Bagaimana kamu akan menamainya? 2. Buat variabel untuk menyimpan nama pengunjung saat ini ke website. Bagaimana kamu menamainya? ================================================ FILE: 1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md ================================================ Kita umumnya menggunakan case yang layak untuk konstan yang "dihard-code". Atau, dengan kata lain, ketika nilainya diketahui sebelum eksekusi dan langsung ditulis ke dalam kode. Di dalam kode, `birthday` memang seperti itu. Jadi kita bisa menggunakan kapital besar untuknya. Sebaliknya, `age` dievaluasi saat run-time. Hari ini kita punya satu umur, setahun kemudian kita akan punya umur yang berbeda. Ia termasuk konstan yang takkan berubah melalui eksekusi kode. Tapi ia agak "sedikit bukan konstan" ketimbang `birthday`: ia dihitung, jadi sebaiknya kita menggunakan huruf kecil untuk itu. ================================================ FILE: 1-js/02-first-steps/04-variables/3-uppercast-constant/task.md ================================================ nilai penting: 4 --- # Const huruf kapital? Cek kode berikut: ```js const birthday = '18.04.1982'; const age = someCode(birthday); ``` Di sini kita punya konstan tanggal `birthday` dan `age` dikalkulasi dari `birthday` dengan batuan beberapa kode (tidak tersedia yang pendek-pendek, dan karena detail tidak masalah di sini). Apakah tepat menggunakan huruf kapital untuk `birthday`? Untuk `age`? Atau bahkan untuk keduanya? ```js const BIRTHDAY = '18.04.1982'; // buat huruf kapital? const AGE = someCode(BIRTHDAY); // buat huruf kapital? ``` ================================================ FILE: 1-js/02-first-steps/04-variables/article.md ================================================ # Variabel Seringnya, aplikasi JavaScript butuh kerja dengan informasi. Di sini ada dua contoh: 1. Toko online -- informasinya mungkin berisi barang-barang yang dijual dan kereta belanja. 2. Aplikasi chat -- informasinya mungkin berisi user, pesan, dan banyak lagi. Variabel digunakan untuk menyimpan informasi ini. ## Variabel [Variabel](https://en.wikipedia.org/wiki/Variable_(computer_science)) ialah "simpanan bernama" untuk data. Kita bisa memakainya untuk menyimpan barang, pengunjung, dan data lain. Untuk membuat variabel di JavaScript, gunakan katakunci `let`. Pernyataan di bawah membuat (dengan kata lain: *declares* or *defines*) variabel dengan nama "message": ```js let message; ``` Kini, kita bisa menaruh beberapa data ke dalamnya dengan menggunakan operator penetapan `=`: ```js let message; *!* message = 'Hello'; // simpan string */!* ``` String ini kini disimpan ke area memori yang terasosiasi dengan variabel. Kita bisa mengakses itu menggunakan nama variabel: ```js run let message; message = 'Hello!'; *!* alert(message); // tampilkan isi variabel */!* ``` Supaya ringkas, kita bisa mengkombinasi deklarasi variabel dan penetapan ke baris tunggal: ```js run let message = 'Hello!'; // definisikan variabel dan tetapkan nilai alert(message); // Hello! ``` Kita juga bisa mendeklarasi variabel ganda dalam satu baris: ```js no-beautify let user = 'John', age = 25, message = 'Hello'; ``` Kelihatannya pendek, tapi itu tidak disarankan. Demi kemudahan dibaca, tolong gunakan bari tunggal per variabel. Varian multibaris agak panjang, tapi lebih mudah dibaca: ```js let user = 'John'; let age = 25; let message = 'Hello'; ``` Beberapa orang juga mendefinisi variabel ganda dalam gaya multibaris ini: ```js no-beautify let user = 'John', age = 25, message = 'Hello'; ``` ...Atau bahkan dalam gaya "comma-first": ```js no-beautify let user = 'John' , age = 25 , message = 'Hello'; ``` Secara teknis, semua varian ini melakukan hal yang sama. Jadi, cuma masaleh selera personal dan estetika saja. ````smart header="`var` ketimbang `let`" Di script jadul, kamu mungkin juga menemukan katakunci lain: `var` ketimbang `let`: ```js *!*var*/!* message = 'Hello'; ``` Katakunci `var` *hampir* sama dengan `let`. Gunanya untuk mendeklarasi variabel, tapi caranya agak sedikit beda, "jadul". Ada perbedaan halus antara `let` dan `var`, tapi itu tak masalah buat kita sekarang ini. Kita akan mengcover mereka lebih detil di bab . ```` ## Analogy kehidupan nyata Kita bisa dengan mudah memahami konsep "variabel" jika kita membayangkannya sebagai "box" untuk data, dengan stiker nama yang unik. Misalnya, variabel `message` bisa dibayangkan sebagai box berlabel `"message"` dengan nilai `"Hello!"` di dalamnya: ![](variable.svg) Kita bisa menaruh nilai apapun di dalam box. Kita juga bisa mengubahnya sebanyak yang kita mau: ```js run let message; message = 'Hello!'; message = 'World!'; // value changed alert(message); ``` Ketika nilainya berubah, data lama dihapus dari variabel: ![](variable-change.svg) Kita juga bisa mendeklarasi dua variabel dan mengkopi data dari satu ke yang lainnya. ```js run let hello = 'Hello world!'; let message; *!* // mengkopi 'Hello world' dari hello ke message message = hello; */!* // sekarang dua variabel punya data yang sama alert(hello); // Hello world! alert(message); // Hello world! ``` ````warn header="Mendeklarasikan variabel dua kali akan menciptakan error" Sebuah variabel seharusnya di deklarasikan hanya sekali. Mendeklarasikan variabel berkali kali akan menciptakan error: ```js run let message = "This"; // repeated 'let' leads to an error // deklarasi 'let' berulang akan mengembalikan sebuah error let message = "That"; // SyntaxError: 'message' has already been declared ``` Jadi, kita harus mendeklarasikan variabel sekali dan menggunakan variabel tersebut tanpa menggunakan 'let'. ```` ```smart header="Bahasa fungsional" Ini sangat menarik untuk diperhatikan bahwa ada bahasa pemrograman [fungsional](https://en.wikipedia.org/wiki/Functional_programming), seperti [Scala](http://www.scala-lang.org/) atau [Erlang](http://www.erlang.org/) yang melarang merubah nilai dari variabel. Di dalam bahasa macam ini, sekali nilai disimpan "dalam box", ia akan di sana selamanya. Jika kita harus menyimpan sesuatu yang lain, bahasa tersebut memaksa kita membuat box baru (mendeklarasi variabel baru). Kita tak bisa menggunakan ulang yang lama. Meski kelihatan sedikit aneh saat pandangan pertama, bahasa-bahasa ini ternyata mumpuni untuk pengembangan yang serius. Lebih dari itu, ada area seperti komputasi paralel di mana keterbatasan ini memberikan keuntungan tertentu. Disarankan mempelajari bahasa macam ini (meski jika kamu tak berencana menggunakannya segera) untuk meningkatkan wawasan. ``` ## Penamaan variabel [#variable-naming] Ada dua keterbatasan pada nama variabel di JavaScript: 1. Nama hanya boleh mengandung huruf, digit, atau simbol seperti `$` and `_`. 2. Karakter pertama tidak boleh digit. Contoh nama valid: ```js let userName; let test123; ``` Ketika namanya mengandung kata ganda, [camelCase](https://en.wikipedia.org/wiki/CamelCase) umum digunakan: kata demi kata digabung, setiap kata kecuali yang pertama dimulai dengan huruf kapital: `myVeryLongName`. Yang menarik ialah -- tanda dollar `'$'` dan underscore `'_'` juga bisa digunakan dalam nama. Mereka simbol reguler, hanya seperti huruf, tanpa makna yang spesial. Nama-nama ini valid: ```js run untrusted let $ = 1; // declared a variable with the name "$" let _ = 2; // and now a variable with the name "_" alert($ + _); // 3 ``` Contoh nama variabel yang tidak valid: ```js no-beautify let 1a; // cannot start with a digit let my-name; // hyphens '-' aren't allowed in the name ``` ```smart header="Case berpengaruh" Variabel dengan nama `apple` dan `AppLE` adalah dua variabel yang berbeda. ``` ````smart header="Huruf non-Latin diperbolehkan, namun tak direkomendasikan" Boleh menggunakan bahasa apapun, termasuk huruf cyrillic atau bahkan hieroglyphs, seperti ini: ```js let имя = '...'; let 我 = '...'; ``` Secara teknis, tak ada error di sini, nama-nama begitu boleh digunakan, tapi ada tradisi internasional untuk menggunakan bahasa Inggris dalam nama variabel. Meski jika kita menulis script kecil, kemungkinan variabelnya akan digunakan terus-menerus. Dan juga orang-orang dari negara lain mungkin harus membaca beberapa kali. ```` ````warn header="Nama-nama yang dikecualikan" Ada [daftar kata yang dikecualikan](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords), yang tidak bisa digunakan sebagai nama variabel karena mereka digunakan oleh bahasa Javascript sendiri. Misalnya: `let`, `class`, `return`, dan `function` dikecualikan. Kode di bawah menghasilkan galat syntax: ```js run no-beautify let let = 5; // tak bisa menamai variable "let", galat! let return = 5; // juga tak bisa menamainya "return", galat! ``` ```` ````warn header="Assignment tanpa `use strict`" Normalnya, kita harus mendefinisi variabel sebelum memakainya. Tapi dulu, secara teknis boleh membuat variabel hanya dengan penetapan nilai tanpa menggunakan `let`. Ini masih berjalan jika kita tak menaruh `use strict` di script kita untuk mengelola kompatibilitas dengan script jadul. ```js run no-strict // note: tak ada "use strict" di contoh ini num = 5; // variabel "num" dibuat jika ia tak ada alert(num); // 5 ``` Ini kebiasaan buruk dan akan mengakibatkan galat dalam mode ketat: ```js "use strict"; *!* num = 5; // galat: num tak terdefinisi */!* ``` ```` ## Konstan Untuk mendeklarasi variabel konstan (tak berubah), gunakan `const` ketimbang `let`: ```js const myBirthday = '18.04.1982'; ``` Variabel yang dideklarasi menggunakan `const` disebut "konstan". Mereka tak bisa diubah. Jika kamu mencoba mengubahnya maka ia menghasilkan galat: ```js run const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // error, tak bisa menetapkan-ulang konstan! ``` Ketika programmer yakin bahwa variabel tak akan berubah, mereka bisa mendeklarasikan `const` untuk menjamin hal itu dan memberitahu semua orang. ### Konstan huruf-besar Ada kebiasaan umum untuk menggunakan konstan sebagai alias untuk nilai yang sulit dihafal yang akan diketahui sebelum dieksekusi. Konstan macam ini dinamai dengan huruf kapital dan underscore. Misalnya, mari kita buat konstan untuk warna dalam sesuatu yang disebut format "web" (hexadecimal): ```js run const COLOR_RED = "#F00"; const COLOR_GREEN = "#0F0"; const COLOR_BLUE = "#00F"; const COLOR_ORANGE = "#FF7F00"; // ...ketika kita harus memilih warna let color = COLOR_ORANGE; alert(color); // #FF7F00 ``` Keuntungan: - `COLOR_ORANGE` lebih mudah diingat ketimbang `"#FF7F00"`. - Lebih rentan salah penulisan `"#FF7F00"` ketimbang `COLOR_ORANGE`. - Ketika membaca code, `COLOR_ORANGE` lebih berarti daripada `#FF7F00`. Kapan kita sebaiknya menggunakan kapital untuk konstan dan kapan itu dinamai dengan normal? Ayo kita perjelas. Menjadi "konstan" hanya berarti jika nilai variable tak pernah berubah. Tapi ada konstan yang diketahui sebelum eksekusi (seperti nilai hexadecimal untuk merah) dan ada konstan yang *dikalkulasi* dalam run-time, selama eksekusi, tapi tak berubah setelah penetapan inisial mereka. Misalnya: ```js const pageLoadTime = /* waktu yang dibutuhkan laman web untuk meload */; ``` Nilai `pageLoadTime` tidak diketahui sebelum laman diload, jadi itu dinamai dengan normal. Tapi ia masih konstan karena ia tak berubah setelah penetapan. Dengan kata lain, konstan berhuruf kapital hanya digunakan sebagai alias untuk nilai yang "dihard-code". ## Namai dengan benar Berbicara tentang variabel, ada satu hal yang sangat penting. Nama variabel sebaiknya punya arti yang bersih dan jelas, yang menjelaskan data yang ia simpan. Penamaan variabel adalah salah satu keahlian yang penting dan rumit dalam pemrograman. Pandangan sekilas pada nama variabel bisa menyingkap kode yang ditulis oleh pengembang pemula versus pengembang berpengalaman. Di proyek nyata, kebanyakan waktu dihabiskan untuk modifikasi dan mengextend code base ketimbang menulis sesuatu yang benar-benar baru dari awal. Ketika kita kembali ke beberapa kode setelah melakukan sesuatu yang lain untuk sementara, akan lebih mudah menemukan informasi yang labelnya tepat. Atau, dengan kata lain, ketika variabel punya nama yang baik. Tolong renungkan tentang nama yang baik untuk variabel sebelum mendeklarasinya. Itu baik untukmu. Beberapa aturan yang baik untuk ditiru: - Gunakan nama yang manusiawi seperti `userName` atau `shoppingCart`. - Jauhi singkatan atau nama pendek seperti `a`, `b`, `c`, kecuali jika kamu benar-benar tau apa yang kamu lakukan. - Buat nama sedeskriptif dan sejelas mungkin. Contoh nama yang jelek ialah `data` dan `value`. Nama semacam ini tidak punya makna dan hanya OK untuk menggunakannya jika data atau nilai variabel yang mengacu kontex kodenya luar biasa jelas. - Sepakat dalam hal-hal yang ada di dalam timmu dan pikiranmu. Jika pengunjung situs disebut "user" maka kita sebaiknya menamai variabel terkait `currentUser` atau `newUser` ketimbang `currentVisitor` atau `newManInTown`. Kedengaran simpel kan? Jelas saja, tapi membuat nama variabel descriptif dan jelas pada praktiknya tidak mudah. Coba lakukan. ```smart header="Daur ulang atau buat baru?" Dan catatan terakhirnya. Ada juga programmer malas yang, ketimbang mendeklarasi variabel baru, cenderung menggunakan variabel yang sudah ada. Hasilnya, variabel mereka seperti box yang orang-orang lempar tanpa menganti stikernya. Apa yang ada di dalam boxnya? Siapa yang tahu? Kita harus mengeceknya dengan seksama. Programmer macam ini pelit terhadap satu deklarasi variabel namun boros sepuluh kali saat debugging. Variabel extra itu baik, tidak jahat. JavaScript minifier dan peramban modern mengoptimisasi kode dengan cukup baik, sehingga ia tak akan mengakibatkan isu performa. Menggunakan variabel yang berbeda untuk nilai yang berbeda bahkan bisa membantu engine mengoptimisasi kodemu. ``` ## Kesimpulan Kita bisa mendeklarasi variabel untuk menyimpan data menggunakan katakunci `var`, `let`, atau `const`. - `let` -- adalah deklarasi variabel modern. - `var` -- adalah deklarasi variabel jadul. Normalnya kita tak menggunakannya sama sekali, tapi kita akan mengcover perbedaan halus dari `let` di bab , hanya jika kamu membutuhkannya. - `const` -- seperti `let`, tapi nilai variabelnya tak bisa diubah. Variabel sebaiknya diberi nama yang memudahkan kita untuk memahami apa isinya. ================================================ FILE: 1-js/02-first-steps/05-types/1-string-quotes/solution.md ================================================ Backtick mengembed expresi di dalam `${...}` ke dalam string. ```js run let name = "Ilya"; // expresinya ialah angka 1 alert( `hello ${1}` ); // hello 1 // expresinya ialah nama string alert( `hello ${"name"}` ); // hello name // expresinya ialah variabel, embed dia alert( `hello ${name}` ); // hello Ilya ``` ================================================ FILE: 1-js/02-first-steps/05-types/1-string-quotes/task.md ================================================ nilai penting: 5 --- # Petik string Apa output dari script ini? ```js let name = "Ilya"; alert( `hello ${1}` ); // ? alert( `hello ${"name"}` ); // ? alert( `hello ${name}` ); // ? ``` ================================================ FILE: 1-js/02-first-steps/05-types/article.md ================================================ # Tipe data Dalam Javascript terdapat beberapa tipe data yang dapat digunakan, sebuah *string* atau sebuah *number* Terdapat delapan tipe data dasar dalam Javascript. Disini, kita akan mempelajarinya dan di bagian selanjutnya kita akan mempelajarinya secara detail. Kita bisa menggunakan tipe apa saja didalam sebuah variabel. Contoh, Untuk sesaat sebuah variabel bisa saja memiliki tipe data *string* dan selanjutnya variabel tersebut menyimpan *number*: ```js // Tidak ada error let message = "hello"; message = 123456; ``` Bahasa pemrograman yang mendukung hal semacam ini, seperti Javascript, disebut dengan "dynamically typed", berarti terdapat tipe data, akan tetapi variabel tidak terikat pada tipe data apapun. ## Number ```js let n = 123; n = 12.345; ``` Tipe *number* merepresentasikan angka baik integer maupun floating point. Ada banyak operasi untuk angka, misal perkalian `*`, pembagian `/`, penambahan `+`, pengurangan `-`, dan lainnya. Selain angka reguler, ada yang disebut "nilai numerik spesial" yang juga bagian dari tipe data ini: `Infinity`, `-Infinity` dan `NaN`. - `Infinity` mewakili [Infinity](https://en.wikipedia.org/wiki/Infinity) matematis ∞. Ia merupakan nilai spesial yang lebih besar dari angka apapun. Kita bisa mendapatkannya sebagai hasil dari pembagian oleh nol: ```js run alert( 1 / 0 ); // Infinity ``` Atau langsung referensikan saja dia: ```js run alert( Infinity ); // Infinity ``` - `NaN` mewakili error komputasional. Ia merupakan hasil operasi matematis yang salah atau tak-terdefinisi, misalnya: ```js run alert( "not a number" / 2 ); // NaN, pembagian macam ini keliru ``` `NaN` itu lengket. Operasi lanjutan apapun pada `NaN` menghasilkan `NaN`: ```js run alert( "not a number" / 2 + 5 ); // NaN ``` Jadi, jika ada `NaN` di manapun di expresi matematika, ia mempropagasi hasil keseluruhan. ```smart header="Operasi matematika itu aman" Melakukan matematika itu "aman" dalam JavaScript. Kita bisa melakukan apapun: pembagian dengan nol, memperlakukan string non-numerik sebagai angka, dll. Script ini takkan pernah stop dengan fatal error ("die"). Paling buruk, kita akan mendapat `NaN` sebagai hasilnya. ``` Nilai numerik spesial formalnya merupakan bagian dari tipe "number". Tentu saja mereka bukan angka dalam pandangan umum dari kata ini. Kita akan melihat lebih tentang cara bekerja dengan angka di bab . ## BigInt [#bigint-type] Didalam Javascript, tipe data "number" tidak bisa mengandung nilai lebih dari (253-1) (sama dengan `9007199254740991`) atau kurang dari -(253-1). Itu adalah batasan teknik yang dibuat. Untuk kebanyakan kebutuhan sebenarnya sudah cukup, dan terkadang kita membutuhkan nilai yang lebih besar, contohnya untuk kriptografy atau perhitungan waktu microsecond. Tipe data `BigInt` lalu ditambahkan kedalam Javascript untuk menampilkan nilai *integer* yang sangat panjang. Tipe data `BigInt` dibuat dengan menambahkan `n` diakhir dari nilai sebuah *integer*. ```js // arti dari "n" pada akhir menandakan bahwa contoh dibawah adalah sebuah `BigInt` const bigInt = 1234567890123456789012345678901234567890n; ``` Sebenarnya `BigInt` jarang dibutuhkan, kita tidak akan mempelajarinya disini, tetapi akan dipisahkan didalam bagian . Baca saja saat kamu membutuhkan nilai *integer* yang sangat panjang. ```smart header="Masalah Kompabilitas" Sekarang `BigInt` sudah didukung oleh Firefox/Chrome/Edge, tapi tidak didalam Safari/Internet Explorer. You can check [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) to know which versions of a browser are supported. ## String String di JavaScript harus dikelilingi petik. ```js let str = "Hello"; let str2 = 'Single quotes are ok too'; let phrase = `can embed another ${str}`; ``` Di JavaScript, ada 3 tipe petik. 1. Petik ganda: `"Hello"`. 2. Petik tunggal: `'Hello'`. 3. Backtick: `Hello`. Petik tunggal dan ganda merupakan petik "simpel". Tak ada perbedaan antara mereka di JavaScript. Backtick merupakan petik "fungsional lanjutan". Mereka memungkinkan kita mengembed variabel dan expresi ke dalam string dengan membungkus mereka dalam `${…}`, misalnya: ```js run let name = "John"; // mengembed satu variabel alert( `Hello, *!*${name}*/!*!` ); // Hello, John! // mengembed expresi alert( `the result is *!*${1 + 2}*/!*` ); // hasilnya 3 ``` Expresi di dalam `${…}` dievaluasi dan hasilnya menjadi bagian dari string. Kita bisa menaruh apapun di sana: variabel seperti `name` atau expresi aritmatika seperti `1 + 2` atau sesuatu yang lebih rumit. Tolong diingat bahwa ini hanya bisa dilakukan dalam backtick. Petik lain tidak punya fungsionalitas pengembedan! ```js run alert( "the result is ${1 + 2}" ); // hasilnya ${1 + 2} (petik ganda tak akan berpengaruh) ``` Kita akan mengcover string lebih dalam di bab . ```smart header="Tidak ada tipe *character*." Dalam beberapa bahasa, ada tipe "character" spesial untuk karakter tunggal. Misalnya, di bahasa C dan di Java adalah `char`. Di JavaScript, tak ada tipe semacam itu. Cuma ada satu tipe: `string`. String bisa berisi satu karakter atau lebih. ``` ## Boolean (tipe logika) Tipe boolean cuma punya dua nilai: `true` dan `false`. Tipe ini umumnya digunakan untuk menyimpan niai ya/tidak: `true` artinya "ya, betul", dan `false` artinya "tidak, salah". Misalnya: ```js let nameFieldChecked = true; // ya, field nama dicek let ageFieldChecked = false; // tidak, field usia tak dicek ``` Nilai boolean juga datang dari perbandingan: ```js run let isGreater = 4 > 1; alert( isGreater ); // benar (hasil perbandingan yaitu "ya") ``` Kita akan mengcover boolean lebih dalam di bab . ## Nilai "null" Nilai `null` spesial bukan bagian dari tipe apapun yang dijelaskan di atas. Ia membentuk tipe terpisah miliknya sendiri yang cuma berisi nilai `null`: ```js let age = null; ``` Di JavaScript, `null` tidak "mereferensi ke objek yang tak ada" atau "null pointer" seperti beberapa bahasa lain. Ia cuma nilai spesial yang mewakili "hampa", "kosong" atau "nilai tak-diketahui". Kode diatas mengatakan bahwa `age` tidak diketahui. ## Nilai "undefined" Nilai spesial `undefined` juga berbeda lagi. Ia punya tipe miliknya sendiri, sama seperti `null`. Arti `undefined` ialah "nilai yang tak ditetapkan". Jika variabel dideklarasi, namun tak ditetapkan, maka nilainya `undefined`: ```js run let age; alert(age); // menampilkan "undefined" ``` Secara teknis, kita bisa secara jelas menetapkan `undefined` kedalam sebuah variabel: ```js run let age = 100; // mengubah nilai menjadi undefined age = undefined; alert(age); // "undefined" ``` ...Tapi kita tidak menyarankan itu. Normalnya, kita gunakan `null` untuk menetapkan nilai "kosong" atau "tak-diketahui" ke variabel, dan kita gunakan `undefined` untuk pengecekan seperti melihat apakah nilai dari variabel telah ditetapkan. ## Objek dan Simbol Tipe `object` itu special. Seluruh tipe data lainnya disebut "primitive" karena hanya bisa mengandung satu buah nilai (entah itu sebuah string ataupun number atau lainnya). Sebaliknnya, object digunakan untuk menyimpan koleksi dari data dan entitas lainnya. Objek itu penting, objek membutuhkan perlakuan yang spesial. Kita akan pelajari objek lebih lanjut di bagian , setelah kita pelajari lebih lanjut tentang tipe data primitif. Tipe `symbol` digunakan untuk menciptakan identifier unik untuk sebuah objek. Untuk kelengkapan kita akan menyebutkannya disini, tetapi akan ditunda hingga kita tahu tentang objek ## Operator typeof [#type-typeof] Operator `typeof` mengembalikan tipe argumen. Berguna ketika kita ingin memproses nilai dari tipe berbeda secara berbeda atau cuma ingin mengecek sekilas. Ia mendukung dua bentuk syntax: 1. Sebagai operator: `typeof x`. 2. Sebagai fungsi: `typeof(x)`. Dengan kata lain, ia berjalan dengan atau tanpa kurung. Hasilnya sama saja. Panggilan ke `typeof x` mengembalikan string dengan nama tipenya: ```js typeof undefined // "undefined" typeof 0 // "number" typeof 10n // "bigint" typeof true // "boolean" typeof "foo" // "string" typeof Symbol("id") // "symbol" *!* typeof Math // "object" (1) */!* *!* typeof null // "object" (2) */!* *!* typeof alert // "function" (3) */!* ``` Tiga baris terakhir mungkin butuh penjelasan tambahan: 1. `Math` ialah objek built-in yang menyediakan operasi matematik. Kita akan belajar itu di bab . Di sini, ia cuma sekedar contoh dari objek. 2. Hasil `typeof null` yaitu `"object"`. Itu salah. Ia merupakan error yang terkenal resmi dalam `typeof`, yang dijaga untuk kompatibilitas. Tentu saja, `null` bukanlah objek. Ia merupakan nilai spesial dengan tipe terpisah miliknya sendiri. Jadi, lagi, ini merupakan error dalam bahasa. 3. Hasil dari `typeof alert` yaitu `"function"`, karena `alert` merupakan fungsi. Kita akan belajar fungsi di bab berikutnya di mana kita juga akan melihat bahwa tak ada tipe "fungsi" spesial di JavaScript. Fungsi merupakan bagian dari tipe objek. Tapi `typeof` memperlakukan mereka secara berbeda, yang mengembalikan `"fungsi"`. Itu tak sepenuhnya benar, tapi sangat nyaman pada praktiknya. ## Kesimpulan Ada 7 tipe data dasar dalam JavaScript. - `number` untuk nomor dengan bentuk apapun: integer ataupun nilai yang memiliki nilai desimal, batas dari integer adalah ±253. - `bigint` untuk nomor integer yang sangat panjang. - `string` untuk string. Sebuah string mungkin memiliki 0 atau lebih karakter, tidak ada tipe data untuk string yang memiliki panjang 1 karakter. - `boolean` untuk `true`/`false`. - `null` untuk nilai yang tidak diketahui -- sebuah tipe data mandiri yang memiliki satu nilai yaitu `null`. - `undefined` untuk nilai yang tidak ada atau tidak diberikan nilai -- sebuah tipe data mandiri yang memiliki satu nilai yaitu `null`. - `object` untuk struktur data yang lebih rumit. - `symbol` untuk identifier atau pengenal yang unik. Operator `typeof` memungkinkan kita melihat tipe mana yang disimpan dalam variable. - Dua form: `typeof x` atau `typeof(x)`. - Mengembalikan string dengan nama tipe, seperti `"string"`. - Untuk `null` mengembalikan `"object"` -- ada error dalam bahasa, yang sebenarnya bukan objek. Di bab berikutnya, kita akan fokus pada nilai primitive dan sekali kita familiar dengan mereka, kita akan lanjut ke objek. ================================================ FILE: 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md ================================================ Kode JavaScript: ```js demo run let name = prompt("Siapakah nama Anda?", ""); alert(name); ``` Laman penuh: ```html ``` ================================================ FILE: 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md ================================================ nilai penting: 4 --- # Laman simpel Buat laman web yang meminta nama dan menampilkannya. [demo] ================================================ FILE: 1-js/02-first-steps/06-alert-prompt-confirm/article.md ================================================ # Interaksi: alert, prompt, confirm sebagaimana kita akan menggunakan peramban sebagai lingkungan percobaan kode, ayo kita lihat beberapa fungsi untuk berinteraksi dengan pengguna: `alert`, `prompt` dan `confirm`. ## alert Untuk yang satu ini kita sudah pernah melihatnya. Ini akan menampilkan pesan dan menunggu pengguna untuk menekan tombol "OK". Contoh: ```js run alert("Hello"); ``` Mini-window dengan pesan ini disebut *modal window*. Kata "modal" artinya pengunjung tak bisa berinteraksi dengan apapun di laman, menekan tombol lain, dll. hingga mereka selesai berurusan dengan window ini. Dalam hal ini -- hingga mereka menekan "OK". ## prompt Fungsi `prompt` menerima dua argumen: ```js no-beautify result = prompt(title, [default]); ``` Ia menampilkan modal window dengan pesan teks, input field untuk pengunjung, dan tombol OK/CANCEL. `title` : Teks untuk ditampilkan ke pengunjung. `default` : Parameter kedua opsional, nilai inisial untuk input field. ```smart header="Kurung siku didalam sintaks `[...]`" Kurung siku di sintaks `default` di kode sintaks di atas menandakan bahwa parameter itu bersifat opsional, tidak benar-benar dibutuhkan. ``` Pengunjung halaman bisa mengetik sesuatu didalam kotak prompt dan menekan tombol OK. Lalu kita akan mendapatkan teksnya didalam `result`. Atau pengunjung halaman bisa membatalkan kotak promp dengan menekan *Cancel* atau menekan `key:Esc` pada *keyboard*, lalu kita akan mendapatkan `null` sebagai `result`. Panggilan ke `prompt` mengembalikan teks dari input field atau `null` jika input dibatalkan. Misalnya: ```js run let age = prompt('Berapakah umut anda?', 100); alert(`Umur Anda ${age} tahun`); // Umur Anda 100 tahun! ``` ```warn header="In IE: selalu isikan nilai `default`" Parameter kedua ini opsional, tapi jika kita tidak menyuplai, Internet Explorer akan menyisipkan teks `"undefined"` ke dalam prompt. Jalan kode ini di Internet Explorer untuk melihat: ```js run let test = prompt("Test"); ``` Jadi, supaya prompt terlihat bagus di IE, sebaiknya sediakan argumen kedua: ```js run let test = prompt("Test", ''); // <-- for IE ``` ## confirm Syntaxnya: ```js result = confirm(question); ``` Fungsi `confirm` menampilkan modal window dengan `pertanyaan` dan dua tombol: OK dan Cancel. Hasilnya `true` jika OK ditekan dan `false` jika tidak. Misalnya: ```js run let isBoss = confirm("Are you the boss?"); alert( isBoss ); // true jika OK ditekan ``` ## Kesimpulan Kita membahas 3 fungsi spesifik peramban untuk berinteraksi dengan pengunjung: `alert` : menampilkan pesan. `prompt` : menampilkan pesan yang minta input teks pengguna. Ia mengembalikan teks atau, jika Cancel atau `key:Esc` diklik, `null`. `confirm` : menampilkan pesan dan menunggu pengguna menekan "OK" atau "Cancel". Ia mengembalikan `true` untuk OK dan `false` untuk Cancel/`key:Esc`. Semua metode ini ialah modal: mereka menyela exekusi script dan tak membolehkan pengunjung berinteraksi dengan apapun di laman hingga window ditutup. Ada dua batasan yang dibagikan semua metode di atas: 1. Lokasi tepat modal window ditentukan oleh peramban. Biasanya, di tengah. 2. Tampilan tepat window juga tergantung peramban. Kita tak bisa modifikasi ini. Itulah harga untuk kesederhanaan. Ada banyak cara menampilkan window lebih manis dan kaya akan interaksi dengan pengguna, tapi jika "bells and whistles" tak jadi masalah, metode ini baik-baik saja. ================================================ FILE: 1-js/02-first-steps/07-type-conversions/article.md ================================================ # Konversi Tipe Seringkali, operator dan fungsi otomatis mengkonversi nilai yang diberikan ke mereka ke tipe yang sesuai. Misalnya, `alert` otomatis mengkonversi nilai apapun ke string untuk menampilkannya. Operasi mathematika mengkonversi nilai ke angka. Ada juga kasus di mana kita harus explisit mengkonversi nilai ke tipe yang diharapkan. ```smart header="Belum bicara objek dulu" Di bab ini, kita takkan mengcover objek. Daripada itu, kita akan belajar primitives dulu. Lalu, setelah kita belajar tentang objek, kita akan lihat cara konversi objek bekerja di bab . ``` ## Konversi String Konversi string terjadi ketika kita butuh bentuk string dari nilai. Misalnya, `alert(value)` menampilkan nilai. Kita juga bisa memanggil fungsi `String(value)` untuk mengkonversi nilai string: ```js run let value = true; alert(typeof value); // boolean *!* value = String(value); // sekarang nilainya string "true" alert(typeof value); // string */!* ``` Konversi string kebanyakan jelas. `false` menjadi `"false"`, `null` menjadi `"null"`, dll. ## Konversi Numerik Konversi numerik terjadi otomatis dalam fungsi dan expresi matematis. Misalnya, ketika pembagian `/` dilakukan ke non-angka: ```js run alert( "6" / "2" ); // 3, string dikonversi ke angka ``` Kita bisa gunakan fungsi `Number(value)` untuk explisit mengkonversi `value` ke angka: ```js run let str = "123"; alert(typeof str); // string let num = Number(str); // menjadi angka 123 alert(typeof num); // angka ``` Konversi explisit biasanya dibutuhkan ketika kita membaca nilai dari sumber berbasis string seperti form teks namun mengharapkan angka untuk dienter. Jika stringnya angka tak valid, hasilnya konversi macam ini ialah `NaN`. Misalnya: ```js run let age = Number("an arbitrary string instead of a number"); alert(age); // NaN, konversi gagal ``` Aturan konversi numerik: | Value | Becomes... | |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| |true dan false | `1` and `0` | | `string` | Whitespaces dari awal dan akhir dibuang. Jika string sisanya kosong, hasilnya `0`. Sebaliknya, angkanya "dibaca" dari stringnya. Error memberikan `NaN`. | Misalnya: ```js run alert( Number(" 123 ") ); // 123 alert( Number("123z") ); // NaN (error membaca angka pada "z") alert( Number(true) ); // 1 alert( Number(false) ); // 0 ``` Tolong diingat bahwa kelakuan `null` dan `undefined` berbeda di sini: `null` menjadi nol namun `undefined` menjadi `NaN`. Hampir semua operasi matematik melakukan konversi semacam ini, yang akan kita lihat di bab berikutnya. ## Konversi Boolean Konversi boolean ialah yang paling simpel. Ini terjadi dalam operasi logika (nanti kita akan menemui tes kondisi dan hal mirp lainnya) tapi bisa juga berjalan explisit dengan panggilan ke `Boolean(value)`. Aturan konversi: - Nilai yang secara intuitif "kosong", seperti `0`, string kosong, `null`, `undefined`, dan `NaN`, menjadi `false`. - Nilai lainnya menjadi `true`. Misalnya: ```js run alert( Boolean(1) ); // true alert( Boolean(0) ); // false alert( Boolean("hello") ); // true alert( Boolean("") ); // false ``` ````warn header="Please note: the string with zero `\"0\"` is `true`" Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript, a non-empty string is always `true`. ```js run alert( Boolean("0") ); // true alert( Boolean(" ") ); // spaces, also true (any non-empty string is true) ``` ```` ## Kesimpulan Tiga tipe konversi yang paling digunakan ialah ke string, ke angka, dan ke boolean. **`Konversi String`** -- Terjadi ketika kita mengoutput sesuatu. Bisa berjalan dengan `String(value)`. Konversi ke string biasanya untuk nilai primitif. **`Konversi Numerik`** -- Terjadi di operasi matematika. Bisa berjalan dengan `Number(value)`. Konversinya mengikuti aturan ini: | Value | Becomes... | |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | | `string` | Stringnya dibaca "apa adanya", whitespace dari kedua sisi diabaikan. String kosong menjadi `0`. Error memberikan `NaN`. | **`Konversi Boolean`** -- Terjadi di operasi logika. Bisa berjalan dengan `Boolean(value)`. Ikuti aturan ini: | Value | Becomes... | |-------|-------------| |`0`, `null`, `undefined`, `NaN`, `""` |`false`| |nilai lain apapun| `true` | Kebanyakan aturan ini mudah dipahami dan diingat. Pengecualian penting di mana orang biasanya membuat kesalahan yaitu: - `undefined` ialah `NaN` sebagai angka, buka `0`. - `"0"` dan string yang cuma-spasi seperti `" "` ialah true sebagai boolean. Objek tidak dicover di sini. Kita akan kembali ke mereka nanti di bab yang khusus exclusif untuk objeck setelah kita belajar hal-hal lebih dasar tentang JavaScript. ================================================ FILE: 1-js/02-first-steps/08-operators/1-increment-order/solution.md ================================================ Jawabannya adalah: - `a = 2` - `b = 2` - `c = 2` - `d = 1` ```js run no-beautify let a = 1, b = 1; alert( ++a ); // 2, bentuk prefix mengembalikan nilainya alert( b++ ); // 1, bentuk postfix mengembalikan nilai lamanya alert( a ); // 2, diinkremen sekali alert( b ); // 2, diinkremen sekali ``` ================================================ FILE: 1-js/02-first-steps/08-operators/1-increment-order/task.md ================================================ nilai penting: 5 --- # Bentuk postfix dan prefix Berapa nilai final dari semua variabel `a`, `b`, `c` dan `d` setelah kode berikut? ```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 ================================================ Jawabannya adalah: - `a = 4` (dikali 2) - `x = 5` (dikalkulasi sebagai 1 + 4) ================================================ FILE: 1-js/02-first-steps/08-operators/2-assignment-result/task.md ================================================ nilai penting: 3 --- # Hasil penetapan Berapa nilai dari `a` dan `x` setelah kode berikut? ```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. Penambahan dengan string `"" + 1` mengkonversi `1` ke string: `"" + 1 = "1"`, dan kita punya `"1" + 0`, aturan yang sama berlaku. 2. Pengurangan `-` (seperti kebanyakan operasi matematika) cuma berjalan dengan angka, ia mengkonversi string kosong `""` ke `0`. 3. Penambahan dengan string mengappend angka `5` ke string. 4. Pengurangan selalu mengkonversi ke angka, jadi ia membuat `" -9 "` menjadi angka `-9` (mengabaikan spasi sekitarnya). 5. `null` menjadi `0` setelah konversi numerik. 6. `undefined` menjadi `NaN` setelah konversi numerik. 7. Karakter spasi, ialah string yang depan dan belakangnya ditrim ketika string dikonversi ke angka. Berikut seluruh string berisi karakter spasi, seperti `\t`, `\n` dan spasi "reguler" di antaranya. Jadi, serupa dengan string kosong, ia menjadi `0`. ================================================ FILE: 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md ================================================ nilai penting: 5 --- # Konversi tipe Apa hasil dari expresi ini? ```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 ``` Pikirkan dengan baik, tulis dan bandingkan dengan jawaban. ================================================ FILE: 1-js/02-first-steps/08-operators/4-fix-prompt/solution.md ================================================ Alasannya adalah karena kotak *prompt* mengembalikan inputan dari user sebagai string. Jadi nilai dari masing-masing variabel adalah `"1"` dan `"2"`. ```js run let a = "1"; // prompt("Angka pertama?", 1); let b = "2"; // prompt("Angka kedua?", 2); alert(a + b); // 12 ``` Apa yang kita harus lakukan untuk mengubah string menjadi angka sebelum `+`, gunakan `Number()` atau menambahkannya dengan `+`. Contoh, tepat sebelum `prompt`: ```js run let a = +prompt("Angka pertama?", 1); let b = +prompt("Angka kedua?", 2); alert(a + b); // 3 ``` Atau didalam `alert`: ```js run let a = prompt("Angka pertama?", 1); let b = prompt("Angka kedua?", 2); alert(+a + +b); // 3 ``` Menggunakan *unary* dan *binary* `+` dicontoh kode terakhir terlihat lucu, kan? ================================================ FILE: 1-js/02-first-steps/08-operators/4-fix-prompt/task.md ================================================ nilai penting: 5 --- # Benarkan penambahan Ini adalah kode yang menanyakan pengguna untuk memasukan dua angka dan menampilkan jumlahnya. Kodenya tidak berjalan dengan semestinya. Keluaran dari contoh kode dibawah adalah `12` (nilai dimasukan secara default didalam kotak prompt). Kenapa? Benarkan. Hasilnya haruslah `3`. ```js run let a = prompt("Angka pertama?", 1); let b = prompt("Angka kedua?", 2); alert(a + b); // 12 ``` ================================================ FILE: 1-js/02-first-steps/08-operators/article.md ================================================ # Operator dasar, maths Kita tahu banyak operator dari sekolah. Mereka adalah penambahan `+`, perkalian `*`, pengurangan `-`, dll. Didalam bagian ini, kita akan memulai dengan menggunakan operator sederhana, lalu kita akan fokus pada aspek khusus Javascript, yang tidak dipelajari pada aritmatika di sekolah. ## Istilah: "unary", "binary", "operand" Sebelum kita lanjut, mari pahami dulu terminologi umum. - *Operand* -- untuk apa operator diaplikasikan. Misalnya, dalam perkalian `5 * 2` ada dua operand: operand kiri `5` dan operand kanan `2`. Kadang, orang memanggil ini "argumen" ketimbang "operand". - Operator disebut *unary* jika ia punya operand tunggal. Misalnya, negasi unary `-` membalikkan tanda dari angka: ```js run let x = 1; *!* x = -x; */!* alert( x ); // -1, negasi unary diaplikasikan ``` - Operator disebut *binary* jika ia punya dua operand. Minus yang sama juga berada dalam bentuk binary: ```js run no-beautify let x = 1, y = 3; alert( y - x ); // 2, minus binary mengurangi nilai ``` Formalnya, di contoh di atas kita punya dua operator berbeda yang berbagi simbol yang sama: operator negasi, operator unary yang membalik tanda, dan operator pengurangan, operator biner yang mengurangi angka satu dengan lainnya. ## Maths Operasi matematika dibawah telah didukung didalam Javascript: - Penambahan `+`, - Pengurangan `-`, - Perkalian `*`, - Pembagian `/`, - Sisa Bagi `%`, - Eksponensial `**`. Keempat operasi pertama sudah cukup jelas, sementara `%` dan `**` membutuhkan lebih banyak kata-kata untuk dijelaskan. ### Sisa bagi % Operator sisa bagi `%` sebagaimana kelihatannya, tidak berhubungan dengan persen. Hasil dari `a % b` adalah [nilai sisa](https://en.wikipedia.org/wiki/Remainder) dai pembagian antara `a` oleh `b`. Contoh ```js run alert( 5 % 2 ); // 1, sisa dari pembagian antara 5 dibagi 2 alert( 8 % 3 ); // 2, sisa dari pembagian antara 8 dibagi 3 ``` ### Eksponensial ** Operator eksponensial `a ** b` mengkalikan `a` dengan nilai itu sendiri sebanyak `b` kali. Dalam matematika sekolah, kita menuliskannya sebagaib. alert( 2 ** 2 ); // 2² = 4 alert( 2 ** 3 ); // 2³ = 8 alert( 2 ** 4 ); // 2⁴ = 16 ``` Sama seperti dalam matematika, operator eksponensial juga didefinisikan untuk bilangan non-bilangan bulat. Misalnya, akar kuadrat adalah eksponensial dengan : ```js run alert( 4 ** (1/2) ); // 2 (pangkat 1/2 sama dengan akar kuadrat) alert( 8 ** (1/3) ); // 2 (pangkat 1/3 sama dengan akar kubik) ``` ## Penambahan string dengan + Ayo kita bertemu dengan fitur dari operator Javascript yang berada diatas aritmatika di sekolah. Biasanya, operator plus `+` menambah angka. Tapi, jika binary `+` diaplikasikan ke string, ia menggabungkan (konkatenasi) mereka: ```js let s = "my" + "string"; alert(s); // mystring ``` Ingat bahwa jika salah satu operand berupa string, maka yang satunya dikonversi ke string juga. Misalnya: ```js run alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` Lihat, tak masalah operand manapun yang berupa string. Aturannya simpel: jika salah satu operand adalah string, maka yang satunya dikonversi ke string juga. Namun, ingat bahwa operasi berjalan dari kiri ke kanan. Jika ada dua angka diikuti string, angka akan ditambah sebelum dikonversi ke string: Ini adalah contoh yang lebih rumit: ```js run alert(2 + 2 + '1' ); // "41" dan bukan "221" ``` Disini, operator bekerja secara bergantian. Pertama `+` menambahkan dua angka, jadi akan menghasilkan `4`, lalu selanjutnya `+` menambahkan string `1` kedalamnya, jadi akan menjadi seperti `4 + '1' = 41`. ```js run alert('1' + 2 + 2); // "122" dan bukan "14" ``` disini, bilangan pertama adalah string, kompiler memperlakukan dua bilangan lainnya sebagai string juga. Bilangan `2` ditambahkan dengan `1`, jadi `'1' + 2 = "12"` dan `"12" + 2 = "122"`. Operator `+` hanyalah satu-satunya operator yang mendukung penggunaan string dengan cara semacan itu. Operator aritmatika lainnya hanya bekerja dengan angka dan selalu mengubah operannya menjadi angka. Ini adalah contoh untuk pengurangan dan pembagian: ```js run alert( 6 - '2' ); // 4, mengubah '2' menjadi angka alert( '6' / '2' ); // 3, mengubah keduanya menjadi angka ``` ## Konversi angka, unary + Plus `+` ada dalam dua bentuk: bentuk binary yang kita gunakan di atas dan bentuk unary. Plus unary atau, dalam kata lain, operator plus `+` diaplikasikan ke nilai tunggal, tak berefek apapun ke angka. Tapi jika operand bukan angka, plus unary dikonversi ke dalam angka. Misalnya: ```js run // Tak ada efek ke angka let x = 1; alert( +x ); // 1 let y = -2; alert( +y ); // -2 *!* // Mengkonversi non-angka alert( +true ); // 1 alert( +"" ); // 0 */!* ``` Sebenarnya ia melakukan hal yang sama seperti `Number(...)`, tapi lebih pendek. Kebutuhan mengkonversi string ke angka sangat sering meningkat. Misalnya, jika kita memperoleh nilai dari kolom di form HTML, mereka biasanya string. Bagaimana jika kita ingin menjumlahkan mereka? Plus binary akan menambah mereka sebagai string: ```js run let apples = "2"; let oranges = "3"; alert( apples + oranges ); // "23", plus binary mengkonkatenasi string ``` Jika kita ingin memperlakukan mereka sebagai angka, kita harus mengkonversi, lalu menjumlahkan mereka: ```js run let apples = "2"; let oranges = "3"; *!* // kedua nilai dikonversi ke angka sebelum plus binary alert( +apples + +oranges ); // 5 */!* // varian lebih panjang // alert( Number(apples) + Number(oranges) ); // 5 ``` Dari sisi pandang matematikawan, melimpahnya plus terlihat aneh. Tapi dari sisi pandang programmer, tak ada yang spesial: plus unary diaplikasikan dahulu, lalu mengkonversi string ke angka, dan lalu binary plus menjumlahkan mereka. Kenapa plus unary diaplikasi ke nilai sebelum binarynya? Seperti yang kita lihat, itu karena *peresedensi lebih tinggi* mereka. ## Presedensi operator Jika expresi punya lebih dari satu operator, urutan eksekusi ditentukan oleh *presedensi* mereka, atau dengan kata lain, urutan prioritas default operator. Dari sekolah, kita semua tahu bahwa perkalian dalam expresi `1 + 2 * 2` harus dihitung sebelum penambahan. Itulah arti dari presedensi. Perkalian disebut memiliki *presedensi lebih tinggi* dari penambahan. Tanda kurung mengesampingkan presedensi apapun, jadi jika kita tak puas dengan urutan default, kita bisa gunakan mereka untuk mengubahnya. Misalnya: tulis `(1 + 2) * 2`. Ada banyak operator di JavaScript. Tiap operator punya nomor presedensi masing-masing. Nomor yang lebih besar dieksekusi terlebih dahulu. Jika presedensinya sama, urutan eksekusinya dari kiri ke kanan. Di sini adalah extrak dari [tabel presedensi](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (kamu tak usah mengingat ini, tapi catat bahwa operator unary lebih tinggi dari binary terkait): | Presedensi | Nama | Tanda | |------------|------|------| | ... | ... | ... | | 17 | plus unary | `+` | | 17 | negasi unary | `-` | | 16 | akar pangkat | `**` | | 15 | perkalian | `*` | | 15 | pembagian | `/` | | 13 | penambahan | `+` | | 13 | pengurangan | `-` | | ... | ... | ... | | 3 | penetapan | `=` | | ... | ... | ... | Seperti yang kita lihat, "plus unary" punya prioritas `17` yang lebih tinggi dari `13` "penambahan" (plus binary). Itulah kenapa, dalam expresi `"+apples + +oranges"`, plus unary bekerja sebelum penambahan. ## Penetapan Mari ingat bahwa penetapan `=` juga merupakan operator. Ia terdaftar di tabel presedensi dengan prioritas sangat rendah `3`. Itulah kenapa, ketika kita tetapkan variabel, seperti `x = 2 * 2 + 1`, kalkulasinya dilakukan pertama dan kemudian `=` dievaluasi, menyimpan hasilnya dalam in `x`. ```js let x = 2 * 2 + 1; alert( x ); // 5 ``` ### Assignment = mengembalikan nilai Fakta dari `=` menjadi sebuah operator, bukanlah sebuah hal yang "fantastis" konstruksi dari bahasa memiliki implikasi yang menarik. Kebanyakan operator di Javascript mengembalikan sebuah nilai. Sudah jelas untuk `+` dan `-`, tetapi berlaku juga untuk `=`. Panggilan `x = value` menulis `value` ke dalam `x` *dan mengembalikannya*. Ini adalah demo yang menggunakan penetapan sebagai bagian dari expresi yang rumit: ```js run let a = 1; let b = 2; *!* let c = 3 - (a = b + 1); */!* alert( a ); // 3 alert( c ); // 0 ``` Di contoh di atas, hasil dari expresi `(a = b + 1)` ialah nilai yang ditetapkan ke `a` (yaitu `3`). Ia kemudian digunakan untuk evaluasi berikutnya. Kodenya lucu, kan? kita harus mengerti bagaimana itu bekerja, karena terkadang kita melihat hal itu didalam library Javascript. Dan juga, tolong jangan tulis kode seperti itu. Trik semacam itu tidak akan membuat kode menjadi jelas dan mudah dibaca. ### Chaining assignments / Assignments berantai Fitur menarik lainnya adalah kemampuan untuk melakukan assignments berantai: ```js run let a, b, c; *!* a = b = c = 2 + 2; */!* alert( a ); // 4 alert( b ); // 4 alert( c ); // 4 ``` Assignments berantai mengevaluasi dari kanan ke kiri. Pertama, dari ekspresi paling kanan `2 + 2` di evaluasi terlebih dahulu dan dimasukan kedalam variabel disebelah kiri: `c`, `b` dan `a`. Dan diakhir, seluruh variabel saling membagi satu nilai. Sekali lagi, untuk tujuan kode yang mudak dibaca akan lebih baik untuk membagi kode kedalam beberapa baris: ```js c = 2 + 2; b = c; a = c; ``` Ini akan mudah untuk dibaca, terutama jika melihatnya secara sekilas. ## Mengubah variabel secara langsung Kita terkadang membutuhkan sebuah operator untuk sebuah variabel dan menyimpan hasil baru didalam variabel yang sama Contoh: ```js let n = 2; n = n + 5; n = n * 2; ``` Notasi ini bisa diperpendek dengan menggunakan operator `+=` dan `*=`: ```js run let n = 2; n += 5; // sekarang n = 7 (sama dengan n = n + 5) n *= 2; // sekarang n = 14 (sama dengan n = n * 2) alert( n ); // 14 ``` Operator dari "ubah-dan-simpan" atau bisa disebut mengubah variabel secara langsung hadir pada setiap aritmatik dan operator bitwise: `/=`, `-=`, etc. Operator semacam itu memiliki hak dengan tingkat yang sama dengan assignment yang biasa, jadi mereka akan berjalan setelah kalkulasi lainnya selesai: ```js run let n = 2; n *= 3 + 5; alert( n ); // 16 (bagian paling kanan dievaluasi terlebih dahulu, sama seperti n *= 8) ``` ## Inkremen/dekremen Menaikkan atau menurunkan satu angka ialah salah satu operasi numerik paling umum. Jadi, ada operator spesial untuk itu: - **Inkremen** `++` menaikkan variabel sebanyak 1: ```js run no-beautify let counter = 2; counter++; // cara kerjanya sama dengan counter = counter + 1, tapi lebih pendek alert( counter ); // 3 ``` - **Decrement** `--` menurunkan variabel sebanyak 1: ```js run no-beautify let counter = 2; counter--; // cara kerjanya sama dengan counter = counter - 1,tapi lebih pendek alert( counter ); // 1 ``` ```warn Inkremen/dekremen cuma bisa diaplikasikan ke variabel. Mencoba menggunakan itu pada nilai seperti `5++` akan menghasilkan galat. ​``` Operator `++` dan `--` bisa ditaruh sebelum atau setelah variabel. - Ketika operatornya ditaruh setelah variabel, ia ada dalam "bentuk postfix": `counter++`. - "Bentuk prefix" ialah ketika operatornya ditaruh sebelum variabel: `++counter`. Kedua pernyataan ini melakukan hal yang sama: menambah `counter` sebanyak `1`. Apakah ada perbedaan? Ya, tapi kita cuma bisa melihatnya jika kita menggunakan nilai kembalian `++/--`. Mari kita klarifikasi. Seperti yang kita tahu, semua operator mengembalikan nilai. Inkremen/dekremen bukan pengecualian. Bentuk prefix mengembalikan nilai baru sedangkan bentuk postfix mengembalikan nilai lama (sebelum inkremen/dekremen). Untuk melihat perbedaannya, berikut misalnya: ```js run let counter = 1; let a = ++counter; // (*) alert(a); // *!*2*/!* ​``` Dalam baris `(*)`, bentuk *prefix*`++counter` menginkremen `counter` dan mengembalikan nilai baru, `2`. Jadi, `alert` menampilkan `2`. Sekarang, mari kita gunakan bentuk postfix: ```js run let counter = 1; let a = counter++; // (*) ganti ++counter ke counter++ alert(a); // *!*1*/!* ​``` Dalam baris `(*)`, bentuk *postfix* `counter++` juga menginkremen `counter` tapi mengembalikan nilai *lama* (sebelum inkremen). Jadi, `alert` menampilkan `1`. Ringkasnya: - Jika hasil dari inkremen/dekremen tak digunakan, tak ada bedanya bentuk mana yang dipakai: ```js run let counter = 0; counter++; ++counter; alert( counter ); // 2, kedua counter diatas melakukan hal yang serupa. ``` - Jika kita ingin menaikkan nilai *dan* langsung memakai hasil dari operator, kita butuh bentuk prefix: ```js run let counter = 0; alert( ++counter ); // 1 ``` - Jika kita ingin menginkremen suatu nilai tanpa memakai nilai sebelumnya, kita butuh bentuk postfix: ```js run let counter = 0; alert( counter++ ); // 0 ``` ````smart header="Inkremen/dekremen di antara operator lainnya" Operator `++/--` bisa juga digunakan di dalam expresi. Presedensi mereka lebih tinggi dari kebanyakan operasi aritmatika lainnya. Misalnya: ```js run let counter = 1; alert( 2 * ++counter ); // 4 ​``` Bandingkan dengan: ```js run let counter = 1; alert( 2 * counter++ ); // 2, karena counter++ mengembalikan nilai "lama" ​``` Meski secara teknis OK, notasi macam ini biasanya membuat kode kurang dapat dibaca. Satu baris melakukan banyak hal -- tak baik. Sambil membaca kode, dan melihatnya secara sekilas kita bisa saja melewatkan kode seperti `counter++` dan tidak akan jelas bahwa nilai variabel telah bertambah. Jadi direkomendasikan menuliskan kode dengan gaya "satu baris -- satu aksi": ```js run let counter = 1; alert( 2 * counter ); counter++; ​``` ```` ## Operator bitwise Operator bitwise memperlakukan argumen sebagai angka integer 32-bit dan bekerja pada level representasi biner mereka. Operator ini bukan spesifik JavaScript. Mereka didukung di banyak bahasa pemrograman. Daftar operator: - AND ( `&` ) - OR ( `|` ) - XOR ( `^` ) - NOT ( `~` ) - LEFT SHIFT ( `<<` ) - RIGHT SHIFT ( `>>` ) - ZERO-FILL RIGHT SHIFT ( `>>>` ) Operator seperti diatas sangat jarang digunakan, ketika kita membutuhkan untuk memainkan angka di level paling rendah (bitwise). Kita tidak akan membutuhkan operator seperti ini dalam waktu dekat, sebagaimana dalam pengembangan web penggunaan operator seperti itu lebih sedikit, tetapi di area yang spesial, seperti kriptographi, operator seperti itu sangan dibutuhkan. Kamu bisa membaca artikel [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) di MDN ketika kamu membutuhkannya. ## Koma Operator koma `,` ialah satu dari banyak operator paling langka dan tak biasa. Kadang, ia digunakan untuk menulis kode lebih pendek, jadi kita harus tahu itu untuk memahami apa yang terjadi. Operator koma memperbolehkan untuk mengevaluasi beberapa expresi, membagi mereka dengan koma `,`. Each of them is evaluated but only the result of the last one is returned. Misalnya: ```js run *!* let a = (1 + 2, 3 + 4); */!* alert( a ); // 7 (hasil dari 3 + 4) ``` Di sini, expresi pertama `1 + 2` dievaluasi dan hasilnya dibuang. Lalu, `3 + 4` dievaluasi dan dikembalikan sebagai hasilnya. ```smart header="Koma punya presedensi sangat kecil" Harap ingat bahwa operator koma punya presedensi sangat kecil, lebih kecil dari `=`, jadi tanda kurung penting dalam contoh di atas. Tanpa mereka: `a = 1 + 2, 3 + 4` mengevaluasi `+` terlebih dahulu, menjumlahkan mereka menjadi `a = 3, 7`, lalu operator penetapan `=` menetapkan `a = 3`, dan sisanya diabaikan. Ini seperti `(a = 1 + 2), 3 + 4`. ``` Kenapa kita butuh operator yang membuang semuanya kecuali expresi terakhir? Kadang, orang memakai itu dalam konstruksi rumit untuk menaruh beberapa aksi dalam satu baris. Misalnya: ```js // tiga operasi dalam satu baris for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { ... } ``` Trik macam ini dipakai di banyak framework JavaScript. Itulah kenapa kita membahas mereka. Tapi, biasanya, mereka tak membuat kode mudah dibaca sehingga kita sebaiknya pikir-pikir dulu sebelum menggunakan mereka. ================================================ 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 ``` Beberapa alasan: 1. Sudah jelas, true. 2. Pembandingan kamus, jadi false. `"a"` lebih kecil dari `"p"`. 3. Lagi, pembandingan kamus, karakter pertama `"2"` lebih besar dari karakter pertama `"1"`. 4. Nilai `null` dan `undefined` selalu bernilai sama. 5. Equalitas ketat memang ketat. Tipe berbeda dari kedua sisi menghasilkan false. 6. Serupa dengan `(4)`, `null` hanya sama dengan `undefined`. 7. Equalitas ketat dari tipe berbeda. ================================================ FILE: 1-js/02-first-steps/09-comparison/1-comparison-questions/task.md ================================================ nilai penting: 5 --- # Pembandingan Apa hasil dari expresi ini? ```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 ================================================ # Perbandingan Kita tahu beberapa operator pembanding dari matematika. Didalam Javascript operator-operator itu ditulis seperi ini: - Lebih besar/kurang dari: a > b, a < b. - Lebih besar/kurang dari atau sama: a >= b, a <= b. - Sama dengan: `a == b`, perhatikan tanda dua `=` digunakan untuk test persamaan, jika menggunakan satu `=` seperti `a = b` itu adalah sebuah asignment atau memasukan nilai kedalam variabel. - Tidak sama dengan: Didalam matematika notasinya seperti , tetapi didalam Javascript ditulis seperti a != b. Didalam artikel ini kita akan belajar lebih lanjut tentang perbedaan tipe dari perbandingan, bagaimana cara Javascript membuatnya, termasuk sifat-sifat penting. Diakhir nanti kamu akan menemukan hal yang bagus untuk menghindari masalah yang berhubungan dengan "kebiasaan Javascript". ## Boolean ialah hasilnya Semua operator pembanding mengembalikan nilai boolean: - `true` -- berarti "ya", "betul" atau "fakta". - `false` -- berarti "tidak", "salah" atau "bukan fakta". Misalnya: ```js run alert( 2 > 1 ); // true (benar) alert( 2 == 1 ); // false (salah) alert( 2 != 1 ); // true (benar) ``` Hasil perbandingan bisa ditetapkan ke variabel, sama seperti nilainya: ```js run let result = 5 > 4; // tetapkan hasil perbandingan alert( result ); // true ``` ## Perbandingan string Untuk melihat apakah satu string lebih besar dari yang lain, JavaScript menggunakan yang disebut "kamus" atau urutan "lexicografik". Dengan keta lain, string diperbandingkan huruf-demi-huruf. Misalnya: ```js run alert( 'Z' > 'A' ); // true alert( 'Glow' > 'Glee' ); // true alert( 'Bee' > 'Be' ); // true ``` algoritma untuk membandingkan dua string sederhana: 1. Bandingkan karakter pertama dari kedua string. 2. Jika karakter pertama dari string pertama lebih besar (atau lebih kecil) dari string kedua, maka string pertama lebih besar (atau lebih kecil) dari string kedua. Kita selesai. 3. Sebaliknya, jika karakter pertama kedua string sama, bandingkan karakter kedua dengan cara sama. 4. Ulangi sampai string berakhir. 5. Jika kedua string berakhir pada panjang yang sama, maka mereka sama. Sebaliknya, string lebih panjang yang lebih besar. Pada contoh di atas, pembandingan `'Z' > 'A'` menghasilkan pada langkah pertama sedangkan string `"Glow"` dan `"Glee"` dibandingkan karakter-demi-karakter: 1. `G` sama dengan `G`. 2. `l` sama dengan `l`. 3. `o` lebih besar dari `e`. Berhenti di sini. String pertama lebih besar. ```smart header="Bukan dictionary riil, tapi urutan Unicode" Algoritma pembangingan yang diberikan di atas secara kasar equivalen dengan yang digunakan dalam kamus atau buku telpon, tapi tak sepenuhnya sama. Misalnya, case diperhitungkan. Huruf besar `"A"` tak sama dengan huruf kecil `"a"`. Yang mana yang lebih besar? Huruf kecil `"a"`. Kenapa? Karena karakter huruf kecil punya index lebih besar dalam tabel encoding internal yang dipakai JavaScript (Unicode). Kita akan kembali ke detil spesifik dan konsekuensinya dalam bab . ``` ## Pembandingan dari tipe yang berbeda Ketika membandingkan nilai dari tipe yang berbeda, JavaScript mengkonversi nilai tersebut ke angka. Misalnya: ```js run alert( '2' > 1 ); // true, string '2' menjadi angka 2 alert( '01' == 1 ); // true, string '01' menjadi angka 1 ``` Untuk nilai boolean, `true` menjadi `1` dan `false` menjadi `0`. Misalnya: ```js run alert( true == 1 ); // true alert( false == 0 ); // true ``` ````smart header="Konsekuensi lucu" Memungkinkan juga bahwa pada saat yang sama: - Dua nilai equal. - Satu dari mereka `true` sebagai boolean dan satunya lagi `false` sebagai boolean. Misalnya: ```js run let a = 0; alert( Boolean(a) ); // false let b = "0"; alert( Boolean(b) ); // true alert(a == b); // true! ``` Dari cara pandang JavaScript', hasil ini terbilang normal. Pengecekan equalitas mengkonversi nilai menggunakan konversi numerik (jadi `"0"` menjadi `0`), sedangkan konversi explisit `Boolean` menggunakan set aturan yang lain. ```` ## Equalitas ketat Pengecekan equalitas reguler `==` punya satu problem. Ia tak bisa membedakan `0` dari `false`: ```js run alert( 0 == false ); // true ``` Hal yang sama terjadi pada string kosong: ```js run alert( '' == false ); // true ``` Ini terjadi karena operand dari tipe yang berbeda dikonversi ke angka oleh operator equalitas `==`. String kosong, sama seperti `false`, menjadi nol. Apa yang dilakukan jika kita ingin membedakan `0` dari `false`? **Operator equalitas ketat `===` mengecek equalitas tanpa konversi tipe.** Dengan kata lain, jika `a` dan `b` tipenya berbeda, maka `a === b` segera menghasilkan `false` tanpa konversi apapun terhadap mereka. Mari kita coba: ```js run alert( 0 === false ); // false, karena tipenya berbeda ``` Ada juga operator "non-equalitas ketat" `!==` yang analog dengan `!=`. Operator equalitas ketat sedikit lebih panjang untuk ditulis, tapi lebih memperlihatkan apa yang terjadi dan meninggalkan ruang lebih kecil untuk galat. ## Pembandingan dengan null dan undefined Ada sikap non-intuitif ketika `null` atau `undefined` diperbandingkan dengan nilai lain. Untuk pengecekan equalitas ketat `===` : Nilai ini berbeda, karena setiap dari mereka tipenya berbeda. ```js run alert( null === undefined ); // false ``` Untuk pengecekan non-ketat `==` : Ada aturan spesial. Kedyanya merupakan "pasangan manis": mereka sama (karena `==`), tapi tidak dengan nilai lain manapun. ```js run alert( null == undefined ); // true ``` Untuk pembandingan matematika dan lainnya `< > <= >=` : `null/undefined` dikonversi ke angka: `null` menjadi `0`, sedangkan `undefined` menjadi `NaN`. Sekarang mari kita lihat beberapa hal lucu yang terjadi ketika kita menerapkan aturan ini. Dan, yang lebih penting, bagaimana supaya tidak jatuh ke dalam perangkap ini. ### Hasil aneh: null vs 0 Mari kita bandingkan `null` dengan nol: ```js run alert( null > 0 ); // (1) false alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) *!*true*/!* ``` Secara matematik, ini aneh. Hasil terakhir menyatakan bahwa "`null` lebih besar atau sama dengan nol", seharusnya salah satu dari pembandingan di atasnya `true`, tapi nyatanya malah tidak. Alasannya ialah pengecekan equalitas `==` dan pembandingan `> < >= <=` bekerja secara berbeda. Pembandingan mengkonversi `null` ke angka `0`. Itulah kenapa (3) `null >= 0` true dan (1) `null > 0` false. Di sisi lain, pengecekan equalitas `==` untuk `undefined` and `null` dijelaskan bahwa, tanpa konversi apapun, mereka sama dengan satu sama lain dan tidak sama dengan nilai lain manapun. Itulah kenapa (2) `null == 0` is false. ### Uundefined yang tak bisa diperbandingkan Nilai `undefined` sebainya tak diperbandingkan dengan nilai lain: ```js run alert( undefined > 0 ); // false (1) alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3) ``` Kenapa ia tak suka sekali dengan nol? Selalu false! Kita dapatkan hasil ini karena: - Pembandingan `(1)` dan `(2)` menghasilkan `false` karena `undefined` dikonversi ke `NaN` dan `NaN` merupakan numerik spesial yang mengembalikan `false` untuk semua pembandingan. - Pengecekan equalitas `(3)` mengembalikan `false` karena `undefined` hanya sama dengan `null` dan tidak dengan nilai lain manapun. ### Hindari problem Kenapa kita menggunakan contoh ini? Haruskah kita ingat semua keanehan ini? Tidak juga. Sebenarnya, hal tricky macam ini akan menjadi akrab seiring waktu, tapi ada cara solid untuk menghindari masalah dengan mereka: Perlakukan pembandingan manapun dengan `undefined/null` kecuali equalitas ketat `===` dengan hati-hati. Jangan gunakan pembandingan `>= > < <=` dengan variabel yang bisa jadi `null/undefined`, kecuali kamu paham apa yang kamu lakukan. Jika variabel bisa punya nilai ini, cek mereka secara terpisah. ## Kesimpulan - Operator pembandingan menghasilkan nilai boolean. - String dibandingkan huruf-demi-huruf dalam urutan "kamus". - Ketika nilai dari tipe berbeda diperbandingkan, mereka dikonversi ke angka (kecuali pengecekan equalitas ketat). - Nilai `null` dan `undefined` sama dengan `==` satu sama lain dan tidak sama dengan nilai lain manapun. - Waspada ketika menggunakan pembandingan seperti `>` atau `<` dengan variabel yang kadang bisa jadi `null/undefined`. Pengecekan secara terpisah `null/undefined` merupakan ide yang bagus. ================================================ FILE: 1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md ================================================ **Ya, tentu saja.** Setiap string kecuali yang kosong (ingat bahwa `" 0 "` tidak kosong) menjadi `true` dalam konteks logika boolean. Kita dapat mengeksekusi dan mengecek kode di bawah ini : ```js run if ("0") { alert( 'Hello' ); } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md ================================================ Nilai penting: 5 --- # if (string berisi angka nol) Apakah `alert` akan dieksekusi? ```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 ================================================ Nilai penting: 2 --- # Nama JavaScript Dengan menggunakan konstruksi `if..else`, tulis kode yang menanyakan: 'Apa nama "official" JavaScript?' Jika pengunjung memasukkan "ECMAScript", maka tunjukkan output "Benar!", Jika tidak - tunjukkan output: "Tidak tahu? 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('Masukan sebuah angka', 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 ================================================ Nilai penting: 2 --- # Tunjukkan tandanya Dengan menggunakan `if..else`, tulis kode yang mendapatkan nomor melalui` prompt` dan kemudian tampilkan nomornya dengen menggunakan `alert`: - `1`, jika nilainya lebih besar dari nol, - `-1`, jika kurang dari nol, - `0`, jika sama dengan nol. Dalam tugas ini kita mengasumsikan bahwa input selalu berupa angka. [demo src="if_sign"] ================================================ FILE: 1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md ================================================ ```js let result = (a + b < 4) ? 'Kurang' : 'Lebih'; ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md ================================================ Nilai penting: 5 --- # Tulis ulang 'if' menggunakan '?' Tulis ulang `if` menggunakan operator kondisional `'?'`: ```js let result; if (a + b < 4) { result = 'Kurang'; } else { result = 'Lebih'; } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/solution.md ================================================ ```js let message = (login == 'Karyawan') ? 'Hallo' : (login == 'Direksi') ? 'Salam Hangat!' : (login == '') ? 'Belum Login' : ''; ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md ================================================ Nilai penting: 5 --- # Tulis ulang 'if..else' menjadi '?' Tulis ulang `if..else` menggunakan beberapa operator ternary` '?' `. Agar mudah dibaca, disarankan untuk membagi kode menjadi beberapa baris. ```js let message; if (login == 'Karyawan') { message = 'Hallo'; } else if (login == 'Direksi') { message = 'Salam Hangat!'; } else if (login == '') { message = 'Belum Login'; } else { message = ''; } ``` ================================================ FILE: 1-js/02-first-steps/10-ifelse/article.md ================================================ # Kondisi bercabang: if, '?' Terkadang, kita perlu melakukan tindakan berbeda berdasarkan kondisi yang berbeda. Untuk melakukan itu, kita dapat menggunakan pernyataan `if` dan operator kondisional `?`, yang juga merupakan "question mark" operator (operator tanda tanya). ## Pernyataan "if" Pernyataan `if` mengevaluasi suatu kondisi dan, jika hasil kondisi itu `true`, maka blok kode di dalam `if` akan diexekusi. Sebagai contoh: ```js run let tahun = prompt('Ditahun berapa spesifikasi ECMAScript-2015 dipublikasikan?', ''); *!* if (tahun == 2015) alert( 'Kamu Benar!' ); */!* ``` Pada contoh di atas, persyaratannya adalah pemeriksaan kesetaraan sederhana (`tahun == 2015`), tetapi pada kode lain, bisa menjadi jauh lebih kompleks. Jika kita ingin menjalankan lebih dari satu pernyataan di dalam suatu kondisional blok kode, kita harus membungkus blok kode kita di dalam kurung kurawal {} : ```js if (tahun == 2015) { alert( "Kamu Benar!" ); alert( "Kamu Memang Pintar!" ); } ``` Disarankan untuk membungkus blok kode Anda dengan kurung kurawal `{}` setiap kali Anda menggunakan pernyataan `if`, bahkan jika hanya ada satu pernyataan di dalam suatu blok kode. Hal ini agar kode anda mudah dibaca oleh anda di masa depan,dan tentu saja oleh orang lain. ## Konversi Boolean Pernyataan `if (…)` mengevaluasi ekspresi dalam tanda kurung dan mengubah hasilnya menjadi boolean. Mari kita mengingat kembali aturan konversi dari bab : - Angka `0`, string kosong `""`, `null`, `undefined`, dan `NaN` semuanya menjadi salah (`false`). Oleh sebab itu,mereka disebut nilai-nilai "falsy" (palsu/salah) - Nilai-nilai yang lain menjadi benar (`true`), sehingga kadang mereka disebut "truthy" (benar) Jadi, kode dalam kondisi ini tidak akan pernah berjalan: ```js if (0) { // 0 adalah falsy ... } ``` ... dan di dalam kondisi ini - selalu akan berjalan: ```js if (1) { // 1 adalah truthy ... } ``` Kamu juga dapat memberikan nilai boolean yang telah dievaluasi ke `if`, seperti ini:: ```js let kondisi = (tahun == 2015); // mengevaluasi nilai apakah *true* atau *false* if (kondisi) { ... } ``` ## Klausa "else" Pernyataan `if` dapat berisi blok opsional "else" opsional. Block "else" dijalankan ketika semua kondisi di atas blok "else" salah (false) semua. Contohnya: ```js run let tahun = prompt('Ditahun berapa spesifikasi ECMAScript-2015 dipublikasikan?'', ''); if (tahun == 2015) { alert( 'Jawaban kamu benar!' ); } else { alert( 'Hah, kok jawaban kamu salah?' ); // nilai lainnya selain 2015 } ``` ## Pernyataan kondisional lebih dari satu: "else if" Terkadang, kita ingin menguji beberapa kondisi yang berbeda-beda Klausa `else if` memungkinkan kita melakukan itu. Sebagai contoh: ```js run let tahun = prompt('Ditahun berapa spesifikasi ECMAScript-2015 dipublikasikan?', ''); if (tahun < 2015) { alert( 'Terlalu dini...' ); } else if (tahun > 2015) { alert( 'Terlalu akhir' ); } else { alert( 'Tepat sekali!' ); } ``` Pada kode di atas, pertama JavaScript mengecek ekspresi `tahun < 2015`. Jika itu salah, maka masuk ke kondisi selanjutnya `tahun > 2015`. Jika itu juga salah, pernyataan di dalam blok "else" akan dijalankan,yang merupakan sebuah alert Blok `else if`bisa digunakan berkali-kali. Pernyataan final `else` hanya opsional. ## Operator Kondisional '?' Terkadang, kita perlu memberi nilai ke suatu variabel tergantung pada suatu kondisi. Contohnya: ```js run no-beautify let accessAllowed; let umur = prompt('Berapakah umur kamu?', ''); *!* if (umur > 18) { accessAllowed = true; } else { accessAllowed = false; } */!* alert(accessAllowed); ``` Operator "question mark" (tanda tanya) memungkinkan kita melakukan kode di atas dengan cara yang lebih singkat dan sederhana. Operator tersebut direpresentasikan oleh tanda tanya `?`. Nama lain dari operator ini adalah "ternary", karena operator ini memiliki tiga operan (ternary bahasa Indonesia adalah "terdiri dari 3 bagian"). Ini sebenarnya satu-satunya operator dalam JavaScript yang memiliki 3 operan. Sintaksnya adalah: ```js let result = condition ? value1 : value2; ``` Di kode di atas, `condition` dievaluasi: jika itu benar maka value1 akan dikembalikan, jika tidak - value2 yang akan dikembalikan. Contohnya: ```js let accessAllowed = (age > 18) ? true : false; ``` Secara teknis, kita dapat menghilangkan tanda kurung di sekitar `age > 18` . Operator tanda tanya memiliki prioritas rendah, sehingga hanya akan dijalankan setelah perbandingan `>` . Contoh di bawah ini akan melakukan hal yang sama seperti kode sebelumnya: ```js // perbandingan operator "age > 18" dieksekusi pertama kali // (tidak perlu dibungkus dengan kurung) let accessAllowed = age > 18 ? true : false; ``` Tetapi tanda kurung membuat kode lebih mudah dibaca, jadi kami sarankan untuk tetap menggunakannya di kode-kode anda. ````smart Pada contoh di atas, Anda dapat menghindari pengunaan operator tanda tanya karena perbandingan itu sendiri menghasilkan `true/false`: ```js // the same let accessAllowed = age > 18; ``` ```` ## Multiple '?' Rangkaian operator tanda tanya `?` dapat mengembalikan nilai yang tergantung pada lebih dari satu kondisi. Contohnya: ```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 ); ``` Mungkin sulit pada awalnya untuk memahami apa yang terjadi. Tetapi setelah melihat lebih dekat, kita dapat melihat bahwa itu hanya serangkaian tes biasa: 1. Tanda tanya pertama memeriksa apakah `age < 3`. 2. Jika benar -- `'Hi, baby!'` akan dikembalikan. Jika tidak, kode akan melanjutkan ke ekspresi setelah titik dua '":"', memeriksa apakah `age < 18`. 3. Jika itu benar -- `'Hello!'` akan dikembalikan. . Jika tidak, kode akan melanjutkan ke ekspresi setelah titik dua berikutnya '":"', memeriksa `age < 100`. 4. Jika itu benar -- `'Greetings!'` akan dikembalikan. Jika tidak, kode akan melanjutkan ke ekspresi setelah titik dua terakhir '":"', dan akhirnya akan mengembalikan `'What an unusual age!'`. Kode bawah ini memperlihatkan apabila menggunakan `if..else` untuk kode di atas: ```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!'; } ``` ## Penggunaan '?' yang non-tradisional Terkadang tanda tanya `?` digunakan sebagai pengganti `if`: ```js run no-beautify let company = prompt('Which company created JavaScript?', ''); *!* (company == 'Netscape') ? alert('Right!') : alert('Wrong.'); */!* ``` Bergantung pada kondisional `company == 'Netscape' , ekspresi pertama atau kedua setelah `?` akan dieksekusi dan menunjukkan sebuah "alert". Kita tidak memberikan nilai hasil ke suatu variable di sini. Sebagai gantinya, kita mengeksekusi kode yang berbeda tergantung pada kondisinya. **Tidak disarankan memakai operator tanda tanya dengan cara ini.** Notasinya memang lebih pendek daripada apabila menggunakan pernyataan `if` , yang mungkin menarik bagi beberapa programmer, tetapi hal ini membuat kode anda lebih susah dibaca. Berikut adalah kode yang sama menggunakan `if` untuk perbandingan: ```js run no-beautify let company = prompt('Which company created JavaScript?', ''); *!* if (company == 'Netscape') { alert('Right!'); } else { alert('Wrong.'); } */!* ``` Mata kita memindai kode secara vertikal. Blok kode yang terdiri dari beberapa baris akan lebih mudah dipahami daripada suatu set instruksi horizontal yang panjang. Tujuan operator tanda tanya `?` adalah mengembalikan satu nilai atau nilai yang lainnya tergantung pada kondisinya. Mohon untuk gunakan operator tanda tanya hanya untuk fungsi tersebut. Gunakan `if ketika Anda perlu menjalankan berbagai cabang kode yang berbeda-beda ================================================ FILE: 1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md ================================================ Jawabannya `2`, itu nilai truthy pertama. ```js run alert( null || 2 || undefined ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md ================================================ nilai penting: 5 --- # Apa hasil dari OR? Apakah keluaran dari kode dibawah? ```js alert( null || 2 || undefined ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md ================================================ Jawabannya: pertama `1`, lalu `2`. ```js run alert( alert(1) || 2 || alert(3) ); ``` Panggilan `alert` tak mengembalikan nilai. Atau, dengan kata lain, ia mengembalikan `undefined`. 1. Pertama OR `||` mengevaluasi operand kiri `alert(1)`. Ia menampilkan pesan pertama dengan `1`. 2. `alert` mengembalikan `undefined`, jadi OR jalan ke operand kedua mencari nilai truthy. 3. Operand kedua `2` truthy, jadi eksekusinya disela, `2` dikembalikan dan ditampilkan oleh alert terluar. Tak akan ada `3`, karena evaluasinya tidak mencapai `alert(3)`. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/2-alert-or/task.md ================================================ niai penting: 3 --- # Apa hasil dari alert yang di-OR-kan? Apa output kode di bawah? ```js alert( alert(1) || 2 || alert(3) ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md ================================================ Jawabannya: `null`, karena `null` adalah nilai falsy pertama yang ada di daftar. ```js run alert( 1 && null && 2 ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md ================================================ nilai penting: 5 --- # Apa hasil AND? Kode ini akan menampilkan apa? ```js alert( 1 && null && 2 ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md ================================================ Jawabannya: `1`, dan kemudian `undefined`. ```js run alert( alert(1) && alert(2) ); ``` Panggilan `alert` mengembalikan `undefined` (ia cuma menampilkan pesan, jadi tak ada kembalian berarti). Karena itu, `&&` mengevaluasi operand kiri (output `1`), dan langsung berhenti, karena `undefined` adalah nilai falsy. Dan `&&` mencari nilai falsy dan mengembalikannya, jadi begitulah. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/4-alert-and/task.md ================================================ nilai penting: 3 --- # Apa hasil dari alert yang di-AND-kan? Kode ini akan menampilkan apa? ```js alert( alert(1) && alert(2) ); ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md ================================================ Jawabannya: `3`. ```js run alert( null || 2 && 3 || 4 ); ``` Presedensi AND `&&` lebih tinggi dari `||`, jadi ia jalan pertama. Hasil dari `2 && 3 = 3`, jadi expresinya menjadi: ``` null || 3 || 4 ``` Sekarang hasilnya jadi nilai truthy pertama: `3`. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md ================================================ nilai penting: 5 --- # Hasil dari OR AND OR Hasilnya akan jadi apa? ```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 ================================================ nilai penting: 3 --- # Cek kisaran antara Tulis satu kondisi "if" untuk mengecek bahwa `age` ada di antara `14` dan `90` secara inklusif. "Secara inklusif" artinya bahwa `age` bisa mencapai `14` atau `90`. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/7-check-if-out-range/solution.md ================================================ Varian pertama: ```js if (!(age >= 14 && age <= 90)) ``` Varian kedua: ```js if (age < 14 || age > 90) ``` ================================================ FILE: 1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md ================================================ nilai penting: 3 --- # Cek kisaran luar Tulis satu kondisi `if` untuk mengecek bahwa `age` BUKAN di antara 14 dan 90 secara inklusif. Buat dua varian: pertama menggunakan NOT `!`, kedua -- tanpaanya. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/8-if-question/solution.md ================================================ Jawabannya: pertama dan ketiga akan diexekusi. Detil: ```js run // Berjalan. // Hasil dari -1 || 0 = -1, truthy if (-1 || 0) alert( 'first' ); // Tidak berjalan // -1 && 0 = 0, falsy if (-1 && 0) alert( 'second' ); // Eksekusi // Operator && mempunyai hak yang lebih tinggi daripada || // jadi -1 && 1 dieksekusi pertama, dan memberikan rentetan: // 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 ================================================ nilai penting: 5 --- # Pertanyaan tentang "if" Mana dari `alert` berikut yang akan diexekusi? Hasil expresinya akan jadi seperti apa di dalam `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" ); } ``` Perhatikan indent vertkal di dalam blok `if`. Mereka secara teknis tak dibutuhkan, tapi membuat kode lebih mudah dibaca. ================================================ FILE: 1-js/02-first-steps/11-logical-operators/9-check-login/task.md ================================================ nilai penting: 3 --- # Cek login Tulis kode yang meminta login dengan `prompt`. Jika pengunjung menekan `"Admin"`, maka `prompt` untuk katasandi, jika inputannya beruba baris kosong atau `key:Esc` -- tampilkan "Canceled.", jika string lain -- maka tampilkan "I don't know you". Katasandinya dicek sebagai berikut: - Jika ia sama dengan "TheMaster", maka tampilkan "Welcome!", - String lain -- tampilkan "Wrong password", - Untuk string kosong atau batal input, tampilkan "Canceled." Skemanya: ![](ifelse_task.svg) Silakan gunakan blok `if` bersarang. Abaikan kemudahan-baca seluruh kode. Petunjuk: mengoper inputan kosong ke prompt mengembalikan string kosong `''`. Menekan `key:ESC` saat prompt mengembalikan `null`. [demo] ================================================ FILE: 1-js/02-first-steps/11-logical-operators/article.md ================================================ # Operator logika Ada tiga operator logika di JavaScript: `||` (OR), `&&` (AND), `!` (NOT). Meski mereka dipanggil "logika", mereka bisa diaplikasikan ke nilai tipe apapun, bukan cuma boolean. Hasil mereka bisa juga tipe apapun. Mari kita lihat detilnya. ## || (OR) Operator "OR" diwakili dengan dua simbol garis vertical: ```js result = a || b; ``` Di pemrograman klasik, logika OR gunanya cuma untuk memanipulasi nilai boolean. Jika argumennya ada yang `true`, ia mengembalikan `true`, tapi jika tidak, maka ia mengembalikan `false`. Di JavaScript, operator ini agak tricky dan lebih kuat. Tapi pertama-tama, ayo kita lihat apa yang terjadi pada nilai boolean. Ada empat kemungkinan kombinasi logika: ```js run alert( true || true ); // true alert( false || true ); // true alert( true || false ); // true alert( false || false ); // false ``` Seperti yang kita lihat, hasilnya selalu `true` kecuali jika kedua operand sama-sama `false`. Jika operand bukan boolean, ia dikonversi ke boolean untuk evaluasi. Misalnya, angka `1` diperlakukan sebagai `true`, angka `0` sebagai `false`: ```js run if (1 || 0) { // bekerja seperti if( true || false ) alert( 'truthy!' ); } ``` Seringkali, OR `||` digunakan di pernyataan `if` untuk menguji apakah ada satu kondisi *manapun* yang `true`. Misalnya: ```js run let hour = 9; *!* if (hour < 10 || hour > 18) { */!* alert( 'The office is closed.' ); } ``` Kita bisa menyampaikan kondisi lebih: ```js run let hour = 12; let isWeekend = true; if (hour < 10 || hour > 18 || isWeekend) { alert( 'The office is closed.' ); // akhir minggu } ``` ## OR "||" mencari nilai benar pertama [#or-finds-the-first-truthy-value] Logika di atas memang klasik. Sekarang, mari bawa fitur "extra" JavaScript. Algoritma luas bekerja seperti berikut. Untuk nilai yang diORkan: ```js result = value1 || value2 || value3; ``` Operator OR `||` melakukan hal berikut: - Mengevaluasi operand dari kiri ke kanan. - Untuk tiap operand, konversikan ia ke boolean. Jika hasilnya `true`, stop dan mengembalikan nilai original dari operand. - Jika semua operand telah dievaluasi (misal semuanya `false`), mengembalikan operand terakhir. Nilai dikembalikan di bentuk originalnya, tanpa konversi. Dengan kata lain, rantai OR `"||"` mengembalikan nilai truthy pertama atau yang terakhir jika tak ada nilai benar. Misalnya: ```js run alert( 1 || 0 ); // 1 (1 truthy) alert( true || 'no matter what' ); // (true ialah truthy) alert( null || 1 ); // 1 (1 ialah nilai truthy pertama) alert( null || 0 || 1 ); // 1 (nilai truthy pertama) alert( undefined || null || 0 ); // 0 (semua falsy, mengembalikan nilai terakhir) ``` Hal ini menjadikan penggunaan yang menarik dibanding "OR booleanpure, classical, boolean-only OR". 1. **Dapatkan nilai truthy dari daftar variabel atau expresi.** Untuk contoh, kita punya variabel `firstName`, `lastName` dan `nickName`, semuanya bersifat opsional. Kita gunakan OR `||` untuk memilih satu-satunya yang memiliki data dan menampilkannya (atau `anonymous` jika belum ada yang ditentukan atau di set): ```js run let firstName = ""; let lastName = ""; let nickName = "SuperCoder"; *!* alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder */!* ``` Jika semua variabel bernilai falsy, `Anonymous` akan muncul. 2. **Evaluasi Short-circuit.** Fitur lainnya dari operator OR `||` adalah evaluasi "short-circuit". Itu berarti bahwa `||` memproses argumennya sampai nilai pertama bersifat truthy tercapai, lalu nilainya dikembalikan langsung, bahkan tanpa menyentuh argumen lainnya. Pentingnya dari fitur ini menjadi jelas jika sebuah operan bukan hanya sebuah nilai, tapi sebuah ekspresi yang melakukan aksi, seperti assignment sebuah variabel atau sebuah pemanggilan fungsi. Didalam contoh dibawah, hanya pesan kedua yang di jalankan: ```js run no-beautify *!*true*/!* || alert("not printed"); *!*false*/!* || alert("printed"); ``` Di baris pertama, operator OR `||` langsung berhenti mengevaluasi karena nilai pertama bersifat `true`, jadi `alert`nya tidak berjalan. Terkadang, orang-orang menggunakan fitur ini untuk mengeksekusi perintah hanya jika kondisi di paling kiri bersifat falsy. ## && (AND) Operator AND diwakili dua ampersand `&&`: ```js result = a && b; ``` Dalam pemrograman klasik, AND mengembalikan `true` jika kedua operand sama-sama truthy dan `false` jika sebaliknya: ```js run alert( true && true ); // true alert( false && true ); // false alert( true && false ); // false alert( false && false ); // false ``` Contoh dengan `if`: ```js run let hour = 12; let minute = 30; if (hour == 12 && minute == 30) { alert( 'The time is 12:30' ); } ``` Sama seperti OR, nilai apapun boleh menjadi operand dari AND: ```js run if (1 && 0) { // dievaluasi sebagai true && false alert( "won't work, because the result is falsy" ); } ``` ## AND "&&" mencari nilai falsy pertama Misal ada beberapa nilai di-AND-kan: ```js result = value1 && value2 && value3; ``` Yang dilakukan operator AND `&&` adalah sebagai berikut: - Mengevaluasi operand dari kiri ke kanan. - Untuk tiap operand, konversi ia ke boolean. Jika hasilnya `false`, stop dan kembalikan nilai original operand tersebut. - Jika semua operand dievaluasi (i.e. semua truthy), mengembalikan operand terakhir. Dengan kata lain, AND mengembalikan nilai falsy pertama atau nilai terakhir jika tak ketemu satupun nilai falsy. Aturan di atas mirip dengan OR. Bedanya ialah AND mengembalikan niai *falsy* pertama sedangkan OR mengembalikan nilai *truthy* pertama. Misalnya: ```js run // jika operand pertama truthy, // AND mengembalikan operand kedua: alert( 1 && 0 ); // 0 alert( 1 && 5 ); // 5 // jika operand pertama falsy, // AND mengembalikan itu. Operand kedua diabaikan alert( null && 5 ); // null alert( 0 && "no matter what" ); // 0 ``` Kita juga bisa mengoper beberapa nilai dalam satu barus. Lihat bagaimana nilai falsy pertama dikembalikan: ```js run alert( 1 && 2 && null && 3 ); // null ``` Ketika semua nilai truthy, nilai terakhir dikembalikan: ```js run alert( 1 && 2 && 3 ); // 3, the last one ``` ````smart header="Precedence of AND `&&` is higher than OR `||`" Presedensi operator AND `&&` lebih tinggi dari OR `||`. Jadi kode `a && b || c && d` esensinya sama dengan jika expresi `&&` dibungkus tanda kurung: `(a && b) || (c && d)`. ```` ````warn header="Jangan ganti `if` dengan || atau &&" Terkadang, orang-orang menggunakan operator AND `&&` untuk "memperpendek instruksi `if`". Misalnya: ```js run let x = 1; (x > 0) && alert( 'Greater than zero!' ); ``` Aksi di bagian kanan `&&` akan diexekusi hanya jika evaluasinya mencapai itu. Yaitu, hanya jika `(x > 0)` true. Jadi pada dasarnya kita punya analogi untuk: ```js run let x = 1; if (x > 0) alert( 'Greater than zero!' ); ``` Walaupun, versi dengan `&&` muncul lebih pendek, `if` menjadi jelas dan sedikit lebih mudah dibaca. Jadi kita merekomendasikan menggunakannya untuk setiap kebutuhan: gunakan `if` jika kita ingin if dan gunakan `&&` jika kita ingin AND. ```` ## ! (NOT) Operator boolean NOT diwakili dengan tanda exklamasi `!`. Syntaxnya cukup simpel: ```js result = !value; ``` Operator ini menerima argumen tunggal dan menjalankan hal berikut: 1. Mengkonversi operand ke tipe boolean: `true/false`. 2. Mengembalikan nilai kebalikan. Misalnya: ```js run alert( !true ); // false alert( !0 ); // true ``` NOT ganda `!!` kadang dipakai untuk mengkonversi nilai ke tipe boolean: ```js run alert( !!"non-empty string" ); // true alert( !!null ); // false ``` Yaitu, NOT pertama mengkonversi nilai ke boolean dan mengembalikan kebalikannya, dan NOT kedua membaliknya lagi. Ujungnya, kita punya konversi nilai-ke-boolean biasa. Ada sedikit cara rewel untuk melakukan hal serupa -- fungsi `Boolean` built-in: ```js run alert( Boolean("non-empty string") ); // true alert( Boolean(null) ); // false ``` Presedensi NOT `!` paling tinggi dari semua operator logika, jadi ia selalu jalan pertama, sebelum `&&` or `||`. ================================================ FILE: 1-js/02-first-steps/12-nullish-coalescing-operator/article.md ================================================ # Operator penggabungan nullish '??' [browser terbaru="baru"] Operator penggabungan nullish ditulis sebagai dua tanda tanya `??`. Karena memperlakukan `null` dan `undefined` sama, kita akan menggunakan istilah khusus di sini, di artikel ini. Kami akan mengatakan bahwa ekspresi adalah "didefinisikan" ketika itu bukan `null` atau `undefined`. Hasil dari `a?? b` adalah: - jika `a` didefinisikan, maka `a`, - jika `a` tidak didefinisikan, maka `b`. Dengan kata lain, `??` mengembalikan argumen pertama jika bukan `null/undefined`. Kalau tidak, yang kedua. Operator penggabungan nullish bukanlah sesuatu yang benar-benar baru. Ini hanya sintaks yang bagus untuk mendapatkan nilai "didefinisikan" pertama dari keduanya. Kita dapat menulis ulang `result = a ?? b` menggunakan operator yang sudah kita kenal, seperti ini: ```js result = (a !== null && a !== undefined) ? a : b; ``` Sekarang harus benar-benar jelas apa yang dilakukan `??`. Mari kita lihat di mana itu membantu. Kasus penggunaan umum untuk `??` adalah memberikan nilai default untuk variabel yang berpotensi tidak terdefinisi. Misalnya, di sini kami menampilkan `pengguna` jika didefinisikan, jika tidak `Anonim`: ```js dijalankan biarkan pengguna; alert(pengguna ?? "Anonim"); // Anonim (pengguna tidak ditentukan) ``` Berikut ini contoh dengan `pengguna` ditetapkan ke sebuah nama: ```js dijalankan biarkan pengguna = "John"; alert(pengguna ?? "Anonim"); // John (ditentukan pengguna) ``` Kita juga dapat menggunakan urutan `??` untuk memilih nilai pertama dari daftar yang bukan `null/undefined`. Katakanlah kita memiliki data pengguna dalam variabel `FirstName`, `lastName` atau `nickName`. Semuanya mungkin tidak ditentukan, jika pengguna memutuskan untuk tidak memasukkan nilai. Kami ingin menampilkan nama pengguna menggunakan salah satu variabel ini, atau menampilkan "Anonim" jika semuanya tidak ditentukan. Mari kita gunakan operator `??` untuk itu: ```js dijalankan biarkan namadepan = null; biarkan namabelakang = null; biarkan nickName = "Supercoder"; // menunjukkan nilai yang ditentukan pertama: *!* alert(FirstName ?? LastName ?? nickName ?? "Anonim"); // kode super */!* ``` ## Perbandingan dengan || Operator OR `||` dapat digunakan dengan cara yang sama seperti `??`, seperti yang dijelaskan di [bab sebelumnya](info:logical-operators#or-finds-the-first-truthy-value). Misalnya, pada kode di atas kita dapat mengganti `??` dengan `||` dan tetap mendapatkan hasil yang sama: ```js dijalankan biarkan namadepan = null; biarkan namabelakang = null; biarkan nickName = "Supercoder"; // menunjukkan nilai kebenaran pertama: *!* alert(Namadepan || Nama Belakang || Nama Panggilan || "Anonim"); // kode super */!* ``` Secara historis, operator OR `||` ada terlebih dahulu. Itu ada sejak awal JavaScript, jadi pengembang menggunakannya untuk tujuan seperti itu untuk waktu yang lama. Di sisi lain, operator penggabungan nullish `??` baru saja ditambahkan ke JavaScript, dan alasannya adalah karena orang-orang tidak terlalu senang dengan `||`. Perbedaan penting di antara mereka adalah bahwa: - `||` mengembalikan nilai *truth* pertama. - `??` mengembalikan nilai *yang ditentukan* pertama. Dengan kata lain, `||` tidak membedakan antara `false`, `0`, string kosong `""` dan `null/undefined`. Semuanya sama -- nilai yang salah. Jika salah satu dari ini adalah argumen pertama dari `||`, maka kita akan mendapatkan argumen kedua sebagai hasilnya. Namun dalam praktiknya, kita mungkin ingin menggunakan nilai default hanya jika variabelnya `null/undefined`. Artinya, ketika nilainya benar-benar tidak diketahui/tidak disetel. Misalnya, pertimbangkan ini: ```js dijalankan misalkan tinggi = 0; waspada(tinggi || 100); // 100 waspada (tinggi ?? 100); // 0 ``` - `tinggi || 100` memeriksa `height` sebagai nilai yang salah, dan itu adalah `0`, memang salah. - jadi hasil dari `||` adalah argumen kedua, `100`. - `tinggi ?? 100` memeriksa `height` sebagai `null/undefined`, dan bukan, - jadi hasilnya adalah `height` "sebagaimana adanya", yaitu `0`. Dalam praktiknya, ketinggian nol seringkali merupakan nilai yang valid, yang tidak boleh diganti dengan default. Jadi `??` melakukan hal yang benar. ## Prioritas Prioritas operator `??` hampir sama dengan `||`, hanya sedikit lebih rendah. Ini sama dengan `5` di [tabel MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table), sedangkan `||` adalah `6` . Artinya, seperti halnya `||`, operator penggabungan nullish `??` dievaluasi sebelum `=` dan `?`, tetapi setelah sebagian besar operasi lain, seperti `+`, `*`. Jadi, jika kita ingin memilih nilai dengan `??` dalam ekspresi dengan operator lain, pertimbangkan untuk menambahkan tanda kurung: ```js dijalankan biarkan tinggi = nol; biarkan lebar = nol; // penting: gunakan tanda kurung biarkan luas = (tinggi ?? 100) * (lebar ?? 50); waspada (daerah); // 5000 ``` Jika tidak, jika kita menghilangkan tanda kurung, maka karena `*` memiliki prioritas yang lebih tinggi daripada `??`, tanda kurung akan dieksekusi terlebih dahulu, sehingga menghasilkan hasil yang salah. ```js // tanpa tanda kurung misal luas = tinggi?? 100 * lebar ?? 50; // ...berfungsi sama seperti ini (mungkin bukan yang kita inginkan): misal luas = tinggi?? (100 * lebar) ?? 50; ``` ### Menggunakan ?? dengan && atau || Karena alasan keamanan, JavaScript melarang penggunaan `??` bersama dengan operator `&&` dan `||`, kecuali jika prioritas secara eksplisit ditentukan dengan tanda kurung. Kode di bawah ini memicu kesalahan sintaks: ```js dijalankan misalkan x = 1 && 2 ?? 3; // Kesalahan sintaks ``` Batasan ini tentu masih bisa diperdebatkan, hal itu ditambahkan ke spesifikasi bahasa dengan tujuan untuk menghindari kesalahan pemrograman, ketika orang mulai beralih dari `||` ke `??`. Gunakan tanda kurung eksplisit untuk mengatasinya: ```js dijalankan *!* misalkan x = (1 && 2) ?? 3; // Bekerja */!* waspada(x); // 2 ``` ## Ringkasan - Operator penggabungan nullish `??` menyediakan cara singkat untuk memilih nilai "ditentukan" pertama dari daftar. Ini digunakan untuk menetapkan nilai default ke variabel: ```js // atur tinggi=100, jika tinggi nol atau tidak terdefinisi tinggi = tinggi?? 100; ``` - Operator `??` memiliki prioritas yang sangat rendah, hanya sedikit lebih tinggi dari `?` dan `=`, jadi pertimbangkan untuk menambahkan tanda kurung saat menggunakannya dalam ekspresi. - Dilarang menggunakannya dengan `||` atau `&&` tanpa tanda kurung eksplisit. ================================================ FILE: 1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md ================================================ Jawabannya: `1`. ```js run let i = 3; while (i) { alert( i-- ); } ``` Setiap pengulangan mengurangi `i` dengan `1`. pengecekan `while(i)` menghentikan perulangan ketika `i = 0`. Oleh karena itu, langkah-langkah perulangan membentuk urutan sebagai berikut ("loop unrolled"): ```js let i = 3; alert(i--); // menampilkan 3, mengurangi i menjadi 2 alert(i--) // menampilkan 2, mengurangi i menjadi 1 alert(i--) // menampilkan 1, mengurangi i menjadi 0 // selesai, pengecekan while(i) menghentikan pengulangan ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/1-loop-last-value/task.md ================================================ importance: 3 --- # Nilai terakhir perulangan Apa nilai terakhir yang diperingatkan oleh kode ini? Mengapa? ```js let i = 3; while (i) { alert( i-- ); } ``` ================================================ FILE: 1-js/02-first-steps/13-while-for/2-which-value-while/solution.md ================================================ Tugas mendemonstrasikan bagaimana bentuk postfix/prefix dapat menyebabkan hasil yang berbeda ketika digunakan dalam perbandingan 1. **Dari 1 ke 4** ```js run let i = 0; while (++i < 5) alert( i ); ``` nilai pertama adalah `i = 1`, karena `++i` menambah terlebih dahulu `i` dan mengembalikan nilai baru. Jadi perbandingan pertama adalah `1 < 5` dan `alert` menampilkan `1`. lalu diikuti `2, 3, 4…` -- nilainya muncul satu per satu. Perbandingan selalu menggunakan nilai yang ditambah, karna ada `++` sebelum variabel. Akhirnya, `i = 4` bertambah menjadi `5`, perbandingan `while(5 < 5)` gagal, dan pengulangan berhenti. Jadi `5` tidak ditampilkan. 2. **Dari 1 ke 5** ```js run let i = 0; while (i++ < 5) alert( i ); ``` Lagi, nilai pertama adalah `i = 1`. bentuk postfix dari `i++` menambah `i` dan kemudian mengembalikan nilai yang *lama*, jadi perbandingan `i++ < 5` akan menggunakan `i = 0` (berbeda dengan `++i < 5`). Namun panggilan `alert` terpisah. ini adalah pernyataan lain yang berjalan setelah pertambahan dan perbandingan. Jadi ini mendapatkan nilai yang saat ini `i = 1`. Lalu diikuti `2, 3, 4…` Mari berhenti di `i = 4`. bentuk prefix `++i` akan menaikannya dan menggunakan `5` di perbandingan. Tapi disini kita mempunyai bentuk postfix `i++`. jadi ini menambah `i` menjadi `5`, namun mengembalikan nilai yang lama. Karna perbandingan yang sebenarnya adalah `while(4 < 5)` -- benar, dan kontrol berlanjut ke `alert`. Nilai `i = 5` adalah yang terkahir, karena pada langkah berikutnya `while(5 < 5)` adalah salah. ================================================ FILE: 1-js/02-first-steps/13-while-for/2-which-value-while/task.md ================================================ importance: 4 --- # Nilai mana yang ditampilkan perulangan while? Untuk setiap iterasi, tulis nilai mana yang dikeluarkan dan bandingkan dengan solusinya. Kedua perulangan `alert` nilai yang sama, atau tidak? 1. Bentuk prefix `++i`: ```js let i = 0; while (++i < 5) alert( i ); ``` 2. Bentuk postfix `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 ================================================ **Jawabanya: dari `0` ke `4` pada kedua kasus.** ```js run for (let i = 0; i < 5; ++i) alert( i ); for (let i = 0; i < 5; i++) alert( i ); ``` Itu dapat dengan mudah dikurangkan dari algoritma `for`: 1. Jalankan sekali `i = 0` sebelum apapun (begin). 2. Cek kondisinya `i < 5` 3. Jika `true` -- jalankan loop body `alert(i)`, dan kemudian `i++` pertambahan `i++` terpisah dari pengecekan kondisi (2). itu hanya pernyataan lain. Nilai yang dikembalikan oleh pertambahan tidak digunakan disini, jadi tidak ada bedanya antara `i++` dan `++i`. ================================================ FILE: 1-js/02-first-steps/13-while-for/3-which-value-for/task.md ================================================ importance: 4 --- # Nilai mana yang ditampilkan oleh perulangan "for" ? Untuk setiap perulangan tulis nilai mana yang akan ditampilkan. lalu bandingkan dengan jawabanya Kedua perulangan `alert` nilai yang sama atau tidak? 1. Bentuk postfix: ```js for (let i = 0; i < 5; i++) alert( i ); ``` 2. Bentuk prefix: ```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 ); } } ``` Kita menggunakan operator "modulo" `%` untuk mendapatkan nilai sisa dan untuk mengecek kegenapan disini. ================================================ FILE: 1-js/02-first-steps/13-while-for/4-for-even/task.md ================================================ importance: 5 --- # Menghasilkan angka genap di perulangan Gunakan perulangan `for` untuk menghasilkan angka genap dari `2`sampai `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 --- # Ganti "for" dengan "while" Tulis ulang kode ubah perulangan `for` ke `while` tanpa mengubah perilakunya (hasilnya harus tetap sama). ```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("Masukan angka lebih dari 100?", 0); } while (num <= 100 && num); ``` Perulangan `do..while` diulangi selagi kedua cek itu bernilai benar: 1. Pengecekan untuk `num <= 100` -- itu adalah, nilai yang dimasukan masih tidak lebih besar dari `100`. 2. Pengecekan `&& num` adalah salah ketika `num` adalah `null` atau sebuah string kosong. kemudian perulangan `while` berhenti juga. P.S. Jika `num` adalah `null` lalu `num <= 100` adalah `true`, jadi tanpa pengecekan kedua, perulangan tidak akan berhenti jika pengguna mengeclick CANCEL. Kedua pengecekan dibutuhkan. ================================================ FILE: 1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md ================================================ importance: 5 --- # Ulangi sampai inputnya benar Tulis sebuah perulangan yang meminta angka lebih dari `100`. Jika pendatang memasukan angka lainnya -- tanya mereka untuk menginput kembali. Perulangan harus menanyakan untuk sebuah angka sampai pengunjung memasukan angka lebih dari `100` atau membatalkan input/memasukan sebuah baris kosong. Disini kita dapat menganggap bahwa pengunjung hanya menginput angka. Tidak perlu menerapkan penanganan spesial untuk input non-numerik di tugas ini. [demo] ================================================ FILE: 1-js/02-first-steps/13-while-for/7-list-primes/solution.md ================================================ Ada banyak algoritma untuk tugas ini. Mari gunakan perulangan bersarang: ```js For each i in the interval { cek if i has a divisor from 1..i if yes => the value is not a prime if no => the value is a prime, show it } ``` Kode menggunakan label: ```js run let n = 10; nextPrime: for (let i = 2; i <= n; i++) { // untuk setiap i... for (let j = 2; j < i; j++) { // mencari pembagi.. if (i % j == 0) continue nextPrime; // bukan prima, pergi ke i berikutnya } alert( i ); // prima } ``` Ada banyak ruang untuk mengoptimalkannya. Misalnya, kita dapat mencari pembagi dari `2` ke akar kuadrat dari `i`. Tapi bagaimanapun, jika kita ingin sangat efisien untuk interval besar, kita perlu mengubah pendekatan dan mengandalkan matematika lanjutan dan algotima rumit seperti [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) dsb. ================================================ FILE: 1-js/02-first-steps/13-while-for/7-list-primes/task.md ================================================ importance: 3 --- # Menghasilkan bilangan prima Angka integer lebih dari `1` disebut [bilangan prima](https://en.wikipedia.org/wiki/Prime_number) jika itu tidak bisa dibagi tanpa sisa oleh siapapun kecuali `1` dan bilangan itu sendiri. Dengan kata lain, `n > 1` adalah prima jika tidak dapat dibagi secara merata oleh apapun kecuali `1` dan `n`. Contohnya, `5` adalah prima, karna tidak bisa dibagi tanpa sisa oleh `2`, `3` dan `4`. **Tulis kode yang menghasilkan bilangan prima dalam interval dari `2` sampai `n`.** Untuk `n = 10` hasilnya akan `2,3,5,7`. P.S. Kode harus bekerja untuk segala `n`, tidak disetel untuk nilai tetap apapun. ================================================ FILE: 1-js/02-first-steps/13-while-for/article.md ================================================ # Perulangan: while dan for Kita sering perlu untuk mengulangi tindakan. Contohnya, Mengeluarkan barang dari sebuah daftar satu per satu atau hanya menjalankan kode yang sama untuk setiap nomor dari 1 hingga 10. *Perulangan* adalah sebuah cara untuk mengulangi kode yang sama beberapa kali. ## Perulangan "while" Perulangan `while` memiliki sintaks sebagai berikut: ```js while (kondisi) { // kode // disebut "badan perulangan" } ``` Ketika `kondisi` bernilai truthy, `kode` dari badan perulangan dijalankan. Contohnya, perulangan di bawah mengeluarkan `i` selagi `i < 3`: ```js run let i = 0; while (i < 3) { // menampilkan 0, lalu 1, lalu 2 alert( i ); i++; } ``` Eksekusi tunggal dari badan perulangan disebut *sebuah pengulangan*. Perulangan pada contoh diatas membuat tiga kali pengulangan. Jika `i++` hilang dari contoh di atas, perulangan akan mengulangi (dalam teori) secara terus-menerus. Pada praktiknya, browser menyediakan cara untuk menghentikan perulangan, dan pada sisi server JavaScript, kita dapat mematikan prosesnya. Ekspresi atau variable apapun bisa menjadi sebuah kondisi perulangan, tidak hanya perbandingan: kondisi terevalusasi dan terkonversi menjadi boolean oleh `while`. Contohnya, cara cepat untuk menulis `while (i != 0)` adalah `while (i)`: ```js run let i = 3; *!* while (i) { // ketika i menjadi 0, kondisi bernilai salah, dan perulangan berhenti */!* alert( i ); i--; } ``` ````smart header="Kurung kurawal tidak dibutuhkan untuk badan baris tunggal" Jika badan perulangan mempunyai sebuah pernyataan tunggal, kita dapat menghilangkan kurung kurawal `{…}`: ```js run let i = 3; *!* while (i) alert(i--); */!* ``` ```` ## Perulangan "do..while" Pengecekan kondisi dapat dipindahkan *di bawah* badan perulangan menggunakan `do..while` sintaks: ```js do { // badan perulangan } while (condition); ``` Perulangan akan mengeksekusi badan terlebih dahulu, lalu memeriksa kondisi, dan, selagi itu bernilai truthy, jalankan itu lagi dan lagi. Contohnya: ```js run let i = 0; do { alert( i ); i++; } while (i < 3); ``` Format penulisan ini hanya digunakan ketika kamu ingin badan dari perulangan tereksekusi **setidaknya sekali** Terlepas dari kondisi menjadi bernilai benar. Biasanya, format lain yang dipilih: `while(…) {…}`. ## Perulangan "for" Perulangan `for` lebih complex, tapi merupakan perulangan yang paling umum digunakan. Itu terlihat seperti ini: ```js for (awal; kondisi; langkah) { // ... badan perulangan ... } ``` Mari belajar makna dari bagian ini dari contoh. Perulangan dibawah menjalankan `alert(i)` untuk `i` dari `0` sampai dengan (tapi tidak termasuk) `3`: ```js run for (let i = 0; i < 3; i++) { // menampilkan 0, lalu 1, lalu 2 alert(i); } ``` Mari bahas pernyataan `for` bagian demi bagian: | bagian | | | |-------|----------|----------------------------------------------------------------------------| | begin | `i = 0` | Jalankan sekali masuk ke loop. | | condition | `i < 3`| Cek sebelum tiap iterasi loop. Jika salah, loop berhenti. | | body | `alert(i)`| Jalankan lagi dan lagi selama kondisi bernilai truthy. | | step | `i++` | Exekusi setelah badan di tiap iterasi. | Cara kerja algoritma perulangan umum seperti ini: ``` Jalankan begin → (jika condition → jalankan body dan jalankan step) → (jika condition → jalankan body dan jalankan step) → (jika condition → jalankan body dan jalankan step) → ... ``` Dikatakan, `begin` diexekusi sekali, kemudian ia beriterasi: setelah tiap `condition` dites, `body` dan `step` diexekusi. Jika kamu baru pada perulangan, ini bisa membantumu kembali ke contoh dan mereproduksi bagamana ini berjalan selangkah demi selangkah pada sebuah selembar kertas. Inilah yang sebenarnya terjadi pada kasus kita: ```js // for (let i = 0; i < 3; i++) alert(i) // jalankan awal let i = 0 // jika kondisi → jalankan badan dan jalankan langkah if (i < 3) { alert(i); i++ } // jika kondisi → jalankan badan dan jalankan langkah if (i < 3) { alert(i); i++ } // jika kondisi → jalankan badan dan jalankan langkah if (i < 3) { alert(i); i++ } // ...selesai, karena sekarang i == 3 ``` ````smart header="Deklarasi varibel sebaris" Disini, "penghitung" variabel `i` dideklarasikan di dalam perulangan. Ini disebut deklarasi variabel "sebaris". variabel ini hanya akan terlihat di dalam perulangan. ```js run for (*!*let*/!* i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } alert(i); // error, tidak ada variabel ``` Daripada mendefinisikan sebuah variabel, kita dapat menggunakan yang sudah ada: ```js run let i = 0; for (i = 0; i < 3; i++) { // gunakan variabel yang sudah ada alert(i); // 0, 1, 2 } alert(i); // 3, terlihat, karena dideklarasikan diluar dari perulangan ``` ```` ### Melewatkan bagian Bagian apapun dari `for` dapat dilewati. Contoh, kita dapat menghilangkan `awal` jika kita tidak butuh untuk melakukan apapun pada awal perulangan. Seperti ini: ```js run let i = 0; // kita punya i yang sudah dideklarasikan dan telah ditetapkan for (; i < 3; i++) { // tidak butuh "awal" alert( i ); // 0, 1, 2 } ``` Kita juga bisa menghilangkan bagian `langkah`: ```js run let i = 0; for (; i < 3;) { alert( i++ ); } ``` Ini membuat perulangan sama dengan `while (i < 3)`. Kita sebenarnya dapat menghilangkan semuanya, membuat sebuah perulangan tak terhingga: ```js for (;;) { // ulangi tanpa batas } ``` Tolong dicatat bahwa dua `for` titik koma `;` harus ada, jika tidak, akan ada sintaks error. ## Menghentikan perulangan Biasanya, sebuah perulangan keluar ketika kondisinya menjadi bernilai salah. Tapi kita dapat memaksanya keluar pada waktu apapun menggunakan perintah spesial `break`. Contohnya, perulangan dibawah menanyakan pengguna untuk serangkaian angka, "hentikan" ketika tidak ada angka yang dimasukan: ```js run let sum = 0; while (true) { let value = +prompt("Masukan sebuah angka", ''); *!* if (!value) break; // (*) */!* sum += value; } alert( 'Sum: ' + sum ); ``` Perintah `break` teraktivasi pada baris `(*)` jika pengguna memasukan baris kosong atau membatalkan input. itu akan langsung berhenti, melewati kontrol ke baris pertama setelah perulangan. yang bernama, `alert`. Kombinasi "perulangan tak terhingga + `break` sesuai kebutuhan" bagus untuk situasi dimana sebuah kondisi perulangan harus diperiksa tidak di awal atau akhir dari perulangan, tapi di pertengahan atau bahkan di beberapa tempat tubuhnya. ## Lanjutkan ke perulangan berikutnya [#lanjutkan] Perintah `continue` adalah "versi ringan" dari `break`. Ini tidak mengentikan keseluruhan perulangan. sebagai gantinya, ini menghentikan perulangan saat ini dan memaksa perulangan untuk memulai yang baru (jika kondisi diperbolehkan). Kita dapat menggunakan ini jika kita selesai dengan perulangan saat ini dan ingin pindah ke yang berikutnya. Perulangan dibawah menggunakan `continue` untuk hanya menampilkan nilai ganjil: ```js run no-beautify for (let i = 0; i < 10; i++) { // jika benar, lewati bagian badan perulangan yang tersisa *!*if (i % 2 == 0) continue;*/!* alert(i); // 1, then 3, 5, 7, 9 } ``` Untuk nilai genap dari `i`, perintah `continue` mengentikan menjalankan badan dan melewati kontrol ke perulangan `for` berikutnya (dengan nomor berikutnya). jadi `alert` hanya terpanggil untuk nilai ganjil. ````smart header="Perintah `continue` membantu mengurangi penyarangan" Sebuah perulangan yang menampilkan nilai ganjil dapat terlihat seperti ini: ```js run for (let i = 0; i < 10; i++) { if (i % 2) { alert( i ); } } ``` Dari sudut pandang teknis, ini identik dengan contoh diatas. Tentunya, kita dapat membungkus kode dalam sebuah blok `if` daripada menggunakan `continue`. Tapi sebagai efeknya, hal ini akan menciptakan kode lebih dalam satu tingkat (pemanggilan `alert` didalam kurung kurawal). Jika kode didalam `if` lebih panjang beberapa baris, hal itu akan membuat tingkat keterbacaan kode menjadi berkurang. ```` ````warn header="Tidak ada `break/continue` ke sisi kanan '?'" Harap perhatikan bahwa sintaks yang membangun yang bukan ekspresi tidak dapat digunakan dengan operator ternary `?`. Khususnya, perintah seperti `break/continue` tidak diperbolehkan. Misalnya, jika kita mengambil kode ini: ```js if (i > 5) { alert(i); } else { continue; } ``` ...dan tulis ulang menggunakan sebuah tanda tanya: ```js no-beautify (i > 5) ? alert(i) : *!*continue*/!*; // continue tidak diperbolehkan disini ``` ...ia berhenti jalan: ada galat syntax: Ini hanya alasan lain untuk tidak menggunakan operator tanda tanya `?` daripada `if`. ```` ## Label untuk break/continue Terkadang kita perlu keluar dari beberapa perulangan bersarang sekaligus. Misalnya, dalam kode di bawah kita lakukan perulangan terhadap `i` dan `j`, meminta koordinat `(i, j)` dari `(0,0)` ke`(3,3)`: ```js run no-beautify for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let input = prompt(`Nilai pada koordinasi (${i},${j})`, ''); // bagaimana jika saya ingin keluar dari sini ke Done (dibawah)? } } alert('Done!'); ``` Kita butuh cara untuk menghentikan proses jika pengguna membatalkan input. `break` biasa setelah `input` hanya akan menghentikan perulangan dalam. Itu tidak cukup--label, datang untuk menyelamatkan! Label adalah sebuah pengidentifikasi dengan sebuah titik dua sebelum perulangan: ```js labelName: for (...) { ... } ``` Pernyataan `break ` di dalam loop di bawah menghentikan pada label: ```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})`, ''); // jika sebuah string kosong atau terbatalkan, lalu hentikan kedua perulangan if (!input) *!*break outer*/!*; // (*) // lakukan sesuatu dengan nilai... } } alert('Done!'); ``` Pada kode diatas, `break outer` melihat keatas untuk label bernama `outer` dan menghentikan perulangan itu. Jadi kontrol pergi langsung dari `(*)` ke `alert('Done!')`. Kita juga dapat memindah label ke sebuah baris terpisah: ```js no-beautify outer: for (let i = 0; i < 3; i++) { ... } ``` Perintah `continue` dapat juga digunakan dengan sebuah label. pada kasus ini, eksekusi kode berpindah ke perulangan label berikutnya. ````warn header="Label tidak mengizinkan \"lompat\" ke manapun" Label tidak mengizinkan kita untuk lompat ke sembarang tempat dalam kode. Misalnya, mustahil melakukan ini: ```js break label; // tidak lompak ke label di bawah label: for (...) ``` Direktif `break` harus berada di dalam blok kode. Secara teknis, setiap blok kode berlabel akan dilakukan, contoh.: ```js label: { // ... break label; // berjalan // ... } ``` ...Meskipun, 99,9% dari waktu `break` yang digunakan adalah loop dalam, seperti yang telah kita lihat pada contoh di atas. `continue` hanya dimungkinkan dari dalam loop. ```` ## Ringkasan Kita membahas 3 jenis perulangan: - `while` -- Kondisi diperiksa sebelum setiap perulangan. - `do..while` -- Kondisi diperiksa setelah setiap perulangan. - `for (;;)` -- Kondisi diperiksa sebelum setiap perulangan, pengaturan tambahan tersedia. Untuk membuat sebuah perulangan "tak terhinggaa", biasanya konstruksi `while(true)` digunakan. Demikian sebuah perulangan, seperti yang lainnya, dapat berhenti dengan perintah `break`. Jika kita tidak ingin melakukan apapun di perulangan saat ini dan ingin meneruskan ke yang berikutnya, kita dapat menggunakan perintah `continue`. `break/continue` mendukung label sebelum perulangan. Label adalah satu-satunya cara untuk `break/continue` menghindari loop bersarang untuk pergi ke luar ================================================ FILE: 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md ================================================ Supaya fungsionalitas `switch` persis sama, `if` harus memakai pembandingan ketat `'==='`. Tapi untuk string yang diberikan, `'=='` sederhana cukup bekerja juga. ```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!' ); } ``` Tolong ingat: konstruksi `browser == 'Chrome' || browser == 'Firefox' …` dipecah menjadi beberapa baris untuk kemudahan keterbacaan. Tapi konstruksi `switch` masih lebih bersih dan lebih deskriptif. ================================================ FILE: 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md ================================================ nilai penting: 5 --- # Tulis kembali "switch" menjadi "if" Tulis kode menggunakan `if..else` yang akan berkorespon dengan `switch` berikut: ```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 ================================================ Dua cek pertama berubah ke `case`. Cek ketiga dipecah menjadi dua 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; */!* } ``` Tolong ingat: `break` di paling bawah tidak wajib. Tapi kita taruh itu supaya kodenya future-proof. Di masa depan, ada kans bahwa kita ingin menambah `case` lebih, misalnya `case 4`. Dan jika kita lupa menambah break sebelum itu, di akhir `case 3`, akan ada error. Jadi itu lebih ke semacam asuransi diri. ================================================ FILE: 1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md ================================================ nilai penting: 4 --- # Tulis kembali "if" ke dalam "switch" Tulis ulang kode berikut menggunakan pernyataan `switch` tunggal: ```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 ================================================ # Pernyataan "switch" Pernyataan `switch` bisa mengganti pengecekan ganda `if`. Ia memberi cara lebih deskriptif untuk membandingkan nilai dengan varian ganda. ## Syntax `switch` punya satu atau lebih blok `case` dan satu default opsional. Rupanya seperti ini: ```js no-beautify switch(x) { case 'value1': // if (x === 'value1') ... [break] case 'value2': // if (x === 'value2') ... [break] default: ... [break] } ``` - Nilai `x` dicek ekaulitasnya secara ketat dengan nilaid dari `case` pertama (yaitu, `value1`) lalu ke kedua (`value2`) dan seterusnya. - Jika ekualitas ditemukan, `switch` mulai mengexekusi kode mulai dari `case` yang berkorespondensi, hingga `break` terdekat (atau hingga akhir `switch`). - Jika tak ada case yang cocok maka kode `default` diexekusi (jika ada). ## Contoh Contoh `switch` (kode yang dieksekusi dihighlight): ```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" ); } ``` Di sini `switch` mulai membandingkan `a` dari varian `case` pertama yang bernilai `3`. Kecocokan gagal. Lalu `4`. Ada kecocokan, jadi exekusi mulai dari `case 4` hingga `break` terdekat. **Jika tak ada `break` maka exekusi lanjut dengan `case` berikutnya tanpa pengecekan.** Contoh tanpa `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" ); */!* } ``` Di contoh di atas kita akan lihat exekusi sekuensial dari tiga `alert`: ```js alert( 'Exactly!' ); alert( 'Too big' ); alert( "I don't know such values" ); ``` ````smart header="Expresi apapun bisa berupa argumen `switch/case`" Baik `switch` maupun `case` membolehkan sembarang expresi. Misalnya: ```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"); } ``` Di sini `+a` memberikan `1`, yang dibandingkan dengan `b + 1` ndalam `case`, and the corresponding code is executed. ```` ## Pengelompokan "case" Beberapa varian `case` yang berbagi kode yang sama bisa dikelompokkan. Misalnya, jika kita ingin kode yang sama berjalan untuk `case 3` dan `case 5`: ```js run no-beautify let a = 3; 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.'); } ``` Sekarang baik `3` maupun `5` menampilkan pesan yang sama. Kemampuan "mengelompokkan" case adalah efek samping dari bagaimana `switch/case` bekerja tanpa `break`. Di sini exekusi dari `case 3` mulai dari baris `(*)` dan tembus ke `case 5`, karena tidak ada `break`. ## Tipe berpengaruh Mari kita tekankan bahwa pengecekan ekualitas selalu ketat. Nilainya harus bertipe sama supaya cocok. Misalnya, mari kita pertimbangkan kode ini: ```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. Untuk `0`, `1`, `alert` pertama berjalan. 2. Untuk `2` `alert` kedua berjalan. 3. Tapi untuk `3`, hasil dari `prompt` ialah string `"3"`, yang berbeda secara ketat `===` dengan angka `3`. Jadi kita punya kode mati di `case 3`! Varian `default` akan diexekusi. ================================================ FILE: 1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md ================================================ Tidak ada perbedaan. ================================================ FILE: 1-js/02-first-steps/15-function-basics/1-if-else-required/task.md ================================================ nilai penting: 4 --- # Apakah "else" dibutuhkan ? Fungsi berikut mengembalikan nilai `true` jika parameter `age` lebih besar daripada `18`. Jika tidak, fungsi tersebut akan meminta konfirmasi dan mengembalikan nilainya: ```js function checkAge(age) { if (age > 18) { return true; *!* } else { // ... return confirm('Did parents allow you?'); } */!* } ``` akankah fungsi bekerja berbeda jika `else` dibuang ? ```js function checkAge(age) { if (age > 18) { return true; } *!* // ... return confirm('Did parents allow you?'); */!* } ``` apakah ada perbedaan pada tingkah laku dari kedua variasi ? ================================================ FILE: 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md ================================================ Menggunakan tanda tanya operator `'?'`: ```js function checkAge(age) { return (age > 18) ? true : confirm('Did parents allow you?'); } ``` Using OR `||` (the shortest variant): Menggunakan OR `||` (variasi yang terpendek): ```js function checkAge(age) { return (age > 18) || confirm('Did parents allow you?'); } ``` Catatan bahwa tanda kurung sekitar `age > 18` tidak dibutuhkan disini. Mereka ada hanya untuk lebih enak dibaca. ================================================ FILE: 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md ================================================ nilai penting: 4 --- # Tulis ulang fungsi menggunakan '?' atau '||' Fungsi berikut mengembalikan nilai `true` jika parameter `age` lebih besar daripada `18`. Jika tidak, fungsi akan meminta sebuah konfirmasi dan mengembalikan nilainya. ```js function checkAge(age) { if (age > 18) { return true; } else { return confirm('Did parents allow you?'); } } ``` Tulis ulang fungsi, untuk melakukan dengan sama, tetapi tanpa `if`, dalam satu baris. Buatlah dua variasi dari `checkAge`: 1. Menggunakan sebuah tanda tanya operator `?` 2. Mengguunakan OR `||` ================================================ FILE: 1-js/02-first-steps/15-function-basics/3-min/solution.md ================================================ Solusi menggunakan `if`: ```js function min(a, b) { if (a < b) { return a; } else { return b; } } ``` Solusi menggunakan tanda tanya operator `'?'`: ```js function min(a, b) { return a < b ? a : b; } ``` P.S. Pada kasus persamaan `a == b` tidak menjadi penting apa yang dikembalikan ================================================ FILE: 1-js/02-first-steps/15-function-basics/3-min/task.md ================================================ nilai penting: 1 --- # Fungsi min(a,b) Tulis sebuah fungsi `min(a,b)` yang mengembalikan nilai paling terkecil dari dua angka `a` dan `b`. Sebagai gambaran: ```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 menjalankan 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 ================================================ nilai penting: 4 --- # Fungsi pow(X,n) Tulis sebuah fungsi `pow(x,n)` yang mengembalikkan nilai `x` pada pangkat `n`. Atau, dengan kata lain, kalikan `x` dengan dirinya sendiri sebanyak `n` kali dan mengembalikan hasilnya. ```js pow(3, 2) = 3 * 3 = 9 pow(3, 3) = 3 * 3 * 3 = 27 pow(1, 100) = 1 * 1 * ...* 1 = 1 ``` Buatlah sebuah halaman website yang meminta untuk nilai `x` dan `n`, dan tampilkan hasilnya pada `pow(x,n)`. [demo] P.S. pada tugas ini, fungsi seharusnya mendukung hanya nilai bilangan natural dari `n`: bilangan integer mulai dari `1`. ================================================ FILE: 1-js/02-first-steps/15-function-basics/article.md ================================================ # Fungsi Sering kali, kita harus melakukan tindakan yang sama pada skrip di banyak tempat Sebagai contoh, kita mengharuskan untuk menampilkan pesan yang terlihat indah ketika pengunjung melakukan log in, log out dan mungkin di tempat lain. Fungsi adalah program utama yang membentuk "struktur bangunan". Mereka memungkinkan kode untuk dipanggil sebanyak mungkin tanpa harus mengetik berulang-ulang. Kita telah melihat contoh dari fungsi built-in, seperti `alert(message)`, `prompt(message, default)` dan `confirm(question)`. ## Deklarasi Fungsi Untuk membuat fungsi, kita dapat menggunakan *deklarasi fungsi*. Itu terlihat seperti ini: ```js function showMessage() { alert( 'Hello everyone!' ); } ``` Katakunci `fungsi` ditulis duluan, lalu *nama fungsinya*, kemudian daftar semua *parameter* antara tanda kurung () (pada contoh di atas, tanda kurung kosong) dan bagian terakhir adalah fungsi kode, yang juga disebut sebagai "badan fungsi", antara kurung kurawal {}. ```js function name(parameter1, parameter2, ... parameterN) { ...body... } ``` Fungsi baru kita dapat disebut dengan nama: `showMessage()`. Sebagai contoh: ```js run function showMessage() { alert( 'Hello everyone!' ); } *!* showMessage(); showMessage(); */!* ``` Pemanggilan fungsi `showMessage()` mengeksekusi fungsi kode. Disini kita akan melihat pesan keluaran sebanyak dua kali. Contoh ini secara jelas memaparkan satu fungsi utama dari penggunaan fungsi: untuk menghindari duplikasi kode. Jika kita ingin mengubah pesan atau bagaimana pesan itu ingin ditampilkan, itu cukup untuk mengubah kode di satu tempat: yaitu fungsi yang menampilkannya. ## Variabel lokal Variabel yang diumumkan dalam fungsi hanya akan terlihat di dalam fungsi tersebut. Misalnya: ```js run function showMessage() { *!* let message = "Hello, I'm JavaScript!"; // variabel lokal */!* alert( message ); } showMessage(); // Halo, saya adalah JavaScript! alert( message ); // <-- Error! Variabel terlihat secara lokal menurut fungsi ``` ## Variabel luar Suatu fungsi juga dapat mengakses variabel luar, sebagai contoh: ```js run no-beautify let *!*userName*/!* = 'John'; function showMessage() { let message = 'Hello, ' + *!*userName*/!*; alert(message); } showMessage(); // Halo, John ``` Fungsi memiliki hak akses penuh kepada variabel luar fungsi. Juga variabel tersebut dapat diubah. Sebagai contoh: ```js run let *!*userName*/!* = 'John'; function showMessage() { *!*userName*/!* = "Bob"; // (1) changed the outer variable let message = 'Hello, ' + *!*userName*/!*; alert(message); } alert( userName ); // *!*John*/!* sebelum pemanggilan fungsi showMessage(); alert( userName ); // *!*Bob*/!*, nilai dimodifikasi oleh fungsi ``` Variabel luar hanya dapat digunakan jika tidak ada variabel lokal yang menggunakan. Jika terdapat variabel yang memiliki nama identik yang dideklarasikan di dalam fungsi, lalu variabel luar akan *tertumpukkan*. Sebagai gambaran, pada kode di bawah, fungsi menggunakan variabel lokal bernama `userName`. Variabel luar akan terabaikan: ```js run let userName = 'John'; function showMessage() { *!* let userName = "Bob"; // deklarasikan lokal variabel */!* let message = 'Hello, ' + userName; // *!*Bob*/!* alert(message); } // fungsi akan membuat dan menggunakan userName dirinya sendiri showMessage(); alert( userName ); // *!*John*/!*, tidak berubah, fungsi tidak dapat mengakses variabel luar ``` ```smart header="Global variables" Variabel yang dideklarasikan di luar dari fungsi, seperti variabel luar `userName` pada kode di atas, disebut sebagai *global*. Variabel global terlihat dari semua fungsi (terkecuali jika ditumpukkan oleh variabel lokal). Ini menjadi suatu cara yang baik untuk mengurangi penggunaan variabel global. Kode yang modern hanya memiliki sedikit bahkan tidak ada variabel global. Kebanyakan variabel dideklarasikan dan digunakan di dalam fungsi masing-masing. Kadang-kadang, mereka dapat digunakan untuk menyimpan data setingkat projek. ``` ## Parameters Kita dapat meloloskan data yang begitu acak kepada fungsi sebagai parameter (disebut juga sebagai *fungsi argumen*). Pada contoh di bawah, fungsi memiliki dua paramter: `from` dan `text`. ```js run function showMessage(*!*from, text*/!*) { // parameters: from, text alert(from + ': ' + text); } *!* showMessage('Ann', 'Hello!'); // Ann: Hallo! (*) showMessage('Ann', "What's up?"); // Ann: Ada apa? (**) */!* ``` Ketika fungsi dipanggil pada penanda `(*)` dan `(**)`, nilai yang diberikan dipindahkan ke variabel lokal `from` dan `text`. Lalu fungsi menggunakan nilai-nilai tersebut. Ini terdapat satu lagi contoh: kita memiliki variabel `from` dan memindahkannya ke fungsi. Dengan catatan: fungsi akan mengubah `from`, tapi perubahan ini tidak akan terlihat di luar fungsi, karena sebuah fungsi akan selalu mendapatkan salinan nilai: ```js run function showMessage(from, text) { *!* from = '*' + from + '*'; // membuat "from" terlihat lebih indah */!* alert( from + ': ' + text ); } let from = "Ann"; showMessage(from, "Hello"); // *Ann*: Hallo // Nilai dari "from" adalah sama, fungsi melakukan perubahan pada variabel lokal alert( from ); // Ann ``` Ketika sebuah nilai dilewatkan sebagai parameter fungsi, itu juga disebut *argumen*. Dengan kata lain, untuk meluruskan istilah-istilah ini: - Parameter adalah variabel yang tercantum di dalam tanda kurung dalam deklarasi fungsi (ini adalah istilah waktu deklarasi) - Argumen adalah nilai yang diteruskan ke fungsi saat dipanggil (ini adalah istilah waktu panggilan). Kami mendeklarasikan fungsi yang mencantumkan parameternya, lalu memanggilnya lewat argumen. Dalam contoh di atas, seseorang mungkin mengatakan: "fungsi `sayMessage` dideklarasikan dengan dua parameter, kemudian dipanggil dengan dua argumen: `from` dan `"Hello"`". ## Nilai default Jika suatu fungsi dipanggil, tetapi argumen tidak diberikan, maka nilai yang sesuai menjadi `tidak terdefinisi`. Sebagai gambaran, fungsi yang telah tersebut di atas `showMessage(from, text)` dapat dipanggil dengan argumen tunggal: ```js showMessage("Ann"); ``` Itu tidak terjadi kesalahan. Malah pemanggilan tersebut akan menghasilkan `"Ann: undefined"`. Tidak ada argumen untuk parameter `text`, jadi ini diasumsikan bahwa `text === undefined`. Jika kita ingin menggunakan suatu `text` "default" pada kasus ini, lalu kita dapat menentukannya setelah `=`: ```js run function showMessage(from, *!*text = "no text given"*/!*) { alert( from + ": " + text ); } showMessage("Ann"); // Ann: tidak diberikan teks ``` Sekarang, jika parameter `text` tidak ditentukan, parameter tersebut akan mengambil nilai `"no text give"` Disini `"no text give"` adalah string, tapi ia bisa menjadi suatu expresi nilai lebih kompleks, yang hanya dievaluasi dan ditetapkan jika tak ada nilai pada parameter. Jadi, ini juga mungkin ditetapkan: ```js run function showMessage(from, text = anotherFunction()) { // anotherFunction() hanya akan mengeksekusi jika tidak adanya teks // hasilnya menjjadi nilai pada teks } ``` ```smart header="Evaluasi parameter default" Di Javascript, parameter default dievaluasi tiap kali fungsi dipanggil tanpa parameter. Pada contoh di atas, `anotherFunction()` dipanggil tiap kali `showMessage()` dipanggil tanpa parameter `text`. Di sisi lain, itu dipanggil secara independen setiap kali `teks` hilang. ``` ### Alternatif nilai default parameter Terkadang akan dapat dimengerti untuk memberikan nilai default untuk variabel bukan didalam deklarasi fungsi, tapi di tahap selanjutnya, didalam proses eksekusinya. Untuk memeriksa parameter yang tidak ada, kita bisa membandingkannya dengan `undefined`: ```js run function showMessage(text) { // ... *!* if (text === undefined) { // if the parameter is missing text = 'empty message'; } */!* alert(text); } showMessage(); // empty message ``` ...Atau kita bisa menggunakan operator `||`: ```js // jika teks parameter tidak ada atau "", set variabel ke 'empty' function showMessage(text) { // if text is undefined or otherwise falsy, set it to 'empty' text = text || 'empty'; ... } ``` Javascript yang modern mendukung [nullish coalescing operator/operator penggabung nullish](info:nullish-coalescing-operator) `??`, akan lebih baik jika nilai falsy, seperti `0`, dianggap biasa: ```js run // jika tidak ada parameter "count", tampilkan "unknown" function showCount(count) { // if count is undefined or null, show "unknown" alert(count ?? "unknown"); } showCount(0); // 0 showCount(null); // unknown showCount(); // unknown ``` ## Mengembalikan nilai Fungsi dapat mengembalikan nilai kepada kode pemanggil sebagai hasil akhir. Contoh yang paling sederhana adalah fungsi yang menjumlahkan dua nilai: ```js run no-beautify function sum(a, b) { *!*return*/!* a + b; } let result = sum(1, 2); alert( result ); // 3 ``` Penulisan kata `return` dapat ditulis dimana saja pada fungsi. Ketika proses eksekusi kode mencapai kata tersebut, proses eksekusi akan berhenti, dan nilai akan dikembalikan kepada kode pemanggil (yang ditentukan pada variabel `result` di atas). Dapat dimungkinkan kehadiran banyak kata `return` pada suatu fungsi tunggal. Misalnya: ```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' ); } ``` Sangat dimungkinkan menggunakan kata `return` tanpa nilai. Hal ini akan menyebabkan fungsi untuk langsung keluar. Misalnya: ```js function showMovie(age) { if ( !checkAge(age) ) { *!* return; */!* } alert( "Showing you the movie" ); // (*) // ... } ``` Pada contoh kode di atas, jika `checkAge(age)` mengembalikan nilai `false`, maka `showMovie` tidak akan memproses `alert`. ````smart header="Jika fungsi tidak mengembalikan nilai, hal ini sama saja dengan mengembalikan nilai `undefined`" ```js run function doNothing() { /* kosong */ } alert( doNothing() === undefined ); // true ``` `return` kosong tanpa nilai memiliki nilai yang sama dengan `return undefined`: ```js run function doNothing() { return; } alert( doNothing() === undefined ); // true ``` ```` ````warn header="Jangan tambahkan baris baru diantara `return` dan nilainya" Untuk ekspresi yang lebih panjang pada penggunaan `return`, ini mungkin akan menciptakan suatu penulisan yang singkat untuk menuliskannya pada baris yang berbeda, seperti contoh berikut: ```js return (some + long + expression + or + whatever * f(a) + f(b)) ``` Hal ini tidak akan berhasil karena Javascript akan menganggap tanda titik koma setelah kata `return`. Hal ini juga akan berlangsung sama dengan contoh berikut: ```js return*!*;*/!* (some + long + expression + or + whatever * f(a) + f(b)) ``` Jadi, ia efektif menjadi kembalian kosong. Jika kita ingin expresi kembalian membungkus beberapa baris, kita mesti mulai di baris yang sama dengan `return`. Atau minimal taruh tanda kurung pembuka di sana seperti ini: ```js return ( some + long + expression + or + whatever * f(a) + f(b) ) ``` Dan ia akan berjalan seperti harapan kita. ```` ## Menamakan fungsi [#penamaan fungsi] Fungsi adalah tindakan. Sehingga nama fungsi mencerminkan kata kerja. Ia harus ringkas, sebisa mungkin harus akurat dan menjelaskan fungsi apa yang dikerjakan, sehingga ketika seseorang yang membaca kode tersebut mendapatkan penjelasan atau indikasi fungsi apa tersebut. Sudah menjadi khalayak umum bahwa untuk membuat fungsi harus dibarengi dengan awalan verbal yang secara tidak langsung menjelaskan tindakannya. Sebagai gambaran, fungsi yang dimulai dengan kata `"show"` biasanya melakukan tindakan menunjukkan sesuatu. Fungsi yang dimulai dengan... - `"get"` -- mengembalikan suatu nilai, - `"calc"` -- menghitungkan sesuatu, - `"create"` -- membuat sesuatu, - `"check"` -- melakukan pengecekkan dan mengembalikan nilai boolean, dst. Contoh dari nama yang diberikan di atas: ```js no-beautify showMessage(..) // menampilkan pesan getAge(..) // mengembalikan nilai umur (bagaimanapun mengembalikkan umur) calcSum(..) // menghitung penjumlahan dan mengembalikan hasilnya createForm(..) // membuat formulir (dan biasanya mengembalikan nilai) checkPermission(..) // pengecekkan terhadap ijin, mengembalikan true/false ``` Dengan awalan yang tertera, secara sekilas pada nama fungsi memberikan pemahaman tindakan apa yang dilakukan dan nilai apa yang dikembalikan. ```smart header="Satu fungsi -- Satu aksi" Fungsi sebaiknya mengerjakan apa yang telah ditulis pada namanya, tidak lebih. Dua tindakan independen biasanya berasal dari dua fungsi, walaupun mereka dipanggil secara bersamaan (pada kasus ini, kita mampu membuat fuungsi ketiga yang memanggil keduanya). Sedikit contoh yang mematahkan aturan ini: - `getAge` -- akan menjadi buruk jika menunjukkan `alert` yang menunjukkan umur (seharusnya hanya get). - `createForm` -- akan menjadi buruk jika fungsi tersebut mengubah dokumen, menambahkan formulir pada dokumen tersebut (seharusnya hanya membentuk dokumen dan mengembalikannya). - `checkPermission` -- akan menjadi buruk jika fungsi tersebut menampilkan pesan `akses diberikan/ditolak` (seharusnya hanya melakukan pengecekkan dan mengembalikkan nilainya). Pada contoh-contoh ini diasumsikan arti-arti umum pada kata awalan. Kamu dan tim kamu memiliki kehendak bebas untuk menentukan arti lainnya, tapi biasanya penentuan arti tersebut tidaklah jauh berbeda. Pada contoh lain, kamu seharusnya memiliki pemahaman yang kuat dari arti kata awalan yang digunakan, kata awalan apa yang dapat digunakan pada fungsi dan tidak dapat diguunakan. Semua kata awalan fungsi harus mengikuti aturan tertentu. Dan tim seharusnya dapat saling memberikan pemahaman satu sama lain. ``` ```smart header="Ultrashort function names" Fungsi yang *digunakan secara sering* kadang-kadang memiliki nama yang sangat pendek. Sebagai contoh, framework [jQuary](http://jquary.com) mendefinisikan fungsi dengan simbol `$`. Library [Lodash](https://lodash.com) memiliki fungsi inti yang dinamakan dengan `_`. Hal-hal tersebut adalah pengecualian. Secara umum, nama fungsi sebaiknya ringkas dan menjelaskan maksudnya. ``` ## Fungsi == komen Fungsi seharusnya memiliki nama yang pendek dan hanya melakukan satu tindakan. Jika tindakan tersebut mengerjakan hal yang cukup kompleks, mungkin sebaiknya fungsi tersebut dibagi menjadi fungsi yang lebih sederhana. Kadang-kadang, mengikuti aturan ini tidaklah mudah, tetapi tentu adalah hal yang baik. Fungsi yang terpisah bukan hanya mudah untuk diuji coba dan debug -- kehadirannya sangat baik untuk diberikan komentar! Sebagai gambaran, bandingkan dua fungsi `showPrimes(n)` di bawah. Setiap satu dari keluarannya [bilangan prima](https://en.wikipedia.org/wiki/Prime_number) mencapai hingga `n`. Variasi pertama menggunakan 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 ); // bilangan prima } } ``` Pada variasi yang kedua mengguunakan fungsi tambahan `isPrime(n)` untuk dilakukan uji coba keutamaannya: ```js function showPrimes(n) { for (let i = 2; i < n; i++) { *!*if (!isPrime(i)) continue;*/!* alert(i); // bilangan prima } } function isPrime(n) { for (let i = 2; i < n; i++) { if ( n % i == 0) return false; } return true; } ``` Pada variasi yang kedua lebih mudah untuk dipahami, benarkan ? Malah daripada potongan kode yang kita lihat pada tindakan (`isPrime`). Kadang-kadang, orang-orang merujuk kepada penulisan kode yang *menjelaskan dirinya*. Jadi, fungsi dapat dibuat walaupun kita tidak terlalu sering menggunakannya. Mereka membuat kode lebih terstruktur dan lebih mudah untuk dibaca. ## Kesimpulan Deklarasi fungsi terlihat seperti ini: ```js function name(parameters, delimited, by, comma) { /* code */ } ``` - Nilai yang diberikan kepada fungi sebagai parameter dipindahkan ke variabel lokal. - Fungsi mungkin dapat diakses dengan variabel luar. Tetapi fungsi tersebut hanya dapat bekerja melalui internal fungsi keluar. Kode di luar daripada fungsi bersangkutan tidak dapat melihat variabel lokal. - Fungsi dapat mengembalikan suatu nilai. Jika tidak demikian, maka akan mengembalikan nilai `undefined`. Untuk membuat kode yang bersih dan mudah untuk dipahami, sangat dianjurkan untuk menggunakan variabel lokal dan parameter pada fungsi, tidak mengguunakan variabel luar / variabel global. Akan menjadi hal yang mudah untuk dipahami pada fungsi yang mendapatkan parameter, yang bekerja dengan parameter tersebut dan mengembalikan nilainya daripada fuungsi yang tidak memilki parameter, tetapi melakukan modifikasi terhadap variabel luar akan memiliki efek samping. Penamaan fungsi: - Nama seharusnya ditulis dengan jelas dan mendeskripsikan apa yang dikerjakan. Ketika kita melihat fungsi dipanggil pada kode, penamaan yang baik secara langsuung akan memberikan kita pemahaman apa yang dikerjakan dan nilai apa yang dikembalikan. - Fungsi adalah tindakan, sehingga nama fungsi biasanya kata kerja (verbal). - Banyak nama-nama awalan fungsi seperti `create`, `show`, `get`, `check` dan lainnya. Gunakan awalan tersebut untuk memberikan kata kunci apa yang dikerjakan oleh fungsi. Fungsi adalah bagaikan fondasi bangunan dari skrip. Sekarang, kita telah mempelajari dasarnya, sehingga sekarang kita dapat memumlai untuk membuat dan menggunakannya. Tapi hal itu baru permulaan dari awal perjalanan. Kita akan kembali menggunakan mereka berulang kali, secara terus-menerus menggunakan secara mendalam hingga fitur yang lebih kompleks. ================================================ FILE: 1-js/02-first-steps/16-function-expressions/article.md ================================================ # Expresi fungsi Di JavaScript, fungsi bukan "struktur bahasa magis", melaikan satu bentuk nilai spesial. Syntax yang kita gunakan sebelumnya disebut *Deklarasi Fungsi*: ```js function sayHi() { alert( "Hello" ); } ``` Ada syntax lain untuk membuat fungsi yang disebut *Expresi Fungsi*. Rupanya seperti ini: ```js let sayHi = function() { alert( "Hello" ); }; ``` Di sini, fungsi dibuat dan diisi variabel secara explisit, seperti nilai lain manapun. Tak peduli bagaimana fungsi didefinisi, ia hanya suatu nilai yang disimpan dalam variabel `sayHi`. Arti dari sampel kode ini sama: "buatlah fungs dan taruhlah di dalam variabel `sayHi`". Kita bahkan bisa mencetak nilai itu menggunakan `alert`: ```js run function sayHi() { alert( "Hello" ); } *!* alert( sayHi ); // menampilkan kode fungsi */!* ``` Tolong ingat bahwa baris terakhir tidak menjalankan fungsi, karena tak ada tanda kurung setelah `sayHi`. Ada bahasa pemrograman di mana satu penyebutan nama fungsi menyebabkan exekusi fungsi, tapi JavaScript tak seperti itu. Di JavaScript, fungsi adalah nilai, jadi kita bisa menghadapinya sebagai nilai. Kode di atas menunjukkan representasi stringnya, yang mana kode sumbernya. Pastinya, fungsi adalah nilai spesial, dengan anggapan bahwa kita bisa memanggilnya seperti `sayHi()`. Tapi ia tetaplah nilai. Jadi kita bisa memakainya seperti macam nilai lainnya. Kita bisa mengkopi fungsi ke variabel lain: ```js run no-beautify function sayHi() { // (1) buat alert( "Hello" ); } let func = sayHi; // (2) kopi func(); // Hello // (3) jalankan kopinya (ia bekerja)! sayHi(); // Hello // ini juga masih bekerja (kenapa tidak) ``` Inilah yang terjadi di atas secara detil: 1. Deklarasi Fungsi `(1)` membuat fungsi dan menaruhnya ke variabel bernama `sayHi`. 2. Baris `(2)` mengkopinya ke variabel `func`. Tolong ingat lagi: tak ada tanda kurung setelah `sayHi`. Jika ada, maka `func = sayHi()` akan menulis *hasil panggilan* `sayHi()` ke `func`, bukan *fungsi* `sayHi` itu sendiri. 3. Sekarang fungsi bisa dipanggil baik sebagai `sayHi()` maupun `func()`. Catat bahwa kita jusa bisa menggunakan Expresi Fungsi untuk mendeklarasi `sayHi`, di baris pertama: ```js let sayHi = function() { alert( "Hello" ); }; let func = sayHi; // ... ``` Semua akan berjalan sama. ````smart header="Kenapa ada semicolon di akhir?" Kamu mungkin penasaran, kenapa Expresi Fungsi punya semicolon `;` di akhir, tapi Deklarasi Fungsi tidak: ```js function sayHi() { // ... } let sayHi = function() { // ... }*!*;*/!* ``` Jawabannya simpel: - `;` tidak dibutuhkan di akhir blok kode dan struktur syntax yang memakai mereka seperti `if { ... }`, `for { }`, `function f { }` dll. - Expresi Fungsi digunakan di dalam pernyataan: `let sayHi = ...;`, sebagai nilai. Ia bukan blok kode, tapi lebih ke penetapan. Semicolon `;` disarankan di akhir pernyataan, tak peduli nilainya apa. Jadi semicolon di sini tak ada hubungannya dengan Expresi Fungsi itu sendiri, ia hanya menstop pernyataan. ```` ## Fungsi callback Ayo kita lihat pada contoh lain mengoper fungsi sebagai nilai dan menggunakan expresi fungsi. Kita akan menulis fungsi `ask(question, yes, no)` dengan tiga parameter: `question` : Teks pertanyaan `yes` : Fungsi yang berjalan jika jawabannya "Yes" `no` : Fungsi yang berjalan jika jawabannya "No" Fungsinya akan menanyakan `question` dan, tergantung jawabannya pengguna, panggil `yes()` atau `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." ); } // usage: functions showOk, showCancel are passed as arguments to ask ask("Do you agree?", showOk, showCancel); ``` Pada praktiknya, fungsi macam ini agak berguna. Perbedaan besar antara `ask` kehidupan-nyata dan contoh di atas adalah fungsi kehidupan-nyata memakai cara komplex untuk berinteraksi dengan pengguna daripada sekedar `confirm`. Di peramban, fungsi macam ini biasanya menarik window pertanyaan menarik. Tapi itu cerita lain lagi. **Argumen `showOk` dan `showCancel` dari `ask` dipanggil *fungsi callback* atau hanya *callback*.** Idenya adalah kita mengoper fungsi dan berharap ia "dipanggil kembali" kemudian jika dibutuhkan. Pada kasus kita, `showOk` menjadi callback untuk jawaban "yes", dan `showCancel` untuk jawaban "no". Kita bisa memakai Expresi Fungsi untuk menulis fungsi yang sama lebih pendek: ```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."); } ); */!* ``` Di sini, fungsi dideklarasi tepat di dalam panggilan `ask(...)`. Mereka tak punya nama, jadinya disebut *anonymous*. Fungsi macam ini tak bisa diakses di luar `ask` (karena mereka tak diset ke variabel), tapi itulah yang kita mau di sini. Kode macam ini muncul di script kita secara sangat alamiah, ia ada di dalam semangat JavaScript. ```smart header="Fungsi adalah nilai yang mewakili \"aksi\"" Nilai reguler seperti string atau angka mewakiliki *data*. Fungsi bisa dianggap sebagai *aksi*. Kita bisa mengopernya antara variabel dan menjalankannya kapanpun kita mau. ``` ## Expresi Fungsi vs Deklarasi Fungsi Mari kita rumuskan kunci perbedaan antara Deklarasi dan Expresi Fungsi. Pertama, syntax: bagaimana membedakan antara mereka dalam kode. - *Deklarasi Fungsi:* fungsi, yang dideklarasi sebagai pernyataan terpisah, dalam aliran kode utama. ```js // Deklarasi Fungsi function sum(a, b) { return a + b; } ``` - *Expresi Fungsi:* fungsi, yang dibuat dalam expresi atau dalam konstruksi syntax lain. Di sini, fungsi dibuat pada sisi kanan dari "expresi penetapan" `=`: ```js // Expresi Fungsi let sum = function(a, b) { return a + b; }; ``` Perbedaan yang lebih halus ialah *ketika* fungsi dibuat oleh JavaScript engine. **Expresi Fungsi dibuat ketika exekusi mencapainya dan hanya dapat dipakai saat itu saja.** Sekali aliran exekusi melewati sisi kanan penetapan `let sum = function…` -- di sinilah, fungsi dibuat dan bisa digunakan (diset, dipanggil, dll. ) dari sekarang. Deklarasi Fungsi berbeda. **Deklarasi Fungsi bisa dipanggil lebih cepat dari ia didefinisi.** Misalnya, Deklarasi Fungsi global terlihat di seluruh script, tak peduli di mana ia berada. Itu karena algoritma internal. Saat JavaScript siap menjalankan script, pertama ia melihat Deklarasi Fungsi global di dalam dan membuat fungsi. Kita bisa anggap itu sebagai "tahap inisialisasi". Dan setelah semua Deklarasi Fungsi diproses, kodenya diexekusi. Jadi ia punya akses ke fungsi-fungsi ini. Misalnya, ini berjalan: ```js run refresh untrusted *!* sayHi("John"); // Hello, John */!* function sayHi(name) { alert( `Hello, ${name}` ); } ``` Deklarasi Fungsi `sayHi` dibuat saat JavaScript siap memulai scriptnya dan terlihat di manapun di dalam. ...Jika ia Expresi Fungsi, maka ia tak akan berjalan: ```js run refresh untrusted *!* sayHi("John"); // galat! */!* let sayHi = function(name) { // (*) tak ada magic lagi alert( `Hello, ${name}` ); }; ``` Expresi Fungsi dibuat saat exekusi mencapainya. Itu hanya akan terjadi pada baris `(*)`. Sudah telat banget. Fitur spesial lain dari Deklarasi Fungsi ialah scope blok mereka. **Di mode ketat, ketika Deklarasi Fungsi berada di jangkauan blok kode, ia terlihat di manapun di dalam blok. Tapi tidak di luarnya.** Misalnya, bayangkan kita harus mendeklarasi fungsi `welcome()` tergantung variabel `age` yang kita dapat saat runtime. Lalu kita berencana akan menggunakannya kemudian. Jika kita memakai Deklarasi Fungsi, ia tak akan bekerja sesuai harapan: ```js run let age = prompt("What is your age?", 18); // secara kondisional mendeklarasi fungsi if (age < 18) { function welcome() { alert("Hello!"); } } else { function welcome() { alert("Greetings!"); } } // ...pakai ini kemudian *!* welcome(); // Error: welcome is not defined */!* ``` Itu karena Deklarasi Fungsi cuma terlihat di dalam blok kode yang ia tinggali. Ini contoh lainnya: ```js run let age = 16; // ambil 16 sebagai contoh if (age < 18) { *!* welcome(); // \ (berjalan) */!* // | function welcome() { // | alert("Hello!"); // | Deklarasi Fungsi tersedia } // | di manapun dalam blok kode tempat dia dideklarasi // | *!* welcome(); // / (berjalan) */!* } else { function welcome() { alert("Greetings!"); } } // Di sini kita di luar kurung kurawal, // jadi kita tak bisa melihat Deklarasi Fungsi yang dibuat di dalamnya. *!* welcome(); // Error: welcome is not defined */!* ``` Apa yang bisa kita lakukan supaya `welcome` terlihat di luar `if`? Pendekatan yang benar ialah menggunakan Expresi Fungsi dan menetapkan `welcome` ke variabel yang dideklarasi di luar `if` dan punya visibilitas yang cukup. Kode ini berjalan sesuai harapan: ```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(); // sekarang ok */!* ``` Atau kita bisa menyederhanakannya lebih lanjut menggunakan operator tanda tanya `?`: ```js run let age = prompt("What is your age?", 18); let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); }; *!* welcome(); // sekarang ok */!* ``` ```smart header="Kapan harus memilih Deklarasi Fungsi versus Expresi Fungsi?" Sebagai aturan praktis, saat kita harus mendeklarasi fungsi, hal pertama yang kita pertimbangkan ialah syntax Deklarasi Fungsi. Ia memberi kebebasan lebih dalam bagaimana mengorganisir kode kita, karena kita bisa memanggil fungsi macam ini sebelum mereka dideklarasi. Itu juga untuk keterbacaan yang lebih baik, karena lebih mudah melihat `function f(…) {…}` dalam kode ketimbang `let f = function(…) {…}`. Deklarasi Fungsi lebih "eye-catching". ...Tapi jika Deklarasi Fungsi tak cocok untuk beberapa alasan, atau kita butuh deklarasi kondisional (kita sudah lihat contohnya), maka Expresi Fungsi sebaiknya digunakan. ``` ## Kesimpulan - Fungsi adalah nilai. Mereka bisa diset, dikopi atau dideklarasi di kode manapun. - Jika fungsi dideklarasi sebagai pernyataan terpisah di aliran kode utama, ia disebut "Deklarasi Fungsi". - Jika fungsi dibuat sebagai bagian expresi, ia disebut "Expresi Fungsi". - Deklarasi Fungsi diproses sebelum blok kode diexekusi. Mereka terlihat di manapun dalam blok. - Expresi Fungsi dibuat saat aliran exekusi mencapai mereka. Di banyak kasus saat kita harus mendeklarasi fungsi, Deklarasi Fungsi disenangi, karena ia terlihat sebelum deklarasi itu sendiri. Itu memberi kita flexibilitas lebih dalam organisasi kode, dan biasa lebih mudah terbaca. Jadi sebaiknya kita gunakan Expresi Fungsi hanya saat Deklarasi Fungsi tak cocok digunakan. Kita sudah melihat beberapa contoh itu di bab ini, dan kita akan melihat lebih banyak lagi nanti. ================================================ 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.") */!* ); ``` Terlihat pendek dan bersih, kan? ================================================ FILE: 1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md ================================================ # Rewrite with arrow functions Ganti Expresi Fungsi dengan fungsi panah dalam kode berikut: ```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 ================================================ # Dasar-dasar fungsi Arrow Terdapat sintaks lain yang sangat sederhana dan ringkas untuk membuat fungsi-fungsi, bahkan sering kali lebih baik ketimbang fungsi-fungsi *Expression*. Disebut sebagai "fungsi *arrow* (panah)", karena sintaks fungsinya terlihat seperti ini: ```js let func = (arg1, arg2, ..., argN) => expression ``` ...Ini membuat sebuah fungsi `func` yang menerima argumen `arg1..argN`, kemudian mengevaluasi `expression` yang ada di sisi kanan serta kegunaannya dan mengembalikan hasilnya. Dalam kata lain, fungsi tersebut adalah versi yang lebih pendek dari: ```js let func = function(arg1, arg2, ..., argN) { return expression; }; ``` Mari kita lihat contoh konkritnya: ```js run let sum = (a, b) => a + b; /* Fungsi arrow ini adalah bentuk yang lebih pendek dari: let sum = function(a, b) { return a + b; }; */ alert( sum(1, 2) ); // 3 ``` Seperti yang bisa dilihat, perhatikan `(a, b) => a + b` berarti sebuah fungsi yang menerima dua argumen yang diberinama `a` dan `b`. Ketika eksekusi, fungsi tersebut mengevaluasi ekpresi `a + b` dan mengembalikan hasilnya. - Jika kita memiliki satu argumen saja, maka *parentheses* di sekitar parameter bisa diabaikan, membuat sintaksnya jadi semakin pendek. Sebagai contoh: ```js run *!* let double = n => n * 2; // secara kasar sama dengan: let double = function(n) { return n * 2 } */!* alert( double(3) ); // 6 ``` - Jika tidak ada argumen, *parentheses* akan kosong(tapi harus ditunjukkan): ```js run let sayHi = () => alert("Hello!"); sayHi(); ``` Fungsi-fungsi *arrow* dapat digunakan dengan cara yang sama dengan fungsi ekpresi (*expression*). Untuk membuat sebuah fungsi secara dinamis contohnya: ```js run let age = prompt("What is your age?", 18); let welcome = (age < 18) ? () => alert('Hello') : () => alert("Greetings!"); welcome(); ``` Fungsi-fungsi *arrow* bisa terlihat tidak familiar dan sulit dibaca pada awalnya, namun hal tersebut bisa cepat berubah seiring dengan mata (kita) yang terbiasa dengan struktur tersebut. Fungsi *arrow* sangat memudahkan untuk sintaks-sintaks sederhana, saat kita terlalu malas untuk menulis banyak kata. ## Fungsi *arrow* multi-baris Contoh-contoh di atas mengambil argumen dari sisi kiri dari `=>` dan mengevaluasi ekpresi di sisi kanan. Terkadang kita memerlukan sesuatu yang agak sedikit rumit, seperti ekspresi atau pernyataan (*statement*) multi-baris (berbaris-baris). Hal tidaklah tidak mungkin, tapi kita harus menutup ekspresi atau pernyataan tersebut dengan tanda kurung kurawal. Kemudian menggunakan sebuah `return` normal diantaranya. Seperti ini: ```js run let sum = (a, b) => { // tanda kurung kurawal membuka fungsi multi-baris let result = a + b; *!* return result; // jika kita menggunakan kurung kurawal, selajutnya kita perlu menuliskan "return" */!* }; alert( sum(1, 2) ); // 3 ``` ```smart header="More to come" Kini kita memuji fungsi arrow karena keringkasannya. Namun tidak hanya itu! Fungsi arrow memiliki fitur-fitur menarik lainnya. Untuk mempelajarinya lebih mendalam, pertama-tama kita perlu untuk mengetahui aspek-aspek lain dari JavaScript, jadi kita akan kembali (mempelajari) fungsi arrow di bab selanjutnya . Untuk sekarang, kita sudah bisa menggunakan fungsi arrow untuk sintaks-sintaks sebaris dan callback. ``` ## Ringkasan Fungsi-fungsi *arrow* itu memudahkan untuk sintaks-sintaks yang hanya sebaris. Fungsi *arrow* juga hadir dengan dua cara: 1. Tanpa tanda kurung kurawal: `(...args) => expression` -- sisi kanan adalah sebuah ekspresi: fungsi tersebut mengevaluasi ekspresi dan mengembalikan hasilnya. 2. Dengan tanda kurung kurawal: `(...args) => { body }` -- tanda kurung kurawal membuat kita bisa menuliskan pernyataan-pernyataan berbaris-baris/multi-baris dalam fungsi tersebut, tapi kita perlu untuk menulsikan `return` untuk mengembalikan (hasil) sesuatu. ================================================ FILE: 1-js/02-first-steps/18-javascript-specials/article.md ================================================ # Spesial JavaScript Bab ini secara singkat merekap fitur JavaScript yang sudah kita pelajari sekarang, membayar perhatian khusus ke momen-momen halus. ## Struktur kode Pernyataan didelimisi dengan semicolon: ```js run no-beautify alert('Hello'); alert('World'); ``` Biasanya, line-break juga diperlakukan sebagai delimiter, jadi itu juga akan bekerja: ```js run no-beautify alert('Hello') alert('World') ``` Itu disebut "penyisipan semicolon otomatis". Kadang ia tidak bekerja, misalnya: ```js run alert("There will be an error after this message") [1, 2].forEach(alert) ``` Kebanyakan panduan codestyle setuju bahwa kita sebaiknya menaruh semicolon di tiap akhir pernyataan. Semicolon tak dibutuhkan setelah blok kode `{...}` dan konstruksi syntax dengan mereka yang seperti loop: ```js function f() { // semicolon tak dibutuhkan setelah deklarasi fungsi } for(;;) { // semicolon tak dibutuhkan setelah loop } ``` ...Tapi meskipun kita taruh semicolon "extra" di suatu tempat, itu bukan galat. Ia akan diabaikan. Lebih lanjut di: . ## Mode ketat Untuk mengaktifkan penuh semua fitur modern JavaScript, kita sebaiknya mulai script dengan `"use strict"`. ```js 'use strict'; ... ``` Directive ini harus ada di paling atas script atau di awal badan fungsi. Tanpa `"use strict"`, apapun akan bekerja, tapi beberapa fitur bersikap dengan cara kuno, "kompatibel". Secara umum kita akan pilih sikap modern. Beberapa fitur modern bahasa ini (seperti kelas yang akan kita pelajari di kemudian) mengaktifkan mode ketat secara implisit. Lebih lanjut di: . ## Variabel Bisa dideklarasi menggunakan: - `let` - `const` (konstan, tak bisa berubah) - `var` (kuno, akan lihat kemudian) Nama variabel bisa mengandung: - Huruf dan digit, tapi karakter pertama bisa tak boleh digit. - Karakter `$` dan `_` itu normal, setara dengan huruf. - Alfabet non-latin dan hieroglyph juga boleh, tapi jarang dipakai. Variabel adalah tipe dinamis. Mereka bisa menyimpan nilai apapun: ```js let x = 5; x = "John"; ``` Terdapat 8 tipe data: - `number` untuk floating-point(bilangan pecahan) dan integer, - `bigint` untuk integer yang sangat panjang, - `string` untuk string, - `boolean` untuk nilai logik: `true/false`, - `null` tipe data dengan nilai tunggal `null`, yang sama dengan "empty/kosong" atau "does not exist/tidak ada nilai", - `undefined` -- tipe data dengan nilai tunggal `undefined`, yang sama dengan "not assigned/belum didefinisikan", - `object` dan `symbol` -- untuk struktur data yang kompleks dan identifier unik, sampai saat ini kita belum belajar ini. Operator `typeof` mengembalikan tipe untuk satu nilai, dengan dua pengecualian: ```js typeof null == "object" // galat di bahasa typeof function(){} == "function" // fungsi diperlakukan spesial ``` Lebih lanjut di: and . ## Interaksi Kita menggunakan peramban sebagai lingkungan kerja, jadi fungsi UI dasar akan menjadi: [`prompt(question, [default])`](mdn:api/Window/prompt) : Menanyakan `question`, dan mengembalikan apa yang pengunjung isikan atau `null` jika mereka mengklik "cancel". [`confirm(question)`](mdn:api/Window/confirm) : Menanyakan `question` dan menyarakan memilih antara Ok dan Cancel. Pilihannya dikembalikan sebagai `true/false`. [`alert(message)`](mdn:api/Window/alert) : Menampilkan a `message`. Semua fungsi ini adalah *modal*, mereka menyela exekusi kode dan mencegah pengunjung dari berinteraksi dengan laman hingga mereka menjawab. Misalnya: ```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 ``` Lebih lanjut di: . ## Operator JavaScript mendukung operator berikut: Arithmatika : Regular: `* + - /`, juga `%` untuk remainder dan `**` untuk pangkat bilangan. Operator biner plus `+` menggabungkan string. Dan juka ada operan yang string, maka yang lainnya akan diubah menjadi string juga: ```js run alert( '1' + 2 ); // '12', string alert( 1 + '2' ); // '12', string ``` Penetapan : Ada penetapan simpel: `a = b` dan penetapan kombinasi seperti `a *= 2`. Bitwise : Operator bitwise bekerja dengan integer 32-bit di bit-level paling kecil: lihat [docs](mdn:/JavaScript/Reference/Operators/Bitwise_Operators) ketika mereka dibutuhkan. Kondisional : Satu-satunya operator dengan tiga parameter: `cond ? resultA : resultB`. Jika `cond` truthy, mengembalikan `resultA`, jika tidak `resultB`. Operator logika : Logika AND `&&` dan OR `||` menyajikan evaluasi sirkuit-pendek dan mengembalikan nilai di mana ia berhenti. Logika NOT `!` mengkonversi operand ke tipe boolean dan mengembalikan nilai kebalikannya. Nullish coalescing operator/Operator penggabung nullish : Operator `??` menyediakan cara untuk memilih nilai yang terdefinisikan dari sebuah daftar variabel. Hasil dari `a ?? b` adalah a kecuali jika nilainya `null/undefined`, lalu `b`. Perbandingan : Persamaan nilai `==` dari type yang berbeda akan mengubahnya menjadi angka (kecuali `null` dan `undefined` yang sama dengan nilai itu sendiri), jadi contoh dibawah adalah sama: ```js run alert( 0 == false ); // true alert( 0 == '' ); // true ``` Pembandingan lainnya tentu saja mengubah nilainya menjadi angka. Operator pembanding `===` tidak melakukan perubahan tipe: untuk operator pembanding ini, berbeda tipe sama dengan berbeda nilai. Nilai `null` dan `undefined` adalah spesial: mereka sama `==` satu sama lainnya dan tidak sama dengan nilai lain manapun. Pembanding lebih/kurang dari membandingkan string karakter-demi-karakter, tipe lain akan diubah menjadi angka. Operator lainnya : Ada beberapa operator lainnya, seperti operator koma. Lebih lanjut di: , , , . ## Loop - Kita meliput 3 tipe loop: ```js // 1 while (condition) { ... } // 2 do { ... } while (condition); // 3 for(let i = 0; i < 10; i++) { ... } ``` - Variabel yang dideklarasi di loop `for(let...)` terlihat cuma di dalam loop. Tapi kita juga bisa membuang `let` dan memakai kembali variabel yang sudah eksis. - Directive `break/continue` membolehkan untuk keluar iterasi loop/current. Guakan label untuk menghancurkan loop bersarang. Detil di: . Nanti kita akan pelajari tipe loop lainnya untuk berhadapan dengan object. ## Konstruksi "switch" Konstruksi "switch" bisa mengganti pengecekan ganda `if`. Ia memakai `===` (ekualitas ketat) untuk pembandingan. Misalnya: ```js run let age = prompt('Your age?', 18); switch (age) { case 18: alert("Won't work"); // hasil dari prompt adalah string, bukan angka break; case "18": alert("This works!"); break; default: alert("Any value not equal to one above"); } ``` Detal di: . ## Fungsi Kita meliput tiga cara membuat fungsi di JavaScript: 1. Deklarasi Fungsi: fungsi di aliran kode utama ```js function sum(a, b) { let result = a + b; return result; } ``` 2. Expresi Fungsi: fungsi di dalam kontex expresi ```js let sum = function(a, b) { let result = a + b; return result; }; ``` 3. Fungsi panah: ```js // expresi di sisi kanan let sum = (a, b) => a + b; // atau syntax baris-ganda dengan { ... }, butuh kembalian di sini: let sum = (a, b) => { // ... return a + b; } // tanpa argumen let sayHi = () => alert("Hello"); // dengan argumen tunggal let double = n => n * 2; ``` - Fungsi bisa punya variabel lokal: mereka yang dideklarasi dalam badannya. Variabel macam itu cuma terlihat di dalam fungsi. - Parameter bisa punya nilai default: `function sum(a = 1, b = 2) {...}`. - Fungsi selalu mengembalikan sesuatu. Jika tak ada pernyataan `return`, maka kembaliannya `undefined`. Detil: lihat , . ## Lebih banyak yang akan datang Ini daftar ringkas fitur JavaScript. Untuk sekarang kita belajar hanya dasar. Kemudian di tutorial nanti kamu akan menemui fitur JavaScript yang lebih canggih dan spesial. ================================================ FILE: 1-js/02-first-steps/index.md ================================================ # JavaScript Dasar Mari kita belajar dasar-dasar pembuatan skrip. ================================================ FILE: 1-js/03-code-quality/01-debugging-chrome/article.md ================================================ # Mendebug di Chrome Sebelum menulis kode lebih komplex, ayo kita bahas tentang mendebug. [Mendebug](https://en.wikipedia.org/wiki/Debugging) ialah proses mencari dan membetulkan galat di dalam script. Semua peramban modern dan kebanyakan lingkungan lain mendukung debugging tools -- UI spesial di developer tools yang membuat debugging jauh lebih mudah. Ia juga membolehkan menjejak kode pelan-pelan untuk melihat apa yang sebenarnya terjadi. Kita akan menggunakan Chrome di sini, karena ia punya cukup fitur, kebanyakan peramban lain punya proses serupa`. ## Panel "Sources" Versi Chrome kamu mungkin terlihat berbeda, tapi tetap kelihatan jelas bedanya. - Buka [laman contoh](debugging/index.html) di Chrome. - Nyalakan developer tools dengan `key:F12` (Mac: `key:Cmd+Opt+I`). - Pilih panel `Sources`. Inilah apa yang mesti kamu lihat jika kamu baru melakukannya pertama kali: ![](chrome-open-sources.svg) Tombol toggler membuka tab dengan file. Klik itu dan pilih `hello.js` di tree view. Inilah yang mestinya muncul: ![](chrome-tabs.svg) Panel Sources punya 3 bagian: 1. Pane **File Navigator** melist HTML, JavaScript, CSS, dan file lainnya, termasuk image yang dilampirkan ke laman. Extensi Chrome bisa muncul juga di sini. 2. Pane **Editor Kode** menampilkan kode sumber. 3. Pane **Javascript Debugging** untuk debugging, kita akan mengexplorasi itu segera. Sekarang kamu bisa mengklik toggler yang sama lagi untuk menyembunyikan daftar sumber daya dan memberi spasi ke kode. ## Konsol Jika kita menekan `key:Esc`, maka konsol terbuka di bawah. Kita bisa mengetik command di sana dan menekan `key:Enter` untuk exekusi. Setelah pernyataan diexekusi, hasilnya ditampilkan di bawah. Misalnya, `1+2` menghasilkan `3`, dan `hello("debugger")` tak mengembalikan apa-apa, jadi hasilnya `undefined`: ![](chrome-sources-console.svg) ## Breakpoint Mari periksa apa yang terjadi di dalam kode [laman contoh](debugging/index.html). Di `hello.js`, klik nomor baris `4`. Ya, tepat di digit `4`, bukan di kode. Selamat! Kamu mengeset breakpoint. Silakan klik juga angka untuk baris `8`. Rupanya mesti seperti ini (biru adalah tempat di mana kamu klik): ![](chrome-sources-breakpoint.svg) *Breakpoint* ialah poin kode di mana debugger akan otomatis menjeda exekusi JavaScript. Ketika kode dijeda, kita bisa periksa variabel kini, mengexekusi command di konsol dsb. Dengan kata lain, kita bisa mendebug itu. Kita selalu bisa menemukan daftar breakpoint di panel kanan. Ini berguna ketika kita punya banyak breakpoint di berbagai file. Ia membolehkan kita untuk: - Lompak cepat ke breakpoint di kode (dengan mengklik itu di panel kanan). - Mematikan sementara breakpoint dengan meng-uncheck itu. - Buang breakpoint dengan mengklik-kanan dan memilik Remove. - ...Dan banyak lagi. ```smart header="Breakpoint kondisional" *Klik kanan* di nomor baris bisa membuat breakpoint *kondisional*. Ia hanya terpicu saat expresi yang diberikan truthy. Ini praktis saat kita harus berhenti cuma untuk nilai variabel tertentu atau untuk parameter fungsi tertentu. ``` ## Command debugger Kita juga bisa menjeda kode menggunakan command `debugger` di dalamnya, seperti ini: ```js function hello(name) { let phrase = `Hello, ${name}!`; *!* debugger; // <-- debugger berhenti di sini */!* say(phrase); } ``` Ini sangat nyaman saat kita di dalam editor kode dan tak ingin berubah ke peramban dan mencari script di developer tools untuk mengeset breakpoint. ## Jeda dan lihat sekitar Di contoh kita, `hello()` dipanggil selama page load, jadi cara termudah untuk mengaktivasi debugger (setelah kita set breakpoint) ialah memuat-ulang laman. Jadi mari tekan `key:F5` (Windows, Linux) atau `key:Cmd+R` (Mac). Ketika breakpoint diset, exekusi terjeda di baris ke-4: ![](chrome-sources-debugger-pause.svg) Silakan buka dropdown informasional ke kanan (dilabeli panah). Mereka membolehkan kamu memeriksa code state sekarang: 1. **`Watch` -- menampilkan nilai sekarang untuk expresi apapun.** Kamu bisa mengklik `+` dan menginput expresi. Debugger akan menampilkan nilainya kapan saja, otomatis merekalkulasi itu di proses exekusi. 2. **`Call Stack` -- menampilkan rantai panggilan bersarang.** Di momen sekarang debugger berada di dalam panggilan `hello()`, dipanggil script di `index.html` (tak ada fungsi di sana, jadi ia disebut "anonymous"). If you click on a stack item (e.g. "anonymous"), the debugger jumps to the corresponding code, and all its variables can be examined as well. 3. **`Scope` -- current variables.** `Local` menampilkan variabel fungsi lokal. Kita juga bisa melihat nilai mereka yang dihighlight tepat di sebelah kanan kode. `Global` has global variables (out of any functions). Ada juta katakunci `this` di sana yang tidak kita pelajari sekarang, tapi nanti kemudian. ## Menjejak exekusi Sekarang waktunya *menjejak* script. Ada tombol untuk itu di ujung atas panel kanan. Ayo kita ikuti mereka. -- melanjutkan exekusi, hotkey `key:F8`. : Melanjutkan exekusi. Jika tak ada breakpoint tambahan, maka exekusi berlanjut dan debugger hilang kontrol. Inilah apa yang bisa kita lihat setelah satu klik ke dia: ![](chrome-sources-debugger-trace-1.svg) Exekusi dilanjutkan, mencapai breakpoint lain di dalam `say()` dan terjeda di sana. Perhatikan "Call Stack" di kanan. Ia meningkat satu panggilan lagi. Kita di dalam `say()` sekarang. -- "Langkahi": jalankan command berikutnya, hotkey `key:F9`. : Jalankan pernyataan berikutnya. Jika kita klik itu sekarang, `alert` akan muncul. Mengklik ini akan melangkahi semua aksi script satu-satu. -- "Langkahi atas": jalankan command berikutnya, tapi *jangan masuk ke fungsi*, hotkey `key:F10`. : Serupa dengan command "Step" sebelumnya, tapi berbeda jika pernyataan berikutnya berupa panggilan fungsi. Yaitu: bukan built-in, seperti `alert`, tapi fungsi kita sendiri. Command "Langkahi" masuk ke dalam dan menjeda exekusi di baris pertama, sedangkan "Kangkangi" mengexekusi panggilan fungsi bersarang secara tak terlihat, mengabaikan internal fungsi. Exekusi kemudian segera dijeda setelah fungsi itu. Itu baik jika kita tak tertarik mencaritahu ada apa di dalam panggilan fungsi. -- "Langkahi masuk", hotkey `key:F11`. : Serupa dengan "Langkahi", tapi berbeda dalam hal panggilan fungsi asynchronous. Jika kamu baru mulai belajar JavaScript, maka kamu bisa abaikan perbedaan, karena kita tak punya panggilan asynchronous sekarang. Untuk masa depan, perhatikan bahwa command "Langkahi" mengabaikan aksi async, seperti `setTimeout` (panggilan fungsi terjadwal), itu diexekusi nanti. "Langkahi masuk" masuk ke dalam kode mereka, menunggu mereka jika perlu. Lihat [DevTools manual](https://developers.google.com/web/updates/2018/01/devtools#async) untuk detil lebih. -- "Langkahi keluar": lanjutkan exekusi hingga akhir fungsi sekarang, hotkey `key:Shift+F11`. : Lanjutkan exekusi dan berhenti di baris terakhir dari fungsi sekarang. Ini praktis saat kita tak sengaja masuk ke panggilan bersarang memakai , tapi itu tak menarik bagi kita, dan kita mau lanjut ke ujungnya sesegera mungkin. -- nyalakan/matikan semua breakpoint. : Tombol itu tidak menggerakkan exekusi. Cuma on/off massal untuk breakpoint. -- nyalakan/matikan jeda otomatis jika ada galat. : Ketika menyala, dan developer tools terbuka, galat script otomatis menjeda exekusi. Lalu kita bisa menganalisa variabel untuk melihat apa yang salah. Jadi jika script kita mati karena galat, kita bisa buka debugger, menyalakan opsi ini dan memuat-ulang laman untuk melihat di mana ia mati dan apa kontexnya saat itu. ```smart header="Lanjut ke sini" Klilk kanan di satu baris kode membuka context menu dengan opsi hebat yang disebut "Lanjut ke sini". Ini praktis saat kita mau pindah beberpaa langkah maju ke baris itu, tapi kita terlalu malas mengeset breakpoint. ``` ## Logging Untuk mengoutput sesuatu ke konsol dari kode kita, ada fungsi `console.log`. Misalnya, ini mengoutput nilai dari `0` ke `4` ke konsol: ```js run // buka konsol untuk melihat for (let i = 0; i < 5; i++) { console.log("value,", i); } ``` Pengguna reguler tak melihat output itu, itu di dalam konsol. Untuk melihat itu, buka panel Console developer tool atau tekan `key:Esc` ketika di panel lain: yang membukakan konsol di bawah. Jika kita punya cukup logging di kode kita, maka kita bisa melihat apa yang terjadi dari record, tanpa debugger. ## Kesimpulan Seperti yang kita lihat, ada tiga cara utama untuk menjeda script: 1. Breakpoint. 2. Pernyataan `debugger`. 3. Galat (jika dev tools terbuka dan tombol "menyala"). Ketika terjeda, kita bisa mendebug - periksa variabel dan menjejak kode untuk melihat di mana terjadi kesalahan exekusi. Ada banyak lebih opsi di developer tool dari yang diliput di sini. Manual lengkap ada di . Informasi dari bab ini cukup untuk memulai debugging, tapi nanti, terutama jika kamu melakukan banyak hal-hal berkaitan dengan peramban, silakan masuk ke sana dan mencari kemampuan canggih lainnya dari developer tool. Oh, dan juga kamu bisa mengklik di berbagai tempat dev tools dan cuma melihat apa yang muncul. Itu mungkin rute tercepat untuk mempelajari dev tools. Jangan lupa tentang klik kanan dan context menu! ================================================ 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 ================================================ Contoh 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 ================================================ Kamu bisa perhatikan berikut: ```js no-beautify function pow(x,n) // <- tak ada spasi diantara arguments { // <- menemukan bracket di baris terpisah let result=1; // <- tak ada spasi sebelum atau setelah = for(let i=0;i Sekarang kita bahas aturannya dan alasannya secara detil. ```warn header="Tak ada aturan yang memaksa" Tak ada yang diset sekeras batu di sini. Mereka cuma preferensi gaya saja, buka dogma agama. ``` ### Kurung Kurawal Di kebanyakan proyek JavaScript kurung kurawal ditulis dalam gaya "Egyptian" dengan kurawal buka di baris yang sama dengan katakunci -- bukan di baris baru. Juga harus ada spasi sebelum bracket pembuka, seperti ini: ```js if (condition) { // lakukan ini // ...dan itu // ...dan itu } ``` Konstruksi sebaris, seperti `if (condition) doSomething()`, ialah hal penting sampingan. Haruskah kita memakai kurawal sama sekali? Ini adalah varian yang teranotasi jadi kamu bisa menilai sendiri keterbacaan mereka: 1. 😠 Pemula kadang melakukan itu. Buruk! Kurung kurawal tak dibutuhkan: ```js if (n < 0) *!*{*/!*alert(`Power ${n} is not supported`);*!*}*/!* ``` 2. 😠 Pisahkan baris berbeda tanpa kurawal. Jangan pernah lakukan, mudah membuat galat saat menambah baris baru: ```js if (n < 0) alert(`Power ${n} is not supported`); ``` 3. 😏 Sebaris tanpa kurawal - bisa diterima, jika pendek: ```js if (n < 0) alert(`Power ${n} is not supported`); ``` 4. 😃 Varian terbaik: ```js if (n < 0) { alert(`Power ${n} is not supported`); } ``` Untuk kode ringkas, sebaris dibolehkan, misal `if (cond) return null`. Tapi blok kode (varian terakhir) biasanya lebih dapat terbaca. ### Panjang Baris Tak ada orang suka membaca kode horizontal yang panjang. Memisahkan mereka ialah praktik yang baik. Misalnya: ```js // backtick quote ` memperbolehkan memecah string jadi beberapa baris 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. `; ``` Dan, untuk pernyataan `if`: ```js if ( id === 123 && moonPhase === 'Waning Gibbous' && zodiacSign === 'Libra' ) { letTheSorceryBegin(); } ``` Panjang baris maximum sebaiknya disepakati di level-tim. Biasanya 80 atau 120 karakter. ### Indent Ada dua tipe indent: - **Indent horizontal: 2 atau 4 spasi.** Indentasi horizontal dibuat menggunakan 2 atau 4 spasi atau simbol tab horizontal (kunci `key:Tab`). Pemilihan ini sudah lama jadi perang suci. Spasi jadi lebih umum sekarang ini. Satu keutamaan spasi dari tab ialah konfigurasi indent spasi lebih flexibel dari simbol tab. Misalnya, kita bisa mengalinea argumen dengan bracket pembuka, seperti ini: ```js no-beautify show(parameters, aligned, // 5 padding spasi di kiri one, after, another ) { // ... } ``` - **Indent vertikal: baris kosong untuk memecah kode menjadi blok logika.** Bahkan satu fungsi pun sering bisa dibagi jadi blok logika. Di contoh berikut, inisialisasi variabel, loop utama dan hasil kembalian dipecah secara vertikal: ```js function pow(x, n) { let result = 1; // <-- for (let i = 0; i < n; i++) { result *= x; } // <-- return result; } ``` Sisipkan extra baris baru di mana ini membantu membuat kode lebih terbaca. Tak boleh lebih dari sembilan baris kode tanpa indentasi vertikal. ### Semicolon Semicolon sebaiknya ada di tiap ujung pernyataan, bahkan meski jika itu bisa dilewati. Ada bahasa di mana semicolon benar-benar opsional dan jarang dipakai. Tapi di JavaScript, ada kasus di mana line break tidak diinterpretasi sebagai semicolon, membuat kode rentan galat. Lihat lebih lanjut tentang itu di bab . Jika kamu programmer JavaScript berpengalaman, kamu bisa pilih gaya kode tanpa semicolon seperti [StandardJS](https://standardjs.com/). Atau, lebih baik memakai semicolon untuk menghindari kemungkinan jurang nista. Mayoritas pengembang menaruh semicolon. ### Level Bersarang Coba hindari kode bersarang dengan level terlalu dalam. Misalnya, dalam loop, kadang ide bagus memakai directive [`continue`](info:while-for#continue) untuk menghindari sarang extra. Misalnya, ketimbang menambah kondisional `if` bersarang seperti ini: ```js for (let i = 0; i < 10; i++) { if (cond) { ... // <- satu lagi level bersarang } } ``` Kita bisa tulis: ```js for (let i = 0; i < 10; i++) { if (!cond) *!*continue*/!*; ... // <- tak ada extra level bersarang } ``` Hal serupa bisa dilakukan dengan `if/else` dan `return`. Misalnya, dua konstruksi berikut identik. Opsi 1: ```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; } } ``` Opsi 2: ```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; } ``` Yang kedua lebih terbaca karena "kasus spesial" `n < 0` ditangani segera. Sekali pengecekan selesai kita bisa pindah ke aliran kode "utama" tanpa sarang tambahan. ## Penempatan Fungsi Jika kamu menulis beberapa fungsi "pembantu" dan kode yang menggunakan mereka, ada tiga cara untuk mengorganisir fungsi. 1. Deklarasi fungsi *di atas* kode yang menggunakan mereka: ```js // *!*deklarasi fungsi*/!* function createElement() { ... } function setHandler(elem) { ... } function walkAround() { ... } // *!*kode yang menggunakan mereka*/!* let elem = createElement(); setHandler(elem); walkAround(); ``` 2. Kode pertama, lalu fungsi ```js // *!*kode yang menggunakan mereka*/!* let elem = createElement(); setHandler(elem); walkAround(); // --- *!*fungsi pembantu*/!* --- function createElement() { ... } function setHandler(elem) { ... } function walkAround() { ... } ``` 3. Campuran: fungsi dideklarasi di mana ia pertama dipakai. Seringnya, varian kedua jadi pilihan. Itu karena saat membaca kode, kita pertama mau tahu *itu bisa apa*. Jika kode mulai duluan, maka jadi lebih jelas dari awal. Lalu, mungkin kita tak mau membaca fungsinya sama sekali, terutama jika nama mereka deskriptif, sesuai dengan kelakuan mereka. ## Panduan Gaya Panguan gaya berisi aturan umum tentang "bagaimana menulis" kode, misal quote mana yang dipakai, berapa spasi indent, panjang baris maximal, dll. Banyak hal remeh. Saat semua anggota tim menggunakan panduan gaya yang sama, kodenya terlihat seragam, tak peduli anggota tim mana yang menulisnya. Tentu saja, tim bisa saja menulis panduan gaya mereka sendiri, tapi biasanya mereka tak butuh. There are many existing guides to choose from. Beberapa pilihan populer: - [Panduan Gaya JavaScript Google](https://google.github.io/styleguide/javascriptguide.xml) - [Panduan Gaya JavaScript Airbnb](https://github.com/airbnb/javascript) - [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) - [StandardJS](https://standardjs.com/) - (plus banyak lainnya) Jika kamu pengembang pemula, mulai dengan cheatsheet di awal bab ini. Lalu kamu bisa menjelajah panduan gaya lain untuk mencari ide lebih dan menentukan mana yang terbaik. ## Linter Terotomasi Linter ialah tool yang bisa otomatis mengecek gaya kodemu dan memberi saran improvisasi. Yang keren dari ini ialah pengecekan gaya sekaligus menemukan bug., like typos in variable or function names. Because of this feature, using a linter is recommended even if you don't want to stick to one particular "code style". Berikut beberapa linting tool terkenal: - [JSLint](http://www.jslint.com/) -- one of the first linters. - [JSHint](http://www.jshint.com/) -- more settings than JSLint. - [ESLint](http://eslint.org/) -- probably the newest one. Semuanya bisa melakukan itu. Penulis ini menggunakan [ESLint](http://eslint.org/). Kebanyakan linter terintegrasi dengan banyak editor populer: cuma mengaktifkan plugin di editor dan mengkonfigurasi gayanya. Misalnya, untuk ESLint kamu harus melakukan hal ini: 1. Instal [Node.js](https://nodejs.org/). 2. Instal ESLint dengan command `npm install -g eslint` (npm ialah installer package JavaScript). 3. Buat file konfig bernama `.eslintrc` di root proyek JavaScriptmu (di folder yang berisi semua filemu). 4. Instal/aktifkan plugin untuk editormu yang berintegrasi dengan ESLint. Mayoritas editor punya satu. Berikut contoh file `.eslintrc`: ```js { "extends": "eslint:recommended", "env": { "browser": true, "node": true, "es6": true }, "rules": { "no-console": 0, "indent": 2 } } ``` Di sini directive `"extends"` menunjukkan bahwa konfigurasinya berdasarkan set pengaturan "eslint:recommended". Setelah itu, kita spesifikasikan punya kita sendiri. Selain itu, dimungkinkan juga mengunduh set aturan gaya dari web dan mengextend mereka. Lihat untuk detil lebih tentang instalasi. Juga IDE tertentu punya linting built-in, yang nyaman tapi tak bisa dikustomisasi seperti ESLint. ## Kesimpulan Semua aturan syntax yang dideskripsikan di bab ini (dan di referensi panduan gaya) bertujuan untuk meningkatkan keterbacaan kodemu. Mereka dapat diperdebatkan. Saat kita berpikir tentang menulis kode "lebih baik", pertanyaannya ialah haruskah kita tanya diri kita sendiri: "Apa yang membuat kode dapat lebih terbaca dan lebih mudah dipahami?" dan "Apa yang bisa membantu kita menghindari galat?" Inilah hal utama yang harus dipikirkan saat memilih dan memperdebatkan gaya kode. Membaca panduan gaya populer akan membuatmu selalu terupdate dengan ide terbaru tentang tren gaya kode dan praktik terbaiknya. ================================================ FILE: 1-js/03-code-quality/03-comments/article.md ================================================ # Komentar Seperti yang kita tahu dari bab , komentar bisa sebaris tunggal: mulai dari `//` dan baris-ganda: `/* ... */`. Normalnya kita pakai mereka untuk menjelaskan bagaimana dan kenapa kode bekerja. Di awal pandangan, berkomentar itu sudah jelas, tapi pemula sering salah memakainya dalam pemrograman. ## Komentar jelek Pemula cenderung memakai komentar untuk menjelaskan "ada apa di dalam kode". Seperti ini: ```js // Kode ini akan melakukan ini (...) dan itu (...) // ...dan entah apa lagi... very; complex; code; ``` Tapi di dalam kode yang baik, jumlah komentar "penjelasan" seperti ini sebaiknya minimal. Seriusnya, kode harus bisa dimengerti tanpa mereka. Ada aturan besar tentang itu: "jika kode begitu tak jelas hingga ia butuh komentar, mungkin malah ia harus ditulis ulang". ### Resep: fungsi faktor keluar Kadang menguntungkan untuk mengganti sepotong kode dengan fungsi, seperti ini: ```js function showPrimes(n) { nextPrime: for (let i = 2; i < n; i++) { *!* // cek apakah i angka prima for (let j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; } */!* alert(i); } } ``` Varian lebih baiknya, dengan fungsi faktor keluar `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; } ``` Sekarang kita mudah mengerti kodenya. Fungsinya itu sendiri menjadi komentar. Kode macam ini disebut *menjelaskan-diri-sendiri*. ### Resep: membuat fungsi Dan jika kita punya "code sheet" sangat panjang seperti ini: ```js // kita tambah \whiskey di sini for(let i = 0; i < 10; i++) { let drop = getWhiskey(); smell(drop); add(drop, glass); } // kita tambah jus di sini for(let t = 0; t < 3; t++) { let tomato = getTomato(); examine(tomato); let juice = press(tomato); add(juice, glass); } // ... ``` Maka mungkin varian lebih baiknya ialah merefaktornya menjadi fungsi seperti: ```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(); //... } } ``` Sekali lagi, fungsi mereka sendiri menjelaskan apa yang terjadi. Tak ada yang dikomentari. Dan juga struktur kodenya lebih baik saat dipisah. Kelakuan tiap fungsi jadi lebih jelas, apa yang ia ambil dan yang ia kembalikan. Relitanya, kita tak bisa menghindar total dari komentar "penjelasan". Ada algoritma rumit. Dan ada "tweak" pintar dengan tujuan optimisasi. Tapi umumnya kita harus coba menjaga kodenya simpel dan menjelaskan-diri-sendiri. ## Komentar baik Jadi, komentar penjelasan biasanya jelek. Komentar mana yang bagus? Jelaskan arsitekturnya : Sediakan overview tingkat-tinggi dari komponen, bagaimana mereka berinteraksi, apa aliran kontrolnya di berbagai situasi... Singkatnya -- gambaran umum dari kode. Ada bahasa spesial [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) untuk membangun diagram arsitektur tingkat-tinggi yang menjelaskan kode. Sangat berfaedah untuk dipelajari. Parameter dan kegunaan fungsi dokumen : Ada syntax spesial [JSDoc](http://en.wikipedia.org/wiki/JSDoc) untuk mendokumentasi fungsi: kegunaan, parameter, nilai kembalian. Misalnya: ```js /** * Kembalikan x yang diberi pangkat n. * * @param {number} x Angka yang mau dinaikkan. * @param {number} n Pangkat, harus angka asli. * @return {number} x hasil setelah pangkat n. */ function pow(x, n) { ... } ``` Komentar macam ini membolehkan kita memahami maksud fungsi dan memakainya dengan tepat tanpa melihat isi kode. Oya, banyak editor seperti [WebStorm](https://www.jetbrains.com/webstorm/) bisa memahami mereka juga dan memakai mereka untuk menyediakan autocomplete dan beberapa pengecekan-kode otomatis. Juga, Ada tool seperti [JSDoc 3](https://github.com/jsdoc3/jsdoc) yang bisa menggenerate dokumentasi-HTML dari komentar. Kamu bisa baca informasi lebih tentang JSDoc di . Kenapa tugas ini diselesaikan begini? : Apa yang tertulis itu penting. Tapi apa yang *tak* tertilis mungkin lebih penting lagi untuk memahami yang terjadi. Kenapa tugas ini diselesaikan tepat seperti ini? Kodenya tak memberikan jawaban. Jika ada banyak cara menyelesaikan tugas, kenapa harus ini? Apalagi ini belum jelas. Tanpa komentar macam ini situasi berikut memungkinkan: 1. Kamu (atau kolegamu) membuka kode yang ditulis kapan hari, dan melihat bahwa ini "suboptimal". 2. Kamu berpikir: "Koq bego ya saya waktu itu, dan lihat betapa pintar saya sekarang", dan menulis-ulang menggunakan varian yang "lebih jelas dan benar". 3. ...Urgensi menulis-ulang itu bagus. Tapi di proses yang kamu lihat adalah solusi "lebih jelas" tersebut jadi minus. Kamu bahkan agak lupa kenapa, karena kamu sudah mencobanya di masa lalu. Kamu balikkan ke varian yang benar, tapi waktumu terbuang percuma. Komentar yang menjelaskan solusi itu penting. Mereka membantu melanjutkan pengembangan ke arah yang benar. Ada fitur halus dari kodenya? Di mana mereka dipakai? : Jika kodenya punya sesuatu yang halus dan kontra-intuitif, itu sudah pasti bagus untuk dikomentari. ## Kesimpulan Tanpa penting dari pengembang yang baik ialah komentar: kehadiran mereka dan kealfaan mereka. Komentar yang baik membuat kita memelihara kode lebih baik, kembali ke situ setelah delay dan memakainya secara efektif. **Komentari ini:** - Arsitektur keseluruhan, pemandangan tingkat-tinggi. - Kegunaan fungsi. - Solusi penting, terutama yang masih belum jelas. **Hindari komentar:** - Yang menjelaskan "bagaimana kode bekerja" dan "apa kelakuannya". - Menaruh mereka hanya jika sudah tak mungkin menyederhanakan kode dan menjelaskan diri sendiri yang mana itu tak dibutuhkan. Komentar juga dipakai untuk mengotodokumentasikan tool seperti JSDoc3: mereka membacanya dan menggenerate HTML-docs (atau docs di format lainnya). ================================================ FILE: 1-js/03-code-quality/04-ninja-code/article.md ================================================ # Kode ninja ```quote author="Confucius (Analects)" Learning without thought is labor lost; thought without learning is perilous. ``` Ninja programmer dari masa lalu memakai trik ini untuk mempertajam pikiran maintainer kode. Code review guru mencari-cari mereka dalam tugas pengujian. Pengembang pemula kadang memakai mereka lebih baik dari ninja programmer. Baca mereka dengan hati-hati dan cari tahu siapa kamu -- ninja, pemula, atau mungkin code reviewer? ```warn header="Ironi terdeteksi" Banyak yang mencoba mengikuti jalan ninja. Tapi cuma sedikit yang sukses. ``` ## Singkat adalah jiwa kecerdasan Buat kode sependek mungkin. Tunjukkan kepintaranmu. Biarkan fitur bahasa halus memandumu. Misalnya, perhatikan operator ternary ini `'?'`: ```js // diambil dari library javascript terkenal i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ``` Keren, kan? Kalau kamu menulis begitu, pengembang yang melihat baris ini dan mencoba memahami apa nilai `i` akan merasa senang. Lalu datanglah kamu, mencari jawaban. Katakan pada mereka bahwa yang lebih pendek selalu lebih baik that. Inisasikan mereka ke dalam langkah ninja. ## Variabel satu-huruf ```quote author="Laozi (Tao Te Ching)" The Dao hides in wordlessness. Only the Dao is well begun and well completed. ``` Cara lain untuk mengkode cepat ialah memakai nama variabel huruf-tunggal di manapun. Seperti `a`, `b` atau `c`. Variabel pendek hiland dalam kode seperti ninja sungguhan di hutan. Tak ada yang mampu mencarinya memakai "pencari" editor. Dan bahkan jika seseorang menemukannya, mereka tak mampu men"decipher" arti nama `a` atau `b`. ...Tapi ada pengecualian. Ninja riil tak akan pernah memakai `i` sebagai counter di loop `"for"`. Di tempat lain iya, tapi di sini tidak. Lihat saja, ada banyak huruf exotis. Misalnya, `x` atau `y`. Variabel exotis sebagai counter loop itu keren terutama jika badan loop memakan 1-2 laman (buat ia lebih panjang lagi jika kamu bisa). Lalu jika ada orang lihat lebih dalam ke loop, mereka tak akan cepat menerka nama variabel `x` sebagai counter loop. ## Gunakan singkatan Jika aturan tim melarang penggunaan nama satu-huruf dan geje -- perpendek mereka, buat singkatan. Seperti ini: - `list` -> `lst`. - `userAgent` -> `ua`. - `browser` -> `brsr`. - ...etc Hanya orang-orang yang memiliki intuisi bagus yang akan mengerti nama-nama seperti itu. Cobalah untuk memendekan segalanya. Hanya orang-orang yang layak yang bisa bertahan dengan perkembangan kodemu. ## Terbang Tinggi. Jadilah abstrak. ```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. ``` Saat memilih nama cobalah pakai kata paling abstrak. Seperti `obj`, `data`, `value`, `item`, `elem` dan seterusnya. - **Nama ideal untuk variabel ialah `data`.** Pakai itu di manapun kamu bisa. Jelas, tiap variabel memegang *data*, kan? ...Tapi bagaimana jika `data` sudah diambil? Coba `value`, itu juga universal. Setelahnya, sebuah variabel akhirnya mendapatkan sebuah *nilai*. - **Beri nama variabel berdasarkan tipe: `str`, `num`...** Coba saja mereka. Pemula mungkin heran -- apa nama-nama begini berguna bagi ninja? Tentu! Pasti, nama variabel masih punya arti. Ia menerangkan apa isi variabel: string, number atau yang lain. Tapi saat orang asing mencoba memahami kodenya, mereka akan terkejut melihat tak ada informasi sama sekali! Dan pasti akan gagal mengubah kodemu yang sudah dipikirkan matang-matang. Tipe nilai mudah dicaritahu dari debugging. Tapi apa arti dari variabel? String/number mana yang disimpan? Tak ada cara mencaritahu tanpa meditasi yang bagus! - **...Tapi bagaimana jika tak ada name lagi?** Tambahkan saja angka: `data1, item2, elem5`... ## Uji perhatian Hanya programmer sungguhan bisa memahami kodemu. Tapi bagaimana mengeceknya? **Salah satu caranya -- pakai nama variabel yang serupa, seperti `date` dan `data`.** Campurkan saja mereka sebisamu. Baca cepan kode begini makin mustahil. Dan saat ada typo... Ummm... Kita stuck cukup lama, waktunya minum teh. ## Synonym pintar ```quote author="Laozi (Tao Te Ching)" The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name. ``` Memakai nama *serupa* untuk hal *sama* membuat hidup menarik dan menampilkan kreatifitasmu ke publik. Misalnya, pikirkan prefix fungsi. Jika fungsi menunjukkan pesan ke layar -- mulai dengan `display…`, seperti `displayMessage`. Lalu jika fungsi lain menampilkan yang lain di layar, seperti nama pengguna, mulai dengan `show…` (seperti `showName`). Menyindir bahwa ada perbedaan halus antara kedua fungsi, padahal tidaka da. Buatlah sebuah team kawan ninja: jika John mulai *menampilkan* fungsi dengan `display...` didalam kodenya, lalu Peter menggunakan `render...`, dan Ann -- `paint...`. Perhatikan akan seberapa menarik dan berbedakah nantinya. ...Dan sekarang untuk hat trick! Untuk dua fungsi dengan perbedaan mencolok -- pakai prefix yang sama! Misalnya, fungsi `printPage(page)` akan memakai printer. Dan fungsi `printText(text)` akan menaruh teks ke layar. Biarkan pembaca asing berpikir baik tentang fungsi bernama `printMessage`: "Ke mana ia taruh pesannya? Ke printer atau ke layar?". Supaya lebih bersinar, `printMessage(message)` sebaiknya mengoutput itu di jendela baru! ## Pakai-ulang nama ```quote author="Laozi (Tao Te Ching)" Once the whole is divided, the parts
need names.
There are already enough names.
One must know when to stop. ``` Tambah variabel baru hanya saat diperlukan. Lebih baik gunakan ulang nama yang sudah ada. Tulis saja nilai baru ke dalamnya. Di dalam fungsi cobalah hanya memakai variabel yang dioper sebagai parameter. Itu akan menyulitkan identifikasi apa yang ada di variabel *sekarang*. Dan juga darimana asalnya. Tujuannya untuk mengembangka intuisi dan memori orang yang membaca kodenya. Orang dengan intuisi lemah akan menganalisa kodenya baris per-baris dan menjejak perubahan ke seluruh cabang kode. **Varian canggih dari pendekatan ini ialah mengganti diam-diam (!) nilai dengan sesuatu yang serupa di tengah loop atau fungsi.** Misalnya: ```js function ninjaFunction(elem) { // 20 baris kode berjalan dengan elem elem = clone(elem); // 20 baris lagi, sekarang berjalan dengan clone dari elem! } ``` Sobat programmer yang mau bekerja dengan `elem` di pertengahan kedua dari fungsi akan terkejut... Cuma selama debugging, setelah memeriksa kodenya mereka akan menemukan bahwa mereka bekerja dengan clone! Terlihat dalam kode secara reguler. Benar-benar efektif bahkan melawan ninja berpengalaman. ## Underscore untuk kesenangan Taruh underscores `_` dan `__` sebelum nama variabel. Seperti `_name` atau `__value`. Akan lebih bagus jika cuma kamu yang tahu artinya. Atau, lebih baik, tambahkan mereka hanya untuk kesenangan, tanpa ada arti sama sekali. Atau arti berbeda di tempat berbeda. Kamu membunuh dua kelinci satu tembakan. Pertama, kodenya jadi lebih panjang dan kurang terbaca, dan kedua, sobat pengembang menghabiskan banyak waktu mencaritahu arti underscores. Ninja pintar menaruh underscore di satu spot kode dan menghindari mereka di tempat lain. Itu membuat kodenya jadi lebih rapuh dan meningkatkan kemungkinan muncul galat masa depan. ## Tunjukkan cintamu Biarkan orang melihat betapa indahnya entiti kamu! Nama seperti `superElement`, `megaFrame` dan `niceItem` pasti akan mencerahkan pembacamu. Tentu, di satu sisi, satu hal tertulis: `super..`, `mega..`, `nice..` Tapi di sisi lain -- ia tak membawa detil. Pembaca mungkin memutuskan untuk melihat arti tersembunyi dan meditasi selama sejam atau dua jam dari waktu kerja mereka. ## Tumpang-tindih variabel terluar ```quote author="Guan Yin Zi" When in the light, can't see anything in the darkness.
When in the darkness, can see everything in the light. ``` Pakai nama yang sama untuk variabel di dalam dan di luar fungsi. Simpelnya. Tak perlu usaha menemukan nama baru. ```js let *!*user*/!* = authenticateUser(); function render() { let *!*user*/!* = anotherValue(); ... ...many lines... ... ... // <-- programmer mau bekerja dengan pengguna di sini dan... ... } ``` Programmer yang lompat ke dalam `render` mungkin akan gagal melihat ada `user` lokal yang membayangi user yang terluar. Lalu mereka akan mencoba bekerja dengan `user` dengan asumsi ia variabel external, hasil `authenticateUser()`... Jebakan muncul! Halo, debugger... ## Efek-samping di manapun! Ada fungsi yang kelihatan tak mengubah apapun. Seperti `isReady()`, `checkPermission()`, `findTags()`... Mereka dikira melakukan kalkulasi, mencari dan menghasilkan data, tanpa mengubah apapun di luar mereka. Dengan kata lain, tanpa "efek-samping". **Trik yang sangat cantik ialah menambah aksi "berfaedah" ke mereka, selain tugas utamanya.** Expresi linglung kaget dari muka kolegamu saat mereka melihat fungsi bernama `is..`, `check..` atau `find...` yang mengubah sesuatu -- pastinya akan memperluas batas alasanmu. **Cara lain membuat kejutan ialah mengembalikan hasil non-standar.** Tunjukan pemikiran orisinilmu! Biarkan panggilan `checkPermission` mengembalikan bukan `true/false`, tapi objek rumit dengan hasil pengecekan. Pengembang itu yang mencoba `if (checkPermission(..))`, akan heran kenapa itu tak bekerja. Katakan ke mereka: "Baca docs!". Dan berikan artikel ini. ## Fungsi yang kuat! ```quote author="Laozi (Tao Te Ching)" The great Tao flows everywhere,
both to the left and to the right. ``` Jangan batasi fungsi karena tulisan namanya. Melebarlah. Misalnya, fungsi `validateEmail(email)` bisa (selain mengecek keebenaran email) menampilkan pesan galat dan meminta masukan ulang email. Aksi tambahan sebaiknya jangan diperjelas dari nama fungsi. Coder ninja sejati akan membuat mereka tidak jelas dari kodenya juga. **Menggabung beberapa aksi jadi satu melindungi kodemu dari penggunaan ulang.** Bayangkan, pengembang lain mau mengecek email, dan tak menghasilkan pesan apapun. Fungsimu `validateEmail(email)` yang melakukan keduanya tak akan cocok dengan mereka. Jadi mereka tak akan mengganggu meditasimu dengan menanyakan itu. ## Kesimpulan Semua "potongan saran" di atas berasal dari real kode sungguhan... Kadang, tertulis dari pengembang berpengalaman. Bahkan lebih berpengalaman dari kamu ;) - Ikuti beberapa dari mereka, dan kodemu akan menjadi penuh kejutan. - Ikuti banyak dari mereka, dan kodemu akan menjadi milikmu sepenuhnya, tak ada yang mau mengubahnya. - Ikuti semua, dan kodemu akan menjadi pelajaran berharga untuk pengembang muda yang mencari pencerahan. ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/solution.md ================================================ Tes ini mendemonstrasikan satu dari godaan yang pengembang temui dan saat menulis tes. Apa yang kita punya di sini sebenarnya 3 tes, tapi diletakkan sebagai satu fungsi tunngal dengan 3 assert. Kadang lebih mudah menulis di cara ini, tapi jika muncul galat, kurang jelas apa yang salah. Jika galat terjadi di tengah alur exekusi rumit, maka kita harus caritahu data di poin itu. Kita sebenarnya harus *mendebug tes*. Akan jauh lebih baik memecah tes jadi beberapa blok `it` dengan input dan output yang tertulis jelas. Seperti ini: ```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); }); }); ``` Kita mengganti `it` tunggal dengan `describe` dan grup blok `it`. Sekarang jika sesuatu gagal kita akan lihat jelas apa datanya. Juga kita bisa mengisolasi tes tunggal dan menjalankannya dalam mode standalone dengan menulis `it.only` ketimbang `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 hanya akan menjalankan blok ini 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 ================================================ nilai penting: 5 --- # Apa yang salah dalam tes ini? Apa yang salah dalam tes `pow` di bawah? ```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. Secara sintaktis tesnya benar dan lulus. ================================================ FILE: 1-js/03-code-quality/05-testing-mocha/article.md ================================================ # Pengetesan terotomasi dengan Mocha Pengetesan terotomasi akan dipakai di tugas lebih lanjut, dan juga luas dipakai di proyek riil. ## Kenapa kita butuh tes? Saat kita menulis fungsi, kita biasanya akan membayangkan apa yang ia harus lakukan: parameter apa memberikan hasil apa. Selama pengembangan, kita bisa mengecek fungsi dengan menjalankannya dan membandingkan keluaran yang muncul dengan yang keluaran diharapkan. Misalnya, kita bisa melakukannya di konsol. Jika sesuatu buruk terjadi -- maka kita membetulkan kode, menjalankan lagi, mengecek hasil -- dan begitu terus hingga bekerja. Tapi proses "jalan-ulang" manual seperti ini tak sempurna. **Ketika pengetesan kode dengan jalan-ulang manual, sangat rentang untuk kelupaan sesuatu.** Misalnya, kita membuat fungsi `f`. Membuat beberapa kode, mengetes: `f(1)` bekerja, tapi `f(2)` tak bekerja. Kita betulkan kodenya dan sekarang `f(2)` bekerja. Sudah lengkap? Tapi kita lupa mengetes-ulang `f(1)`. Di situ mungkin terjadi galat. Ini sangat tipikal. Saat kita mengembangkan sesuatu, kita menyimpan banyak use case di kepala. Tapi sulit mengharapkan programmer mengecek semuanya secara manual setelah setiap perubahan. Jadi lebih mudah membetulkan satu hal dan merusak hal lainnya. **Pengecekan terotomasi artinya tes itu ditulis terpisah, sebagai tambahan ke kode. Mereka menjalankan kode kita dalam berbagai cara dan membandingkan hasil dengan harapan.** ## Behavior Driven Development (BDD) Ayo mulai dengan teknik bernama [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) atau, singkatnya, BDD. **BDD adalah tiga hal dalam satu: tes DAN dokumentasi DAN contoh.** Untuk memahami BDD, kita akan periksa kasus praktik pengembangan. ## Pengembangan "pow": spek Katakan kita mau membuat fungsi `pow(x, n)` yang menaikkan `x` ke bilangan pangkat `n`. Kita asumsikan `n≥0`. Tugas itu cuma contoh: ada operator `**` di JavaScript yang bisa melakukan itu, tapi di sini kita koncentrasi di alur pengembangan yang bisa ditiru di tugas komplex lainnya juga. Sebelum membuat kode `pow`, kita bisa bayangkan apa yang harus dilakukan fungsi itu dan menjelaskannya. Deskripsi begitu disebut _spesifikasi_ atau, singkatnya, spek, dan berisi deskripsi use case bersama dengan tes untuk mereka, seperti ini: ```js describe("pow", function () { it("raises to n-th power", function () { assert.equal(pow(2, 3), 8); }); }); ``` Spek punya tiga blok bangunan utama yang bisa kamu lihat di bawah: `describe("title", function() { ... })` : Fungsionalitas apa yang kita jelaskan. Di kasus ini kita akan menjelaskan fungsi `pow`. Dipakai untuk mengelompokkan "pekerja" -- blok `it`. `it("use case description", function() { ... })` : Di judul `it` kita _dalam bahasa manusia_ menjelaskan use case tertentu, dan argumen kedua ialah fungsi yang mengetes itu. `assert.equal(value1, value2)` : Kode di dalam blok `it`, jika implementasinya benar, harus berjalan tanpa galat. Fungsi `assert.*` dipakai untuk mengecek apakah `pow` bekerja sesuai harapan. Tepat di sini kita memakai salah satunya -- `assert.equal`, ia membandingkan argumen dan menghasilkan galat jika mereka tak sama. Di sini ia mengecek apakah hasil `pow(2, 3)` sama dengan `8`. Ada tipe perbandingan dan pengecekan lain, yang akan kita tambah nanti. Spesifikasi ini bisa diexekusi, dan ia akan menjalankan tes yang dispesifikasi dalam blok `it`. Kita akan lihat nanti. ## Alur pengembangan Alur pengembangan biasanya seperti ini: 1. Spek inisial ditulis, dengan tes untuk kebanyakan fungsionalitas dasar. 2. Implementatsi inisial dibuat. 3. Untuk mengecek apakah ia bekerja, kita jalankan framework pengetesan [Mocha](http://mochajs.org/) (detil lebih segera) yang menjalankan spek. Saat fungsionalitas tak lengkap, galat ditampilkan. Kita buat koreksi hingga semuanya bekerja. 4. Sekarang kita punya implementasi inisial yang bekerja dengan tes. 5. Kita tambah use case lain ke spek, mungkin belum didukung implementasinya. Tes mulai gagal. 6. Pergi ke 3, perbaharui implementasinya hingga tes tak memberikan galat. 7. Ulangi langkah 3-6 hingga fungsionalitasnya siap. Jadi, pengembangannya _iteratif_. Kita tulis spek, implementasikan, memastikan tes lulus, lalu menulis tes lainnya, memastikan mereka bekerja dll. Akhirnya kita punya implementasi yang bekerja dan tes untuk itu. Ayo lihat alur pengembangan ini di kasus praktik kita. Langkap pertama sudah lengkap: kita punya spek inisial untuk `pow`. Sekarang, sebelum membuat implementasinya, ayo pakai beberapa librari JavaScript untuk menjalankan tes, hanya untuk melihat mereka bekerja (mereka semua akan gagal). ## Spec dalam aksi Di sini di tutorial ini kita akan memakai librari JavaScript untuk tes: - [Mocha](http://mochajs.org/) -- inti framework: menyediakan fungsi testing umum `describe` dan `it` dan fungsi utama yang menjalankan test. - [Chai](http://chaijs.com) -- librari dengan banyak penambahan. Ini membuatmu bisa untuk menggunakan banyak penambahan yang berbeda, untuk sekarang kita hanya membutuhkan `assert.equal`. - [Sinon](http://sinonjs.org/) -- sebuah librari untuk memata-matai fungsi, meniru fungsi bawaan dan lainnya, kita akan butuh ini nanti. Librari ini cocok baik untuk pengetesan in-browser dan server-side. Di sini kita akan mempertimbangkan varian peramban. Laman HTML lengkap dengan framework ini dan spek `pow`: ```html src="index.html" ``` Laman ini bisa dibagi jadi lima bagian: 1. `` -- menambah librari dan gaya pihak-ketiga untuk tes. 2. `
================================================ 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 ================================================ # Polyfill dan transpiler JavaScript secara konsisten terus berevolusi. Proposal-proposal untuk menambah fitur-fitur baru terus bermunculan. Proposal-proposal ini akan didaftarkan pada jika memang berpotensi dan layak untuk ditambahkan dalam standard dalam bahasa pemrograman JavaScript. Kemudian proposal-proposal yang telah diterima akan dimasukkan dalam [daftar spesifikasi](http://www.ecma-international.org/publications/standards/Ecma-262.htm) JavaScript. Tim yang mengurus JavaScript mengerti dan akan mengusulkan mana dari proposal-proposal ini yang akan diimplementasikan terlebih dahulu. Tim ini boleh saja nanti memasukkan proposal-proposal ini kedalam kategori 'draft / dalam perancangan' atau 'postpone / tunda' karena mungkin menurut mereka proposal-proposal ini menarik untuk didiskusikan lebih dalam atau sulit untuk direalisasikan. Sangat wajar jika kebanyakan dari browser-browser yang ada hanya mengimplementasikan bagian-bagian yang tidak terlalu sulit. Sebuah halaman yang bagus untuk melihat kondisi terkini dari fitur yang didukung oleh bahasa ini ialah (isinya banyak, kita masih banyak yang belum dipelajari) Sebagai programmer, kita suka untuk menggunakan fitur yang terbaru. lebih banyak fitur bagus - lebih baik lagi! di sisi lain, bagaimana membuat kodingan modern bekerja di mesin yang lama yang tidak mengetahui fitur-fitur terbaru ? Ada dua cara untuk itu: 1. Transpilers. 2. Polyfills. di chapter ini, tujuan kita adalah untuk mendapatkan intisari cara kerjanya, dan tempatnya dalam proses pengembangan web. ## Transpilers Sebuah [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) adalah perangkat lunak khusus yang dapat mengurai ("membaca dan memahami") kode modern, dan menulis ulang menggunakan konstruksi sintaks yang lebih lama, sehingga hasilnya akan sama. Misalnya. JavaScript sebelum tahun 2020 tidak memiliki "nullish coalescing operator" `??`. Jadi, jika pengunjung menggunakan browser yang sudah ketinggalan zaman, ia mungkin gagal memahami kode seperti `height = height ?? 100` Sebuah transpiler akan menganalisa kodingan kita dan menulis `height ?? 100` menjadi `(height !== undefined && height !== null) ? height : 100`. ```js // sebelum menjalankan transpiler height = height ?? 100; // setelah transpile dijalankan height = (height !== undefined && height !== null) ? height : 100; ``` Sekarang kode yang ditulis ulang cocok untuk mesin JavaScript lama. Biasanya, pengembang menjalankan transpiler di komputer mereka sendiri, dan kemudian menyebarkan kode yang ditranspilasi ke server. Berbicara tentang nama, [Babel](https://babeljs.io) adalah salah satu transpiler paling terkenal di luar sana. Sistem pembangunan proyek modern, seperti [webpack](http://webpack.github.io/), menyediakan sarana untuk menjalankan transpiler secara otomatis pada setiap perubahan kode, sehingga sangat mudah untuk diintegrasikan ke dalam proses pengembangan. ## Polyfills Fitur bahasa baru tidak hanya mencakup konstruksi dan operator sintaks, tetapi juga fungsi bawaan. Misalnya, `Math.trunc (n)` adalah fungsi yang "memotong" bagian desimal dari sebuah angka, misalnya `Math.trunc (1.23) = 1`. Di beberapa mesin JavaScript (sangat usang), tidak ada `Math.trunc`, jadi kode seperti itu akan gagal. karena kita berbicara tentang fungsi baru, bukan perubahan sintaks, tidak perlu mentranspilasi apa pun di sini. Kita hanya perlu mendeklarasikan fungsi yang hilang. Skrip yang memperbarui / menambahkan fungsi baru disebut "polyfill". Ini "mengisi" celah dan menambahkan implementasi yang hilang. Untuk kasus khusus ini, polyfill untuk `Math.trunc` adalah skrip yang mengimplementasikannya, seperti ini: ```js if (!Math.trunc) { // kalo ga ada fungsi seperti ini // implementasikan Math.trunc = function(number) { // Math.ceil dan Math.floor ada bahkan di mesin JavaScript yang lama // mereka akan dibahas nanti di tutorial return number < 0 ? Math.ceil(number) : Math.floor(number); }; } ``` JavaScript adalah bahasa yang sangat dinamis, skrip dapat menambah / memodifikasi fungsi apa pun, bahkan termasuk yang sudah ada di dalamnya. Dua library polyfill yang menarik adalah: - [core js](https://github.com/zloirock/core-js) yang mendukung banyak hal, memungkinkan untuk kita memasukkan hanya fitur yang dibutuhkan. - [polyfill.io](http://polyfill.io) layanan yang menyediakan skrip dengan polyfills, bergantung pada fitur dan browser pengguna. ## Kesimpulan Di bab ini, kami ingin memotivasi Anda untuk mempelajari fitur bahasa modern dan bahkan "yang paling mutakhir", meskipun fitur tersebut belum didukung dengan baik oleh mesin JavaScript. Jangan lupa untuk menggunakan transpiler (jika menggunakan sintaks atau operator modern) dan polyfill (untuk menambahkan fungsi yang mungkin hilang). Dan mereka akan memastikan bahwa kodenya berfungsi. Misalnya, nanti saat Anda sudah terbiasa dengan JavaScript, Anda dapat menyiapkan sistem pembuatan kode berdasarkan [webpack](http://webpack.github.io/) dengan [babel-loader](https://github.com/babel/babel-loader). Sumber daya bagus yang menunjukkan status dukungan saat ini untuk berbagai fitur: - - untuk JavaScript murni. - - untuk fungsi terkait dengan browser. P.S. Google Chrome biasanya paling mutakhir dengan fitur bahasa, coba saja jika demo tutorial gagal. Sebagian besar demo tutorial berfungsi dengan browser modern apa pun. ================================================ FILE: 1-js/03-code-quality/index.md ================================================ # Kualitas Kode Bab ini menjelaskan tentang *coding practices* yang akan kita selanjutnya gunakan di proses *development*. ================================================ 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 ================================================ nilai penting: 5 --- # Hello, object Tulis kode, satu baris untuk setiap aksi: 1. Buatlah sebuah objek kosong `user`. 2. Tambahkan properti `name` dengan nilai `John`. 3. Tambahkan properti `surname` dengan nilai `Smith`. 4. Ubah nilai `name` menjadi `Pete`. 5. Hapus properti `name` dari objek. ================================================ FILE: 1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js ================================================ function isEmpty(obj) { for (let key in obj) { // jika perulangan dimulai, berarti ada sebuah properti 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 ================================================ Lakukanlah perulangan pada objek dan segera `return false` jika ada setidaknya satu properti ================================================ FILE: 1-js/04-object-basics/01-object/3-is-empty/task.md ================================================ nilai penting: 5 --- # cek kekosongan Tulislah sebuah fungsi `isEmpty(obj)` yang mana akan mengembalikan `true` jika objek tidak memiliki properti dan `false` jika sebaliknya. Harus bekerja seperti ini: ```js let schedule = {}; alert( isEmpty(schedule) ); // true schedule["8:30"] = "get up"; alert( isEmpty(schedule) ); // false ``` ================================================ 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 ================================================ nilai penting: 5 --- # Tambahkan properti objek Kita punya sebuah objek untuk menyimpan gaji dari tim kita: ```js let salaries = { John: 100, Ann: 160, Pete: 130 } ``` Tulislah kode untuk menambahkan seluruh gaji dan simpanlah didalam sebuah variabel `sum`. Haruslah menjadi `390` di contoh diatas. Jika `salaries` kosong, selanjutnya hasilnya haruslah `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) { /* Tulis kodemu disini */ } 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 ================================================ nilai penting: 3 --- # Kalikan properti numerik dengan 2 Buatlah sebuah fungsi `multiplyNumerik(obj)` yang mengkalikan seluruh properti numerik dari `obj` dengan `2`. Contoh: ```js // sebelum dipanggil let menu = { width: 200, height: 300, title: "My menu" }; multiplyNumeric(menu); // setelah dipanggil menu = { width: 400, height: 600, title: "My menu" }; ``` Perhatikan bahwa `multiplyNumeric` tidak butuh mengembalikan apapun. Fungsinya harus memodifikasi objectnya langsung. Catatan, gunakan `typeof` untuk memeriksa angka disini. ================================================ FILE: 1-js/04-object-basics/01-object/article.md ================================================ # Objek Seperti yang kita tahu dari bab , ada delapan tipe data di JavaScript. Enak dari mereka disebut "primitif", karena nilai mereka berisi cuma satu hal tunggal (entah string atau angka atau apapun). Kontrasnya, objek dipakai untuk menyimpan koleksi terkunci dari berbagai data dan entitas rumit lainnya. Di JavaScript, objek menembus hampir tiap aspek bahasa. Jadi kita harus memahami mereka dulu sebelum masuk lebih dalam ke manapun. Tiap objek bisa dibuat dengan tanda bracket `{…}` dengan daftar *properti* opsional. Properti ialah pasangan "key: value", di mana `key` string (juga disebut "nama properti"), dan `value` bisa apapun. Kita bisa bayangkan objek sebagai kabinet dengan file bertanda. Tiap potong data disimpan di dalam filenya dengan kuncinya. Mudah mencari filenya dengan namanya atau menambah/menghapus satu file. ![](object.svg) Objek kosong ("kabinet kosong") bisa dibuat memakai salah satu dari dua syntax: ```js let user = new Object(); // syntax "konstruktor objek" let user = {}; // syntax "literal objek" ``` ![](object-user-empty.svg) Biasanya, tanda bracket `{...}` dipakai. Deklarasi itu disebut *literal objek*. ## Literal dan properti Kita bisa segera menaruh beberapa properti ke dalam `{...}` sebagai pasangan "key: value": ```js let user = { // objek name: "John", // dengan kunci "name" menyimpan nilai "John" age: 30 // dengan kunci "age" menyimpan nilai 30 }; ``` Properti punya kunci (juga disebut "nama" atau "identifier") sebelum colon `":"` dan nilai di sebelah kanannya. Dalam objek `user`, ada dua properti: 1. Properti pertama punya nama `"name"` dan nilai `"John"`. 2. Yang kedua punya nama `"age"` dan nilai `30`. Hasil objek `user` bisa dibayangkan sebagai kabinet dengan dua file bertanda dengan label "name" dan "age". ![user object](object-user.svg) Kita bisa tambah, hapus dan baca file darinya kapanpun. Nilai properti bisa diakses memakai notasi dot: ```js // ambil nilai properti objek: alert( user.name ); // John alert( user.age ); // 30 ``` Nilainya bisa tipe apapun. Ayo tambah nilai boolean: ```js user.isAdmin = true; ``` ![user object 2](object-user-isadmin.svg) Untuk menghapus properti, kita bisa pakai operator `delete`: ```js delete user.age; ``` ![user object 3](object-user-delete.svg) Kita juga bisa memakai nama properti multi-kata, tapi mereka harus diquotasi: ```js let user = { name: "John", age: 30, "likes birds": true // nama properti multi-kata harus diquotasi }; ``` ![](object-user-props.svg) Properti terakhir di daftar bisa berakhir dengan koma: ```js let user = { name: "John", age: 30*!*,*/!* } ``` Itu disebut koma "buntut" atau "menggantung". Memudahkan kita menambah/menghapus/memindahkan properti, karena semua barus menjadi mirip. ## Kurung siku Untuk properti multi-kata, akses dot tak bekerja: ```js run // ini akan memberi galat syntax user.likes birds = true ``` Javascript tidak akan mengerti itu dan akan menganggap kita mencoba mengakses `user.likes`, lalu akan memberikan sintaks error saat sampai ke bagian yang tidak terduga `birds`. Titik/dot membutukan sebuah kunci/key untuk menjadi identifier yang valid. Berarti: tidak ada spasi, tidak dimulai dengan angka dan tidak mengandung karakter khusus (`$` dan `_` diperbolehkan). Ada alternatif "notasi bracket kotak" yang bekerja dengan string apapun: ```js run let user = {}; // set user["likes birds"] = true; // get alert(user["likes birds"]); // true // delete delete user["likes birds"]; ``` Sekarang semuanya oke. Tolong catat bahwa string di dalam bracket diquotasi dengan benar (bisa tipe quotasi apapun). Bracket kotak juga menyediakan cara memperoleh nama properti sebagai hasil expresi -- lawannya string literal -- seperti dari variabel berikut: ```js let key = "likes birds"; // sama dengan user["likes birds"] = true; user[key] = true; ``` Di sini, variabel `key` bisa dikalkulasi saat run-time atau tergantung input user. Lalu kita pakai untuk mengakses properti. Ini memberi kita flexibilitas yang sangat besar. Misalnya: ```js run let user = { name: "John", age: 30 }; let key = prompt("What do you want to know about the user?", "name"); // akses dari variabel alert( user[key] ); // John (jika mengenter "name") ``` Notasi dot tak bisa dipakai dalam cara serupa: ```js run let user = { name: "John", age: 30 }; let key = "name"; alert( user.key ) // undefined ``` ### Properti terkomputasi Kita bisa menggunakan kurung siku didalam objek literal, ketika kita membuat objek. Dipanggil dengan *properti terkomputasi* Misalnya: ```js run let fruit = prompt("Which fruit to buy?", "apple"); let bag = { *!* [fruit]: 5, // nama properti diambil dari variabel fruit */!* }; alert( bag.apple ); // 5 jika fruit="apple" ``` Arti properti terkomputasi simpel: `[fruit]` artinya nama properti harus diambil dari `fruit`. Jadi, jika pengunjung mengenter `"apple"`, `bag` akan menjadi `{apple: 5}`. Essensinya, ia bekerja mirip dengan: ```js run let fruit = prompt("Which fruit to buy?", "apple"); let bag = {}; // ambil nama properti dari variabel fruit bag[fruit] = 5; ``` ...Tapi lebih manis. Kita bisa pakai expresi rumit di dalam bracket kotak: ```js let fruit = 'apple'; let bag = { [fruit + 'Computers']: 5 // bag.appleComputers = 5 }; ``` Bracket kotak jauh lebih kuat dari notasi dot. Mereka membolehkan variabel dan nama properti apapun. Tapi mereka juga lebih rumit untuk ditulis. Jadi seringnya, saat nama properti diketahui dan simpel, dot dipakai. Dan jika kita butuh sesuatu yang rumit, maka kita ganti ke bracket kotak. ## Singkatan nilai properti Di kode riil kita sering memakai variabel sebagai nilai untuk nama properti. Misalnya: ```js run function makeUser(name, age) { return { name: name, age: age, // ...properti lainnya }; } let user = makeUser("John", 30); alert(user.name); // John ``` Di contoh di atas, properti punya nama sama dengan variabel. Use-case penggunaan properti dari variabel sangat umum, bahwa ada *singkatan nilai properti* spesial yang memperpendek itu. Ketimbang `name:name` kita bisa menuliskan `name`, seperti ini: ```js function makeUser(name, age) { *!* return { name, // sama dengan name: name age // sama dengan age: age // ... }; */!* } ``` Kita bisa pakai baik singkatan dan properti normal bersamaan dalam satu objek: ```js let user = { name, // sama dengan name:name age: 30 }; ``` ## Batasan nama properti Seperti yang sudah kita tahu, sebuah variabel tidak bisa memiliki nama yang sama dengan salah satu "kata yang telah dimiliki bahasa pemrograman" seperti "for", "let", "return" dan lainnya. Tapi dari sebuah properti objek, tidak ada batasan seperti itu: ```js run // properti seperti ini bisa digunakan let obj = { for: 1, let: 2, return: 3 }; alert( obj.for + obj.let + obj.return ); // 6 ``` Singkatnya, tidak ada batasan dalam pemberian nama properti. Bisa digunakan string atau simbol apapun (untuk tipe identifier spesial, akan dibahas nanti). Untuk tipe lainnya akan secara otomatis diubah kebentuk string. Contoh, angka `0` menjadi string `"0"` ketika digunakan sebagai kunci/key properti: ```js run let obj = { 0: "test" // sama dengan "0": "test" }; // kedua alert mengakses properti yang sama (angka 0 diubah menjadi string "0") alert( obj["0"] ); // test alert( obj[0] ); // test (properti yang sama) ``` Ada hal kecil dengan properti spesial yang bernama `__proto__`. Kita tidak bisa mengatur nilai non-objek kedalamnya: ```js run let obj = {}; obj.__proto__ = 5; // memasukan angka alert(obj.__proto__); // [object Object] - tidak ada nilai didalam objek, tidak bekerja seperti yang diinginkan ``` Seperti yang kita lihat didalam kode, memasukan nilai primitif `5` akan diabaikan. Kita akan membahas sifat alami dari `__proto__` didalam [bab selanjutnya](info:prototype-inheritance), dan menyarankan [cara untuk membenarkan](info:prototype-methods) sifatnya. ## Test sebuah keberadaan properti, operator "in" Fitur yang bisa dicatat didalam objek Javascript, dibandingkan dengan bahasa lainnya, adalah memungkinkannya untuk mengakses properti apapun. Tidak akan terjadi error jika propertinya tidak ada! Membaca sebuah properti yang tidak ada akan mengembalikan `undefined`. Jadi kita akan dengan mudah mengetest apakah propertinya ada atau tidak: ```js run let user = {}; alert( user.noSuchProperty === undefined ); // true artinya "tak ada properti macam ini" ``` Ada juga operator spesial `"in"` untuk mengecek existensi properti. Syntaxnya: ```js "key" in object ``` Misalnya: ```js run let user = { name: "John", age: 30 }; alert( "age" in user ); // true, user.age ada alert( "blabla" in user ); // false, user.blabla tak ada ``` Tolong ingat bahwa di sebelah kiri `in` harus ada *nama properti*. Itu biasanya string yang dikuotasi. Jika kita menghilangkan kutipnya, berarti sebuah variabel, itu haruslah mengandung nama yang akan dites. Contoh: ```js run let user = { age: 30 }; let key = "age"; alert( *!*key*/!* in user ); // true, properti "age" ada ``` Lalu kenapa ada operator `in`? Bukankah cukup untuk membandingkannya dengan `undefined`? Baik, kebanyakan waktu perbandingan dengan `undefined` bekerja dengan semestinya. Akan tetapi ada kasus spesial dimana itu akan gagal, tapi dengan `"in"` akan berjalan dengan baik. Itu ialah saat ada properti objek, tapi menyimpan `undefined`: ```js run let obj = { test: undefined }; alert( obj.test ); // mengembalikan undefined, apakah propertinya tidak ada? alert( "test" in obj ); // true, propertinya ada! ``` Di contoh kode di atas, properti `obj.test` ada secara teknis. Tapi operator `in` bekerja dengan baik. Situasi seperti ini jarang terjadi, karena `undefined` biasanya tak ditetapkan. Kita sering memakai `null` untuk nilai "unknown" atau "empty". Jadi operator `in` merupakan tamu exotik dalam kode. ```` ## "for..in" Untuk mengitari semua kunci objek, ada bentuk spesial dari loop: `for..in`. Ini sangat berbeda dari konstruksi `for(;;)` yang kita pelajari sebelumnya. Syntaxnya: ```js for (key in object) { // mengexekusi badan untuk tiap kunci dalam properti objek } ``` Misalnya, mari mengoutkan semua properti `user`: ```js run let user = { name: "John", age: 30, isAdmin: true }; for (let key in user) { // keys alert( key ); // name, age, isAdmin // nilai untuk key alert( user[key] ); // John, 30, true } ``` Catat bahwa semua konstruksi "for" membolehkan kita mendeklarasi variabel looping di dalam loop, seperti `let key` di sini. Juga, kita bisa memakai nama variabel lain di sini ketimbang `key`. Misalnya, `"for (let prop in obj)"` juga banyak dipakai. ### Berurut seperti objek Apa objek terurut? Dengan kata lain, jika kita meloop satu objek keseluruhan, apa kita mengambil semua properti dengan urutan yang sama saat mereka ditambahkan? Apa kita bisa percaya itu? Jawaban pendeknya ialah: "terurut dalam cara spesial": properti integer terurut, yang lainnya muncul dalam urutan pembuatan. Detilnya mengikuti. Misalnya, mari pertimbangkan objek dengan kode telpon: ```js run let codes = { "49": "Germany", "41": "Switzerland", "44": "Great Britain", // .., "1": "USA" }; *!* for (let code in codes) { alert(code); // 1, 41, 44, 49 } */!* ``` Objek ini digunakan untuk mensugesti daftar opsi ke pengguna. Jika kita membuat situs khusus untuk audiensi Jerman maka kita kemungkinan mau `49` jadi yang pertama. Tapi jika kita menjalankan kodenya, kita lihat potret yang berbeda: - USA (1) goes first - then Switzerland (41) and so on. Kode telpon berurut secara ascending, karena mereka integer. Jadi kita lihat `1, 41, 44, 49`. ````smart header="Properti integer? Apa itu?" Istilah "properti integer" di sini artinya string yang bisa dikonversi ke-dan-dari integer tanpa perubahan. Jadi, "49" nama properti integer, karena mereka ditransform ke angka integer dan kebalikannya, ia masih sama saja. Tapi "+49" dan "1.2" tidak: ```js run // Math.trunc is a built-in function that removes the decimal part alert( String(Math.trunc(Number("49"))) ); // "49", sama, properti integer alert( String(Math.trunc(Number("+49"))) ); // "49", tidak sama "+49" ⇒ bukan properti integer alert( String(Math.trunc(Number("1.2"))) ); // "1", tidak sama "1.2" ⇒ bukan properti integer ``` ```` ...Di sisi lain, jika kuncinya non-integer, maka mereka didaftar dalam urutan kreasi, misalnya: ```js run let user = { name: "John", surname: "Smith" }; user.age = 25; // tambah satu lagi *!* // properti non-integer didaftar dalam order kreasi */!* for (let prop in user) { alert( prop ); // name, surname, age } ``` Jadi, untuk mengatasi isu kode telpon, kita bisa berbuat "curang" dengan menjadikan kode non-integer. Cukup menambahkan tanda plus `"+"` sebelum tiap kode. Seperti ini: ```js run let codes = { "+49": "Germany", "+41": "Switzerland", "+44": "Great Britain", // .., "+1": "USA" }; for (let code in codes) { alert( +code ); // 49, 41, 44, 1 } ``` Sekarang itu bekerja sesuai yang diinginkan. ## Ringkasan Objek adalah array asosiatif dengan beberapa fitur spesial. Objek menyimpan properti (pasangan key-value), dimana: - kunci/key properti haruslah sebuah string atau simbol (biasanya string). - Nilai bisa tipe apapun. Untuk mengakses properti, kita bisa gunakan: - Notasi dot: `obj.properti`. - Notasi kurung siku `obj["properti"]`. Kurung siku memperbolehkan mengambil key dari sebuah variabel, seperti `obj[varDenganKey]`. Operator tambahan: - Untuk menghapus properti: `delete obj.prop`. - Untuk memeriksa jika properti dengan nilai yang diberikan ada: `"key" in obj`. - Untuk mengiterasi sebuah objek: `for (let key in obj)` loop. Di bab ini kita sudah belajar apa yang dipanggil dengan "plain object" atau "Objek sederhana" atau `Object`. Masuk ada banyak hal tentang objek didalam Javascript: - `Array` untuk menyimpan koleksi data, - `Date` untuk menyimpan informasi tentang tanggal dan waktu, - `Error` untuk menyimpan informasi tentang sebuah error. - ...Dan lainnya. Mereka mempunyai fitur spesial lainnya yang akan kita pelajari nanti. Terkadang orang-orang berkata seperti "Tipe array" atau "tipe tanggal/waktu", akan tetapi secara formal mereka bukanlah tipe yang mereka miliki sendiri, tapi milik sebuah tipe data "objek" tunggal. dan mereka bisa meluas ke berbagai arah. Objek didalam Javascript sangatlah kuat. Kita baru saja belajar sedikit saja tentang topiknya yang sebenarnya sangat luas. Kita akan belajar lebih tentang objek dan belajar tentang objek di bagian selanjutnya. ================================================ FILE: 1-js/04-object-basics/02-object-copy/article.md ================================================ # Referensi objek dan menyalinnya Salah satu perbedaan mendasar dari objek versus primitif adalah bahwa objek disimpan dan disalin "dengan referensi", sedangkan nilai primitif: string, angka, boolean, dll - selalu disalin "sebagai nilai keseluruhan". Itu mudah dipahami jika kita melihat sedikit ke belakang tentang apa yang terjadi saat kita menyalin sebuah nilai. Mari kita mulai dengan yang primitif, seperti string. Di sini kami memasukkan salinan `pesan` ke dalam` frase`: ```js let message = "Hello!"; let phrase = message; ``` Sebagai hasilnya kita punya dua variabel yang berdiri sendiri, dan keduanya menyimpan nilai string `"Hello!"`. ![](variable-copy-value.svg) Hasil yang cukup jelas, bukan? Objek tidak seperti itu. **Sebuah variabel tidak menyimpan objek itu sendiri, akan tetapi "disimpan didalam memori", dengan kata lain "mereferensi" kepadanya (ke data didalam memori).** Mari kita lihat contoh variabel tersebut: ```js let user = { name: "John" }; ``` Dan ini bagaimana kita menyimpannya di dalam memory: ![](variable-contains-reference.svg) Objek disimpan di suatu tempat di memori (di sebelah kanan gambar), sedangkan variabel `user` (di sebelah kiri) memiliki" referensi "padanya. Kita mungkin menganggap variabel objek, seperti `pengguna`, seperti selembar kertas dengan alamat objek di atasnya. Saat kita melakukan tindakan dengan objek, misalnya: mengambil properti `user.name`, mesin JavaScript melihat apa yang ada di alamat itu dan melakukan operasi pada objek sebenarnya. Sekarang inilah mengapa itu penting. **Ketika sebuah variabel objek disalin -- referensinya akan tersalin, objeknya tidak terduplikasi.** Contoh: ```js no-beautify let user = { name: "John" }; let admin = user; // menyalin referensinya ``` Kita sekarang punya dua variabel, masing-masing mereferensi ke objek yang sama: ![](variable-copy-reference.svg) Seperti yang Anda lihat, masih ada satu objek, sekarang dengan dua variabel yang mereferensikannya. Kita dapat menggunakan variabel apa saja untuk mengakses objek dan mengubah isinya: ```js run let user = { name: 'John' }; let admin = user; *!* admin.name = 'Pete'; // mengganti admin dengan menggunakan "referensi" */!* alert(*!*user.name*/!*); // 'Pete', perubahan akan terlihat pada "user" ``` Seolah-olah kita memiliki lemari dengan dua kunci dan menggunakan salah satunya (admin) untuk masuk ke dalamnya dan membuat perubahan. Kemudian, jika nanti kita menggunakan kunci lain (pengguna), kita masih membuka lemari yang sama dan dapat mengakses konten yang diubah. ## Perbandingan dengan referensi **Dua objek adalah sama jika mereka objek yang sama.** Dibawah adalah dua variabel yang mereferensi ke objek yang sama, dengan demikian mereka sama: Contohnya, di sini a dan b mereferensikan objek yang sama, sehingga keduanya sama: ```js run let a = {}; let b = a; // menyalin referensi alert( a == b ); // true, kedua variabel mereferensi ke objek yang sama alert( a === b ); // true ``` Dan dibawah adalah dua objek yang berdiri sendiri, tidaklah sama, walaupun keduanya kosong: ```js run let a = {}; let b = {}; // dua objek yang berdiri sendiri alert( a == b ); // false ``` Untuk perbandingan seperti `obj1 > obj2` atau untuk perbandingan dengan sebuah nilai primitif `obj == 5`, objek akan diubah dahulu menjadi primitif. Kita akan belajar bagaimana perubahan objek sebentar lagi, akan tetapi sebenarnya, perbandingan seperti itu muncul sangat jarang, biasanya hanya sebuah hasil dari kesalahan koding. ## Penggandaan dan penggabungan, Object.assign [#cloning-and-merging-object-assign] Jadi, menyalin sebuah variabel objek akan menciptakan satu lagi referensi kepada objek yang sama. Tapi bagaimana jika kita butuh untuk menduplikasi objek? Membuat salinan yang berdiri sendiri, menggandakan atau meng-klon? Hal itu juga bisa dilakukan, tapi sedikit lebih sulit, karena tidak ada method bawaan untuk hal itu di javascript. Sebenarnya, hal itu juga jarang dibutuhkan. Di kebanyakan waktu, menyalin referensinya sudah cukup. Tapi bagaimana jika kita benar-benar ingin hal itu, lalu kita membutuhkan untuk menciptakan sebuah objek dan mengulangi struktur dari objek yang sama dengan meng-iterasi propertinya dan menyalin mereka didalam level primitif. Seperti ini: ```js run let user = { name: "John", age: 30 }; *!* let clone = {}; // objek kosong baru // salin semua properti user kedalamnya for (let key in user) { clone[key] = user[key]; } */!* // sekarang clone adalah sebuah objek yang berdiri sendiri dengan konten yang sama clone.name = "Pete"; // ubah data didalamnya alert( user.name ); // masih John didalam objek yang asli ``` Juga kita bisa menggunakan method [Object.assign](mdn:js/Object/assign) untuk itu. sintaksnya adalah: ```js Object.assign(dest, [src1, src2, src3...]) ``` - Argumen pertama `dest` adalah sebuah objek target. - Argumen selanjutnya `src1, ..., srcN` (bisa sebanyak yang dibutuhkan) adalah objek sumber. - Itu akan menyalin properti dari seluruh objek sumber `src1, ..., srcN` kedalam target `dest`. Dengan kata lain, properti dari semua argumen dimulai dari argumen kedua akan disalin kedalam object pertama. - Setelah pemanggilan akan mengembalikan `dest`. Contoh, kita bisa menggunakan untuk menggabungkan beberapa objek menjadi satu: ```js let user = { name: "John" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; *!* // menyalin seluruh properti dari permission1 dan permission2 kedalam user Object.assign(user, permissions1, permissions2); */!* // sekarang user = { name: "John", canView: true, canEdit: true } ``` Jika nama dari properti yang disali sudah ada, propertinya akan ditimpa: ```js run let user = { name: "John" }; Object.assign(user, { name: "Pete" }); alert(user.name); // sekarang user = { name: "Pete" } ``` Kita juga bisa menggunakan `Object.assign` untuk mengganti perulangan `for...in` untuk penggandaan yang sederhana. ```js let user = { name: "John", age: 30 }; *!* let clone = Object.assign({}, user); */!* ``` Kode diatas akan menyalin seluruh properti dari `user` kedalam objek yang kosong dan mengembalikan/me-return hasilnya. Ada juga metode lain untuk mengkloning objek, mis. menggunakan [sintaksis spread](info:rest-parameters-spread) `clone = {...user}`, dibahas nanti dalam tutorial. ## Nested cloning Sampai sekarang kita telah berasumsi bahwa seluruh properti dari `user` adalah primitif. Tapi properti bisa di referensi ke objek lainnya. Apa yang harus dilakukan dengan mereka? Like this: ```js run let user = { name: "John", sizes: { height: 182, width: 50 } }; alert( user.sizes.height ); // 182 ``` Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes: Sekarang hal itu tidak cukup untuk menyalin `clone.sizes = user.sizes`, karena `user.sizes` adalah sebuah objek, itu akan tersalin secara referensi. Jadi `clone` dan `user` akan berbagi objek yang sama: Like this: ```js run let user = { name: "John", sizes: { height: 182, width: 50 } }; let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // true, objek yang sama // user dan clone akan berbagi objek yang sama user.sizes.width++; // ganti properti dari satu tempat alert(clone.sizes.width); // 51, melihat hasilnya ditempat yang lain ``` Untuk membenarkan hal itu, kita harus menggunakan perulangan kloning yang memeriksa setip nilai dari `user[key]` dan, jika itu adalah sebuah objek, lalu duplikasi strukturnya juga. Hal itu dinamakan dengan "deep cloning". We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). ````smart header="Const objects can be modified" An important side effect of storing objects as references is that an object declared as `const` *can* be modified. For instance: ```js run const user = { name: "John" }; *!* user.name = "Pete"; // (*) */!* alert(user.name); // Pete ``` Sepertinya baris `(*)` akan menyebabkan kesalahan, tetapi sebenarnya tidak. Nilai dari `user` adalah konstan, itu harus selalu mereferensikan objek yang sama, tetapi properti dari objek tersebut bebas untuk berubah. Dengan kata lain, `const user` memberikan kesalahan hanya jika kita mencoba menyetel` user = ... `secara keseluruhan. yang berarti, jika kita benar-benar perlu membuat properti objek konstan, itu juga mungkin, tetapi menggunakan metode yang sama sekali berbeda. Kita akan membahasnya di bab . ```` ## Ringkasan objek dibuat dan disalin dengan menggunakan referensi. Dengan kata lain, sebuah variable menyimpan bukanlah "nilai objek", tapi sebuah "referensi" (address/alamat di memori) untuk nilainya. Jadi menyalin sebuah variabel atau memindahkannya sebagai fungsi argumen akan menyalin referensinya, bukan objeknya. Semua operasi yang disalin dengan menggunakan referensi (seperti menambah/menghapus properti) dilakukan didalam satu objek yang sama. Untuk membuat "salinan asli" (kloning) kita dapat menggunakan `Object.assign` untuk apa yang disebut "shallow copy"(objek bersarang disalin dengan referensi) atau fungsi"deep cloning", seperti [_.cloneDeep (obj)](https://lodash.com/docs#cloneDeep). ================================================ FILE: 1-js/04-object-basics/03-garbage-collection/article.md ================================================ # Pengumpulan sampah (_Garbage collection_) Manajemen memori di JavaScript dilakukan secara otomatis dan tak terlihat oleh kita. Kita membuat _primitive_, objek, fungsi... Semua yang membutuhkan memori. Apa yang terjadi ketika sesuatu yang telah kita buat tersebut sudah tidak diperlukan? Bagaimana _engine_ JavaScript menemukan dan membersihkannya? ## Keterjangkauan (_Reachability_) Konsep utama manajemen memori di JavaScript ialah *keterjangkauan*. Sederhananya, sebuah nilai yang "terjangkau" adalah mereka yang masih dapat diakses atau dapat digunakan. Mereka dapat dipastikan tersimpan di memori. 1. Ada sekumpulan nilai-nilai yang terjangkau secara inheren, yang tak dapat dihapus untuk alasan yang jelas. Contohnya: - Variabel lokal dan parameter-parameter dari fungsi (yang di eksekusi) saat ini. - Variabel-variabel dan parameter-parameter dari fungsi-fungsi lain yang terkait dengan rantai panggilan bersarang saat ini. - Variabel-variabel global. - (ada beberapa hal lain, yang internal juga) Nilai-nilai tadi disebut *roots*. 2. Nilai lainnya dianggap terjangkau jika dapat dijangkau dari sebuah _root_ melalui sebuah rujukkan atau rantai rujukkan. Contoh, jika terdapat sebuah objek didalam global variabel, dan objek tersebut memiliki sebuah properti yang mereferensi objek lain, objek itu dianggap dapat dijangkau. Dan referensinya juga bisa dijangkau. Contoh lengkap dibawah ini. Ada sebuah _background process_ di _engine_ JavaScript yang disebut [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). Ia mengamati seluruh objek dan menyingkirkan semua yang sudah tak terjangkau. ## Contoh Sederhana Berikut adalah contoh paling sederhana: ```js // user memiliki rujukkan terhadap objek let user = { name: "John" }; ``` ![](memory-user-john.svg) Tanda panah disini menggambarkan sebuah rujukkan objek. Variabel global `"user"` merujuk objek `{name: "John"}` (kita sebut John supaya singkat). _Property_ `"name"` dari objek John menyimpan sebuah _primitive_, jadi itu disematkan di dalam objek. Jika nilai dari `user` ditimpa, maka rujukkannya hilang: ```js user = null; ``` ![](memory-user-john-lost.svg) Sekarang John menjadi tak terjangkau. Tak ada cara untuk mengaksesnya, tak ada rujukkan terhadapnya. _Garbage collector_ akan membuang data tersebut and membebaskan memori. ## Dua rujukkan Sekarang bayangkan kita menyalin rujukkan dari `user` ke `admin`: ```js // user memiliki rujukkan terhadap objek let user = { name: "John" }; *!* let admin = user; */!* ``` ![](memory-user-john-admin.svg) Sekarang jika kita melakukan hal yang sama: ```js user = null; ``` ...Maka objek "John" tersebut masih bisa dijangkau lewat variabel global `admin`, jadi masih ada di memori. Jika kita menimpa `admin` juga, barulah dapat dihilangkan. ## Objek-objek yang saling terkait Sekarang contoh yang lebih kompleks. Keluarga: ```js function marry(man, woman) { woman.husband = man; man.wife = woman; return { father: man, mother: woman } } let family = marry({ name: "John" }, { name: "Ann" }); ``` Fungsi `marry` "mengawinkan" dua objek dengan memberikan keduanya rujukkan satu sama lain dan mengembalikan sebuah objek baru yang berisikan kedua objek tersebut. Hasil struktur memorinya ialah : ![](family.svg) Disini, semua objek terjangkau. Sekarang mari hapus dua rujukkan: ```js delete family.father; delete family.mother.husband; ``` ![](family-delete-refs.svg) Tak cukup hanya dengan menghapus salah satu dari dua rujukkan tersebut, karena semua objek masih bisa dijangkau. Tetapi jika kita menghapus keduanya, maka dapat kita lihat bahwa John tak lagi memiliki objek yang merujukkannya: ![](family-no-father.svg) Rujukkan keluar (_outgoing reference_) tidak masalah. Hanya rujukkan masuk (_incoming reference_) yang dapat membuat sebuah objek terjangkau. Jadi, sekarang John tak terjangkau dan akan dihapus dari memori bersama semua datanya yang juga tak dapat diakses. Setelah _garbage collection_: ![](family-no-father-2.svg) ## Pulau tak terjangkau Mungkin saja satu kumpulan (pulau) objek yang saling tertaut menjadi tak terjangkau dan dihapus dari memori. Objeknya sama seperti diatas. Kemudian: ```js family = null; ``` Gambaran _in-memory_-nya menjadi: ![](family-no-family.svg) Contoh ini menunjukkan bagaimana pentingnya konsep keterjangkauan (_reachability_). Sudah jelas bahwa John dan Ann masih tertaut, keduanya memiliki rujukkan masuk. Tapi itu saja tak cukup. Objek `"family"` diatas telah menjadi tak terhubung dengan _root_, tak ada lagi rujukkan terhadapnya, sehingga keseluruhan pulau kumpulan objek tersebut menjadi tak terjangkau dan akan dihapus. ## Algoritma internal Algoritma _garbage collection_ dasar disebut _"mark-and-sweep"_. Langkah _"garbage collection"_ berikut dilakukan secara teratur: - _Garbage collector_ mengambil objek _roots_ dan "menandai" (_marks_ / mengingat) mereka. - Kemudian ia mendatangi dan "menandai" semua rujukkannya. - Kemudian ia mendatangi objek yang telah ditandai tersebut dan menandai rujukkan *mereka*. Semua objek yang telah dikunjungi akan diingat, agar nantinya tidak mengunjungi objek yang sama dua kali. - ...Dan seterusnya sampai semua rujukkan yang dapat dijangkau (dari _roots_) telah dikunjungi. - Semua objek kecuali yang ditandai akan dihapus. Contohnya, semisal kita memiliki struktur objek seperti berikut: ![](garbage-collection-1.svg) Dapat kita lihat dengan jelas "pulau tak terjangkau" di sisi kanan. Sekarang mari kita lihat bagaimana _"mark-and-sweep" garbage collector_ berurusan dengannya. Langkah pertama menandai _roots_-nya: ![](garbage-collection-2.svg) Kemudian rujukkannya ditandai: ![](garbage-collection-3.svg) ...Dan kemudian rujukkan dalamnya juga, jika masih ada: ![](garbage-collection-4.svg) Sekarang objek-objek yang tak dapat dikunjungi selama proses berlangsung dianggap tak terjangkau (_unreachable_) dan akan dihapus: ![](garbage-collection-5.svg) Kita juga bisa membayangkan proses tersebut sebagai menumpahkan ember cat dari _roots_, yang mengalir ke semua rujukkan dan menandai semua objek yang terjangkau. Yang tidak tertandai akan dihapus. Itu merupakan konsep dari bagaimana cara kerja _garbage collection_. _Engines_ JavaScript menerapkan banyak optimisasi untuk membuatnya berjalan lebih cepat dan tanpa mempengaruhi eksekusi. Beberapa optimisasi: - **Generational collection** -- objek-objek dibagi kedalam dua set: "yang baru" dan "yang lama". Kebanyakan objek muncul, melakukan tugasnya dan mati dengan cepat, mereka dapat dibersihkan secara agresif. Mereka yang bertahan cukup lama, akan menjadi "yang lama" dan tak akan sering diperiksa. - **Incremental collection** -- Jika terdapat banyak objek-objek, dan kita mencoba menapaki sambil menandai keseluruhan set objek sekaligus, itu dapat memakan waktu dan menimbulkan keterlambatan yang terlihat dalam eksekusi. Jadi _engine_ akan mencoba untuk memecah proses _garbage collection_ menjadi bagian-bagian kecil. Kemudian bagian-bagian kecil tersebut akan dieksekusi satu-persatu, secara terpisah. Itu memerlukan pencatatan ekstra diantara mereka untuk melacak perubahan, tetapi jadinya kta hanya mengalami keterlambatan kecil yang banyak daripada satuan yang besar. - **Idle-time collection** -- _garbage collector_ akan mencoba untuk jalan hanya ketika _CPU_ sedang _idle_, untuk mengurangi kemungkinan efek pada eksekusi. Terdapat optimisasi-optimisasi dan tipe-tipe lain dari algoritma _garbage collection_. Sebesar apapun keinginan untuk menjelaskannya disini, harus kutahan, karena _engines_ yang berbeda mengimplementasikan teknik dan _tweaks_ yang berbeda pula. Dan, yang lebih penting, hal-hal tersebut akan berubah seiring dengan pengembangan _engine_, jadi mempelajarinya lebih dalam "di awal", tanpa kebutuhan yang berarti mungkin akan sia-sia. Kecuali, tentu saja, jika itu merupakan murni masalah ketertarikan, maka ada beberapa tautan untukmu dibawah. ## Ringkasan Hal utama yang perlu diketahui: - Pengumpulan sampah (_Garbage collection_) dilakukan secara otomatis. Kita tidak bisa memaksa ataupun mencegahnya. - Objek-objek dipertahankan dalam memori selagi mereka terjangkau (_reachable_). - Menjadi yang dirujuk tidak sama dengan menjadi terjangkau (dari sebuah _root_): sekumpulan objek yang saling terkait dapat menjadi tak terjangkau sebagai keseluruhan. _Engine_ modern mengimplementasikan algoritma _garbage collection_ canggih (_advance_). Buku "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) mencakup beberapanya. Jika kamu familiar dengan pemrograman _low-level_, informasi mendalam tentang _garbage collector_ V8 terdapat pada artikel [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). [V8 blog](https://v8.dev/) juga mempublikasikan artikel-artikel tentang ubahan-ubahan dalam manajemen memori dari waktu ke waktu. Tentu saja, untuk belajar proses _garbage collection_, kamu lebih baik mempersiapkan diri dengan belajar tentang _internals_ V8 secara umum dan membaca blog [Vyacheslav Egorov](http://mrale.ph) yang merupakan salah seorang _engineer_ V8. Saya bilang: "V8", karena merupakan yang paling komprehensif di _cover_ oleh artikel-artikel di internet. Untuk _engine_ lainnya, pendekatannya kebanyakan mirip-mirip, tetapi _garbage collection_ berbeda dalam banyak aspek. Pengetahuan mendalam mengenai _engines_ itu bagus ketika membutuhkan optimisasi _low-level_. Tapi akan lebih bijak untuk merencanakan itu sebagai langkah selanjutnya setelah kamu akrab dengan bahasanya (JavaScript). ================================================ FILE: 1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md ================================================ **Jawaban: error.** Coba ini: ```js run function makeUser() { return { name: "John", ref: this }; } let user = makeUser(); alert( user.ref.name ); // Error: Tidak bisa membaca properti 'name' dari undefined ``` Hal itu karena aturan-aturan yang mengatur `this` tidak melihat definisi objek. Yang penting hanya momen saat panggilan terjadi. Di sini nilai dari `this` dalam `makeUser()` adalah `undefined`, karena dipanggil sebagai sebuah fungsi, tidak sebagai sebuah metode dengan sintaks "tanda titik". Nilai `this` adalah satu untuk keseluruhan fungsi, blok kode serta penulisan objek tidak mempengaruhi nilai tersebut. Jadi `ref: this` sebenarnya mengambil `this` yang sekarang dari fungsi tersebut. Berikut ini contoh kasus kebalikannya: ```js run function makeUser() { return { name: "John", *!* ref() { return this; } */!* }; } let user = makeUser(); alert( user.ref().name ); // John ``` Kini kode itu berfugsi, karena `user.ref()` adalah sebuah metode. Dan nilai dari `this` ditentukan ke objek sebelum tanda titik `.`. ================================================ FILE: 1-js/04-object-basics/04-object-methods/4-object-property-this/task.md ================================================ importance: 5 --- # Menggunakan "this" dalam penulisan objek Berikut ini adalah fungsi `makeUser` yang mengembalikan sebuah objek. Apa hasil dari mengakses `ref`? Mengapa demikian? ```js function makeUser() { return { name: "John", ref: this }; } let user = makeUser(); alert( user.ref.name ); // Apa hasilnya? ``` ================================================ 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 --- # Membuat sebuah kalkulator Buatlah sebuah objek `calculator` dengan tiga metode: - `read()` mendorong kedua nilai dan menyimpan nilai-nilai tersebut sebagai properti objek. - `sum()` mengembalikan jumlah dari nilai-nilai yang disimpan. - `mul()` mengalikan nilai-nilai yang disimpan dan mengembalikan hasilnya. ```js let calculator = { // ... your 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); } }; ================================================ 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); }); after(function() { ladder.step = 0; alert.restore(); }); }); ================================================ FILE: 1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md ================================================ Solusinya adalah untuk mengembalikan objek itu sendiri dari setiap panggilan. ```js run demo let ladder = { step: 0, up() { this.step++; *!* return this; */!* }, down() { this.step--; *!* return this; */!* }, showStep() { alert( this.step ); *!* return this; */!* } }; ladder.up().up().down().up().down().showStep(); // 1 ``` Kita juga bisa menuliskan sebuah panggilan di setiap baris. Untuk rantai kode yang panjang jadi lebih mudah dibaca seperti ini: ```js ladder .up() .up() .down() .up() .down() .showStep(); // 1 ``` ================================================ FILE: 1-js/04-object-basics/04-object-methods/8-chain-calls/task.md ================================================ importance: 2 --- # *Chaining* (merantaikan) Ada sebuah objek layaknya tangga (`ladder`) yang dapat naik dan turun: ```js let ladder = { step: 0, up() { this.step++; }, down() { this.step--; }, showStep: function() { // menunjukkan langkah yang sekarang alert( this.step ); } }; ``` Kini, jika kita perlu untuk membuat beberapa panggilan secara berurutan, bisa dilakukan dengan cara seperti ini: ```js ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 ``` Modifikasi kode `up`, `down` dan `showStep` untuk membuat panggilan-panggilan tersebut dapat dirantaikan satu sama lain, seperti ini: ```js ladder.up().up().down().showStep(); // 1 ``` Pendekatan demikian digunakan secara luas di banyak *library* JavaScript. ================================================ FILE: 1-js/04-object-basics/04-object-methods/article.md ================================================ # Metode objek, "this" Objek-objek biasanya dibuat untuk merepresentasikan benda di dunia nyata, seperti para pengguna, perintah dan sebagainya: ```js let user = { name: "John", age: 30 }; ``` Dan, di dunia nyata, seorang pengguna bisa *bertindak*: memilih sesuatu dari keranjang belanja, *login*, *logout* dan lain-lain. Tindakan-tindakan direpresentasikan dalam JavaScript dengan fungsi-fungsi dalam properti. ## Contoh metode Sebagai awalan, mari ajarkan `user` untuk bilang hello: ```js run let user = { name: "John", age: 30 }; *!* user.sayHi = function() { alert("Hello!"); }; */!* user.sayHi(); // Hello! ``` Di sini kita hanya menggunakan sebuah fungsi ekspresi untuk membuat fungsi dan menugaskannya ke properti `user.sayHi` pada objek. Kemudian kita memanggil fungsi tersebut. Kini "pengguna" bisa berbicara! Sebuah fungsi yang mana merupakan properti dari sebuah objek disebut sebagai *metode*-nya. Jadi, di sini kita memiliki sebuah metode `sayHi` dari objek`user`. Tentu saja, kita bisa menggunakan sebuah fungsi sebagai sebuah metode pra-deklarasi (*pre-declared*), seperti beriktu: ```js run let user = { // ... }; *!* // pertama, deklarasi function sayHi() { alert("Hello!"); }; // lalu tambbahkan sebagai sebuah metodes user.sayHi = sayHi; */!* user.sayHi(); // Hello! ``` ```smart header="Object-oriented programming" Ketika kita menulis kode kita menggunakan objek-objek untuk merepresentasikan benda, itulah yang disebut sebagai [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), disingkat menjadi: "OOP". OOP adalah hal besar, sebuah sains yang sangat menarik. Bagaimana cara memilih entitas yang benar? bagaimana cara mengorganisir interaksi diantara mereka? Itulah arsitektur, dan terdapat buku yang bagus untuk topik itu, seperti "Design Patterns: Elements of Reusable Object-Oriented Software" oleh E. Gamma, R. Helm, R. Johnson, J. Vissides atau "Object-Oriented Analysis and Design with Applications" by G. Booch, and more. ``` ### Metode ringkas Terdapat sebuah sintaks yang lebih pendek untuk metode-metode dalams sebuah penulisan (kode) objek: ```js // objek-objek ini sama user = { sayHi: function() { alert("Hello"); } }; // metode ringkas terlihat lebih bagus, kan? user = { *!* sayHi() { // sama seperti "sayHi: function()" */!* alert("Hello"); } }; ``` Seperti yang didemonstrasikan, kita bisa mengabaikan `"function"` dan hanya menuliskan `sayHi()`. Sebenarnya, notasi-notasi tersebut tidak sepenuhnya sama. Ada beberapa perbedaan kecil yang berhubungan dengan *object inheritance* atau pewarisan objek (akan dibahas nanti), tetapi untuk sekarang hal-hal tersebut tidak terlalu penting. Dalam hampir kebanyakan kasus sintaks ringkas lebih disukai. ## "this" dalam metode Sudah umum bahwa sebuah metode objek perlu untuk mengakses informasi yang disimpan dalam objek untuk melakukan tugasnya. Contohnya, kode di dalam `user.sayHi()` bisa jadi membutuhkan nama dari `user`. **Untuk mengakses objek yang bersangkutan, sebuah metode dapat menggunakan kata kunci `this`.** Nilai dari `this` adalah objek "sebelum (tanda) titik", yang mana sebelumnya memanggil metode tersebut. Sebagai contoh: ```js run let user = { name: "John", age: 30, sayHi() { *!* // "this" adalah "objek yang sekarang" alert(this.name); */!* } }; user.sayHi(); // John ``` Di sini selama ekseskusi `user.sayHi()`, nilai dari `this` akan menjadi `user`. Secara teknis, memungkingkan juga untuk mengakses objek tanpa `this`, dengan cara mereferensikannya melalui variabel luar: ```js let user = { name: "John", age: 30, sayHi() { *!* alert(user.name); // menggunakan "user" ketimbang "this" */!* } }; ``` ...Namun kode yang demikian tidak dapat diandalkan. Jika kita memilih untuk menyalin `user` ke sebuah variabel lain, misalnya `admin = user` dan menimpa `user` dengan hal lain, akhirnya malah akan mengakses objek yang salah. Hal tersebut dicontohkan seperti berikut ini: ```js run let user = { name: "John", age: 30, sayHi() { *!* alert( user.name ); // akan mengarah ke sebuah error */!* } }; let admin = user; user = null; // timpa/overwrite agar terlihat lebih jelas admin.sayHi(); // Uups! dalam sayHi(), nama yang lama sedang digunakan! error! ``` Jika kita menggunakan `this.name` ketimbang `user.name` dalam `alert`, maka kodenya akhirnya berfungsi. ## "this" tidak ditemukan Dalam JavaScript, kata kunci `this` berperilaku tidak seperti kebanyak bahasa pemrograman lainnya. 'this' juga bisa digunakan dalam fungsi apapun. Tidak ada *syntax error* dalam contoh berikut ini: ```js function sayHi() { alert( *!*this*/!*.name ); } ``` Nilai dari `this` dievaluasi selama proses *run-time*, tergantung dari konteksnya. Contohnya, di sini fungsi yang sama ditugaskan pada dua objek yang berbeda dan memiliki "this" dalam pemanggilannya: ```js run let user = { name: "John" }; let admin = { name: "Admin" }; function sayHi() { alert( this.name ); } *!* // menggunakan fungsi yang sama dalam dua objek user.f = sayHi; admin.f = sayHi; */!* // panggilan-panggilan ini memiliki this yang berbeda // "this" dalam fungsi adalah objek "sebelum tanda titik" user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin (tanda titik atau kurung siku mengakses metode tersebut – bukan masalah) ``` Aturannya sederhana: jika `obj.f()` dipanggil, maka `this` adalah `obj` selama pemanggilan `f`. Jadi antara `user` atau `admin` pada contoh di atas. ````smart header="Pemanggilan tanpa sebuah objek: `this == undefined`" Kita bahkan bisa memanggil fungsi tanpa sebuah objek sama sekali: ```js run function sayHi() { alert(this); } sayHi(); // undefined ``` Dalam kasus ini `this` adalah `undefined` di mode *strict*. Jika kita coba untuk mengakses `this.name`, akan menghasilkan sebuah error. Dalam mode *non-strict* nilai dari `this` dalam kasus demikian akan menjadi *objek global* (`window` dalam sebuah peramban, kita akan membahasnya lebih lanjut dalam bab [](info:global-object)). Ini adalah sebuah perilaku historis yang dibenahi oleh `"use strict"`. Biasanya panggilan yang demikian adalah sebuah kesalahan *programming*. Jika terdapat `this` dalam sebuah fungsi, `this` tersebut kemungkinan besar akan dipanggil dalam konteks sebuah objek. ```` ```smart header="Akibat dari `this` yang tidak terikat" Jika kamu berasal dari bahasa pemrograman lain, mungkin saja kamu menggunakan gagasan sebuah "pengikatan (bound) `this`", dimana metode-metode didefinisikan dalam sebuah objek selalu memiliki `this` yang merujuk pada objek itu. Dalam JavaScript `this` itu "bebas", nilainya dieveluasi saat waktu pemanggilan dan tidak tergantung pada dimana metode tersebut di deklarasikan, namun lebih pada objek apa yang berada "sebelum (tanda) titik". Konsep run-time mengeveluasi `this` memiliki kelebihan dan kekurangan sendiri. Di satu sisi, sebuah fungsi bisa digunakan ulang untuk objek-objek yang berbeda. Pada sisi sebaliknya, fleksibilitas yang besar membuat lebih banyak kemungkinan adanya kesalahan-kesalahan. Di sini posisi kita tidak untuk menghakimi apakah pilihan rancangan bahasa pemrograman ini baik atau buruk. Kita akan mengerti bagaimana bekerja dengan hal itu, serta bagaimana cara mendapatkan keuntungan dari hal tersebut dan menghindari adanya masalah. ```` ## Fungsi arrow tidak memiliki "this" Fungsi-fungsi *arrow* itu istimewa: fungsi tersebut tidak memiliki `this` "milik fungsi itu sendiri". Jika kita mereferensikan `this` dari fungsi demikian, hal itu didapat dari fungsi "normal" di luar. For instance, here `arrow()` uses `this` from the outer `user.sayHi()` method: ```js run let user = { firstName: "Ilya", sayHi() { let arrow = () => alert(this.firstName); arrow(); } }; user.sayHi(); // Ilya ``` Itulah fitur istimewa dari fungsi *arrow*, fitur berguna ketika kita benar-benar tidak ingin memiliki sebuah `this` yang terpisah, namun kita mengambilnya dari luar lingkung tersebut. Selanjutnya dalam bab kita akan mempelajari lebih dalam mengenai fungsi *arrow*. ## Ringkasan - Fungsi-fungsi yang disimpan dalam properti objek disebut sebagai "metode". - Metode membuat objek dapat "bertindak" seperti `object.doSomething()`. - Metode bisa mereferensikan ke objek sebagai `this`. Nilai dari `this` didefiniskan saat *run-time*. - Ketika sebuah fungsi dideklarasikan, fungsi tersebut bisa menggunakan `this`, tetapi `this` tersebut tidak memiliki nilai sampai fungsi tersebut dipanggil. - Sebuah fungsi bisa disalin di antara objek-objek. - Ketika sebuah fungsi dipanggil dalam sintaks "metode": `object.method()`, nilai `this` selama pemanggilan adalah `object`. Mohon diingat bahwa fungsi *arrow* itu istimewa: fungsi tersebut tidak memiliki `this`. Ketika `this` diakses di dalam sebuah fungsi *arrow*, `this` itu diambil dari luar. ================================================ FILE: 1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md ================================================ Ya, hal itu memungkinkan. Jika sebuah fungsi mengembalikan sebuah objek lalu `new` mengembalikan objek tersebut sebagai ganti `this`. Jadi fungsi tersebut dapat, misalnya, mengembalikan objek `obj` yang secara eksternal didefinisikan sama: ```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 --- # Dua fungsi – satu objek Apakah mungkin untuk membuat fungsi `A` dan fungsi `B` seperti `new A()==new B()`? ```js no-beautify function A() { ... } function B() { ... } let a = new A; let b = new B; alert( a == b ); // true ``` Jika bisa, berikan contoh kodenya. ================================================ 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 --- # Buat Kalkulator baru Buatlah sebuah fungsi konstruktor `Calculator` yang membuat objek dengan 3 method: - `read()` tanyakan dua nilai menggunakan `prompt dan masukan mereka kedalam properti objek. - `sum()` mengembalikan jumlah dari properti-properti. - `mul()` mengembalikan perkalian produk dari properti-properti. Contoh: ```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 --- # Membuat Akumulator baru Buatlah sebuah fungsi konstruktor `Accumulator(startingValue)`. Objek yang dibuat fungsi tersebut harus: - Menyimpan "nilai yang sekarang" dalam `value` properti. Nilai awal diatur menjadi argumen konstruktor `startingValue`. - Metode `read()` harus menggunakan `prompt` untuk membaca sebuah angka dan menambahkannya ke `value`. Dalam kata lain, properti `value` adalah hasil penjumlahan dari semua nilai yang dimasukkan oleh pengguna dengan nilai awal `startingValue`. Berikut ini contoh kodenya: ```js let accumulator = new Accumulator(1); // nilai awal 1 accumulator.read(); // menambahkan nilai yang dimasukkan oleh pengguna accumulator.read(); // menambahkan nilai yang dimasukkan oleh pengguna alert(accumulator.value); // menampilkan jumlah dari kedua nilai ``` [demo] ================================================ FILE: 1-js/04-object-basics/06-constructor-new/article.md ================================================ # Konstruktor, operator "new" Sintaks reguler `{...}` memungkinkan kita untuk membuat satu objek. Tapi seringkali kita perlu untuk membuat banyak objek-objek serupa, seperti pengguna atau *item* menu berganda dan sebagainya. Hal tersebut dapat diselesaikan dengan menggunakan fungsi konstruktor dan operator `"new"`. ## Fungsi konstruktor Fungsi konstruktor secara teknis adalah fungsi biasa. Terdapat dua persetujuan sebelumnya, yakni: 1. Fungsi tersebut diberi nama dengan huruf kapital terlebih dulu. 2. Fungsi tersebut harus dieksekusi dengan hanya menggunakan operator `"new"`. Sebagai contoh: ```js run function User(name) { this.name = name; this.isAdmin = false; } *!* let user = new User("Jack"); */!* alert(user.name); // Jack alert(user.isAdmin); // false ``` Ketika sebuah fungsi dieksekusi dengan menggunakan `new`, fungsi tersebut melakukan tahapan-tahapan berikut ini: 1. Sebuah objek kosong yang dibuat dan diserahkan ke `this`. 2. Bagian utama fungsi tersebut bereksekusi. Biasanya memodifikasi `this`, menambahkan properti baru ke `this`. 3. Nilai dari `this` dikembalikan. Dengan kata lain, `new User(...)` berjalan seperti ini: ```js function User(name) { *!* // this = {}; (secara implisit) */!* // menambahkan properti ke this this.name = name; this.isAdmin = false; *!* // mengembalikan this; (secara implisit) */!* } ``` Jadi `let user = new User("Jack")` memberikan hasil yang sama seperti halnya: ```js let user = { name: "Jack", isAdmin: false }; ``` Sekarang jika kita ingin membuat *user* lain, kita bisa memanggil `new User("Ann")`, `new User("Alice")` dan seterusnya. Lebih pendek penulisannya daripada menulis langsung sintaks baru setiap ingin membuat *user* baru, serta lebih mudah dibaca. Itulah tujuan utama dari konstruktor -- untuk mengimplementasikan kode pembuatan objek yang dapat dipakai ulang (*reusable*). Mari ingat sekali lagi -- secara teknis, fungsi apapun dapat digunakan sebagai sebuah konstruktor. Hal tersebut berarti: fungsi apapun dapat dijalankan dengan `new`, dan bisa mengeksekusi algoritma di atas. "Huruf kapital dulu" adalah kesepakatan umum, untuk membuatnya lebih jelas bahwa sebuah fungsi untuk dijalankan dengan `new`. ````smart header="function() { ... } baru" Jika kita memiliki banyak baris kode tentang pembuatan sebuah objek tunggal yang kompleks, kita dapat membungkus kode tersebut dalam fungsi konstruktor, seperti ini: ```js // create a function and immediately call it with new let user = new function() { this.name = "John"; this.isAdmin = false; // ...kode lain untuk pembuatan user // bisa jadi lebih rumit logika dan pernyataannya // variabel lokal dan lain-lain. }; ``` Konstruktor tersebut tidak dapat dipanggil lagi, karena tidak disimpan dimanapun, hanya dibuat dan dipanggil. Jadi trik ini ditujukan untuk mengenkapsulasi kode yang mengonstruksi objek tunggal, tanpa penggunaan di masa yang akan datang. ```` ## Constructor mode test: new.target ```smart header="Pembahasan tingkat lanjut" Sintaks dari bagian ini jarang digunakan, lewati saja kecuali kamu ingin mengetahui semuanya. ``` Di dalam sebuah fungsi, kita dapat memeriksa apakah fungsi tersebut dipanggil dengan atau tanpa `new`, dengan cara menggunakan sebuah properti khusus `new.target`. Fungsi tersebut kosong untuk panggilan-panggilan reguler dan menyamai fungsi jika dipanggil dengan `new`: ```js run function User() { alert(new.target); } // tanpa "new": *!* User(); // undefined */!* // dengan "new": *!* new User(); // fungsi User { ... } */!* ``` Sintaks itu dapat digunakan di dalam fungsi tersebut untuk mengetahui apakah fungsi dipanggil dengan `new`, "dalam mode konstruktor", atau tanpa `new`, "dalam mode reguler". Kita juga bisa membuat baik panggilan dengan `new` serta panggilan reguler untuk melakukan hal yang sama, seperti ini: ```js run function User(name) { if (!new.target) { // jika kamu menjalankanku tanpa new return new User(name); // ...aku akan menambahkan new untukmu } this.name = name; } let john = User("John"); // mengarahkan ulang panggilan ke User baru alert(john.name); // John ``` Pendekatan ini terkadang digunakan dalam *library* untuk membuat sintaks lebih fleksibel. Jadi orang-orang bisa memanggil fungsi dengan atau tanap `new`, dan masih bisa berfungsi. Mungkin saja bukanlah hal baik untuk menggunakan pendekatan tersebut dimana saja, karena melewatkan `new` membuat kurang jelas sintaks atau kode yang sedang berjalan. Dengan `new` kita semua tahu bahwa objek baru sedang dibuat. ## Hasil *return* dari konstruktor Biasanya, konstruktor tidak memiliki sebuah pernyataan `return`. Tugas konstruktor adalah untuk menulis semua hal-hal yang dibutuhkan ke dalam `this`, dan hal tersebut secara otomatis menjadi hasil. Tetapi jika ada sebuah pernyataan `return`, maka aturannya sederhana: - Jika `return` dipanggil dengan sebuah objek, maka objek tersebut dikembalikan sebagai ganti `this`. - Jika `return` dipanggil dengan sebuah *primitive*, panggilan tersebut diabaikan. Dalam kata lain, `return` dengan sebuah objek mengembalikan objek itu, dalam semua kasus lainnya `this` dikembalikan. Sebagai contoh, di sini `return` mengambil alih `this` dengan cara mengembalikan sebuah objek: ```js run function BigUser() { this.name = "John"; return { name: "Godzilla" }; // <-- mengembalikan objek this } alert( new BigUser().name ); // Godzilla, dapat objeknya ``` Dan berikut ini adalah sebuah contoh dengan sebuah `return` kosong (atau bisa kita tempatkan dengan sebuah *primitive* setelahnya, tidak dipermasalahkan): ```js run function SmallUser() { this.name = "John"; return; // <-- mengembalikan this } alert( new SmallUser().name ); // John ``` Biasanya konstruktor tidak memiliki sebuah pernyataan `return`. Di sini kita menyebutkan perilaku khusus dengan cara mengembalikan objek dengan tujuan utamanya yakni hanya untuk melengkapi saja. ````smart header="Mengabaikan parentheses" Omong-omong, kita bisa mengabaikan parentheses setelah `new`, jika tidak memiliki argumen: ```js let user = new User; // <-- tanpa parentheses // sama seperti let user = new User(); ``` Mengabaikan parentheses di sini tidak dipandang sebagai sebuah "gaya yang baik", tetapi sintaks tersebut diperbolehkan oleh spesifikasi. ```` ## Metode dalam konstruktor Menggunakan fungsi-fungsi konstruktor untuk membuat objek memberikan sebuah fleksibilitas yang amat baik. Fungsi konstruktor bisa jadi memiliki parameter yang mendefinisikan bagaimana cara untuk mengonstruksi objek tersebut, serta hal apa yang dimasukkan ke objek tersebut. Tentu saja, kita bisa menambahkan ke `this` tidak hanya properti, tapi metode juga. Sebagai contoh, `new User(name)` di bawah membuat sebuah objek dengan `name` yang diberikan dan metode `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() { ... } } */ ``` Untuk membuat objek yang kompleks, ada sintaks tingkat lanjut, yaitu [classes](info:classes), yang akan kita bahas nanti. ## Ringkasan - Fungsi konstruktor, atau secara singkat, konstruktor, adalah fungsi reguler, tetapi ada sebuah kesepakatan umum untuk memberi nama konstruktor dengan huruf kapital terlebih dahulu. - Fungsi konstruktor seharusnya hanya bisa dipanggil menggunakan `new`. Panggilan yang demkian berarti sebuah pembuatan `this` kosong pada awalnya dan mengembalikan `this` yang sudah berisi di akhir. Kita bisa menggunakan fungsi konstruktor untuk membuat objek-objek serupa sekaligus. JavaScript menyediakan fungsi konstruktor untuk banyak objek bawaan di bahasa pemrograman: seperti `Date` untuk penanggalan, `Set` untuk kumpulan/rangkaian dan banyak lainnya yang akan kita pelajari. ```smart header="Objek, kita akan kembali!" Dalam bab ini kita hanya membahas dasar-dasar tentang objek dan konstruktor. Objek dan konstruktor adalah dasar penting untuk mempelajari lebih banyak tentang tipe data dan fungsi dalam bab-bab selanjutnya. Setelah kita mempelajari itu, kita kembali ke objek dan membahas objek lebih mendalam lagi di bab dan . ``` ================================================ FILE: 1-js/04-object-basics/07-optional-chaining/article.md ================================================ # Optional chaining '?.' [recent browser="new"] The optional chaining `?.` is a safe way to access nested object properties, even if an intermediate property doesn't exist. ## The "non-existing property" problem If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common. As an example, let's say we have `user` objects that hold the information about our users. Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them. In such case, when we attempt to get `user.address.street`, and the user happens to be without an address, we get an error: ```js run let user = {}; // a user without "address" property alert(user.address.street); // Error! ``` That's the expected result. JavaScript works like this. As `user.address` is `undefined`, an attempt to get `user.address.street` fails with an error. In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street"). ...And another example. In the web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element. ```js run // document.querySelector('.elem') is null if there's no element let html = document.querySelector('.elem').innerHTML; // error if it's null ``` Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result. How can we do this? The obvious solution would be to check the value using `if` or the conditional operator `?`, before accessing its property, like this: ```js let user = {}; alert(user.address ? user.address.street : undefined); ``` It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required. E.g. let's try getting `user.address.street.name`. We need to check both `user.address` and `user.address.street`: ```js let user = {}; // user has no address alert(user.address ? user.address.street ? user.address.street.name : null : null); ``` That's just awful, one may even have problems understanding such code. Don't even care to, as there's a better way to write it, using the `&&` operator: ```js run let user = {}; // user has no address alert( user.address && user.address.street && user.address.street.name ); // undefined (no error) ``` AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal. As you can see, property names are still duplicated in the code. E.g. in the code above, `user.address` appears three times. That's why the optional chaining `?.` was added to the language. To solve this problem once and for all! ## Optional chaining The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`. **Further in this article, for brevity, we'll be saying that something "exists" if it's not `null` and not `undefined`.** In other words, `value?.prop`: - works as `value.prop`, if `value` exists, - otherwise (when `value` is `undefined/null`) it returns `undefined`. Here's the safe way to access `user.address.street` using `?.`: ```js run let user = {}; // user has no address alert( user?.address?.street ); // undefined (no error) ``` The code is short and clean, there's no duplication at all. Reading the address with `user?.address` works even if `user` object doesn't exist: ```js run let user = null; alert( user?.address ); // undefined alert( user?.address.street ); // undefined ``` Please note: the `?.` syntax makes optional the value before it, but not any further. E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/undefined` (and returns `undefined` in that case), but that's only for `user`. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more `.` with `?.`. ```warn header="Don't overuse the optional chaining" We should use `?.` only where it's ok that something doesn't exist. For example, if according to our coding logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. So, if `user` happens to be undefined due to a mistake, we'll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug. ``` ````warn header="The variable before `?.` must be declared" If there's no variable `user` at all, then `user?.anything` triggers an error: ```js run // ReferenceError: user is not defined user?.address; ``` The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables. ```` ## Short-circuiting As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist. So, if there are any further function calls or side effects, they don't occur. For instance: ```js run let user = null; let x = 0; user?.sayHi(x++); // no "sayHi", so the execution doesn't reach x++ alert(x); // 0, value not incremented ``` ## Other variants: ?.(), ?.[] The optional chaining `?.` is not an operator, but a special syntax construct, that also works with functions and square brackets. For example, `?.()` is used to call a function that may not exist. In the code below, some of our users have `admin` method, and some don't: ```js run let userAdmin = { admin() { alert("I am admin"); } }; let userGuest = {}; *!* userAdmin.admin?.(); // I am admin */!* *!* userGuest.admin?.(); // nothing (no such method) */!* ``` Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the user object exists, so it's safe read from it. Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors. The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist. ```js run let key = "firstName"; let user1 = { firstName: "John" }; let user2 = null; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined ``` Also we can use `?.` with `delete`: ```js run delete user?.name; // delete user.name if user exists ``` ````warn header="We can use `?.` for safe reading and deleting, but not writing" The optional chaining `?.` has no use at the left side of an assignment. For example: ```js run let user = null; user?.name = "John"; // Error, doesn't work // because it evaluates to undefined = "John" ``` It's just not that smart. ```` ## Summary The optional chaining `?.` syntax has three forms: 1. `obj?.prop` -- returns `obj.prop` if `obj` exists, otherwise `undefined`. 2. `obj?.[prop]` -- returns `obj[prop]` if `obj` exists, otherwise `undefined`. 3. `obj.method?.()` -- calls `obj.method()` if `obj.method` exists, otherwise returns `undefined`. As we can see, all of them are straightforward and simple to use. The `?.` checks the left part for `null/undefined` and allows the evaluation to proceed if it's not so. A chain of `?.` allows to safely access nested properties. Still, we should apply `?.` carefully, only where it's acceptable that the left part doesn't exist. So that it won't hide programming errors from us, if they occur. ================================================ FILE: 1-js/04-object-basics/08-symbol/article.md ================================================ # Tipe simbol Menurut spesifikasi spesifikasi, properti-properti kunci objek bisa saja bertipe *string*, atau bertipe simbol. Bukan angka (*number*), bukan *boolean*, hanya *string* atau simbol-simbol, kedua tipe ini. Hingga kini kita telah menggunakan *string* saja. Mari kita lihat keuntungan-keuntungan apa saja dari simbol yang bisa diberikan ke kita. ## Simbol-simbol Sebuah "simbol" merepresentasikan sebuah pengidentifikasi yang unik. Nilai dari tipe ini dapat dibuat menggunakan `Symbol()`: ```js // id adalah sebuah simbol baru let id = Symbol(); ``` Selama penyusunan, kita bisa memberikan simbol sebuah deskripsi (juga disebut sebagai nama simbol), kebanyakan berguna untuk tujuan-tujuan *debugging*: ```js // id adalah simbol dengan deskripsi "id" let id = Symbol("id"); ``` Simbol-simbol sudah pasti unik. Bahkan jika kita membuat banyak simbol dengan deskripsi yang, mereka memiliki nilai-nilai yang berbeda. Deskripsi hanyalah sebuah label yang tidak mempengaruhi apapun. Sebagai contoh, berikut ini ada dua simbol dengan deskripsi yang sama -- keduanya tidak sama: ```js run let id1 = Symbol("id"); let id2 = Symbol("id"); *!* alert(id1 == id2); // false */!* ``` Jika kamu tidak asing dengan Ruby atau bahasa pemrograman lain yang juga memiliki hal seperti "simbol" -- tolong jangan sampai keliru. Simbol-simbol (di) JavaScript itu berbeda. ````warn header="Simbol-simbol tidak dikonversi otomatis menjadi string" Kebanyakan nilai-nilai dalam JavaScript mendukung konversi implisit menjadi sebuah string. Contohnya, kita bisa memberi `alert` pada hampir nilai apapun, dan masih akan berfungsi. Simbol itu istimewa. Mereka tidak terkonversi otomatis. Sebagai contoh, `alert` ini akan memunculkan sebuah error: ```js run let id = Symbol("id"); *!* alert(id); // TypeError: Cannot convert a Symbol value to a string */!* ``` Hal tersebut adalah sebuah "garda bahasa pemrograman" untuk menghadapi adanya kekacauan, karena string dan simbol itu berbeda secara fundamental dan sudah seharusnya tidak akan terkonversi dari satu ke lainnya secara tidak sengaja. Jika kita benar-benar ingin menunjukkan sebuah simbol, kita perlu secara eskplisit memanggil `.toString()` sintaks tersebut, seperti berikut ini: ```js run let id = Symbol("id"); *!* alert(id.toString()); // Symbol(id), sekarang berfungsi */!* ``` Atau mengambil properti `symbol.description` untuk menunjuukan deskripsinya saja: ```js run let id = Symbol("id"); *!* alert(id.description); // id */!* ``` ```` ## Properti "tersembunyi" (*hidden*) Simbol memungkinkan kita untuk membuat properti-properti yang "tersembunyi" (*hidden*) dari sebuah objek, yang mana tidak akan ada bagian lain dari kode yang bisa mengakses atau meng-*overwrite* tanpa sengaja. Sebagai contoh, jika kita bekerja dengan objek-objek `user`, yang dimiliki oleh sebuah kode pihak ketiga. Kita akan menambahkan pengidentifikasi pada objek-objek tersebut. Mari gunakan sebuah kunci simbol untuk hal tersebut: ```js run let user = { // dimiliki oleh kode lainyya name: "John" }; let id = Symbol("id"); user[id] = 1; alert( user[id] ); // kita bisa mengakses data menggunakan simbol sebagai kunci ``` Apa keuntungan dari menggunakan `Symbol("id")` daripada sebuah *string* `"id"`? Sebagaimana objek-objek `user` adalah milik kode lain, dan kode itu juga berfungsi dengan objek-objek tadi, kita seharusnya tidak hanya menambahkan ruang apapun di situ. Hal tersebut tidak aman. Tetapi sebuah simbol tidak bisa diakses tanpa sengaja, kode pihak ketiga bahkan tidak akan melihatnya, jadi mungkin tidak masalah jika demikian. Juga, bayangkan *script* lain ingin memiliki pengidentifikasi sendiri dalam objek `user`, untuk tujuannya masing-masing. Hal tersebut bisa saja *library* JavaScript lainnya, jadi *script-script* tersebut benar-benar tidak menyadari satu sama lainnya. Kemudian *script* tersebut bisa membuat `Symbol("id")`-nya sendiri, seperti berikut ini: ```js // ... let id = Symbol("id"); user[id] = "Their id value"; ``` Tidak ada konflik antara pengidentifikasi kita dengan pengidentifikasi mereka, karena simbol selalu berebeda, bahkan jika simbol-simbol itu memiliki nama yang sama. ...Tapi jika kita menggunakan sebuah *string* `"id"` bukan sebuah simbol untuk tujuan yang sama, dengan demikian *akan menjadi* sebuah konflik: ```js let user = { name: "John" }; // Script kita menggunakan properti "id" user.id = "Our id value"; // ...Script lain juga menginginkan "id" untuk tujuannya sendiri... user.id = "Their id value" // Boom! properti tertimpa/overwrite oleh script lain! ``` ### Simbol didalam objek literal Jika kita ingin menggunakan sebuah simbol dalam sebuah tulisan objek `{...}`, kita perlu menuliskan simbol tersebut dalam tanda kurung siku. Seperti ini: ```js let id = Symbol("id"); let user = { name: "John", *!* [id]: 123 // bukan "id": 123 */!* }; ``` Itu karena kita memerlukan nilai dari variabel `id` sebagai kunci, bukan *string* dari "id". ### Simbol diabaikan menggunakan *for..in* Properti-properti simbolis tidak ikut serta dalam pengulangan (*loop*) `for..in`. For instance: ```js run let id = Symbol("id"); let user = { name: "John", age: 30, [id]: 123 }; *!* for (let key in user) alert(key); // name, age (bukan simbol) */!* // akses langsung dengan simbol, berfungsi alert( "Direct: " + user[id] ); ``` `Object.keys(user)` juga mengabaikannya. Itu adalah bagian dari prinsip umum "menyembunyikan properti simbolis" (*hiding symbolic properties*). Jika *script* lain atau sebuah *library* melakukan pengulanagn pada objek kita, hal tersebut tidak akan mengakses sebuah properti simbolis tanpa diduga. Sebaliknya, [Object.assign](mdn:js/Object/assign) menyalin baik *string* properti maupun simbol properti: ```js run let id = Symbol("id"); let user = { [id]: 123 }; let clone = Object.assign({}, user); alert( clone[id] ); // 123 ``` Tidak ada paradoks di sini. Hal itu sesuai rancangan. Gagasan bahwa ketika kita meng-*clone* sebuah objek atau menyatukan (*merge*) objek-objek, kita biasanya ingin *semua* properti disalin (termasuk simbol seperti `id`). ## Simbol global Seperti yang kita lihat, biasanya semua simbol itu berbeda, bahkan jika simbol-simbol tersebut memiliki nama yang sama. Tapi terkadang kita ingin simbol yang bernama sama untuk menjadi barang yang sama pula. Sebagai contoh, bagian-bagian lain dari aplikasi kita ingin mengakses simbol `"id"` yang berarti properti yang sama pula. Untuk melaksanakan aksi itu, hadirlah sebuah catatan simbol-simbol global (*global symbol registry*). Kita bisa membuat simbol-simbol di dalamnya dan mengaksesnya nanti, dan hal tersebut menjamin bahwa akses yang berulang oleh nama yang sama (akan) mengembalikan simbol yang sama pula. Agar bisa membaca (atau membuat jika belum ada) sebuah simbol dari catatan, gunakan `Symbol.for(key)`. Hal itu memeriksa catatan (simbol) global, dan jika ada sebuah simbol yang dideskripsikan sebagai `key`, lalu mengembalikannya, jika tidak - buatlah sebuah simbol baru `Symbol(key)` dan menyimpan simbol baru tersebut dalam catatan sesuai namanya `key`. Contohnya: ```js run // membaca dari catatan global let id = Symbol.for("id"); // jika simbol tidak ada, simbol tersebut akan dibuat // baca simbol tersebut lagi (mungkin dari bagian lain dari kode) let idAgain = Symbol.for("id"); // simbol yang sama alert( id === idAgain ); // true ``` Simbol-simbol di dalam catatan (*registry*) disebut sebagai *simbol-simbol global* (*global symbols*). Jika kita ingin sebuah simbol yang berlaku untuk keseluruhan aplikasi, dapat diakses dari mana saja dalam kode -- itulah kegunaan dari simbol global. ```smart header="Simbol global itu seperti dalam Ruby" Dalam beberapa bahasa pemrograman, seperti Ruby, hanya ada satu simbol per nama. Dalam JavaScript, seperti yang bisa kita lihat, yakni simbol-simbol global. ``` ### Symbol.keyFor Untuk simbol-simbol global, tidak hanya `Symbol.for(key)` yang mengembalikan sebuah simbol berdasarkan nama, tetapi ada sebuah panggilan sebaliknya: `Symbol.keyFor(sym)`, sintaks tersebut melakukan hal sebaliknya tadi: mengembalikan sebuah nama berdasarkan sebuah simbol global. Contohnya: ```js run // mendapatkan simbol bersasarkan nama let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // mendapatkan nama berdasarkan simbol alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` `Simbol.keyFor` secara internal menggunakan simbol registry global untuk mencari key/kunci dari simbolnya. Jadi itu tidak akan bekerja dengan simbol non-global. Jika simbolnya bukan global, itu tidak akan bisa menemukannya dan akan mengembalikan `undefined`. Seperti yang dikatakan, simbol apapun memiliki properti `description`. Contohnya: ```js run let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); alert( Symbol.keyFor(globalSymbol) ); // name, simbol global alert( Symbol.keyFor(localSymbol) ); // undefined, bukan simbol global alert( localSymbol.description ); // name ``` ## Simbol-simbol sistem Terdapat banyak simbol-simbol "sistem" yang JavaScript gunakan secara internal, dan kita bisa menggunakan simbol-simbol sistem tersebut untuk mengatur dengan baik berbagai aspek dari objek kita. Simbol-simbol tersebut sudah terdaftar dalam spesifikasi di tabel [Simbol-simbol ternama (*well-known symbols*)](https://tc39.github.io/ecma262/#sec-well-known-symbols): - `Symbol.hasInstance` - `Symbol.isConcatSpreadable` - `Symbol.iterator` - `Symbol.toPrimitive` - ...dan seterusnya. Contohnya, `Symbol.toPrimitive` membuat kita dapat mendeskripsikan objek menjadi hasil konversi yang primitif. Kita akan melihatnya segera. Simbol-simbol lain akan juga menjadi tidak asing ketika kita sedenang mempelajari fitur-fitur di bahasa pemrograman tersebut. ## Ringkasan `Symbol` adalah sebuah jenis primitif dari pengindetifikasi yang unik. Simbol-simbol dibuat menggunakan panggilan `Symbol()` dengan sebuah deskripsi (nama). Simbols selalu berbeda nilainya, bahkan jika mereka memiliki nama yang sama. Jika kita ingin simbol-simbol yang bernama sama tersebut untuk menjadi (simbol yang) sama, maka kita harus menggunakan catatan (*registry*) global: `Symbol.for(key)` mengembalikan (membuat jika perlu) sebuah simbol global dengan `key` yang sama dengan namanya. Panggilan-panggilan berganda pada `Symbol.for` dengan `key` yang sama mengenbalikan simbol yang persis sama. Simbol memiliki dua alasan utama pada pemakaiannya: 1. Properti objek yang "tersembunyi". Jika kita ingin menambahkan sebuah properti ke dala sebuah objek yang "dimiliki" oleh *script* lain atau sebuah *library*, kita bisa membuat sebuah simbol dan menggunakannya sebagai sebuah kunci properti. Sebuah properti simbolis tidak muncul dalam `for..in`, jadi hal tersbeut tidak akan tanpa sengaja terproses bersama properti-properti lain. Juga, simbol tidak akan diakses secara langsung, karena *script* tidak memiliki simbol kita. Jadi properti akan terlindungi dari penggunaan yang tak disengaja maupun tertimpa (*overwrite*). Jadi kita bisa "secara terselubung" menyembunyikan sesuatu ke dalam objek yang kita inginkan, tetapi tidak bisa diliha oleh pihak lain, menggunakn properti simbolis. 2. Terdapat banyak simbol sistem yang digunakan oleh oleh JavaScript yang mana dapat diakses sebagai `Symbol.*`. Kita bisa menggunakan simbol-simbol tersebut untuk mengubah beberapa perilaku bawaan (*built-in*). Sebagai contohnya, di tutorial selanjutnya kita akan menggunakan `Symbol.iterator` untuk [*iterables*](info:iterable), `Symbol.toPrimitive` untuk mengatur [konversi objek-ke-primitif](info:object-toprimitive) dan sebagainya. Secara teknis, simbol-simbol tidak 100% tersembunyi. Ada sebuah metode bawaan [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) yang membuat kita dapat mendapatkan semua simbol. Juga terdapat sebuah metode yang dinamakan [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) yang mengembalikan *semua* kunci dari sebuah objek termasuk yang kunci yang simbolik. Jadi simbol-simbol tersebut tidak sepenuhnya tersembunyi. Namun untuk sebagian besar *library*, fungsi-fungsi bawaan dan kontruksi sintaks constructs tidak menggunakan metode-metode ini. ================================================ FILE: 1-js/04-object-basics/09-object-toprimitive/article.md ================================================ # Menolak konversi primitif Apa yang terjadi ketika objek ditambahkan `obj1 + obj2`, dikurangi `obj1 - obj2` atau dicetak menggunakan `alert(obj)`? JavaScript tidak benar-benar memungkinkan untuk menyesuaikan cara operator bekerja pada objek. Tidak seperti beberapa bahasa pemrograman lain, seperti Ruby atau C++, kami tidak dapat mengimplementasikan metode objek khusus untuk menangani penambahan (atau operator lain). Dalam kasus operasi seperti itu, objek secara otomatis dikonversi ke primitif, dan kemudian operasi dilakukan di atas primitif ini dan menghasilkan nilai primitif. Itu batasan penting, karena hasil dari `obj1 + obj2` tidak bisa menjadi objek lain! Misalnya. kita tidak dapat membuat objek yang mewakili vektor atau matriks (atau pencapaian atau apa pun), menambahkannya dan mengharapkan objek "dijumlahkan" sebagai hasilnya. Prestasi arsitektur seperti itu secara otomatis "di luar papan". Jadi, karena kita tidak bisa berbuat banyak di sini, tidak ada matematika dengan objek dalam proyek nyata. Ketika itu terjadi, biasanya karena kesalahan pengkodean. Dalam bab ini kita akan membahas bagaimana sebuah objek dikonversi ke primitif dan bagaimana menyesuaikannya. Kami memiliki dua tujuan: 1. Ini akan memungkinkan kita untuk memahami apa yang terjadi jika terjadi kesalahan pengkodean, ketika operasi seperti itu terjadi secara tidak sengaja. 2. Ada pengecualian, di mana operasi semacam itu dimungkinkan dan terlihat bagus. Misalnya. mengurangkan atau membandingkan tanggal (objek `Tanggal`). Kami akan menemukan mereka nanti. ## Aturan konversi Dalam bab kita telah melihat aturan untuk konversi numerik, string, dan boolean dari primitif. Tapi kami meninggalkan celah untuk objek. Sekarang, seperti yang kita ketahui tentang metode dan simbol, menjadi mungkin untuk mengisinya. 1. Semua objek `benar` dalam konteks boolean. Hanya ada konversi numerik dan string. 2. Konversi numerik terjadi ketika kita mengurangi objek atau menerapkan fungsi matematika. Misalnya, objek `Tanggal` (akan dibahas dalam bab ) dapat dikurangi, dan hasil dari `tanggal1 - tanggal2` adalah perbedaan waktu antara dua tanggal. 3. Untuk konversi string -- biasanya terjadi ketika kita mengeluarkan objek seperti `alert(obj)` dan dalam konteks yang serupa. Kita dapat menyempurnakan konversi string dan numerik, menggunakan metode objek khusus. Ada tiga varian konversi tipe, yang terjadi dalam berbagai situasi. Mereka disebut "petunjuk", seperti yang dijelaskan dalam [spesifikasi](https://tc39.github.io/ecma262/#sec-toprimitive): `"tali"` : Untuk konversi objek-ke-string, saat kita melakukan operasi pada objek yang mengharapkan string, seperti `alert`: ```js // keluaran waspada (obj); // menggunakan objek sebagai kunci properti lainObj[obj] = 123; ``` `"nomor"` : Untuk konversi objek ke angka, seperti saat kita mengerjakan matematika: ```js // konversi eksplisit misalkan angka = Angka(obj); // matematika (kecuali biner plus) misalkan n = +obj; // unary plus biarkan delta = tanggal1 - tanggal2; // lebih sedikit/perbandingan lebih besar biarkan lebih besar = pengguna1 > pengguna2; ``` `"default"` : Terjadi dalam kasus yang jarang terjadi ketika operator "tidak yakin" jenis apa yang diharapkan. Misalnya, biner plus `+` dapat bekerja baik dengan string (menggabungkannya) dan angka (menambahkannya), jadi string dan angka bisa digunakan. Jadi jika biner plus mendapatkan objek sebagai argumen, ia menggunakan petunjuk `"default"` untuk mengonversinya. Selain itu, jika suatu objek dibandingkan menggunakan `==` dengan string, angka, atau simbol, konversi mana yang harus dilakukan juga tidak jelas, sehingga petunjuk `"default"` digunakan. ```js // binary plus menggunakan petunjuk "default" misalkan total = obj1 + obj2; // obj == nomor menggunakan petunjuk "default" if (pengguna == 1) { ... }; ``` Operator perbandingan yang lebih besar dan lebih kecil, seperti `<` `>`, dapat bekerja dengan string dan angka juga. Namun, mereka menggunakan petunjuk `"number"`, bukan `"default"`. Itu karena alasan historis. Namun dalam praktiknya, kita tidak perlu mengingat detail aneh ini, karena semua objek bawaan kecuali satu kasus (objek `Tanggal`, kita akan mempelajarinya nanti) mengimplementasikan konversi `"default"` dengan cara yang sama seperti ` "nomor"`. Dan kita bisa melakukan hal yang sama. ```smart header="Tidak ada petunjuk `\"boolean\"`" Harap dicatat -- hanya ada tiga petunjuk. Sesederhana itu. Tidak ada petunjuk "boolean" (semua objek `benar` dalam konteks boolean) atau yang lainnya. Dan jika kita memperlakukan `"default"` dan `"number"` sama, seperti kebanyakan built-in, maka hanya ada dua konversi. ``` **Untuk melakukan konversi, JavaScript mencoba menemukan dan memanggil tiga metode objek:** 1. Panggil `obj[Symbol.toPrimitive](hint)` - metode dengan kunci simbolis `Symbol.toPrimitive` (simbol sistem), jika metode tersebut ada, 2. Sebaliknya jika petunjuknya adalah `"string"` - coba `obj.toString()` dan `obj.valueOf()`, apa pun yang ada. 3. Jika petunjuknya adalah `"number"` atau `"default"` - coba `obj.valueOf()` dan `obj.toString()`, apa pun yang ada. ## Symbol.toPrimitive Mari kita mulai dari cara pertama. Ada simbol bawaan bernama `Symbol.toPrimitive` yang harus digunakan untuk menamai metode konversi, seperti ini: ```js obj[Simbol.toPrimitif] = fungsi(petunjuk) { // dia ================================================ FILE: 1-js/04-object-basics/index.md ================================================ # Objects: dasar-dasar ================================================ FILE: 1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md ================================================ Cobalah jalankan: ```js run let str = "Hello"; str.test = 5; // (*) alert(str.test); ``` Tergantung apakah kamu gunakan `use strict` atau tidak, hasilnya mungkin bisa: 1. `undefined` (bukan strict mode) 2. An error (strict mode). Kenapa? Kita lihat apa yang terjadi apda baris `(*)`: 1. Ketika properti dari `str` di akses, sebuah "objek pembungkus" dibuat. 2. Didalam mode strict, menulis kedalamnya adalah sebuah error. 3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears, so in the last line `str` has no trace of the property. 3. Sebaliknya, operasi dengan propertinya dibawa, objeknya mendapatkan properti `test`, tapi setelah itu "objek pembungkus" menghilang, jadi di baris terakhir `str` tidak mempunyai jejak dari properti itu. **Contoh ini dengan jelas membuktikan bahwa primitif bukanlah sebuah objek** Mereka tidak bisa menyimpan data tambahan. ================================================ FILE: 1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md ================================================ nilai penting: 5 --- # Bisakah saya menambahkan properti string? Perhatikan kode berikut: ```js let str = "Hello"; str.test = 5; alert(str.test); ``` Bagaimana menurutmu, akankah itu bekerja? apa yang akan muncul? ================================================ FILE: 1-js/05-data-types/01-primitives-methods/article.md ================================================ # Metode primitif Javascript memperbolehkan kita untuk bekerja dengan primitif (string, angka, dan lain-lain.) seperti jika mereka adalah objek. Mereka juga menyediakan metode untuk dipanggil. Kita akan belajar tentang hal itu nanti, tapi pertama kita akan melihat bagaimana hal itu bekerja, dan juga, primitif bukanlah objek (dan disini kita akan membuat hal itu lebih jelas). Ayo kita lihat pada kunci perbedaan diantara primitif dan objek. Primitif - Adalah sebuah nilai dari tipe primitif. - Ada 7 primitif tipe: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` dan `undefined`. Objek - Mampu untuk menyimpan banyak nilai sebagai properti. - Bisa dibuat dengan menggunakan `{}`, contoh: `{name: "John", age: 30}`. Terdapat beberapa macam objek di Javascript: untuk contoh, fungsi, adalah objek. Salah satu hal yang terbaik tentang objek adalah kita bisa menyimpan fungsi sebagai salah satu dari propertinya. ```js run let john = { name: "John", sayHi: function() { alert("Hi buddy!"); } }; john.sayHi(); // Hi buddy! ``` Jadi disini kita membuat sebuah objek `john` dengan method `sayHi`. Ada juga built-in objek yang tersedia, seperti objek yang bekerja dengan tanggal, error, elemen HTML, dll. Mereka mempunyai properti dan method yang berbeda-beda. Tapi, fitur ini ada dengan efek samping. Objek lebih "berat" dari primitif. Dan mereka membutuhkan sumber daya tambahan untuk mendukung pekerjaannya. ## Sebuah primitif sebagai sebuah objek Ini adalah paradoks yang dihadapi dari pencipta Javascript: - Terdapat banyak hal yang harus dilakukan dengan primitif seperti string atau angka. Akan menjadi lebih baik jika mereka bisa diakses sebagai method. - Primitives must be as fast and lightweight as possible. - Sebisa mungkin primitif haruslah cepat dan ringan. Solusinya terlihat sedikit aneh, tapi inilah solusinya: 1. Primitif masih tetap primitif. Sebuah nilai tunggal, seperti yang diinginkan. 2. Bahasanya membolehkan untuk mengakses method dan properti dari string, number, boolean dan symbols. 3. Untuk membuat itu bekerja, "objek pembungkus" spesial yang menyediakan fungsionalitas tambahan dibuat, dan lalu dihancurkan. "Objek pembungkus" berbeda untuk setiap tipe primitif dan dipanggil: `String`, `Number`, `Boolean` dan `Symbol`. Lalu, mereka menyediakan metode-metode yang berbeda. Contoh, ada methode string [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) yang mengembalikan string `str` yang telah diubah menjadi huruf kapital. Beginilah caranya itu bekerja: ```js run let str = "Hello"; alert( str.toUpperCase() ); // HELLO ``` Simpel, kan? Inilah sebenarnya yang terjadi didalam `str.toUpperCase()`: 1. String `str` adalah sebuah primitif. Jadi sementara waktu untuk mengakses propertinya, sebuah objek spesial yang tahu tentang nilai dari string dibuat, dan memiliki metode yang berguna, seperti `toUpperCase()`. 2. Metode itu menjalankan dan mengembalikan string baru (ditampilkan oleh `alert`). 3. Objek spesial itu lalu dihancurkan, meninggalkan `str` primitif. Jadi primitif bisa menyediakan metode, tapi mereka akan tetap ringan. Mesin Javascript sangat mengoptimasi proses ini. Ini mungkin akan melewatkan pembuatan dari objek tambahan itu. Tapi itu masih melekat pada spesifikasinya dan bertingkah seperti jika itu memang diciptakan. Sebuah angka mempunyai metodenya sendiri, contoh [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) membulatkan angka kedalam presisi yang diberikan: ```js run let n = 1.23456; alert( n.toFixed(2) ); // 1.23 ``` Kita akan melihat metode spesifik lainnya di bab dan . ````warn header="Konstruktor `String/Number/Boolean` hanya digunakan untuk kebutuhan internal" Beberapa bahasa seperti Java membolehkan kita untuk secara jelas membuat "objek pembungkus" untuk primitif menggunakan sintaks seperti `new Number(1)` atau `new Boolean(false)`. Didalam javascript, hal itu bisa dilakukan untuk beberapa alasan, tapi sangat **tidak direkomendasikan**. Beberapa hal akan menjadi rumit di beberapa tempat. Contoh: ```js run alert( typeof 0 ); // "number" alert( typeof new Number(0) ); // "object"! ``` Objek akan selalu truthy didalam `if`, jadi disini alert akan muncul: ```js run let zero = new Number(0); if (zero) { // zero adalah true, karena itu adalah sebuah objek alert( "zero is truthy!?!" ); } ``` Disisi lain, menggunakan fungsi yang sama `String/Number/Boolean` tanpa `new` adalah hal yang masuk akal dan hal yang berguna. Mereka mengubah nilai kedalam tipe yang sesuai: kedalam sebuah string, sebuah number, atau sebuah boolean(primitif). Contoh, hal ini sepenuhnya valid: ```js let num = Number("123"); // mengubah string menjadi angka ``` ```` ````warn header="null/undefined tidak memiliki method" Primitif spesial `null` dan `undefined` adalah pengecualian. Mereka tidak mempunyai "objek pembungkus" yang sesuai dan tidak menyediakan metode. Dalam arti tertentu, mereka adalah "yang paling primitif". Percobaan untuk mengakses properti seperti nilai akan memberikan error: ```js run alert(null.test); // error ```` ## Ringkasan - Primitif kecuali `null` dan `undefined` menyediakan banyak metode yang berguna. Kita akan belajar hal itu di bab selanjutnya. - Secara formal, metode-metode ini akan bekerja dengan menggunakan objek sementara, tapi mesin Javascript telah dibuat dengan baik untuk mengoptimasi hal itu secara internal, jadi mereka tidaklah sulit untuk dipanggil. ================================================ FILE: 1-js/05-data-types/02-number/1-sum-interface/solution.md ================================================ ```js run demo let a = +prompt("The first number?", ""); let b = +prompt("The second number?", ""); alert( a + b ); ``` Catatan bahwa unary plus `+` sebelum `prompt`. Segera mengkonversi nilai ke angka. Jika tidak, `a` dan `b` akan menjadi string jumlah mereka akan menjadi gabungan mereka, yaitu: `"1" + "2" = "12"`. ================================================ FILE: 1-js/05-data-types/02-number/1-sum-interface/task.md ================================================ nilai penting: 5 --- # Jumlahkan angka dari pengunjung Buat skrip yang meminta pengunjung untuk memasukkan dua angka dan kemudian menunjukkan jumlah mereka. [demo] N.B. Ada gotcha dengan tipe. ================================================ FILE: 1-js/05-data-types/02-number/2-why-rounded-down/solution.md ================================================ Secara internal pecahan desimal `6.35` adalah sebuah biner tanpa akhir. Seperti biasa dalam kasus seperti ini, disimpan dengan kehilangan presisi. Ayo lihat: ```js run alert( 6.35.toFixed(20) ); // 6.34999999999999964473 ``` Kehilangan presisi dapat menyebabkan peningkatan dan penurunan angka. Dalam kasus khusus ini jumlahnya menjadi sedikit lebih sedikit, itu sebabnya dibulatkan. Dan apa untuk `1.35`? ```js run alert( 1.35.toFixed(20) ); // 1.35000000000000008882 ``` Di sini kehilangan presisi membuat jumlahnya sedikit lebih besar, jadi itu dibulatkan. **Bagaimana kita dapat memperbaiki masalah dengan `6.35` jika kita ingin itu dibulatkan dengan cara yang benar?** Kita harus membawanya lebih dekat ke integer sebelum pembulatan: ```js run alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000 ``` Perhatikan bahwa `63.5` tidak memiliki kehilangan presisi sama sekali. Itu karena bagian desimal `0,5` sebenarnya` 1 / 2`. Pecahan yang dibagi oleh kekuatan `2` persis diwakili dalam sistem biner, sekarang kita dapat membulatkannya: ```js run alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 ``` ================================================ FILE: 1-js/05-data-types/02-number/2-why-rounded-down/task.md ================================================ nilai penting: 4 --- # Kenapa 6.35.toFixed(1) == 6.3? Berdasarkan dokumentasi `Math.round` dan `toFixed` keduanya membulatkan ke angka terdekat: `0..4` turun sementara `5..9` naik. Contohnya: ```js run alert( 1.35.toFixed(1) ); // 1.4 ``` Dalam contoh serupa di bawah ini, mengapa `6.35` dibulatkan menjadi `6.3`, dan tidak `6.4`? ```js run alert( 6.35.toFixed(1) ); // 6.3 ``` Bagaimana untuk membulatkan `6.35` dengan benar? ================================================ 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("Enter a number please?", 0); } while ( !isFinite(num) ); if (num === null || num === '') return null; return +num; } alert(`Read: ${readNumber()}`); ``` Solusinya sedikit lebih rumit dari itu karena kita perlu menangani `null`/baris kosong. Jadi, kita benar-benar menerima input hingga ini merupakan "angka reguler". Baik `null` (cancel) maupun baris kosong juga cocok dengan kondisi itu, karena dalam bentuk numerik mereka adalah` 0`. Setelah kita berhenti, kita perlu memperlakukan `null` dan khususnya baris kosong (mengembalikan `null`), karena mengonversinya menjadi angka akan mengembalikan `0`. ================================================ FILE: 1-js/05-data-types/02-number/3-repeat-until-number/task.md ================================================ nilai penting: 5 --- # Ulangi sampai masukan adalah sebuah angka Buatlah sebuah fungsi `readNumber` yang meminta (prompts) nomor hingga pengunjung memasukkan nilai numerik yang valid. Nilai yang dihasilkan harus dikembalikan sebagai angka. Pengunjung juga dapat menghentikan proses dengan memasukkan baris kosong atau menekan "BATAL". Dalam hal ini, fungsi tersebut harus mengembalikan `null`. [demo] ================================================ FILE: 1-js/05-data-types/02-number/4-endless-loop-error/solution.md ================================================ Itu karena `i` tidak akan pernah sebanding dengan `10`. Jalankan ini untuk melihat nilai *real* dari `i`: ```js run let i = 0; while (i < 11) { i += 0.2; if (i > 9.8 && i < 10.2) alert( i ); } ``` Tidak satu pun dari mereka yang benar-benar `10`. Hal-hal seperti itu terjadi karena kehilangan presisi ketika menambahkan pecahan seperti `0,2`. Kesimpulan: menghindari pemeriksaan kesetaraan saat bekerja dengan pecahan desimal. ================================================ FILE: 1-js/05-data-types/02-number/4-endless-loop-error/task.md ================================================ nilai penting: 4 --- # Lingkaran tak terbatas tak berkala Loop ini tidak terbatas. Tidak pernah berakhir. Mengapa? ```js let i = 0; while (i != 10) { i += 0.2; } ``` ================================================ FILE: 1-js/05-data-types/02-number/8-random-min-max/solution.md ================================================ Kita perlu "memetakan" semua nilai dari interval 0..1 ke dalam nilai dari `min` ke` max`. Itu bisa dilakukan dalam dua tahap: 1. Jika kita mengalikan angka acak dari 0..1 dengan `max-min`, maka interval nilai yang mungkin meningkat` 0..1` ke `0..max-min`. 2. Sekarang jika kita menambahkan `min`, interval yang mungkin menjadi dari` min` ke `max`. Fungsi: ```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 ================================================ nilai penting: 2 --- # Sebuah angka acak dari min ke max Fungsi bawaan `Math.random()` membuat sebuah angka acak dari `0` ke `1` (tidak termasuk `1`). Tulis fungsi `random(min, max)` untuk menghasilkan angka floating-point acak dari `min` ke` max` (tidak termasuk `max`). Contoh kerjanya: ```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 ================================================ # The simple but wrong solution The simplest, but wrong solution would be to generate a value from `min` to `max` and round it: ```js run function randomInteger(min, max) { let rand = min + Math.random() * (max - min); return Math.round(rand); } alert( randomInteger(1, 3) ); ``` The function works, but it is incorrect. The probability to get edge values `min` and `max` is two times less than any other. If you run the example above many times, you would easily see that `2` appears the most often. That happens because `Math.round()` gets random numbers from the interval `1..3` and rounds them as follows: ```js no-beautify values from 1 ... to 1.4999999999 become 1 values from 1.5 ... to 2.4999999999 become 2 values from 2.5 ... to 2.9999999999 become 3 ``` Now we can clearly see that `1` gets twice less values than `2`. And the same with `3`. # The correct solution There are many correct solutions to the task. One of them is to adjust interval borders. To ensure the same intervals, we can generate values from `0.5 to 3.5`, thus adding the required probabilities to the edges: ```js run *!* function randomInteger(min, max) { // now rand is from (min-0.5) to (max+0.5) let rand = min - 0.5 + Math.random() * (max - min + 1); return Math.round(rand); } */!* alert( randomInteger(1, 3) ); ``` An alternative way could be to use `Math.floor` for a random number from `min` to `max+1`: ```js run *!* function randomInteger(min, max) { // here rand is from min to (max+1) let rand = min + Math.random() * (max + 1 - min); return Math.floor(rand); } */!* alert( randomInteger(1, 3) ); ``` Now all intervals are mapped this way: ```js no-beautify values from 1 ... to 1.9999999999 become 1 values from 2 ... to 2.9999999999 become 2 values from 3 ... to 3.9999999999 become 3 ``` All intervals have the same length, making the final distribution uniform. ================================================ FILE: 1-js/05-data-types/02-number/9-random-int-min-max/task.md ================================================ nilai penting: 2 --- # Sebuah integer acak dari min ke max Buatlah sebuah fungsi `randomInteger(min, max)` yang menghasilkan angka *integer* acak dari `min` ke `max` termasuk keduanya `min` dan `max` sebagai nilai yang mungkin. Angka apa pun dari interval `min..max` harus muncul dengan probabilitas yang sama. Contoh kerjanya: ```js alert( randomInteger(1, 5) ); // 1 alert( randomInteger(1, 5) ); // 3 alert( randomInteger(1, 5) ); // 5 ``` Anda dapat menggunakan solusi dari [tugas sebelumnya](info:task/random-min-max) sebagai basis. ================================================ FILE: 1-js/05-data-types/02-number/article.md ================================================ # Angka Dalam JavaScript modern, ada dua tipe angka: 1. Angka regular di JavaScript yang disimpan dalam format 64-bit [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), juga dikenal sebagai "angka double precision floating point". Inilah angka yang kita paling sering kita pakai, dan kita akan bahas tentang mereka di bab ini. 2. Angka BigInt, untuk mewakili integer dengan panjang sembarang. Mereka kadang dibutuhkan, karena angka regular tak bisa lebih dari 253 atau kurang dari -253. Karena bigint dipakai di sedikit area spesial, kita khususkan mereka bab spesial . Jadi di sini kita akan bahas angka regular. Ayo perluas pengetahuan kita tentang mereka. ## Cara lain menulis angka Bayangkan kita harus menulis 1 milyar. Cara jelasnya begini: ```js let billion = 1000000000; ``` Kita juga bisa menggunakan `_` sebagai pemisahnya: ```js let billion = 1_000_000_000; ``` Di sini, garis bawah `_` memainkan peran sebagai "syntactic sugar", ini membuat angka lebih mudah dibaca. Mesin JavaScript mengabaikan `_` di antara digit, jadi nilainya sama persis dengan satu miliar di atas. Tapi di kehidupan nyata, kita biasanya menghindari menulis string nol yang panjang karena rentan terjadi kesalahan. Selain itu, kita malas. Kita biasanya akan menulis sesuatu seperti `"1bn"` untuk milyar atau `"7.3bn"` untuk 7 milyar 300 juta. Sama halnya dengan angka besar lainnya. Di JavaScript, kita perpendek angka dengan menambah huruf `"e"` ke angka dan menspesifikasi jumlah nol: ```js run let billion = 1e9; // 1 milyar, literalnya: 1 dan 9 nol alert( 7.3e9 ); // 7.3 milyar (7,300,000,000) ``` Dengan kata lain, `"e"` kalikan angkanya dengan `1` dengan jumlah nol yang diberikan. ```js 1e3 = 1 * 1000 // e3 means *1000 1.23e6 = 1.23 * 1000000 // e6 means *1000000 ``` Sekarang ayo tulis sesuatu lebih kecil. Katakan, 1 microsecond (sepersejuta second): ```js let ms = 0.000001; ``` Sama seperti sebelumnya, memakai `"e"` bisa membantu. Jika kita ingin menghindari menulis nol eksplisit, kita bisa katakan hal yang sama: ```js let ms = 1e-6; // enam nol di sebelah kiri dari 1 ``` Jika kita hitung nol di `0.000001`, ada 6 dari mereka. Jadi alaminya `1e-6`. Dengan kata lain, angka negatif setelah `"e"` artinya pembagian 1 dengan jumlah nol yang diberikan: ```js // -3 membagi 1 dengan 3 nol 1e-3 = 1 / 1000 (=0.001) // -6 membagi 1 dengan 6 nol 1.23e-6 = 1.23 / 1000000 (=0.00000123) ``` ### Hex, angka binary dan octal Angka [Hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) secara luas dipakai di JavaScript untuk mewakili warna, encode karakter, dan banyak hal lain. Alaminya, ada cara lebih singkat menulis mereka: `0x` kemudian angkanya. Misalnya: ```js run alert( 0xff ); // 255 alert( 0xFF ); // 255 (sama, case diabaikan) ``` Sistem numeral binary dan octal jarang dipakai, tapi juga didukung menggunakan prefix `0b` dan `0o`: ```js run let a = 0b11111111; // bentuk binary dari 255 let b = 0o377; // bentuk octal dari 255 alert( a == b ); // true, angka sama 255 dari kedua sisi ``` Cuma ada 3 sistem numeral dengan dukungan begitu. Untuk sistem numeral lain, kita sebaiknya memakai fungsi `parseInt` (yang akan kita lihat nanti di bab ini). ## toString(base) Metode `num.toString(base)` mengembalikan representasi string dari `num` di sistem numeral dengan `base` yang diberikan. Misalnya: ```js run let num = 255; alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` `base` bisa bervariasi dari `2` hingga `36`. Defaultnya `10`. Penggunaan umumnya ialah: - **base=16** dipakai untuk warna hex, character encoding dll, digit bisa `0..9` atau `A..F`. - **base=2** paling banyak untuk mendebug operasi bitwise, digit bisa `0` atau `1`. - **base=36** ini maximum, digit bisa `0..9` atau `A..Z`. Seluruh alfabet latin dipakai untuk merepresentasi angka. Hal lucu, tapi berguna untuk `36` ialah saat kita harus mengubah identifier numerik panjang ke dalam sesuatu yang lebih pendek, misalnya untuk membuat url pendek. Bisa direpresentasikan dalam sistem `36`: ```js run alert( 123456..toString(36) ); // 2n9c ``` ```warn header="Dua dot untuk memanggil metode" Tolong ingat bahwa dua dot di `123456..toString(36)` bukan typo. Jika kita mau memanggil langsung metode pada angka, seperti `toString` di contoh di atas, maka kita harus menaruh dua dot `..` setelahnya. Jika kita menaruh dot tunggal: `123456.toString(36)`, maka akan ada galat, karena syntax JavaScript berimplikasi bahwa bagian desimal setelah dot pertama. Dan jika kita menaruh satu dot lagi, maka JavaScript tahu bahwa bagian desimal kosong dan sekarang pergi ke metode. Juga bisa menulis `(123456).toString(36)`. ``` ## Pembulatan Satu dari operasi paling banyak dipakai saat bekerja dengan angka ialah pembulatan. Ada beberapa fungsi built-in untuk pembulatan: `Math.floor` : Membulat ke bawah: `3.1` menjadi `3`, dan `-1.1` menjadi `-2`. `Math.ceil` : Membulat ke atas: `3.1` menjadi `4`, dan `-1.1` menjadi `-1`. `Math.round` : Membulatkan ke bilangan bulat terdekat: `3.1` menjadi` 3`, `3.6` menjadi` 4`, huruf tengah: `3.5` juga dibulatkan ke atas menjadi` 4`. `Math.trunc` (tidak didukung oleh Internet Explorer) : Menghapus apa pun setelah koma desimal tanpa pembulatan: `3.1` menjadi` 3`, `-1.1` menjadi` -1`. Ini tabel untuk meringkas perbedaan di antara mereka: | | `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` | Fungsi ini membahas semua cara yang mungkin untuk berhadapan dengan bagian desimal dari angka. Tapi bagaimana jika kita mau membulatkan angka ke digit `ke-n` setelah desimal? Misalnya, kita punya `1.2345` dan mau membulatkan ke 2 digit, memperoleh `1.23`. Ada dua cara melakukannya: 1. Kali-dan-bagi. Misalnya, untuk membulatkan angka ke digit kedua setelah desimal, kita bisa mengalikan angkanya dengan `100` (atau sebuah pangkat dari 10), panggil fungsi pembulatan lalu membagi itu kembali. ```js run let num = 1.23456; alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 ``` 2. Metode [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) Membulatkan ke digit `n` setelah poin itu dan mengembalikan representasi string dari hasilnya. ```js run let num = 12.34; alert( num.toFixed(1) ); // "12.3" ``` Ini membulatkan ke atas atau ke bawah ke nilai terdekat, serupa dengan `Math.round`: ```js run let num = 12.36; alert( num.toFixed(1) ); // "12.4" ``` Silakan catat hasil dari `toFixed` ialah string. Jika bagian desimal lebih pendek dari yang dibutuhkan, nol ditambahkan di akhir: ```js run let num = 12.34; alert( num.toFixed(5) ); // "12.34000", tambah nol supaya tepat 5 digit ``` Kita bisa mengkonversi itu ke angka menggunakan unary plus atau panggilan `Number()`: `+num.toFixed(5)`. ## Kalkulasi yang tidak tepat Secara internal, angka direpresentasikan dalam format 64-bit [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), jadi ada tepat 64 bit untuk menyimpan angka: 52 di antaranya digunakan untuk menyimpan angka, 11 di antaranya menyimpan posisi titik desimal (nol untuk angka bilangan bulat), dan 1 bit untuk tanda. Jika angka terlalu besar, itu akan meluapkan penyimpanan 64-bit, berpotensi memberikan infinity: ```js run alert( 1e500 ); // Infinity ``` Yang mungkin agak kurang jelas, tetapi sering terjadi adalah hilangnya ketepatan. Pertimbangkan (falsy!) tes ini: ```js run alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* ``` Benar, jika kita memeriksa jumlah dari `0.1` dan `0.2` adalah `0.3`, kita mendapatkan `false`. Aneh! Kenapa hasilnya itu dan tidak `0.3`? ```js run alert( 0.1 + 0.2 ); // 0.30000000000000004 ``` Aduh! Ada lebih banyak konsekuensi daripada perbandingan yang salah di sini. Bayangkan Anda membuat situs e-shopping dan pengunjung memasukkan barang-barang `$ 0,10` dan` $ 0,20` ke troli mereka. Total pesanan akan `$ 0,30000000000000004`. Itu akan mengejutkan siapa pun. Tetapi kenapa hal ini bisa terjadi? Sebuah angka disimpan di memori dalam bentuk binary, sebuah urutan dari bits - satu dan nol. Tetapi bilangan pecahan seperti `0.1`, `0.2` yang terlihat sederhana dalam sistem angka desimal sebenarnya adalah pecahan tak berujung dalam bentuk binernya. Dengan kata lain, apa itu `0,1`? Ini adalah satu dibagi dengan sepuluh `1 / 10`, sepersepuluh. Dalam sistem angka desimal, angka-angka seperti itu mudah diwakili. Bandingkan dengan sepertiga: `1 / 3`. Ini menjadi pecahan yang tak berujung `0,33333 (3)`. Jadi, pembagian dengan kekuatan `10` dijamin bekerja dengan baik dalam sistem desimal, tetapi pembagian dengan `3` tidak. Untuk alasan yang sama, dalam sistem angka biner, pembagian dengan kekuatan `2` dijamin bekerja, tetapi `1 / 10` menjadi fraksi biner tanpa akhir. Tidak ada cara untuk menyimpan * tepat 0,1 * atau * persis 0,2 * menggunakan sistem biner, sama seperti tidak ada cara untuk menyimpan sepertiga sebagai fraksi desimal. Format numerik IEEE-754 memecahkan ini dengan membulatkan ke angka terdekat yang mungkin. Aturan pembulatan ini biasanya tidak memungkinkan kita untuk melihat bahwa "kehilangan presisi kecil", tetapi itu ada. Kita bisa melihat ini dalam aksi: ```js run alert( 0.1.toFixed(20) ); // 0.10000000000000000555 ``` Dan ketika kita menjumlahkan dua angka, "kehilangan presisi" mereka bertambah. Itu sebabnya `0,1 + 0,2` tidak sepenuhnya` 0,3`. ```smart header="Tidak hanya JavaScript" Masalah yang sama ada di banyak bahasa pemrograman lainnya. PHP, Java, C, Perl, Ruby memberikan hasil yang sama persis, karena mereka didasarkan pada format numerik yang sama. ``` Bisakah kita mengatasi masalah ini? Tentu, metode yang paling dapat diandalkan adalah melengkapi hasilnya dengan bantuan metode [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 ``` Harap dicatat bahwa `toFixed` selalu mengembalikan string. Ini memastikan bahwa ia memiliki 2 digit setelah titik desimal. Itu sebenarnya nyaman jika kita memiliki e-shopping dan perlu menunjukkan `$ 0,30`. Untuk kasus lain, kita dapat menggunakan plus unary untuk memaksanya menjadi nomor: ```js run let sum = 0.1 + 0.2; alert( +sum.toFixed(2) ); // 0.3 ``` Kita juga dapat sementara mengalikan angka dengan 100 (atau angka yang lebih besar) untuk mengubahnya menjadi bilangan bulat, menghitung, dan kemudian membaginya kembali. Kemudian, saat kita mengerjakan matematika dengan bilangan bulat, kesalahannya agak berkurang, tetapi kita masih mendapatkannya di pembagian: ```js run alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` Jadi, pendekatan perkalian/pembagian mengurangi kesalahan, tetapi tidak menghapusnya sama sekali. Terkadang kita bisa mencoba menghindari pecahan sama sekali. Seperti jika kita berurusan dengan toko, maka kita dapat menyimpan harga dalam sen, bukan dalam dolar. Tetapi bagaimana jika kita menerapkan diskon 30%? Dalam praktiknya, menghindari pecahan sama sekali jarang dimungkinkan. Hanya bulatkan mereka untuk memotong "ekor" bila diperlukan. ````smart header="Lucunya" Coba jalankan ini: ```js run // Halo! saya adalah angka yang meningkat sendiri! alert( 9999999999999999 ); // menunjukkan 10000000000000000 ``` Penderitaan ini berasal dari masalah yang sama: kehilangan presisi. Ada 64 bit untuk angka, 52 di antaranya dapat digunakan untuk menyimpan digit, tetapi itu tidak cukup. Jadi digit yang paling tidak penting menghilang. JavaScript tidak memicu kesalahan dalam kejadian semacam itu. Itu melakukan yang terbaik untuk memasukkan angka ke dalam format yang diinginkan, tetapi sayangnya, format ini tidak cukup besar. ```` ```smart header="Dua nol" Konsekuensi lucu yang lain dari representasi internal dari angka adalah adanya dua nol: `0` dan` -0`. Itu karena sebuah tanda diwakili oleh satu bit, sehingga dapat diatur atau tidak diatur untuk angka apa pun termasuk nol. Dalam kebanyakan kasus perbedaannya tidak terlalu mencolok, karena operator cocok untuk memperlakukan mereka sebagai sesuatu yang sama. ``` ## Tests: isFinite dan isNaN Ingat dua nilai numerik khusus ini? - `Infinity` (dan `-Infinity`) adalah nilai numerik khusus yang lebih besar (kurang) dari apa pun. - `NaN` mewakili kesalahan. Mereka termasuk dalam tipe `angka`, tetapi bukan angka "normal", jadi ada fungsi khusus untuk memeriksanya: - `isNaN(value)` mengubah argumennya menjadi angka dan kemudian mengujinya `NaN`: ```js run alert( isNaN(NaN) ); // true alert( isNaN("str") ); // true ``` Tetapi apakah kita membutuhkan fungsi ini? Tidak bisakah kita menggunakan perbandingan `=== NaN`? Maaf, tapi jawabannya adalah tidak. Nilai `NaN` unik karena tidak sama dengan apa pun, termasuk dirinya sendiri: ```js run alert( NaN === NaN ); // false ``` - `isFinite(value)` mengonversi argumennya menjadi angka dan mengembalikan `true` jika itu angka biasa, bukan `NaN/Infinity/-Infinity`: ```js run alert( isFinite("15") ); // true alert( isFinite("str") ); // false, karena nilai khusus: NaN alert( isFinite(Infinity) ); // false, karena nilai khusus: Infinity ``` Terkadang `isFinite` digunakan untuk memvalidasi apakah sebuah nilai string adalah sebuah angka reguler: ```js run let num = +prompt("Enter a number", ''); // akan benar kecuali jika Anda memasukkan Infinity, -Infinity atau bukan angka alert( isFinite(num) ); ``` Harap dicatat bahwa string kosong atau spasi-saja diperlakukan sebagai `0` dalam semua fungsi numerik termasuk` isFinite`. ```smart header="Dibandingkan dengan `Object.is`" Ada metode bawaan khusus [Object.is](mdn:js/Object/is) yang membandingkan nilai seperti `===`, tetapi lebih dapat diandalkan untuk dua kasus: 1. Ini bekerja dengan `NaN`: `Object.is(NaN, NaN) === true`, itu hal yang bagus. 2. Nilai `0` and `-0` adalah berbeda: `Object.is(0, -0) === false`, secara teknis adalah benar, karena secara internal nomor tersebut memiliki bit tanda yang mungkin berbeda bahkan jika semua bit lainnya nol. Pada kasus lain, `Object.is(a, b)` adalah sama dengan `a === b`. Cara perbandingan ini sering digunakan dalam spesifikasi JavaScript. Ketika suatu algoritma internal perlu membandingkan dua nilai untuk menjadi persis sama, ia menggunakan `Object.is` (secara internal disebut [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). ``` ## parseInt dan parseFloat Konversi angka menggunakan nilai tambah `+` atau `Number()` sangat ketat. Jika suatu nilai bukan angka, itu gagal: ```js run alert( +"100px" ); // NaN ``` Satu-satunya pengecualian adalah spasi di awal atau di akhir string, karena diabaikan. Tetapi dalam kehidupan nyata kita sering memiliki nilai dalam satuan, seperti `"100px"` atau `"12pt"` dalam CSS. Juga di banyak negara simbol mata uang mengikuti jumlah, jadi kita memiliki `"€19"` dan ingin mengekstraksi nilai numerik dari itu. Untuk itulah `parseInt` dan` parseFloat` ada. Mereka "membaca" angka dari sebuah string sampai mereka tidak bisa. Jika terjadi kesalahan, nomor yang dikumpulkan dikembalikan. Fungsi `parseInt` mengembalikan integer, sementara` parseFloat` akan mengembalikan nomor floating-point: ```js run alert( parseInt('100px') ); // 100 alert( parseFloat('12.5em') ); // 12.5 alert( parseInt('12.3') ); // 12, hanya bagian integer yang dikembalikan alert( parseFloat('12.3.4') ); // 12.3, poin kedua berhenti membaca ``` Ada situasi dimana `parseInt/parseFloat` akan mengembalikan `NaN`. Ini terjadi ketika tidak ada digit yang bisa dibaca ```js run alert( parseInt('a123') ); // NaN, symbol pertama menghentikan proses ``` ````smart header="Pernyataan kedua dari `parseInt(str, radix)`" Fungsi `parseInt()` fungsi memiliki parameter opsional kedua. Ini menentukan dasar dari sistem angka, jadi `parseInt` juga dapat mengurai string nomor hex, angka biner dan sebagainya: ```js run alert( parseInt('0xff', 16) ); // 255 alert( parseInt('ff', 16) ); // 255, tanpa 0x juga bekerja alert( parseInt('2n9c', 36) ); // 123456 ``` ```` ## Fungsi matematika lainnya Javascript memiliki objek [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) bawaan dimana berisi perpustakaan kecil fungsi matematika dan konstanta. Beberapa contoh: `Math.random()` : Mengembalikan angka acak dari 0 hingga 1 (tidak termasuk 1) ```js run alert( Math.random() ); // 0.1234567894322s alert( Math.random() ); // 0.5435252343232 alert( Math.random() ); // ... (angka aca apa saja) ``` `Math.max(a, b, c...)` / `Math.min(a, b, c...)` : Mengembalikan argumen terbesar / terkecil dari jumlah argumen yang arbitrer. ```js run alert( Math.max(3, 5, -10, 0, 1) ); // 5 alert( Math.min(1, 2) ); // 1 ``` `Math.pow(n, power)` : mengembalikan bilangan `n` yang dipangkatkan. ```js run alert( Math.pow(2, 10) ); // 2 pangkat 10 = 1024 ``` Ada lebih banyak fungsi dan konstanta dalam objek `Math`, termasuk trigonometri, yang dapat Anda temukan di [docs untuk objek Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math). ## Kesimpulan Untuk menulis angka dengan banyak nol: - Tambahkan `"e"` dengan hitungan angka nol ke angka. Seperti: `123e6` sama dengan `123` dengan 6 nol `123000000`. - Angka negatif setelah `"e"` menyebabkan angka untuk dibagi 1 dengan nol yang diberikan. Contohnya `123e-6` berarti `0.000123` (`123` millionths). Untuk sistem angka yang berbeda: - Dapat menulis angka secara langsung dalam sistem hex (`0x`), oktal (`0o`) dan sistem biner (`0b`) - `parseInt(str, base)` mem-parsing string `str` menjadi bilangan bulat dalam sistem angka dengan diberikan `base`, `2 ≤ base ≤ 36`. - `num.toString(base)` mengonversi angka menjadi string dalam sistem angka dengan diberikan `base`. Untuk mengkonversi nilai seperti `12pt` dan `100px` menjadi sebuah angka: - Gunakan `parseInt/parseFloat` untuk konversi "lembut", dimana membaca sebuah angka dari string dan mengembalikan nilai yang bisa dibaca sebelum terjadi kesalahan. Untuk pecahan: - Bulangan menggunakan `Math.floor`, `Math.ceil`, `Math.trunc`, `Math.round` atau `num.toFixed(presisi)`. - Pastikan untuk mengingat ada kehilangan presisi saat bekerja dengan pecahan. Lebih banyak fungsi matematika: - Lihat objek [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) ketika Anda membutuhkannya. Perpustakaannya sangat kecil, tetapi dapat memenuhi kebutuhan dasar. ================================================ 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 ================================================ Kita tidak dapat "mengganti" karakter pertama, karena di Javascript string bersifat tidak dapat berubah. Tetapi kita dapat membuat sebuah string baru berdasarkan yang sudah ada, dengan karakter pertama yang besar: ```js let newStr = str[0].toUpperCase() + str.slice(1); ``` Tetapi ada sedikit masalah. Jika `str` bernilai kosong, maka `str[0]` bernilai `undefined`, dan `undefined` tidak memiliki method `toUpperCase()`. Hal tersebut yang menyebabkan error. Ada dua cara di sini: 1. Gunakan `str.charAt(0)`, karena method ini selalu mengembalikan string (mungkin kosong). 2. Tambahkan pengecekan string kosong. Berikut adalah cara yang kedua: ```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 ================================================ Nilai penting: 5 --- # Buat karakter pertama menjadi besar Tulislah sebuah fungsi `ucFirst(str)` yang mengembalikan `str` dengan karakter pertama yang besar, sebagai contoh: ```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 ================================================ Untuk membuat pencarian bersifat case-insensitive, mari kita ubah string menjadi huruf kecil sebelum kita lakukan pencarian: ```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 ================================================ Nilai kepentingan: 5 --- # Mengecek apakah spam Tulislah sebuah fungsi `checkSpam(str)` yang mengembalikan `true` apabila `str` mengandung 'viagra' atau 'XXX', jika tidak kembalikan `false`. Fungsi yang ditulis harus bersifat case-insensitive: ```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 ================================================ Panjang maksimum adalah `maxlength`, jadi kita perlu memotongnya menjadi lebih pendek, untuk memberi tempat bagi elipsis. Perlu diperhatikan bahwa elipsis hanyalah sebuah karakter unicode, bukan tiga karakter titik. ```js run demo 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 ================================================ Nilai kepentingan: 5 --- # Memotong teks menjadi lebih pendek Buatlah sebuah fungsi `truncate(str, maxlength)` yang mengecek panjang dari `str` dan, apabila panjangnya melebihi `maxlength` -- ganti akhir dari `str` menjadi karakter elipsis `"…"`, supaya panjangnya sama dengan `maxlength`. Hasil kembalian dari fungsi seharusnya string yang dipotong (jika diperlukan). Sebagai contoh: ```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 ================================================ Nilai kepentingan: 4 --- # Ambil uangnya Kita memiliki uang dalam bentuk `"$120"`. Yaitu: tanda dolar muncul pertama, lalu diikuti oleh angka. Buatlah sebuah fungsi `extractCurrencyValue(str)` yang mengambil nilai dari bagian angka string tersebut dan mengembalikannya. Sebagai contoh: ```js alert( extractCurrencyValue('$120') === 120 ); // true ``` ================================================ FILE: 1-js/05-data-types/03-string/article.md ================================================ # String Di Javascript, data teks disimpan sebagai string. Tidak ada tipe data sendiri untuk satu buah karakter. [UTF-16](https://en.wikipedia.org/wiki/UTF-16) selalu digunakan sebagai format internal string, hal tersebut tidak terikat dengan jenis encoding yang digunakan oleh halaman. ## Petik Mari kita lihat berbagai jenis petik. String dapat ditutup dengan petik satu, petik dua, maupun backtick: ```js let single = 'single-quoted'; let double = "double-quoted"; let backticks = `backticks`; ``` Petik satu dan petik dua kurang lebih hampir sama. Akan tetapi backtick memiliki perbedaan, yaitu memperbolehkan kita untuk menyisipkan ekspresi ke dalam string, dengan menaruhnya di dalam `${…}`: ```js run function sum(a, b) { return a + b; } alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3. ``` Kelebihan backtick yang lain yaitu backtick memperbolehkan sebuah string untuk terdiri lebih dari satu baris: ```js run let guestList = `Guests: * John * Pete * Mary `; alert(guestList); // list tamu, dipisahkan per baris ``` Lebih rapi, kan? Tetapi petik satu atau dua tidak bekerja seperti itu. Jika kita coba untuk menggunakan mereka untuk lebih dari satu baris, akan terjadi error: ```js run let guestList = "Guests: // Error: Unexpected token ILLEGAL * John"; ``` Petik satu dan petik dua berasal dari masa lalu saat bahasa pemrograman dibuat, dimana kebutuhan untuk string lebih dari satu baris belum dipikirkan. Backtick muncul di kemudian hari, dan lebih fleksibel. Backtick juga memperbolehkan kita untuk menspesifikasi "fungsi template" sebelum backtick pertama. Syntaxnya yaitu: func`string`. Fungsi `func` dipanggil secara otomatis, menerima string dan ekspresi yang berada di dalamnya dan bisa memproses mereka. Ini disebut "tagged templates". Fitur ini membuat implementasi kustom templating lebih mudah, tapi jaran dipakai dalam praktik. Kamu bisa membaca lebih tentang ini di [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). ## Karakter spesial Masih mungkin untuk membuat string dengan banyak baris menggunakan petik satu atau petik dua dengan menggunakan "karakter newline", ditulis seperti berikut `\n`, yang menandakan baris baru: ```js run let guestList = "Guests:\n * John\n * Pete\n * Mary"; alert(guestList); // list tamu yang dipisahkan per baris ``` Sebagai contoh, kedua baris berikut sama saja, hanya ditulis dengan cara yang berbeda: ```js run let str1 = "Hello\nWorld"; // dua baris menggunakan "simbol baris baru" // dua baris menggunakan backtick let str2 = `Hello World`; alert(str1 == str2); // true ``` Ada karakter spesial lain, tetapi mereka jarang digunakan Berikut adalah daftar lengkapnya: | Character | Description | |-----------|-------------| |`\n`|Baris baru| |`\r`|Carriage return: tidak digunakan sendiri. File teks milik di Windows menggunakan kombinasi dari dua karakter `\r\n` untuk menandakan baris baru.| |`\'`, `\"`|Petik-petik| |`\\`|Backslash| |`\t`|Tab| |`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- tetap bisa digunakan untuk kompabilitas, sekarang sudah tidak digunakan. | |`\xXX`|Karakter unicode dengan nilai heksadesimal `XX`, misalnya `'\x7A'` itu sama saja dengan `'z'`.| |`\uXXXX`|Sebuah simbol unicode dengan nilai heksadesimal `XXXX` di dalam encoding UTF-16, sebagai contoh `\u00A9` -- adalah sebuah unicode untuk simbol copyright `©`. Simbol ini harus terdiri dari 4 digit heksadesimal. | |`\u{X…XXXXXX}` (1 to 6 karakter heksadesimal)|Sebuah simbol unicode dengan encoding UTF-32. Beberapa karakter langka menggunakan dua simbol unicode, yang memakan 4 byte. Dengan cara ini kita dapat menggunakan kode yang panjang. | Contoh dengan unicode: ```js run alert( "\u00A9" ); // © alert( "\u{20331}" ); // 佫, sebuah karakter mandarin (unicode panjang) alert( "\u{1F60D}" ); // 😍, sebuah simbol wajah tersenyum (unicode panjang lainnya) ``` Karakter-karakter spesial yang diawali dengan karakter backslash `\` kadang dipanggil dengan sebutan "escape character". Kita kadang dapat menggunakannya apabila kita ingin menggunakan petik di dalam string. Misalnya: ```js run alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! ``` Seperti yang kita lihat, kita harus menambahkan backslash di depan petik yang di dalam string `\'`, karena jika tidak petik tersebut akan menandakan akhir dari sebuah string. Tentu saja, hanya jenis petik yang sama dengan penutup string yang perlu di "escape". Jadi, solusi yang lebih elegan yaitu mengganti petik satu menjadi petik dua atau backtick: ```js run alert( `I'm the Walrus!` ); // I'm the Walrus! ``` Ingat bahwa backslash `\` hanya dipakai untuk Javascript agar dapat membaca string dengan benar. Di dalam memori, string tidak memiliki `\`. Anda dapat melihatnya secara langsung pada contoh `alert` di atas. Tetapi bagaimana jika kita ingin menampilkan backslash `\` di dalam sebuah string? Hal tersebut bisa dilakukan, tetapi kita harus menulisnya dua kali seperti ini `\\`: ```js run alert( `The backslash: \\` ); // The backslash: \ ``` ## Panjang string Properti `length` memiliki panjang dari string: ```js run alert( `My\n`.length ); // 3 ``` Perlu diingat bahwa `\n` adalah sebuah karakter spesial, jadi panjang dari string adalah `3`. ```warn header="`length` adalah sebuah properti" Orang dengan latar belakang di bahasa pemrograman lain kadang salah mengetik `str.length()` alih-alih `str.length`. Hal tersebut tidak bekerja. Perlu diingat bahwa `str.length` adalah properti numerik, bukan sebuah fungsi. Tidak perlu menambahkan kurung di belakangnya. ``` ## Mengakses karakter di dalam string Untuk mengakses karakter pada posisi `pos`, digunakan kurung kotak `[pos]` atau dengan method [str.charAt(pos)](mdn:js/String/charAt). Karakter pertama dimulai dari posisi ke-0: ```js run let str = `Hello`; // karakter pertama alert( str[0] ); // H alert( str.charAt(0) ); // H // karakter terakhir alert( str[str.length - 1] ); // o ``` Kurung kotak adalah cara modern untuk mengakses sebuah karakter, sementara `charAt` ada karena alasan historis. Perbedaan satu-satunya di antara mereka adalah apabila tidak ada karakter yang ditemukan, `[]` mengembalikan `undefined`, dan `charAt` mengembalikan string kosong: ```js run let str = `Hello`; alert( str[1000] ); // undefined alert( str.charAt(1000) ); // '' (string kosong) ``` Kita juga bisa mengakses karakter per karakter menggunakan sintaks `for..of`: ```js run for (let char of "Hello") { alert(char); // H,e,l,l,o (char bernilai "H", lalu "e", lalu "l", dan seterusnya) } ``` ## String bersifat tidak dapat diubah Nilai string tidak dapat diubah di Javascript. Tak mungkin mengubah sebuah karakter. Mari kita coba buktikan: ```js run let str = 'Hi'; str[0] = 'h'; // galat alert( str[0] ); // tidak bekerja ``` Salah satu cara untuk mengatasi hal tersebut adalah untuk membuat string baru lalu memasukkan nilainya ke `str`. Sebagai contoh: ```js run let str = 'Hi'; str = 'h' + str[1]; // mengganti nilai string alert( str ); // hi ``` Di bab ini kita akan melihat contoh lebih banyak dari ini. ## Mengganti case Metode [toLowerCase()](mdn:js/String/toLowerCase) dan [toUpperCase()](mdn:js/String/toUpperCase) mengganti case: ```js run alert( 'Interface'.toUpperCase() ); // INTERFACE alert( 'Interface'.toLowerCase() ); // interface ``` Atau, apabila kita hanya ingin sebuah karakter yang diubah menjadi huruf kecil: ```js alert( 'Interface'[0].toLowerCase() ); // 'i' ``` ## Mencari substring Ada banyak cara untuk mencari sebuah substring di dalam sebuah string. ### str.indexOf Cara yang pertama yaitu [str.indexOf(substr, pos)](mdn:js/String/indexOf). Method ini mencari `substr` di dalam `str`, mulai dari posisi `pos` yang diberikan, dan mengembalikan posisi dimana substring ditemukan atau `-1` jika tidak ditemukan. Misalnya: ```js run let str = 'Widget with id'; alert( str.indexOf('Widget') ); // 0, karena 'Widget' ditemukan di awal string alert( str.indexOf('widget') ); // -1, tidak ditemukan, karena pencarian bersifat case-sensitive alert( str.indexOf("id") ); // 1, "id" ditemukan pada posisi 1 (..idget with id) ``` Parameter kedua yang opsional memperbolehkan kita untuk mencari dari posisi yang ditentukan. Misalnya, kemunculan pertama `"id"` ada di posisi `1`. Untuk mencari kemunculan berikutnya, ayo kita mulai pencarian dari posisi `2`: ```js run let str = 'Widget with id'; alert( str.indexOf('id', 2) ) // 12 ``` Jika kita tertarik dengan semua kemunculan, kita bisa menjalankan `indexOf` di dalam loop. Setiap panggilan dibuat dengan posisi setelah kemunculan sebelumnya: ```js run let str = 'As sly as a fox, as strong as an ox'; let target = 'as'; // mari kita cari let pos = 0; while (true) { let foundPos = str.indexOf(target, pos); if (foundPos == -1) break; alert( `Found at ${foundPos}` ); pos = foundPos + 1; // lanjutkan pencarian dari posisi berikutnya } ``` Algoritma yang sama dapat ditulis lebih pendek: ```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(substr, position)`" Ada juga method yang hampir sama [str.lastIndexOf(substr, position)](mdn:js/String/lastIndexOf) yang mencari dari akhir sebuah string sampai ke awalnya. Cara tersebut akan menemukan kemunculan dalam urutan yang terbalik. ``` Ada sedikit kerepotan dalam menggunakan `indexOf` di dalam `if`. Kita tidak dapat menggunakannya seperti ini: ```js run let str = "Widget with id"; if (str.indexOf("Widget")) { alert("We found it"); // tidak bekerja! } ``` Contoh di atas tidak bekerja karena `str.indexOf("Widget")` mengembalikan `0` (artinya kemunculan ditemukan di awal string). `if` menganggap `0` sebagai `false`. Jadi, kita harus mengecek dengan nilai `-1`, seperti ini: ```js run let str = "Widget with id"; *!* if (str.indexOf("Widget") != -1) { */!* alert("We found it"); // kalau sekarang berhasil! } ``` #### Trik bitwise NOT Salah satu trik lama yang digunakan disini adalah operator [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~`. Operator ini mengubah angka menjadi integer 32-bit (menghilangkan bagian desimal jika ada) lalu menegasikan semua bit pada representasi binernya. Dalam praktik, hal tersebut berarti: untuk integer 32-bit `~n` sama dengan `-(n+1)`. Sebagai contoh: ```js run alert( ~2 ); // -3, sama dengan -(2+1) alert( ~1 ); // -2, sama dengan -(1+1) alert( ~0 ); // -1, sama dengan -(0+1) *!* alert( ~-1 ); // 0, sama dengan -(-1+1) */!* ``` Seperti yang kita lihat, `~n` bernilai nol apabila `n == -1` (untuk semua signed integer `n`). Jadi, pengecekan di dalam `if ( ~str.indexOf("...") )` bernilai benar apabila hasil dari `indexOf` tidak bernilai `-1`. Dengan kata lain, jika kemunculan ditemukan. Orang-orang menggunakannya untuk memperpendek pengecekan `indexOf`: ```js run let str = "Widget"; if (~str.indexOf("Widget")) { alert( 'Found it!' ); // bekerja } ``` Biasanya tidak direkomendasikan untuk menggunakan fitur bahasa dengan cara yang tidak jelas, tetapi trik ini biasa digunakan di kode yang kuno, jadi kita harus memahaminya. Ingat: `if (~str.indexOf(...))` dibaca sebagai "if ditemukan". Untuk lebih detail, karena bilangan yang besar dipotong menjadi 32-bit oleh operator `~`, ada angka lain yang memberikan hasil `0`, angka yang terkecil yaitu `~4294967295=0`. Hal tersebut menyebabkan trik ini benar apabila string yang dites tidak sepanjang itu. Kita dapat melihat bahwa trik ini hanya digunakan di kode yang kuno, karena Javascript modern menyediakan method `.includes` (lihat di bawah). ### includes, startsWith, endsWith Method yang lebih modern [str.includes(substr, pos)](mdn:js/String/includes) mengembalikan `true/false` tergantung dengan apakah `str` mengandung `substr` di dalamnya. Ini adalah pilihan yang cocok apabila kita hanya perlu mengetes apakah `substr` ada, tetapi tidak memerlukan posisinya: ```js run alert( "Widget with id".includes("Widget") ); // true alert( "Hello".includes("Bye") ); // false ``` Parameter opsinal kedua dari `str.includes` adalah posisi dimana pencarian mulai dilakukan: ```js run alert( "Widget".includes("id") ); // true alert( "Widget".includes("id", 3) ); // false, dari posisi 3 tidak ditemukan "id" ``` Method [str.startsWith](mdn:js/String/startsWith) dan [str.endsWith](mdn:js/String/endsWith) melakukan fungsi seperti namanya: ```js run alert( "Widget".startsWith("Wid") ); // true, "Widget" diawali oleh "Wid" alert( "Widget".endsWith("get") ); // true, "Widget" diakhiri oleh "get" ``` ## Mengambil substring Ada 3 cara untuk mengambil sebuah substring di Javascript: `substring`, `substr` dan `slice`. `str.slice(start [, end])` : Mengembalikan bagian dari string dari `start` sampai (tapi tidak termasuk) `end`. Sebagai contoh: ```js run let str = "stringify"; alert( str.slice(0, 5) ); // 'strin', substring dari posisi 0 sampai 5 (tidak termasuk 5) alert( str.slice(0, 1) ); // 's', dari 0 sampai 1, tetapi tidak termasuk 1, jadi hanya karakter pada posisi 0 ``` Jika tidak ada parameter kedua, maka `slice` akan mengambil semua bagian dari `start` sampai akhir string: ```js run let str = "st*!*ringify*/!*"; alert( str.slice(2) ); // ringify, dari posisi kedua sampai terakhir ``` Nilai negatif untuk `start/end` juga bisa digunakan. Nilai negatif berarti posisinya dihitung dari akhir string: ```js run let str = "strin*!*gif*/!*y"; // mulai dari posisi ke-4 dari kanan, berakhir di posisi pertama dari kanan alert( str.slice(-4, -1) ); // gif ``` `str.substring(start [, end])` : Mengembalikan bagian dari string *di antara* `start` dan `end`. Method ini hampir sama dengan `slice`, tetapi nilai `start` boleh lebih besar daripada `end`. Sebagai contoh: ```js run let str = "st*!*ring*/!*ify"; // kedua ini sama saja untuk substring alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" // ...tetapi tidak untuk slice: alert( str.slice(2, 6) ); // "ring" (sama) alert( str.slice(6, 2) ); // "" (string kosong) ``` Parameter negatif tidak didukung (tidak seperti slice), mereka dianggap sebagai `0`. `str.substr(start [, length])` : Mengembalikan substring dari `start`, dengan panjang `length`. Dibandingkan dengan cara-cara sebelumnya, cara ini memperbolehkan kita untuk menyebutkan `length` alih-alih posisi akhir dari string: ```js run let str = "st*!*ring*/!*ify"; alert( str.substr(2, 4) ); // ring, dari posisi ke-2 ambil 4 karakter ``` Parameter pertama mungkin bernilai negatif, untuk menghitung dari akhir string: ```js run let str = "strin*!*gi*/!*fy"; alert( str.substr(-4, 2) ); // gi, dari posisi ke-4 ambil 2 karakter ``` Mari kita review cara-cara tersebut untuk menghindari kebingungan: | method | mengambil... | negatives | |--------|-----------|-----------| | `slice(start, end)` | dari `start` sampai `end` (tidak termasuk `end`) | nilai negatif diperbolehkan | | `substring(start, end)` | antara `start` dan `end` | nilai negatif berarti mean `0` | | `substr(start, length)` | dari `start` ambil `length` karakter | `start` negatif diperbolehkan | ```smart header="Cara mana yang harus kita gunakan?" Semuanya dapat melakukan pekerjaannya. Secara formal, `substr` memiliki kekurangan: fungsi ini tidak tercantum di spesifikasi inti Javascript, tetapi di Annex B, yang mencakup hanya fitur browser yang ada karena alasan historis. Jadi, environment non-browser mungkin gagal untuk mendukungnya. Tetapi dalam praktik fungsi ini bekerja di mana saja. Dibandingkan dengan dua varian yang lain, `slice` lebih fleksibel, karena memperbolehkan parameter negatif dan lebih pendek untuk ditulis. Jadi, dari ketiga cara sudah cukup untuk mengingat `slice`. ``` ## Membandingkan string Seperti yang kita tahu dari bab , string dibandingkan karakter per karakter dengan urutan alfabet. Akan tetapi, ada beberapa pengecualian. 1. Huruf kecil selalu lebih besar dibanding huruf kapital: ```js run alert( 'a' > 'Z' ); // true ``` 2. Karakter dengan tanda diakritik "tidak sesuai urutan": ```js run alert( 'Österreich' > 'Zealand' ); // true ``` Hal ini dapat menyebabkan hasil yang aneh apabila kita mengurutkan nama-nama negara. Biasanya orang mengharapkan `Zealand` muncul setelah `Österreich` di daftar. Untuk memahami apa yang terjadi, mari kita review representasi internal string di Javascript. Semua string menggunakan encoding [UTF-16](https://en.wikipedia.org/wiki/UTF-16). Yaitu: setiap karakter memiliki masing-masing kode numerik. Ada method spesial yang memperbolehkan kita untuk mengambil karakter dari kode dan sebaliknya. `str.codePointAt(pos)` : Mengembalikan kode untuk karakter pada posisi `pos`: ```js run // karakter dengan case yang berbeda memiliki kode berbeda alert( "z".codePointAt(0) ); // 122 alert( "Z".codePointAt(0) ); // 90 ``` `String.fromCodePoint(code)` : Membuat sebuah karakter berdasarkan `code` numeriknya ```js run alert( String.fromCodePoint(90) ); // Z ``` Kita juga dapat membuat karakter unicode dengan kode mereka menggunakan `\u` yang diikuti oleh kode heksadesimal: ```js run // 90 bernilai 5a di dalam sistem heksadesimal alert( '\u005a' ); // Z ``` Sekarang mari kita lihat karakter dengan kode di antara `65..220` (alfabet latin dan sedikit tambahan) dengan membuat string yang terdiri dari mereka: ```js run let str = ''; for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` Kan? Huruf kapital muncul pertama, lalu beberapa karakter spesial, lalu huruf kecil, dan `Ö` berada di dekat akhir string. Sekarang terlihat jelas kenapa `a > Z`. Karakter-karakter dibandingkan berdasarkan kode numeriknya. Kode yang lebih besar berarti karakter tersebut lebih besar. Kode untuk `a` (97) lebih besar dibandingkan dengan kode untuk `Z` (90). - Semua huruf kecil muncul setelah huruf kapital karena kode mereka lebih besar. - Beberapa karakter seperti `Ö` terpisah dari alfabet utama. Disini, kodenya lebih besar dibandingkan semua karakter dari `a` to `z`. ### Perbandingan yang benar Algoritma yang "benar" untuk melakukan perbandingan string lebih kompleks dari kelihatannya, setiap bahasa memiliki alfabet mereka masing-masing. Jadi, browser harus tahu bahasa yang digunakan untuk perbandingan. Beruntungnya, semua browser modern (IE10- memerlukan library tambahan [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) mendukung standar internasionalisasi [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). Hal tersebut menyediakan cara spesial untuk membandingkan stringi di berbeda bahasa, mengikuti peraturan mereka. Method [str.localeCompare(str2)](mdn:js/String/localeCompare) mengembalikan sebuah interger yang menandakan apakah `str` lebih kecil, sama dengan atau lebih besar dari `str2` menurut peraturan-peraturan bahasa: - Mengembalikan nilai negatif jika `str` lebih kecil dibandingkan `str2` - Mengembalikan nilai positif jika `str` lebih besar dibandingkan `str2` - Mengembalikan `0` apabila mereka sama. Seperti contoh: ```js run alert( 'Österreich'.localeCompare('Zealand') ); // -1 ``` Method ini sebenarnya menerima 2 argumen tambahan yang disebutkan di [dokumentasi](mdn:js/String/localeCompare), yang memperbolehkan untuk menyebutkan bahasa (yang biasanya diambil dari environment, urutan huruf bergantung dari bahasa) dan menyebutkan peraturan-peraturan tambahan seperti case sensitivity atau apakah `"a"` and `"á"` dianggap sama dan seterusnya. ## Bagian internal dari unicode ```warn header="Pengetahuan lanjutan" Bagian ini membahas lebih dalam tentang bagian internal string. Pengetahuan ini akan berguna apabila Anda akan berurusan dengan emoji, simbol matematika, hieroglif, atau simbol-simbol lain yang langka. Anda dapat melewati bagian ini jika tidak berurusan dengan mereka. ``` ### Surrogate pairs Semua karakter yang sering digunakan memiliki kode 2-byte. Huruf di kebanyakan negara eropa, angka, dan bahkan kebanyakan hieroglif, memiliki representasi 2-byte. Tetapi 2 byte hanya memperbolehkan 65536 kombinasi dan itu tidak cukup untuk semua kombinasi simbol. Jadi simbol-simbol yang langka menggunakan encoding dengan sepasang karakter 2-byte yang disebut "surrogate pair". Panjang dari simbol tersebut adalah `2`: ```js run alert( '𝒳'.length ); // 2, SIMBOL MATEMATIKA X BESAR alert( '😂'.length ); // 2, MUKA DENGAN TANGISAN BAHAGIA alert( '𩷶'.length ); // 2, karakter mandarin ``` Perlu diingat bahwa surrogate pair tidak ada pada saat Javascript dibuat, oleh karena itu fitur ini tidak diproses secara benar oleh bahasa ini! Kita sebenarnya memiliki sebuah simbol di setiap string di atas, tetapi `length` menunjukkan panjang `2`. `String.fromCodePoint` dan `str.codePointAt` adalah beberapa method yang menangani surrogate pair dengan benar. Belum lama ini mereka muncul di bahasa ini. Sebelum mereka, hanya ada [String.fromCharCode](mdn:js/String/fromCharCode) dan [str.charCodeAt](mdn:js/String/charCodeAt). Method-method tersebut sebenarnya sama saja dengan `fromCodePoint/codePointAt`, tetapi tidak bisa digunakan untuk surrogate pair. Mengambil sebuah simbol terkadang agak susah, karena surrogate pair diperlakukan sebagai dua karakter: ```js run alert( '𝒳'[0] ); // simbol aneh... alert( '𝒳'[1] ); // ...bagian dari surrogate pair ``` Perlu diingat bahwa bagian dari surrogate pair tidak memiliki arti tanpa pasangan yang lain. Jadi contoh di atas menampilkan karakter aneh. Secara teknis, surrogate pair juga dapat dideteksi berdasarkan kode mereka, jika sebuah karakter memiliki kode di antara `0xd800..0xdbff`, maka karakter ini adalah bagian pertama dari surrogate pair. Karakter selanjutnya (bagian kedua) harus berada di antara `0xdc00..0xdfff`. Interval ini sudah dipesan secara khusus untuk surrogate pair oleh standar. Pada kasus diatas: ```js run // charCodeAt is not surrogate-pair aware, so it gives codes for parts alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, diantara 0xd800 dan 0xdbff alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, diantara 0xdc00 dan 0xdfff ``` Anda akan menemukan cara lain untuk bertanganan dengan surrogate pair nanti di bab . Mungkin juga ada library-library yang untuk hal tersebut, tetapi tidak ada yang cukup terkenal untuk disarankan di sini. ### Tanda diakritik dan normalisasi Di banyak bahasa terdapat simbol-simbol yang terdiri dari huruf dasar dengan tanda di atas/bawahnya. Sebagai contoh, karakter `a` dapat menjadi huruf dasar untuk: `àáâäãåā`. Kebanyakan karakter "komposit" memiliki kode mereka sendiri di tabel UTF-16. Hal tersebut tidak selalu terjadi, karena terlalu banyak kemungkinan kombinasi. Untuk mendukung komposisi yang fleksibel, UTF-16 memperbolehkan kita untuk menggunakan beberapa karakter unicode: sebuah huruf dasar yang diikuti oleh satu atau lebih karakter "tanda" yang "menghiasinya". Sebagai contoh, jika kita memiliki `S` diikuti dengan karakter spesial "titik di atas" (kode `\u0307`), maka akan ditampilkan Ṡ. ```js run alert( 'S\u0307' ); // Ṡ ``` Jika kita memerlukan tanda tambahan di atas huruf (atau di bawahnya) -- tidak masalah, tambahkan saja karakter tanda yang diperlukan. Sebagai contoh, jika kita tambahkan sebuah karakter "titik di bawah" (kode `\u0323`), maka kita akan mendapatkan "S dengan titik di atas dan di bawah": `Ṩ`. Sebagai contoh: ```js run alert( 'S\u0307\u0323' ); // Ṩ ``` Hal tersebut memberikan banyak fleksibilitas, tetapi juga masalah yang menarik: dua karakter mungkin terlihat sama, tetapi dapat direpresentasikan dengan komposisi unicode yang berbeda. Sebagai contoh: ```js run let s1 = 'S\u0307\u0323'; // Ṩ, S + titik di atas + titik di bawah let s2 = 'S\u0323\u0307'; // Ṩ, S + titik di atas + titik di bawah alert( `s1: ${s1}, s2: ${s2}` ); alert( s1 == s2 ); // false walaupun karakter terlihat sama (?!) ``` Untuk menyelesaikan masalah ini, terdapat sebuah algoritma "normalisasi unicode" yang membuat setiap string menjadi satu bentuk "normal". Algoritma tersebut diimplementasikan oleh [str.normalize()](mdn:js/String/normalize). ```js run alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true ``` Agak lucu bahwa di situasi kita `normalize()` menjadikan sekumpulan karakter dengan panjang 3 menjadi satu: `\u1e68` (S dengan dua titik). ```js run alert( "S\u0307\u0323".normalize().length ); // 1 alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true ``` Pada kenyataan, hal ini tidak selalu berlaku. Contoh diatas berlaku karena simbol `Ṩ` is "cukup sering digunakan", jadi pembuat UTF-16 memasukkannya di tabel utama dan memberinya sebuah kode. Jika Anda ingin belajar lebih lanjut tentang aturan normalisasi dan variasinya -- mereka dideskripsikan di appendix Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), tetapi untuk kebanyakan kasus informasi yang terdapat di bagian ini sudah cukup. ## Kesimpulan - Terdapat 3 jenis tanda petik. Backtick membolehkan string memiliki baris ganda dan menyisipkan expresi `${…}`. - String di Javascript diencode menggunakan UTF-16. - Kita bisa memakai karakter seperti `\n` dan memasukkan karakter berdasarkan unicode mereka menggunakan `\u...`. - Untuk mendapatkan karakter, gunakan: `[]`. - Untuk mendapatkan substring, gunakan: `slice` atau `substring`. - Untuk mengubah case kecil/besar dari string, gunakan: `toLowerCase/toUpperCase`. - Untuk mencari substring, gunakan: `indexOf`, atau `includes/startsWith/endsWith` untuk pengecekan sederhana. - Untuk membandingkan string mengikuti bahasa, gunakan `localeCompare`, jika tidak mereka akan dibandingkan berdasarkan kode karakter. Ada beberapa metode string lain yang berguna: - `str.trim()` -- menghilangkan ("memotong") spasi dari awal dan akhir dari sebuah string. - `str.repeat(n)` -- mengulang string sebanyak `n` kali. - ...dan masih banyak lagi yang dapat ditemukan di dalam [manual](mdn:js/String). String juga memiliki method-method untuk mencari/mengganti dengan ekspresi reguler (regular expression). Tetapi itu adalah topik yang luas, jadi topik ini dibahas di bagiannya sendiri . ================================================ FILE: 1-js/05-data-types/04-array/1-item-value/solution.md ================================================ The result is `4`: ```js run let fruits = ["Apples", "Pear", "Orange"]; let shoppingCart = fruits; shoppingCart.push("Banana"); *!* alert( fruits.length ); // 4 */!* ``` Itu karena *array* adalah objek. Jadi baik `shoppingCart` dan `fruits` mereferensi ke *array* yang sama. ================================================ FILE: 1-js/05-data-types/04-array/1-item-value/task.md ================================================ importance: 3 --- # Apakah *array* disalin? Apa yang kode ini akan tunjukkan? ```js let fruits = ["Apples", "Pear", "Orange"]; // push sebuah nilai baru ke "copy" let shoppingCart = fruits; shoppingCart.push("Banana"); // apa yang ada di dalam 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 ================================================ # Solusi lamban Kita dapat menghitung semua semua sub-penjumlahan yang memungkinkan. Cara paling sederhana adalah dengan cara mengambil setiap elemen dan menghitung jumlah semua *subarray* dimulai dari situ. Contohnya, untuk `[-1, 2, 3, -9, 11]`: ```js no-beautify // Dimulai dari -1: -1 -1 + 2 -1 + 2 + 3 -1 + 2 + 3 + (-9) -1 + 2 + 3 + (-9) + 11 // Dimulai dari 2: 2 2 + 3 2 + 3 + (-9) 2 + 3 + (-9) + 11 // Dimulai dari 3: 3 3 + (-9) 3 + (-9) + 11 // Dimulai dari -9 -9 -9 + 11 // Dimulai dari 11 11 ``` Kode tersebut sebenarnya adalah sebuah pengulangan *nested* (atau *nested* loop): pengulangan eksternal elemen-elemen *array*, dan yang internal menghitung sub-jumlah dengan elemen yang sekarang. ```js run function getMaxSubSum(arr) { let maxSum = 0; // jika kita tidak mengambil elemen apapaun, angka nol akan dikembalikan 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 ``` Solusi tersebut memiliki waktu penyelesaian [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). Dalam kata lain, jika kita menambah ukuran *array* 2 kali lipat, algoritma akan bekerja 4 kali lipat lebih lama. Untuk *array* yang besar (1000, 10000 *item* atau lebih) algoritma yang demikian akan mengarah pada kelambanan yang parah. # Solusi cepat Mari jalankan *array* tersebut dan simpan hasil penjumlahkan parsial elemen yang sekarang di dalam variabel `s`. Jika `s` menjadi negatif pada beberapa titik, maka tugaskan `s=0`. Nilai maksimum dari semua `s` tersebut akan menjadi jawabannya. Jika deskripsi tersebut terlalu samar, mohon perhatikan kodenya, yang ternyata cukup pendek: ```js run demo function getMaxSubSum(arr) { let maxSum = 0; let partialSum = 0; for (let item of arr) { // untuk setiap item arr partialSum += item; // menambahkannya ke partialSum maxSum = Math.max(maxSum, partialSum); // ingat nilai maxksimum if (partialSum < 0) partialSum = 0; // nol jika negatif } 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 ``` Algoritma tersebut membutuhkan tepat 1 *array* yang lolos, jadi waktu penyelesaian adalah O(n). Kamu dapat menemukan informasi yang lebih rinci tentang algoritma di sini: [Masalah *subarray* maksimum](http://en.wikipedia.org/wiki/Maximum_subarray_problem). Jika masih kurang jelas bagaimana hal tersebut bekerja, maka mohon menelusuri algoritma pada contoh di atas, perhatikan bagaimana algoritmanya bekerja, itulah cara yang paling baik. ================================================ FILE: 1-js/05-data-types/04-array/10-maximal-subarray/task.md ================================================ importance: 2 --- # *Subarray* maksimum Input adalah sebuah *array* angka, e.g. `arr = [1, -2, 3, 4, -9, 6]`. Tugasnya adalah: menemukan *subarray* `arr` yang berdampingan dengan nilai maksimal penjumlahan *item* yang ada. Tulis fungsi `getMaxSubSum(arr)` yang akan mengembalikan nilai penjumlahan tersebut. Sebagai contoh: ```js getMaxSubSum([-1, *!*2, 3*/!*, -9]) == 5 (the sum of highlighted items) 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 (take all) ``` Jika semua *item* adalah negatif, hal tersebut berarti kita tidak mengambil apapun (*subarray* kosong), jadi jumlahnya sama dengan nol: ```js getMaxSubSum([-1, -2, -3]) = 0 ``` Mohon coba untuk memikirkan sebuah solusi cepat: [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation) atau bahkan O(n) jika bisa. ================================================ 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 --- # Operasi *array*. Mari coba 5 operasi *array*. 1. Buat sebuah *array* `styles` dengan elemen "Jazz" dan "Blues" di dalamnya. 2. Tambahkan "Rock-n-Roll" pada akhir *array*. 3. Ganti nilai yang berada di tengah menjadi "Classics". Kodemu untuk menemukan nilai di tengah harus berhasil untuk *array* sepanjang apapun. 4. Hilangkan nilai pertama dari *array* lalu tampilkan *array*. 5. Dahulan *array* dengan `Rap` and `Reggae` di depannya. *Array* selama proses: ```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 ================================================ Panggilan `arr[2]()` secara sintaks adalah `obj[method]()` yang sudah ada dari lama, dalam peran sebagai `obj` kita memiliki `arr`, dan dalam peran sebagai `method` kita memiliki `2`. Jadi kita memiliki sebuah panggilan fungsi `arr[2]` sebagai sebuah metode objek. Secara alami, fungsi terebut menerima `this` yang mereferensikan ke `arr` and menghasilakn *array* berikut: ```js run let arr = ["a", "b"]; arr.push(function() { alert( this ); }) arr[2](); // a,b,function(){...} ``` *Array* tersebut memiliki 3 nilai: sejak awal *array* tersebut memiliki dua nilai, plus fungsi. ================================================ FILE: 1-js/05-data-types/04-array/3-call-array-this/task.md ================================================ importance: 5 --- # Panggilan dalam konteks *array* Apa hasilnya? Mengapa demikian? ```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 ================================================ Tolong perhatikan rincian penting dari solusi yang diberikan berikut. Kita tidak mengonversi `value` menjadi angka secara langsung setelah `prompt`, karena setelah `value = +value` kita tidak bisa memberitahukan sebuah string kosong (tanda berhenti) dari angka nol (angka yang valid). Kita melakukannya setelah langkah tersebut. ```js run demo function sumInput() { let numbers = []; while (true) { let value = prompt("A number please?", 0); // perlukah kita batalkan? 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 --- # Menjumlahkan angka yang di-input Tuliskan fungsi `sumInput()` yang: - Meminta nilai dari dengan menggunakan `prompt` dan menyimpan nilai tersebut dalam *array*. - Berhenti meminta nilai dari pengguna ketika pengguna memasukkan nilai non-numerik, sebuah *string* kosong, atau menekan "Cancel". - Hitung dan mengembalikan hasil penjumlahan *item* dalam *array*. Catatan. Sebuah nol `0` adalah angka yang valid, mohon tidak menghentikan input pada angka nol. [demo] ================================================ FILE: 1-js/05-data-types/04-array/article.md ================================================ # *Array* Objek mengizinkanmu untuk menyimpan koleksi-koleksi tertulis dari nilai. Itu bagus. Namun seringkali kita menemukan bahwa kita butuh sebuah *koleksi yang tertata*, dimana kita memiliki elemen ke-1, ke-2, ke-3 dan seterusnya. Sebagai contohnya, kita butuh menyimpan sebuah daftat sesuatu: pengguna, barang, elemen HTML, dan lain-lain. Tidaklah nyaman untuk menggunakan sebuah objek di kasus ini, karena objek tidak menyediakan metode untuk mengelola elemen. Kita tidak bisa memasukkan sebuah properti baru "di antara" properti yang sudah dulu ada. Objek memang tidak tujukan untuk penggunaan yang demikian. ADa sebuah struktur data spesial yang dinamakan `Array`, untuk menyimpan koleksi-koleksi yang tertata. ## Deklarasi Terdapat dua sintaks untuk membuat sebuah *array* kosong: ```js let arr = new Array(); let arr = []; ``` Hampir tiap waktu, sintaks kedua lah yang digunakan. Kita bisa menyediakan elemen-elemen awal dalam tanda: ```js let fruits = ["Apple", "Orange", "Plum"]; ``` Elemen-elemen *array* itu diberi (urutan) nomor, mulai dari nol. Kita bisa mendapatkan sebuah elemen menggunakan nomor elemen itu dalam tanda kurung siku: ```js run let fruits = ["Apple", "Orange", "Plum"]; alert( fruits[0] ); // Apple alert( fruits[1] ); // Orange alert( fruits[2] ); // Plum ``` Kita bisa mengganti sebuah elemen: ```js fruits[2] = 'Pear'; // kini ["Apple", "Orange", "Pear"] ``` ...Atau menambakan elemen baru ke *array*: ```js fruits[3] = 'Lemon'; // kini ["Apple", "Orange", "Pear", "Lemon"] ``` Jumlah elemen di dalam sebuah *array* adalah `length` (panjang) dari *array* tersebut: ```js run let fruits = ["Apple", "Orange", "Plum"]; alert( fruits.length ); // 3 ``` Kita juga bisa menggunakan `alert` untuk menampilkan keseluruhan *array*. ```js run let fruits = ["Apple", "Orange", "Plum"]; alert( fruits ); // Apple,Orange,Plum ``` Sebuah *array* dapat menyimpan elemen dari tipe (data) apapun. Contohnya: ```js run no-beautify // nilai-nilai campuran let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ]; // mendapatkan objek pada indeks 1 dan menampilkan namanya alert( arr[1].name ); // John // mendapatkan fungsi pada indeks 3 dan menjalankannya arr[3](); // hello ``` ````smart header="Tanda koma yang membuntuti" Sebuah array, seperti halnya sebuh objek, diakhir dengan tanda koma: ```js let fruits = [ "Apple", "Orange", "Plum"*!*,*/!* ]; ``` Gaya "tanda koma yang membuntuti" membuat lebih mudah untuk memasukkan/menghilangkan item dari sebuah array, karena semua baris serupa. ```` ## Metode *pop*/*push*, *shift*/*unshift* Sebuah [antrian (*queue*)](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) adalah bentuk penggunaan paling umum sebuah *array*. Dalam *computer science*, hal ini berarti sebuah koleksi elemen yang tertata yang mendukung dua operasi: - `push` menambahkan sebuah elemen di akhir antrian. - `shift` mengambil sebuah elemen di awal antrian, memajukan antrian elemen, jadi elemen urutan ke-2 menjadi urutan pertama1. ![](queue.svg) *Array* sudah mendukung kedua operasi tersebut. Dalam latihan yang kita sering kali memerlukannya. Contohnya, sebuah antrian pesan yang perlu ditampilkan di layar. Ada kasus kegunaan lain untuk *array* -- struktur data yang dinamakan [tumpukan *(stack)*](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)). *Stack* mendukung dua operasi, yakni: - `push` menambahkan sebuah elemen di akhir tumpukan. - `pop` mengambil sebuah elemen di akhir tumpukan. Jadi elemen-elemen baru ditambahkan atau diambil selalu dari "akhir" tumpukan. Sebuah *stack* biasanya diilustrasikan sebagai sebuah *pack* kartu: kartu-kartu baru ditambahkan di atas tumpukan atau diambil dari atas tumpukan *pack* kartu tersebut: ![](stack.svg) Untuk *stack*, elemen terakhir yang di-*push* diterima lebih dulu, hal itu juga disebut sebagai prinsip LIFO (*Last-In-First-Out*) atau "terakhir masuk, pertama keluar". Sedangkan untuk *queue*, kita memiliki prinsip (*First-In-First-Out*) atau "pertama masuk, pertama keluar". *Array* dalam JavaScript dapat bekerja baik sebagai sebuah *queue* maupun *stack*. Keduanya membuat kamu bisa menambahkan/menghilangkan elemen baik dari/ke awal ataupun akhir. Dalam *computer science* struktur data yang memungkinkan kita bisa melakukan hal-hal demikian disebut sebagai [*deque* (*double-ended queue*)](https://en.wikipedia.org/wiki/Double-ended_queue). **Metode yang berfungsi dengan bagian akhir dari _array_:** `pop` : Mengekstrak elemen terakhir dari *array* dan mengembalikan hasilnya: ```js run let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.pop() ); // menghilangkan "Pear" dan memberi alert alert( fruits ); // Apple, Orange ``` `push` : Mendorong elemen ke bagian akhir *array*: ```js run let fruits = ["Apple", "Orange"]; fruits.push("Pear"); alert( fruits ); // Apple, Orange, Pear ``` Panggilan `fruits.push(...)` sama dengan `fruits[fruits.length] = ...`. **Metode yang berfungsi dengan bagian awal dari _array_:** `shift` : Mengekstrak elemen pertama dari *array* dan mengembalikannya: ```js run let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.shift() ); // menghilangkan Apple dan memberi alert alert( fruits ); // Orange, Pear ``` `unshift` : Menambahkan elemen ke bagian awal *array*: ```js run let fruits = ["Orange", "Pear"]; fruits.unshift('Apple'); alert( fruits ); // Apple, Orange, Pear ``` Metode `push` dan `unshift` dapat menambahkan beberapa elemen sekaligus: ```js run let fruits = ["Apple"]; fruits.push("Orange", "Peach"); fruits.unshift("Pineapple", "Lemon"); // ["Pineapple", "Lemon", "Apple", "Orange", "Peach"] alert( fruits ); ``` ## Internal *array* Sebuah *array* adalah objek yang spesial. Tanda kurung siku digunakan untuk mengakses sebuah properti `arr[0]` sebenarnya berasal dari sintaks objek. Hal itu secara mendasar sama seperti `obj[key]`, dimana `arr` adalah objek, sedangkan angka-angka digunakan selayaknya kunci. Sintaks tersebut memperpanjang objek yang menyediakan metode khusus untuk berfungsi dengan koleksi-koleksi data yang tertata serta properti `length`. Tetapi pada intinya, *array* tetaplah sebuah objek. Ingat, ada 7 tipe (data) dasar dalamJavaScript. *Array* adalah sebuah objek dan oleh karena itu berperilaku selayaknya sebuah objek. Sebagai contoh, *array* disalin oleh referensi: ```js run let fruits = ["Banana"] let arr = fruits; // salinan dari referensi (dua variabel mereferensikan array yang sama) alert( arr === fruits ); // true arr.push("Pear"); // modiikasi array oleh referensi alert( fruits ); // Banana, Pear - 2 elemen sekarang ``` ...Namun apa yang membuat *array* benar-benar istimewa adalah representasi internalnya. Mesin berusaha untuk menyimpan elemen *array* ke dalam area memori yang berdampingan, satu dengan yang lainnya, sepereti yang digambarkan pada ilustrasi di bab ini, serta ada cara optimasi lainnya, untuk membuat *array* bekerja lebih cepat lagi. But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object. Contohnya, secara teknis kita dapat melakukan hal berikut ini: ```js let fruits = []; // membuat sebuah array fruits[99999] = 5; // menugaskan sebuah properti dengan indeks yang jauh lebih panjang dari length/panjang indeks fruits.age = 25; // membuat sebuah properti dengan nama yang dapat berubah-ubah ``` Hal itu memungkinkan, karena *array* adalah objek pada dasarnya. Kita bisa menambahkan properti apapun ke *array*. Tetapi mesin akan melihat bahwa kita sedang *array* sebagai sebuah objek reguler. Optimasi yang khusus untuk *array* tidaklah cocok untuk kasus demikian dan akan dihentikan serta hilang pula keuntungannya. Cara-cara penggunaan *array* yang salah yakni: - Menambahkan sebuah properti non-numerik seperti `arr.test = 5`. - Membuat celah-celah kosong, seperti: menambahkan `arr[0]` dan kemudian `arr[1000]` (dan tanpa ada apapun di antara kedua indeks tersebut). - Mengisi sebuah *array* dalam urutan yang terbalik, seperti `arr[1000]`, `arr[999]` dan seterusnya. Tolong pikirkan *array* sebagai sebuah struktur istimewa untuk bekerja dengan *data yang tersusun*. *Array* menyediakan metode khusus untuk hal tersebut. *Array* dikembangkan secara hati-hati dalam mesin JavaScript untuk bekerja dengan data yang tersusun berdampingan, mohon gunakan *array* dengan cara demikian. Dan jika kamu membutuhkan kunci-kunci yang dapat diubah-ubah, besar kemungkinan bahwa kamu sebenarnya butuh sebuah objek reguler `{}`. ## Performa Metode `push/pop` berjalan cepat, sedangkan `shift/unshift` lamban. ![](array-speed.svg) Mengapa bekerja dengan bagian akhir sebuah *array* ketimbang bagian depannya? Mari lihat apa yang terjadi sewaktu eksekusi: ```js fruits.shift(); // mengambil 1 elemen dari bagian awal ``` Tidaklah cukup mengambil dan menghapus elemen dengan angka `0`. Elemen lainnya juga perlu diberi angka pula. Operasi `shift` harus menjalankan 3 hal: 1. Menghapus elemen dengan indeks `0`. 2. Memindahkan semua elemen ke kiri, memberi nomor ulang semua elemen dari indeks `1` ke `0`, dari `2` ke `1` dan seterusnya. 3. Memperbarui properti `length`. ![](array-shift.svg) **Semakin banyak elemen dalam _array_, semakin banyak waktu yang dibutuhkan untuk memindahkan elemen-elemen tersebut, semakin banyak operasi dalam memori.** Hal serupa terjadi dengan `unshift`: untuk menambahkan sebuah elemen ke bagian awal *array*, kita perlu pertama-tama memindahkan elemen yang ada ke kanan, menambah indeksnya. Dan ada apa dengan `push/pop`? Metode tersebut tidak perlu untuk memindahkan apapun. Untuk mengekstrak sebuah elemen dari bagian akhir, metode `pop` membersihkan indeks dan memendekkan `length`. Sintaks untuk operasi `pop` yakni: ```js fruits.pop(); // mengambil 1 elemen dari bagian akhir ``` ![](array-pop.svg) **Metode `pop` tidak perlu memindahkan apapun, karena elemen lain tetap pada indeks masing-masing. Itulah mengapa metode tersebut sangat cepat.** Hal serupa juga terjadi dengan metode `push`. ## Pengulangan (*loop*) Salah satu cara tertua untuk mensirkulasi elemen-elemen *array* adalah dengan pengulangan indeks dengan `for`: ```js run let arr = ["Apple", "Orange", "Pear"]; *!* for (let i = 0; i < arr.length; i++) { */!* alert( arr[i] ); } ``` Tetapi untuk *array* ada bentuk dari pengulangan lain, `for..of`: ```js run let fruits = ["Apple", "Orange", "Plum"]; // beralih pada elemen array for (let fruit of fruits) { alert( fruit ); } ``` Metode `for..of` tidak memberikan akses pada nomor elemen yang sekarang, hanya nilainya saja, tetapi dalam kebanyakan kasus hal tersebut cukup. Dan lebih pendek. Secara teknis, karena *array* adalah objek, *array* juga memungkinkan untuk menggunakan `for..in`: ```js run let arr = ["Apple", "Orange", "Pear"]; *!* for (let key in arr) { */!* alert( arr[key] ); // Apple, Orange, Pear } ``` Namun sebenernya hal itu adalah ide buruk. Ada beberapa masalah-masalah potensial: 1. Pengulangan `for..in` beralih pada *semua properti*, tidak hanya yang numerik saja. Terdapat apa yang disebut sebagai objek "seperti *array*" dalam peramban serta lingkungan lainnya, yang *terlihat seperti array*. Objek tersebut, memiliki `length` dan properti yang ber-indeks, namun juga bisa memiliki properti non-numerik lain serta metode-metode, yang mana biasanya tidak kita butuhkan. Pengulangan `for..in` akan mendaftarkan properti tersebut. Jadi kita perlu bekerja dengan objek-objek yang seperti *array*, lalu properti-properti "ekstra" ini akan menjadi sebuah masalah. 2. Pengulangan `for..in` dioptimasi untuk objek-objek umum (*generic*), bukan *array*, dan 10-100 kali lebih lambat. Tentu saja, pengulangan tersebut masih begitu cepat. Percepatannya mungkin saja berpengaruh saat kondisi kemacetan saja. Namun tetap kita harus menyadari perbedaannya. Secara umum, kita tidak seharusnya menggunakan `for..in` *array*. ## Sebuah kata untuk "length" Properti `length` secara otomatis diperbarui ketika kita memodifikasi *array*. Lebih tepatnya, `length` bukanlah jumlah nilai yang ada dalam *array*, tapi angka indeks terbesar ditambah satu. Contohnya, sebuah elemen tunggal dengan indeks yang besar juga menghasilkan panjang atau `length` yang besar: ```js run let fruits = []; fruits[123] = "Apple"; alert( fruits.length ); // 124 ``` Catat bahwa kita biasanya tidak menggunakan *array* seperti itu. Hal menarik lain tentang properti `length` adalah bahwa properti tersebut juga dapat dituliskan. Jika kita menambahnya secara manual, tidak hal menarik yang terjadi. Tapi jika kita menguranginya, *array* tersebut terpotong. Prosesnya tidak dapat dibalikkan (seperti semula), berikut ini contohnya: ```js run let arr = [1, 2, 3, 4, 5]; arr.length = 2; // memotong hingga 2 elemen alert( arr ); // [1, 2] arr.length = 5; // mengembalikan length/panjang alert( arr[3] ); // undefined: nilai tidak dapat dikembalikan ``` Jadi, cara paling sederhana untuk membersihkan *array* adalah yakni dengan: `arr.length = 0;`. ## new Array() [#new-array] Ada satu sintaks lagi untuk membuat sebuah *array*: ```js let arr = *!*new Array*/!*("Apple", "Pear", "etc"); ``` Sintaks tersebut jarang digunakan, karena tanda kurung siku `[]` lebih pendek. Juga terdapat sebuah fitur yang sukar di dalamnya. Jika `new Array` dipanggil dengan sebuah argumen tunggal yang mana adalah sebuah angka, maka sintaks tersebut akan membuat sebuah *array* yang *tanpa elemen, namun dengan panjang sesuai yang diberikan*. Mari lihat bagaimana orang-orang bisa terjebak dengan hal ini: ```js run let arr = new Array(2); // akankah membuat sebuah array berisi [2] ? alert( arr[0] ); // undefined! tidak ada elemen. alert( arr.length ); // length atau panjangnya adalah 2 ``` Dalam kode di atas, `new Array(number)` memiliki semua elemen yang `undefined`. Untuk menghindari kejutan-kejutan yang demikian, kita biasanya menggunakan tanda kurung siku, kecuali kita benar-benar tahu kode apa yang sedang kita tulis. ## *Array* multidimensi *Array* dapat memiliki isi yang juga merupakan *array*. Kita bisa menggunakannya untuk *array* multidimensi, sebagai contoh untuk menyimpan matriks: ```js run let matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; alert( matrix[1][1] ); // 5, elemen pusat ``` ## toString *Array* memiliki implementasi metode `toString`-nya sendiri yang mengembalikan daftar elemen yang dipisahkan oleh tanda koma. Contoh: ```js run let arr = [1, 2, 3]; alert( arr ); // 1,2,3 alert( String(arr) === '1,2,3' ); // true ``` Coba juga contoh berikut ini: ```js run alert( [] + 1 ); // "1" alert( [1] + 1 ); // "11" alert( [1,2] + 1 ); // "1,21" ``` *Array* tidak memiliki `Symbol.toPrimitive`, begitu juga `valueOf` dapat diandalkan, *array* hanya mengimplentasikan konversi`toString`, jadi di sini `[]` menjadi sebuah *string* kosong, `[1]` menjadi `"1"` dan `[1,2]` menjadi `"1,2"`. Ketika operator tanda tambah biner `"+"` menambahkan sesuatu pada sebuah *string*, operator tersebut mengonversi hal yang ditambahkan tadi menjadi sebuah *string* juga, jadi langkah selanjutnya akan terlihat seperti berikut ini: ```js run alert( "" + 1 ); // "1" alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` ## Jangan bandingkan array dengan == Array dalam JavaScript, tidak seperti beberapa bahasa pemrograman lainnya, tidak boleh dibandingkan dengan operator `==`. Operator ini tidak memiliki perlakuan khusus untuk array, ia bekerja dengan mereka seperti pada objek lainnya. Mari kita ingat aturannya: - Dua objek sama `==` hanya jika mereka merujuk ke objek yang sama. - Jika salah satu argumen `==` adalah objek, dan argumen lainnya primitif, objek tersebut akan diubah menjadi primitif, seperti yang dijelaskan pada bab . - ... Dengan pengecualian `null` dan` undefined` yang sama `==` satu sama lain dan tidak ada yang lain. Perbandingan ketat `===` bahkan lebih sederhana, karena tidak mengonversi jenis. Jadi, jika kita membandingkan array dengan `==`, keduanya tidak akan pernah sama, kecuali jika kita membandingkan dua variabel yang mereferensikan array yang sama persis. Sebagai contoh: ```js run alert( [] == [] ); // false alert( [0] == [0] ); // false ``` Array ini adalah objek yang secara teknis berbeda. Jadi mereka tidak sama. Operator `==` tidak melakukan perbandingan item-demi-item. Perbandingan dengan primitif mungkin memberikan hasil yang tampak aneh juga: ```js run alert( 0 == [] ); // true alert('0' == [] ); // false ``` Di sini, dalam kedua kasus, kami membandingkan primitif dengan objek array. Jadi, array `[]` diubah menjadi primitif untuk tujuan perbandingan dan menjadi string kosong `` '' `. Kemudian proses perbandingan berlanjut dengan primitif, seperti yang dijelaskan dalam bab : ```js run // after [] was converted to '' alert( 0 == '' ); // true, karena '' diubah menjadi angka 0 alert('0' == '' ); // false, tidak ada konversi tipe data, string berbeda ``` Jadi, bagaimana cara membandingkan array? Sederhana saja: jangan gunakan operator `==`. Sebaliknya, bandingkan item-by-item dalam satu putaran atau gunakan metode iterasi yang dijelaskan di bab berikutnya. ## Kesimpulan *Array* adalah sebuah objek berjenis khusus, cocok untuk menyimpan dan mengelola data yang tersusun. - Deklarasinya: ```js // tanda kurung siku (seperti biasa) let arr = [item1, item2...]; // new Array (pengecualian yang jarang) let arr = new Array(item1, item2...); ``` Panggilan `new Array(number)` membuat sebuah *array* dengan panjang indeks (*length*) yang diberikan, tetapi tanpa elemen. - Properti `length` adalah panjang *array* atau, lebih tepatnya, angka indeks terakhir plus satu. Hal tersebut secara otomatis diatur dengan metode *array*. - Jika kita memendekkan `length` secara manuak, *array* tersebut akan terpotong. Kita bisa menggunakan sebuah *array* sebagai sebuah *deque* dengan operasi sebagai berikut: - `push(...items)` menambahkan `items` ke bagian akhir *array*. - `pop()` menghilangkan elemen dari bagian akhir *array* dan mengembalikannya. - `shift()` menghilangkan elemen dari bagian awal *array* dan mengembalikannya. - `unshift(...items)` menambahkan `items` ke bagian awal *array*. Untuk membuat pengulangan (*loop*) elemen *array*: - `for (let i=0; i`, `<` dan lainnya), karena mereka tidak memiliki perlakuan khusus untuk array. Mereka menanganinya sebagai objek apa pun, dan itu bukan yang biasanya kita inginkan. Sebagai gantinya kamu bisa menggunakan loop `for..of` untuk membandingkan array item-by-item. Kita akan melanjutkan dengan array dan mempelajari lebih banyak metode untuk menambah, menghapus, mengekstrak elemen dan mengurutkan array di bab berikutnya . ================================================ FILE: 1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js ================================================ function camelize(str) { return str .split('-') // pecah 'my-long-word' menjadi array ['my', 'long', 'word'] .map( // Ubah huruf pertama dari setiap item array menjadi huruf kapital kecuali item pertama // Ubah ['my', 'long', 'word'] into ['my', 'Long', 'Word'] (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1) ) .join(''); // satukan ['my', 'Long', 'Word'] menjadi '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 ================================================ nilai penting: 5 --- # Ubah border-left-width menjadi borderLeftWidth Tulis sebuah fungsi `cameize(str)` yang mengubah tulisan dengan tanda garin seperti "my-short-string" menjadi camel-case "myShortString". Itu saja: hapus semua tanda garis, setiap kata setelah tanda garis menjadi camel-case. Contoh: ```js camelize("background-color") == 'backgroundColor'; camelize("list-style-image") == 'listStyleImage'; camelize("-webkit-transition") == 'WebkitTransition'; ``` Catatan. Hint: gunakan `split` untuk memisahkan string menjadi array, ubah itu dan gunakan `join` untuk menyatukannya. ================================================ 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 ================================================ nilai penting: 4 --- # Dapatkan rata-rata umur Tulis fungsi `getAverageAge(users)` yang menerima sebuah array dari objek dengan properti `age` dan mengembalikan rata-rata umur. Rumus dari rata-rata adalah `(age1 + age2 + ... + ageN) / N`. Contoh: ```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 ================================================ Kita lihat seluruh item didalam array: - Untuk setiap item kita memeriksa apakah array keluaran sudah memiliki itemnya. - Jika sudah maka abaikan, sebaliknya tambahkan kedalam array keluaran. ```js run demo 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 ``` Kodenya bekerja, tapi terdapat sebuah masalah performansi didalamnya. Metode `result.includes(str)` secara internal menyusuri arrau `result` dan membandingkan setiap elemen dengan `str` untuk menemukan apakah ada yang sama. Jadi jika didalam `result` terdapat `100` elemen dan tidak ada yang sama dengan `str`, lalu itu akan menyusuri seluruh `result` dan melakukan tepat `100` perbandingan. Dan jika `result` berukuran sangat besar, seperti `10000`, maka akan terjadi `10000` perbandingan. Itu bukanlah masalah bagi mesinnya, karena mesin Javascript sangatlah cepat, jadi menyusuri `10000` array hanya akan terjadi secara microseconds (micro detik). Tapi kita melakukan test untuk setiap elemen dari `arr`, didalam perulangan `for`. Jadi jika `arr.length` adalah `10000` kita akan memiliki sesuatu seperti `10000*10000` = 100 juta perbandingan. Itu sangatlah banyak. Demikian, solusi ini hanya bagus untuk array dengan ukuran kecil. Selanjutnya didalam bab kita akan melihat bagaimana cara mengoptimasinya. ================================================ FILE: 1-js/05-data-types/05-array-methods/11-array-unique/task.md ================================================ nilai penting: 4 --- # Filter untuk anggota array yang unik Buatlah sebuah array `arr`. Buatlah sebuah fungsi `unique(arr)` yang harus mengembalikan array dengan item yang unik dari `arr`. Contoh: ```js function unique(arr) { /* kodemu */ } 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 ================================================ nilai penting: 4 --- # Buatlah objek dengan kunci dari array Anggaplah kita menerima sebuah array dari user didalam form `{id:..., name:..., age... }`. Buatlah sebuah fungsi `groupById(arr)` yang membuat sebuah objek, dengan `id` sebagai key/kunci, dan item array sebagai nilai Contoh: ```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); /* // Setelah pemanggilan kita harus mempunyai: 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}, } */ ``` Fungsi seperti itu sangat berguna ketika bekerja dengan data dari server. Ditugas ini kita asumsikan bahwa `id` adalah unik. Tidak mungkin memiliki dua item array dengan `id` yang sama. Tolong gunakan metode array `.reduce` didalam solusi. ================================================ FILE: 1-js/05-data-types/05-array-methods/2-filter-range/_js.view/solution.js ================================================ function filterRange(arr, a, b) { // menambahkan kurung di sekitar ekspresi untuk membuat keterbacaan menjadi lebih baik 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) { // menambahkan kurung di sekitar ekspresi untuk membuat keterbacaan menjadi lebih baik return arr.filter(item => (a <= item && item <= b)); } let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); alert( filtered ); // 3,1 (mencocokan nilai) alert( arr ); // 5,3,8,1 (tidak diubah) ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/2-filter-range/task.md ================================================ nilai penting: 4 --- # Filter range / menyaring dengan jarak Tulislah sebuah fungsi `filterRange(arr, a, b)` yang mendapatkan sebuah array `arr`, carilah elemen yang berada diantara `a` dan `b` didalamnya dan kembalikanlah array dari mereka. Fungsinya haruslah tidak memodifikasi arraynya. Itu haruslah mengembalikan array baru. Contoh: ```js let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); alert( filtered ); // 3,1 (mencocokan nilai) alert( arr ); // 5,3,8,1 (tidak diubah) ``` ================================================ 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]; // hilangkan jika diluar dari interval 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, 1, 4); assert.deepEqual(arr, [3, 1]); }); 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]; // hilangkan jika berada diluar interval if (val < a || val > b) { arr.splice(i, 1); i--; } } } let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // hilangkan angkanya kecuali dari 1 to 4 alert( arr ); // [3, 1] ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md ================================================ nilai penting: 4 --- # Filter range "in place" / menyaring dengan jarak "secara langsung" Tulis sebuah fungsi `filteredRangeInPlace(arr, a, b)` yang mendapatkan array `arr` dan menghilangkan seluruh nilai yang berada diantara `a` dan `b`. Testnya adalah: `a ≤ arr[i] ≤ b`. Fungsinya haruslah hanya memodifikasi arraynya. Itu harus tidak mengembalikan apapun. For instance: ```js let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // hapus angka kecuali dari 1 to 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 ================================================ nilai penting: 4 --- # Sortir secara menurun ```js let arr = [5, 2, 1, -10, 8]; // ... kodemu untuk menyortir secara menurun alert( arr ); // 8, 5, 2, 1, -10 ``` ================================================ FILE: 1-js/05-data-types/05-array-methods/5-copy-sort-array/solution.md ================================================ Kita bisa menggunakan `slice()` untuk membuat salinan dan menjalankan penyortirannya: ```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 ================================================ nilai penting: 5 --- # Salin dan sortir array Kita mempunyai sebuah array dari string `arr`. Kita ingin mempunyai salinan yang telah disortir, tapi tidak mengubah `arr`. Buat sebuah fungsi `copySorted(arr)` yang mengembalikan kopiannya. ```js let arr = ["HTML", "JavaScript", "CSS"]; let sorted = copySorted(arr); alert( sorted ); // CSS, HTML, JavaScript alert( arr ); // HTML, JavaScript, CSS (tidak berubah) ``` ================================================ 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 ================================================ nilai penting: 5 --- # Memetakan nama Kamu punya sebuah array dari objek `user`, masing-masing memiliki `user.name`. Tulis kode yang mengubah itu menjadi sebuah array dari nama. Contoh: ```js no-beautify 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 = /* ... Kodemu */ 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 ================================================ - Tolong perhatikan bagaimana method disimpan. Mereka secara sederhana ditambahkan kedalam properti `this.methods`. - Semua test dan perubahan numerik dilakukan didalam method `calculate`. Selanjutnya mungkin bisa diperluas untuk mendukung lebih banyak ekspresi yang rumit. ================================================ FILE: 1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md ================================================ nilai penting: 5 --- # Buat sebuah kalkulator yang bisa diperluas Buat sebuah konstructor fungsi `Calculator` yang membuat objek kalkulator "yang dapat diperluas". Tugasnya terdiri dari dua bagian. 1. Pertama, implementasikan method `calculate(str)` yang menerima sebuah string sepertin `"1 + 2"` didalam format "ANGKA operator ANGKA" (membatasi ruang) dan mengembalikan hasilnya. Harus mengerti tambah `+` dan kurang `-`. Contoh penggunaan: ```js let calc = new Calculator; alert( calc.calculate("3 + 7") ); // 10 ``` 2. Lalu tambahkan method `addMethod(name, func)` yang mengajarkan operator operasi baru. methodnya akan menerima `name` dan fungsi dengan dua-argumen `func(a, b)` yang mengimplementasikannya. Contoh, kita tambah perkalian `*`, pembagian `/` dan pangkat `**`: ```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 ``` - Tidak ada tanda kurung atau ekspresi yang rumit didalam tugas ini. - Angka dan operatornya dibatasi dengan hanya satu spasi. - Disana mungkin terdapat penanganan error jika kamu ingin menambahkannya. ================================================ 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 ``` Perhatikan bahwa didalam fungsi arrow kita butuh untuk menggunakan tambahan kurung. Kita tidak bisa menulis seperti ini: ```js let usersMapped = users.map(user => *!*{*/!* fullName: `${user.name} ${user.surname}`, id: user.id }); ``` Seperti yang kita ingat, terdapat dua fungsi arrow: tanpa body `value => expression` dan dengan body `value => {...}`. Disini Javascript akan memperlakukan `{` seperti awal dari fungsi body, bukan awal dari objek. Solusinya adalah untuk membungkus mereka didalam kurung "biasa": ```js let usersMapped = users.map(user => *!*({*/!* fullName: `${user.name} ${user.surname}`, id: user.id })); ``` Sekarang beres. ================================================ FILE: 1-js/05-data-types/05-array-methods/7-map-objects/task.md ================================================ nilai penting: 5 --- # Memetakan objek Kamu mempunyai array dari objek `user`, masing-masing memiliki `name`, `surname` dan `id`. Tulis kode untuk membuat array lainnya dari itu, sebuah objek dengan `id` dan `fullName`, dimana` fullName` dibuat dari `name` dan `surname`. Contoh: ```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 = /* ... kodemu ... */ */!* /* 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 ``` Jadi, sebenarnya kamu harus memetakan satu array dari objek menjadi array dari objek lainnya. Cobalah gunakan `=>` disini. Ada sedikit yang harus ditangkap. ================================================ 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 = [ pete, john, mary ]; sortByAge(arr); // sekarang sudah diurutkan: [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 ================================================ nilai penting: 5 --- # Urutkan user dari umur Tulislah fungsi `sortByAge(users)` yang menerima sebuah array dari objek dengan properti `age` dan urutkan mereka berdasarkan `age`. Contoh: ```js no-beautify let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let arr = [ pete, john, mary ]; sortByAge(arr); // sekarang: [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 ================================================ Solusi simpelnya bisa seperti: ```js run *!* function shuffle(array) { array.sort(() => Math.random() - 0.5); } */!* let arr = [1, 2, 3]; shuffle(arr); alert(arr); ``` Entah bagaimana kode diatas bekerja, karena `Math.random() - 0.5` adalah angka acak yang mungkin bisa positif atau negatif, jadi fungsi untuk pengurutan menyusun ulang elemen secara acak. Tapi karena fungsi untuk mengurutkan bukan digunakan dengan cara seperti ini, tidak semua permutasi memiliki probabilitas yang sama. Contoh, lihat kode dibawah. Itu akan menjalankan `shuffle` 1000000 kali dan menghitung kemunculan dari seluruh hasil yang mungkin terjadi: ```js run function shuffle(array) { array.sort(() => Math.random() - 0.5); } // hitung kemunculan dari seluruh permutasi yang mungkin terjadi 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('')]++; } // tampilkan hitungan permutasi yang mungkin terjadi 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 ``` Kita bisa melihat secara jelas: `123` dan `213` muncul lebih banyak dari lainnya. Hasil dari kodenya mungkin berbeda-beda diantara mesin Javascript, tapi kita sudah bisa melihat pendekatan yang tak bisa diandalkan. kenapa itu tidak bekerja? Secara umum, `sort` adalah sebuah "black box": kita bisa berikan sebuah array dan fungsi perbandingan kedalamnya dan berharap arraynya akan diurutkan. Tapi karena keteracakan dari perbandingan blackbox menjadi tak karuan, dan bagaimana tepatnya itu tergantung dari perbedaan implementasi diantara mesinnya. Terdapat cara yang lebih baik untuk melakukan tugas seperti itu. Contoh, terdapat algoritma bagus yang dipanggil dengan [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle). Idenya adalah untuk membuat array menjadi terbalik dan mengganti setiap elemen dengan elemen acak sebelumnya: ```js function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // index acak dari 0 ke i // ganti elemen array[i] dan array[j] // kita gunakan sintaks "destructuring assignment" untuk mendapatkannya // kamu akan menemukan lebih banyak detail tentang sintaksnya nanti di bab selanjutnya // bisa juga ditulis seperti: // let t = array[i]; array[i] = array[j]; array[j] = t [array[i], array[j]] = [array[j], array[i]]; } } ``` pengetesan dengan cara yang sama: ```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]]; } } // hitung seluruh kemunculan dari permutasi yang mungkin terjadi 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('')]++; } // tampilkan perhitungan dari seluruh permutasi yang mungkin terjadi for (let key in count) { alert(`${key}: ${count[key]}`); } ``` Contoh keluaran: ```js 123: 166693 132: 166647 213: 166628 231: 167517 312: 166199 321: 166316 ``` Sekarang terlihat bagus: seluruh permutasi muncul dengan probabilitas yang sama. Also, performance-wise the Fisher-Yates algorithm is much better, there's no "sorting" overhead. Juga, performansi dari algoritma Fisher-Yates lebih baik, tidak ada "pengurutan" tambahan. ================================================ FILE: 1-js/05-data-types/05-array-methods/9-shuffle/task.md ================================================ nilai penting: 3 --- # Mengacak sebuah array Tulis fungsi `shuffle(array`) yang mengocok (pengurutan secara acak) elemen dari sebuah array. Pemanggilan dari `shuffle` akan menghasilkan urutan yang berbeda-beda. Contoh: ```js let arr = [1, 2, 3]; shuffle(arr); // arr = [3, 2, 1] shuffle(arr); // arr = [2, 1, 3] shuffle(arr); // arr = [3, 1, 2] // ... ``` Seluruh pengurutan elemen harus punya probabilitas yang sama. Contoh, `[1,2,3]` bisa di urutkan menjadi `[1,2,3]` atau `[1,3,2]` atau `[3,1,2]` dll, dengan probabilitas yang sama untuk setiap kasus. ================================================ FILE: 1-js/05-data-types/05-array-methods/article.md ================================================ # Metode *array* *Array* menyediakan begitu banyak metode. Untuk mempermudah, dalam bab ini metode-metode tersebut dibagi menjadi beberapa kelompok. ## Menambahkan/menghapus *item* Kita sudah tahu metode-metode yang menambahkan dan menghapus *item* dari bagian awal atau akhir *array*: - `arr.push(...items)` -- menambahkan *item* ke bagian akhir, - `arr.pop()` -- mengekstrak sebuah *item* dari bagian akhir, - `arr.shift()` -- mengekstrak sebuah *item* dari bagian awal, - `arr.unshift(...items)` -- menambahkan *item* ke bagian awal. Berikut ini ada beberapa metode lainnya. ### *splice* Bagaimana cara untuk menghapus sebuah elemen dari *array*? *Array* merupakan objek, jadi kita bisa coba menggunakan `delete`: ```js run let arr = ["I", "go", "home"]; delete arr[1]; // menghapus "go" alert( arr[1] ); // undefined // kini arr = ["I", , "home"]; alert( arr.length ); // 3 ``` Elemen tersebut telah dihapus, tetapi *array* itu masih memiliki 3 elemen, kita bisa melihat bahwa `arr.length == 3`. Hal itu alami, karena `delete obj.key` menghilangkan sebuah nilai berdasarkan `key`. Itulah yang dilakukannya. Tidak masalah bagi objek. Tapi untuk *array* kita biasanya ingin elemen yang tersisa untuk bergeser dan mengisi ruang yang dikosongkan tadi. Kita ingin memiliki sebuah *array* yang lebih pendek sekarang. Jadi, metode khusus harus digunakan. Metode [arr.splice(start)](mdn:js/Array/splice) adalahs sebuah fungsi serbaguna untuk *array*. *Splice* bisa melakukan banyak hal: memasukkan, menghilangkan serta mengganti elemen. Sintaksnya yakni: ```js arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` Dimulai dari posisi `index`: menghapus elemen `deleteCount` dan kemudian memasukkan `elem1, ..., elemN` di tempatnya masing-masing. Mengembalikan *array* yang tersusun atas elemen yang dihapus. Metode ini mudah untuk dipahami melalui contoh. Mari mulai dengan penghapusan: ```js run let arr = ["I", "study", "JavaScript"]; *!* arr.splice(1, 1); // dari indeks 1 menghapus 1 elemen */!* alert( arr ); // ["I", "JavaScript"] ``` Mudah, kan? Mulai dari indeks `1` metode ini menghilangkan elemen di indeks `1`. Dalam contoh selanjutnya kita menghilangkan 3 element dan menggantinya dengan 2 elemen lain: ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; // menghilangkan 3 elemen pertama dan menggantinya dengan yang lain arr.splice(0, 3, "Let's", "dance"); alert( arr ) // sekarang [*!*"Let's", "dance"*/!*, "right", "now"] ``` Di sini kita dapat melihat bahwa `splice` mengembalikan *array* yang berisi elemen terhapus: ```js run let arr = [*!*"I", "study",*/!* "JavaScript", "right", "now"]; // menghilangkan 2 elemen pertama let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- array yang berisi elemen-elemen yang dihapus ``` Metode `splice` juga mampu memasukkan elemen tanpa menghilangkan elemen apapun yang ada sebelumnya. Untuk itu kita perlu mengatur `deleteCount` menjadi `0`: ```js run let arr = ["I", "study", "JavaScript"]; // dari indeks 2 // menghapus 0 // kemudian masukkan "complex" dan "language" arr.splice(2, 0, "complex", "language"); alert( arr ); // "I", "study", "complex", "language", "JavaScript" ``` ````smart header="Indeks berangka negatif diperbolehkan" Di sini dan di metode array lainnya, indeks (berangka) negatif diperbolehkan. Indeks-indeks tersebut menspesifikasikan posisi dari bagian akhir sebuah array, seperti ini: ```js run let arr = [1, 2, 5]; // dari indeks -1 (satu langkah dari bagian akhir) // menghaous 0 element, // lalu memasukka 3 dan 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5 ``` ```` ### *slice* Metode [arr.slice](mdn:js/Array/slice) lebih sederhana daripada metode serupa sebelumnya yakni `arr.splice`. Sintaksnya adalah: ```js arr.slice([start], [end]) ``` Metode ini mengembalikan sebuah sebuah *array* baru hasil salinan semua *item* yang ada dari indeks `start` hingga `end` (indeks `end` tidak termasuk). Baik `start` maupun `end` bisa saja negatif, dalam kasus tersebut posisi dari bagian akhir *array* sudah diasumsikan/diperkirakan. Mirip dengan metode *string* `str.slice`, namun membuat *subarray* bukan *substring*. Contohnya: ```js run let arr = ["t", "e", "s", "t"]; alert( arr.slice(1, 3) ); // e,s (disalin dari 1 sampai 3) alert( arr.slice(-2) ); // s,t (disalin dari -2 sampai bagian akhir) ``` Kita juga bisa memanggil metode tersebut tanpa argumen: `arr.slice()` membuat sebuah salinan dari `arr`. Cara demikian seringkali digunakan untuk mendapatkan sebuah salinan untuk transformasi yang lebih jauh tanpa mempengaruhi *array* yang asli. ### *concat* Metode [arr.concat](mdn:js/Array/concat) membuat sebuah *array* baru yang sudah termasuk nilai-nilai dari *array* lainnya serta *item-item* tambahan. Sintaksnya sebagai berikut: ```js arr.concat(arg1, arg2...) ``` Sintaks tersebut menerima berapapun argumen -- bisa jadi *array* atau nilai. Hasilnya adalah sebuah *array* yang berisi *item* dari `arr`, kemudian `arg1`, `arg2` dan sebagainya. Jika sebuah argumen `argN` adalah sebuah *array*, makan semua elemennya akan disalin. Jika tidak, argumen itu sendiri yang akan disalin. Sebagai contoh: ```js run let arr = [1, 2]; // membuat sebuah array dari: arr dan [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4 // membuat sebuah array dari: arr dan [3,4] dan [5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // membuat sebuah array dari: arr dan [3,4], lalu menambahkan nilai 5 dan 6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` Normalnya, metode ini hanya menyalin elemen-elemen dari *array*. Objek lainnya, bahkan jika objek-objek tersebut terlihat seperti *array*, akan ditambahkan secara keseluruhan: ```js run let arr = [1, 2]; let arrayLike = { 0: "something", length: 1 }; alert( arr.concat(arrayLike) ); // 1,2,[oobjek Object] ``` ...Tetapi jika sebuah objek mirip *array* memiliki sebuah properti khusus `Symbol.isConcatSpreadable`, maka objek tersebut diperlakukan sebagai sebuah *array* dengan `concat`: elemennya ditambahkan: ```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 ``` ## *Iterate*: forEach Metode [arr.forEach](mdn:js/Array/forEach) membuat kita dapat menjalankan sebuah fungsi untuk setiap elemen yang ada di dalam *array*. Sintaksnya: ```js arr.forEach(function(item, index, array) { // ... do something with item }); ``` Sebagai contoh, kode berikut ini menampilkan tiap elemen dalam *array*: ```js run // untuk setiap (for each) elemen memanggil alert ["Bilbo", "Gandalf", "Nazgul"].forEach(alert); ``` Dan kode ini lebih rinci tentang posisi elemen-elemen tersebut dalam *array* yang dituju: ```js run ["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { alert(`${item} is at index ${index} in ${array}`); }); ``` Hasil dari fungsi tersebut (jika mengembalikan sesuatu) dibuang dan diabaikan. ## Mencari dalam *array* Kini mari membahas metode-metode yang bertugas mencari dalam *array*. ### *indexOf/lastIndexOf* dan *includes* Metode [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) dan [arr.includes](mdn:js/Array/includes) memiliki sintaks yang sama dan pada dasarnya keduanya melakukan fungsi yang samahave the same syntax, namun untuk mengoperasikannya perlu ditujukan *item* bukan karakter: - `arr.indexOf(item, from)` -- mencari `item` dimulai dari indeks `from`, dan mengembalikan indeks dimana *item* yang dicari itu ditemukan, jika tidak akan mengembalikan `-1`. - `arr.lastIndexOf(item, from)` -- serupa, namun mencari dari kiri ke kanan. - `arr.includes(item, from)` -- mencari `item` dimulai dari indeks `from`, mengembalkikan `true` jika yang dicari itu ditemukan. Contohnya: ```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 ``` Perlu diperhatikan bahwa metode tersebut menggunakan perbandingan `===`. Jadi, jika kita mencari `false`, metode ini akan tepat mencari `false` dan bukan nol. Jika kita ingin memeriksa pencantuman, dan tidak ingin tahu indeks yang persis, maka direkomendasikan menggunakan `arr.includes`. Juga, perbedaan kecil dari `includes` yakni metode ini menangani `NaN` dengan tepat, tidak seperti `indexOf/lastIndexOf`: ```js run const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (seharusnya 0, tetapi tanda persamaan === tidak berfungsi pada NaN) alert( arr.includes(NaN) );// true (benar) ``` ### *find* dan *findIndex* Bayangkan kita memiliki sebuah *array* berisi objek. Bagaimana cata kita menemukan sebuah objek dengan kondisi tertentu? Berikut ini ada metode [arr.find(fn)](mdn:js/Array/find) yang dapat mudah digunakan. Sintaksnya: ```js let result = arr.find(function(item, index, array) { // jika true dikembalikan, item dikembalikan dan pengulangan dihentinkan // untuk skenario palsu akan mengembalikan undefined }); ``` Fungsi tersebut dipanggil untuk elemen-elemen dalam *array*, satu sama lainnya: - `item` adalah elemen. - `index` adalah indeks elemen tersebut. - `array` adalah *array* itu sendiri. Jika fungsi tersebut mengembalikan `true`, pencarian dihentikan, lalu `item` akan dikembalikan. Jika tidak ditemukan apa-apa, `undefined` yang dikembalikan. Sebagai contoh, kita memiliki sebuah *array* berisi elemen pengguna, tiap pengguna memiliki *field* `id` dan `name`. Mari cari pengguna dengan `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 ``` Pada kehidupan nayata *array* berisi objek adalah hal yang umum, jadi metode `find` sangatlah berguna. Ingat bahwa contoh yang kami berikan untuk mencari (`find`) fungsi `item => item.id == 1` hanya dengan satu argumen. Ini adalah hal umum, argumen lainnya pada fungsi lainnya jarang digunakan. Metode [arr.findIndex](mdn:js/Array/findIndex) pada dasarnya sama, namun mengembalikan indeks dimana elemen tersebut ditemukan bukan elemen itu sendiri serta mengembalikan `-1` ketika tidak ditemukan apapun. ### *filter* Metode `find` mencari sebuah elemen tunggal (pertama) yang akan membuat fungsi tersebut mengembalikan `true`. Jika ada banyak elemen demikian, kita bisa menggunakan[arr.filter(fn)](mdn:js/Array/filter). Sintaksnya mirip dengan `find`, tetapi `filter` mengembalikan *array* yang berisi elemen-elemen yang cocok: ```js let results = arr.filter(function(item, index, array) { // jika true item di-push ke hasil dan pengulangan berlanjut // mengembalikan array kosong jika tidak ditemukan apapun }); ``` Sebagai contoh: ```js run let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; // mengembalikan array dua user pertama let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2 ``` ## Mengubah *array* Mari lanjutkan ke metode-metode yang mengubah dan mengatur ulang *array*. ### *map* Metode [arr.map](mdn:js/Array/map) adalah salah satu metode yang paling berguna dan paling sering digunakan. Metode ini memanggil fungsi untuk tiap elemen di *array* dan mengembalikan hasilnya dalam bentuk *array*. Sintaksnya yakni: ```js let result = arr.map(function(item, index, array) { // mengembalikan nilai baru, bukan item }); ``` Sebagai contoh, di sini kita mengubah setiap elemen menjadi panjang (*length*) dari *string* elemen tersebut: ```js run let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); alert(lengths); // 5,7,6 ``` ### *sort*(fn) Panggilan [arr.sort()](mdn:js/Array/sort) menata *array* *dalam wadah*, mengubah urutan elemen yang ada. Metode ini juga mengembalikan *array* yang sudah tertata, tetapi nilai yang dikembalikan biasanya diabaikan, mengingat `arr` itu sendiri sudah termodifikasi/diubah. Contohnya: ```js run let arr = [ 1, 2, 15 ]; // metode tersebut mengurutkan ulang konten arr arr.sort(); alert( arr ); // *!*1, 15, 2*/!* ``` Apa kamu menyadari adanya keanehan dari hasil di atas? Urutannya menjadi `1, 15, 2`. Tidak benar. Tapi mengapa demikian? **_Item-item_ tersebut diurutkan sebagai *string* secara pada dasarnya.** Secara harfiah, semua elemen dikonversi menjadi *string* untuk dibandingkan. Sedangkan pada *string*, berlaku pengurutan leksikograpis dan memang benar bahwa `"2" > "15"`. Untuk menggunakan urutan penataan kita sendiri, kita perlu memberikan sebuah fungsi sebagai argumen pada `arr.sort()`. Fungsi tersebut harus membandingkan dua nilai yang berubah-ubah dan mengembalikan (hasilnya): ```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 } ``` Contoh, untuk mengurutkan elemen sebagai angka: ```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*/!* ``` Kini metode tersebu berfungsi seperti yang diinginkan. Mari berhenti sejenak dan pikirkan apa yang terjadi. `arr` bisa jadi *array* berisi apapun, benar? *Array* itu bisa saja berisi angka atau *string* atau objek atau apapun. Kita memiliki sekumpulan *beberapa item*. Untuk mengurutkannya, kita perlu sebuah *fungsi pengurutan* yang tahu bagaimana cara untuk membandingkan elemen-elemen. Setelan awalnya adalah sebuah urutan *string*. Metode `arr.sort(fn)` mengimplementasikan sebuah algoritma pengurutan yang umum. Kita tidak perlu benar-benar tahu bagaimana algoritma itu bekerja (sebuah [cara cepat/*quicksort*](https://en.wikipedia.org/wiki/Quicksort) yang sudah teroptimasi sepanjang waktu). Algoritma itu akan menyusuri *array*, membandungkan elemen-elemennya menggunakan fungsi yang diberikan dan mengurutkan ulang elemen-elemen tersebut, yang perlu kita lakukan yakni memberikan `fn` yang mana akan melakukan operasi perbandingan. *Ngomong-omong*, jika kita pernah ingin tahu elemen mana saja yang dibandingkan -- cukup dengan cara memberi *alert*: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); return a - b; }); ``` Algoritma tersebut dapat membandingkan sebuah elemen dengan banyak elemen lainnya dalam proses ini, tapi algoritma itu akan mencoba membuat sedikit mungkin perbandingan. ````smart header="Sebuah fungsi perbandingan dapat mengembalikan angka manapun" Sebenarnya, sebuah fungsi perbandingan hanya perlu untuk mengembalikan sebuah angka positif untuk mengatakan "lebih besar (dari)" dan angka negatif untuk mengatakan "kurang (dari)". Hal tersebut membuat penulisan fungsi jadi lebih pendek: ```js run let arr = [ 1, 2, 15 ]; arr.sort(function(a, b) { return a - b; }); alert(arr); // *!*1, 2, 15*/!* ``` ```` ````smart header="Fungsi arrow yang terbaik" Ingat [fungsi arrow](info:arrow-functions-basics)? Kita dapat menggunakan fungsi arrow di sini untuk pengurutan yang lebih rapi: ```js arr.sort( (a, b) => a - b ); ``` Fungsi ini berfungsi samahalnya dengan metode versi yang lebih panjang di atasnya. ```` ### *reverse* Metode [arr.reverse](mdn:js/Array/reverse) membalikkan urutan elemen di dalam `arr`. Contohnya: ```js run let arr = [1, 2, 3, 4, 5]; arr.reverse(); alert( arr ); // 5,4,3,2,1 ``` Metode ini juga mengembalikan *array* `arr` setelah proses pembalikan. ### *split* dan *join* Ini adalah situasi dari dunia nyata. Kita menulis sebuah aplikasi olahpesan, dan orang tersebut memasukkan ke dalam daftar penerima yang dipisahkan oleh tanda koma, antara lain: `John, Pete, Mary`. Namun bagi kita *array* yang berisi nama akan jauh lebih apik ketimbang sebuah *string*. Jadi bagaiman cara mendapatkannya? Metode [str.split(delim)](mdn:js/String/split) melakukan tepat hal yang dijelaskan di atas. Metode ini memisahkan *string* ke dalam *array* dengan *delimiter* (pemisah) `delim`. Dalam contoh berikut ini, kita memisahkan elemen dengan tanda koma yang diikuti oleh spasi: ```js run let names = 'Bilbo, Gandalf, Nazgul'; let arr = names.split(', '); for (let name of arr) { alert( `A message to ${name}.` ); // Sebuah pesan untuk (serta nama-nama lainnya) } ``` Metode `split` memiliki argumen numerik oposional kedua -- sebuah batas pada panjang (*length*) *array*. Jika batasan itu diberikan, maka elemen ekstra (lebih dari batas panjang *array* yang diberikan) akan diabaikan. Dalam praktiknya, hal ini jarang digunakan: ```js run let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); alert(arr); // Bilbo, Gandalf ``` ````smart header="Pisah menjadi huruf" Panggilan `split(s)` dengan `s` yang kosong akan memisahkan string menjadi array yang berisi huruf-huruf: ```js run let str = "test"; alert( str.split('') ); // t,e,s,t ``` ```` Panggilan [arr.join(glue)](mdn:js/Array/join) melalukan pembalikan ke `split`. Panggilan ini membuat sebuah *string item-item* `arr` digabungkan oleh `glue` (lem) diantara *item* tersebut. Contohnya: ```js run let arr = ['Bilbo', 'Gandalf', 'Nazgul']; let str = arr.join(';'); // menempelkan/me-lem isi array menjadi sebuah string menggunakan ; alert( str ); // Bilbo;Gandalf;Nazgul ``` ### *reduce*/reduceRight Ketika kita perlu mengulang-ulang sebuah *array* -- kita dapat menggunakan `forEach`, `for` atau `for..of`. Ketika kita perlu mengulang dan mengembalikan data setiap elemen -- kita menggunakan `map`. Metode [arr.reduce](mdn:js/Array/reduce) dan metode [arr.reduceRight](mdn:js/Array/reduceRight) juga termasuk ke dalam kelompok metode-metode tadi, tapi ada sedikit lebih rumit. Kedua metode ini digunakan untuk menghitung sebuah nilai tunggal berdasarkan *array*. Sintaksnya yakni: ```js let value = arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]); ``` Fungsi di atas diterapkan pada semua elemen *array* satu sama lainnya dan "melanjutkan" hasil perhitungannya ke panggilan berikutnya. Argument-argumennya yakni: - `previousValue` -- adalah hasil dari dari pemanggilan fungsi sebelumnya, sama dengan `initial` pertama kalinya (jika `initial` diberikan). - `item` -- adalah *item array* yang sekarang. - `index` -- adalah posisi *item* tersebut. - `array` -- adalah *array*-nya. Jika fungsi sudah diterapkan, masil dari panggilan fungsi sebelumnya dioper ke panggilan selanjutnya sebagai argumen pertama. Memang terdengar rumit, tapi tidak seperti yang kamu pikirkan tentang argumen pertama sebagai "akumulator" yang menyimpan dan menggabungkan jasil dari semua eksekusi sebelumnya. Serta pada akhirnya, itu menjadi hasil dari `reduce`. Cara termudah untuk memahami metode ini adalah dengan melihat contohnya. Di sini kita mendapat jumlah dari sebuah *array* dalam satu baris: ```js run let arr = [1, 2, 3, 4, 5]; let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 ``` Fungsi yang dioper ke `reduce` hanya menggunakan 2 argumen, itu cukup khas. Mari lihat rincian dari apa yang terjadi. 1. Pada *run* pertama, `sum` adalah nilai `initial` (argumen terakhir dari `reduce`), sama dengan `0`, dan `current` adalah elemen pertama *array* tersebut, yang sama dengan `1`. Jadi hasil fungsi tersebut adalah `1`. 2. Pada *run* ke-dua, `sum = 1`, kita tambahkan elemen kedua dari *array* (`2`) dan mengembalikan hasilnya. 3. Pada *run* ke-tiga, `sum = 3` dan kita menambahkan satu elemen lagi, dan seterusnya... Alur perhitungannya: ![](reduce.svg) ATau dalam bentuk sebuah tabel, dimana setiap baris merepresentasikan sebuah panggilan fungsi pada elemen *array* selanjutnya: | |`sum`|`current`|result| |---|-----|---------|---------| |the first call|`0`|`1`|`1`| |the second call|`1`|`2`|`3`| |the third call|`3`|`3`|`6`| |the fourth call|`6`|`4`|`10`| |the fifth call|`10`|`5`|`15`| Di sini kita bisa dengan jelas melihat bagaimana hasil dari panggilan sebelumnya menjadi argumen pertama pada panggilan berikutnya. Kita bisa juga mengjilangkan nilai awal (*initial*): ```js run let arr = [1, 2, 3, 4, 5]; // menghilangkan nilai awal dari reduce (bukan 0) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15 ``` Hasilnya sama. Itu karena tidak ada nilai awal, maka `reduce` mengambil elemen pertama *array* sebagai nilai awal dan memulai pengulangan dari elemen ke-dua. Tabel perhitungan tersebut sama dengan yang di atas, tanpa baris pertama. Akan tetapi, penggunaan yang demikian membutuhkan perhatian lebih. Jika *array* kosong, maka panggilan `reduce` tanpa nilai awal akan menghasilkan error. Berikut ini contohnya: ```js run let arr = []; // Error: Reduce dari array kosong tanpa nilai awal // jika nilai awalnya ada, reduce akan mengembalikannya pada arr yang kosong. arr.reduce((sum, current) => sum + current); ``` Jadi sangat disarankan untuk selalu menspesifikasikan nilai awal. Metode [arr.reduceRight](mdn:js/Array/reduceRight) melaksanakan hal yang sama, tapi beroperasi dari kanan ke kiri. ## Array.isArray *Array* tidak membentuk sebuah bahasa tipe sendiri. *Array* terbentuk berdasarkan objek. Jadi `typeof` tidak membantu membedakan sebuah objek polos dari sebuah *array*: ```js run alert(typeof {}); // objek alert(typeof []); // sama ``` ...Tetapi *array* serung diguakan hingga ada metode khusus untuk hal ini: [Array.isArray(value)](mdn:js/Array/isArray). Metode ini mengembalikan `true` jika `value` adalah sebuah *array*, dan `false` jika sebaliknya. ```js run alert(Array.isArray({})); // false alert(Array.isArray([])); // true ``` ## Kebanyakan metode mendukung "thisArg" Hamppir semua metode *array* yang memanggil fungsi -- seperti `find`, `filter`, `map`, dengan pengecualian `sort`, menerima paramater tambahan yakni `thisArg`. Parameter itu tidak dijelaskan pada bab sebelumnya, karena jarang digunakan. Tetapi demi kelengkapan kita harus menutupi metodenya. Berikut ini adalah sintkas lengkap dari metode-metode tersebut: ```js arr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg); // ... // thisArg adalah argumen akhir yang oposional ``` Nilai dari paramater `thisArg` menjadi `this` untuk `func`. Contohnya, di sini kita menggunakan metode dengan objek `army` sebagai penyaringnya, dan `thisArg` mengoper konteksnya: ```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} ]; *!* // cari user, dari army.canJoin yang megembalikan true let soldiers = users.filter(army.canJoin, army); */!* alert(soldiers.length); // 2 alert(soldiers[0].age); // 20 alert(soldiers[1].age); // 23 ``` Jika dalam contoh di atas kita menggunakan `users.filter(army.canJoin)`, maka `army.canJoin` akan bisa dipanggil sebagai fungsi yang berdisi sendiri, dengan `this=undefined`, itu smeua berujung pada error seketika. Sebuah panggilan ke `users.filter(army.canJoin, army)` bisa diganti dengan `users.filter(user => army.canJoin(user))`, yang mana melakukan hal yang sama. Metode yang pertama (`users.filter(army.canJoin, army)`) lebih sering digunakan, karena sedikit lebih mudah dimengerti oleh banyak orang. ## Ringkasan *Cheat sheet* tentang metode-metode *array*: - Untuk menambah/menghilangkan elemen: - `push(...items)` -- menambah *item* ke bagian akhir, - `pop()` -- mengekstrak sebuah *item* dari bagian akhir, - `shift()` -- mengekstrak sebuah *item* dari bagian awal, - `unshift(...items)` -- menambah *item* ke bagian awal. - `splice(pos, deleteCount, ...items)` -- pada indeks `pos` menghapus elemen `deleteCount` dam memasukkan `items`. - `slice(start, end)` -- membuat *array* baru, menyalin elemen dari posisi `start` hingga `end` (tidak inklusif) ke dalam *array* baru tersebut. - `concat(...items)` --mengembalikan sebuah *array* baru: menyalin semua anggota *array* yang sekarang dan menambahkan `items` ke dalamnya. Jika `items` adalah sebuah *array*, maka elemennya yang akan diambil. - Untuk mencari di antara elemen-elemen: - `indexOf/lastIndexOf(item, pos)` -- mencari `item` mulai dari posisi `pos`, mengembalikan indeksnya atau `-1` jika tidak ditemukan. - `includes(value)` -- mengembalikan `true` jika *array* memiliki `value`, jika tidak akan mengembalikan `false`. - `find/filter(func)` -- menyaring elemen dengan menggunakan fungsi, mengembalikan nilai awal/semua nilai yang membuat hasil *return*-nya menjadi `true`. - `findIndex` seperti `find`, namun mengembalikan indeks bukan nilai. - Untuk mengulang elemen: - `forEach(func)` -- memanggil `func` untuk setiap elemen, tidak mengembalikan apapun. - Untuk mengubah *array*: - `map(func)` -- membuat sebuah *array* dari hasil pemanggilan `func` untuk setiap elemen. - `sort(func)` -- mengurutkan *array* dalam-tempatnya, lalu mengembalikan hasilnya. - `reverse()` -- membalikkan *array* dalam-tempatnya, lalu mengembalikan hasilnya. - `split/join` -- mengonversi sebuah *string* menjadi *array* dan sebaliknya. - `reduce(func, initial)` -- menghitung sebuah nilai tunggal pada *array* dengan cara memanggil `func` untuk setiap elemen dan mengoper hasil tersebut di antara panggilan. - Sebagai tambahan: - `Array.isArray(arr)` memeriksa apakah `arr` merupakan *array* atau bukan. Tolong diingat bahwa metode `sort`, `reverse` dan `splice` memodifikasi *array* itu sendiri. Metode-metode ini adalah yang paling sering digunakan, mencakupi 99% kasus penggunaan. Namun masih ada beberapa metode lainnya: - [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) memeriksa *array* terseebut. Fungsi `fn` dipanggil pada tiap elemen *array* yang serupa dengan `map`. Jika beberapa/semua hasilnya `true`, maka akan mengembalikan `true`, jika tidak maka akan mengembalikan `false`. Metode ini berperilaku seperti operator `||` dan `&&`: jika `fn` mengembalikan nilai yang sebenarnya,` arr.some () `segera mengembalikan` true` dan berhenti melakukan iterasi pada item lainnya; jika `fn` mengembalikan nilai yang salah,` arr.every () `segera mengembalikan` false` dan juga menghentikan iterasi pada item lainnya. Kita bisa menggunakan `every` untuk membandingkan array: ```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) -- mengisi array dengan `nilai` berulang dari indeks` mulai` hingga `akhir`. - [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- menyalin elemen dari posisi `start` hingga posisi `end` ke dalam *array itu sendiri*, pada posisi `target` (menumpuk/*overwrite* elemen yang ada). - [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) buat array baru dari array multidimensi. Untuk daftar lengkapnya, lihat [manual](mdn:js/Array). Sejak pandangan pertama mungkin terlihat ada begitu banyak metode, cukup sulit untuk diingat. Namun sebenarnya hal itu jauh lebih mudah. Lihat *cheat sheet* hanya untuk sekedar tahu semua metode tersebut. Lalu selesaikan *task* bab ini sebagai latihan, jadi kamu memiliki pengalaman mengenai metode *array*. Sesudah itu kapan pun kamu perlu melakukan sesuatu dengan *array*, dan tidak tahu bagaimana caranya -- datanglah ke halaman ini, lihat *cheat sheet* dan temukan metode yang tepat. Contoh-contoh yang diberikan akan membantumu dalam penulisan sintaks yang benar. Setelah itu kamu akan secara otomatis mengingat metode-metode tersebut, tanpa usaha yang terlalu rumit. ================================================ FILE: 1-js/05-data-types/06-iterable/article.md ================================================ # Iterables / Bisa di iterasi Objek yang *bisa di iterasi* adalah sebuah generalisasi dari sebuah array. Konsep itu membolehkan kita untuk membuat objek apapun yang bisa digunakan didalam perulangan `for..of`. Tentu saja, Array bisa di iterasi. Tapi disana terdapat objek bawaan (built-in objek) lainnya, yang tentu saja bisa di iterasi. Contoh string juga bisa di iterasi. Jika sebuah objek secara teknis bukan sebuah array, tapi representasi dari sebuah koleksi (list, set) dari sesuatu, lalu `for..of` adalah sintaks yang bagus untuk melakukan perulangan didalamnya, Jadi ayo kita lihat bagaimana cara membuat itu bekerja. ## Symbol.iterator Kita bisa dengan mudah mendapatkan konsep dari iterasi dengan membuatnya sendiri. Untuk contoh, kita mempunyai sebuah objek yang bukanlah array, tapi cocok untuk `for..of`. Seperti objek `range` yang merepresentasikan sebuah interval dari angka: ```js let range = { from: 1, to: 5 }; // Kita ingin membuat for..of untuk bisa digunakan: // for(let num of range) ... num=1,2,3,4,5 ``` Untuk bisa membuat `range` bisa diiterasi (dan membuat `for..of` bekerja) kita harus menambahkan sebuah metode kedalam objeknya bernama `Symbol.iterator` (Simbol built-in spesian yang hanya digunakan untuk hal itu). 1. Ketika `for.of` dimulai, itu akan memanggil metodenya sekali (atau error jika tidak ditemukan). Metodenya haruslah mengembalikan sebuah *iterator* -- sebuah objek dengan metode `next`. 2. Selanjutnya, `for..of` bekerja *hanya bila itu mengembalikan objek*. 3. Ketika `for..of` menginginkan nilai selanjutnya, itu akan memanggil `next()` didalam objeknya. 4. Hasil dari `next()` harus mempunyai form `{done: Boolean, value: any}`, dimana `done=true` berarti iterasinya telah selesai, sebaliknya `value` adalah nilai selanjutnya. Ini adalah implementasi penuh untuk `range` dengan catatan: ```js run let range = { from: 1, to: 5 }; // 1. panggil for..of pertama kali untuk memanggil ini range[Symbol.iterator] = function() { // ini akan mengembalikan objek iterator: // 2. Selanjutnya, for..of hanya bekerja dengan iterator ini, menanyakan nilai selanjutnya return { current: this.from, last: this.to, // 3. next() dipanggil untuk setiap iterasi oleh perulangan for..of next() { // 4. itu harus mengembalikan nilai sebagai sebuah objek {done:.., nilai:...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; }; // sekarang ini bekerja! for (let num of range) { alert(num); // 1, lalu 2, 3, 4, 5 } ``` Perhatikan fitur utama dari *iterables*: pemisahan perhatian. - `range` sendiri tidak memiliki metode `next()`. - Malah, objek lainnya, yang dipanggil "iterator" dibuat dengan memanggil ke `range[Symbol.iterator]()`, dan `next()` miliknya menghasilkan nilai untuk diiterasi. Jadi, objek iterator berbeda dari objek yang diiterasi. Secara teknis, kita mungkin menyatukannya dan menggunakan `range` nya sendiri sebagai iterator untuk membuat kode lebih simpel. Seperti ini: ```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, lalu 2, 3, 4, 5 } ``` Sekarang `range[Symbol.iterator]()` mengembalikan objek `range`nya sendiri: itu membutuhkan metode `next()` dan mengingat progress iterasi saat ini didalam `this.current`. Lebih pendek? Ya. Dan terkadang bagus juga. Kekurangannya adalah sekarang menjadi mustahil untuk memiliki dua perulangan `for..of` yang berjalan didalam objeknya secara bersamaan: mereka akan membagi bagian-bagian iterasi, karena hanya terdapat satu iterator -- objeknya sendiri. Tapi menggunakan dua for-of adalah hal yang jarang terjadi, bahkan didalam asinkron sekalipun. ```smart header="Iterator tak terbatas/infinite iterator" Interator tak terbatas bisa dilakukan. Contoh, `range` menjadi tak terbatas terhadap `range.to = Infinity`. Atau kita bisa membuat objek yang bisa di iterasi dan menghasilkan rentetan tak terbatas dari angka yang acak. Juga bisa berguna. Tidak ada batasan didalam `next`, itu bisa mengembalikan semakin banyak nilai, itu adalah hal yang normal. Tentu saja, perulangan `for..of` didalam iterasi seperti itu takan ada habisnya. Tapi kita selalu bisa menghentikannya dengan menggunakan `break`. ``` ## String bisa di iterasi Array dan string adalah dua hal yang paling banyak menggunakan iterasi. Untuk string, perulangan `for..of` akan mengiterasi karakternya: ```js run for (let char of "test") { // berjalan 4 kali: sekali tiap karakter alert( char ); // t, lalu e, lalu s, lalu t } ``` Dan itu akan berjalan lancar dengan karakter pengganti (surrogate pairs)! ```js run let str = '𝒳😂'; for (let char of str) { alert( char ); // 𝒳, dan lalu 😂 } ``` ## Memanggil sebuah iterator secara jelas Untuk pemahaman lebih dalam, kita lihat bagaimana untuk menggunakan sebuah iterator secara eksplisit. Kita akan mengiterasi didalam sebuah string dengan cara yang sama seperti `for..of`, tapi dengan pemanggilan yang langsung. Kode ini membuat sebuah iterator string dan mendapatkan nilai dari itu secara "manual": ```js run let str = "Hello"; // melakukan hal yang sama dengan // 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); // karakter keluar satu demi satu } ``` Hal itu sangat jarang dibutuhkan, tapi akan memberikan kita kontrol lebih terhadap prosesnya daripada `for..of`. Contoh, kita bisa membagi proses iterasi: iterasi sedikit, lalu berhenti, lakukan hal lain, dan lalu lanjutkan nanti. ## Bisa di iterasi dan seperti array [#array-like] Terdapat dua istilah resmi yang terlihat mirip, akan tetapi sangat berbeda. Perhatikan mereka baik-baik dan pahamilah untuk terhindar dari kebingungan. - *Iterables/bisa di iterasi* adalah objek yang mengimplementasikan metode `Symbol.iterator`, seperti yang dideskripsikan diatas. - *Array-likes/Seperti array* adalah objek yang memiliki indeks dan `length`, jadi mereka terlihat seperti array. ketika kita menggunakan javascript untuk melakukan prakter didalam browser atau lingkungan pengembangan lainnya, kita mungkin bertemu objek yang bisa diiterasi atau yang seperti array, atau keduanya. Contoh, string adalah keduanya, bisa diiterasi (`for..of` dapat bekerja) dan seperti array(mempunyai indeks angka dan `length`(panjang)). Akan tetapi bisa diiterasi mungkin bukanlah array. Dan sebaliknya sebuah array mungkin tidak bisa diiterasi. Contoh, `range` di contoh diatas bisa diiterasi, tapi tidak seperti array, karena itu tidak memiliki properti indeks dan `length`. Dan disini objek yang seperti array, tapi tidak bisa diiterasi: ```js run let arrayLike = { // punya indeks dan panjang(length) => seperti array 0: "Hello", 1: "World", length: 2 }; *!* // Error (no Symbol.iterator) for (let item of arrayLike) {} */!* ``` Contoh diatas bisa diiterasi dan seperti array yang biasanya *bukan array*, mereka tidak punya `push`, `pop`, dll. Hal seperti itu bisa merepotkan jika kita memiliki sebuah objek dan ingin bekerja dengannya sama seperti sebuah array. Misalnya, kita ingin bekerja dengan `range` menggunakan metode array. Bagaimana cara mencapai hal itu? ## Array.from Terdapat sebuah metode universal [Array.from](mdn:js/Array/from) yang menerima hal yang bisa diiterasi atau nilai yang seperti array dan membuat `Array` "sungguhan darinya. Lalu kita bisa memanggil metode array didalamnya. Contoh: ```js run let arrayLike = { 0: "Hello", 1: "World", length: 2 }; *!* let arr = Array.from(arrayLike); // (*) */!* alert(arr.pop()); // World (metode bekerja) ``` `Array.from` pada baris `(*)` menerima objeknya, memeriksanya apakah itu sesuatu yang bisa diiterasi atau seperti array, lalu membuat array bari dan menyalin seluruh item kedalamnya. Hal yang serupa terjadi untuk sesuatu yang bisa diiterasi: ```js // asumsikan bahwa range diambil dari contoh diatas let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (konversi array toString bekerja) ``` Sintaks penuh dari `Array.from` juga memperbolehkan kita untuk menyediakan fungsi "mapping" opsional: ```js Array.from(obj[, mapFn, thisArg]) ``` Argumen kedua yang opsional `mapFn` bisa saja sebuah fungsi yang akan digunakan untuk setiap elemen sebelum ditambahkan kedalam array, dan `thisArg` memperbolehkan kita untuk menggunakan `this` didalamnya. Contoh: ```js // asumsikan bahwa range diambil dari contoh diatas // kuadratkan setiap angka let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25 ``` Disini kita gunakan `Array.from` untuk mengubah string menjadi array dari karakter-karakter: ```js run let str = '𝒳😂'; // pisahkan str menjadi array dari karakter-karakter let chars = Array.from(str); alert(chars[0]); // 𝒳 alert(chars[1]); // 😂 alert(chars.length); // 2 ``` Tidak seperti `str.split`, itu bergantung pada sifat bisa diiterasi dari string dan juga, sama seperti `for..of`, yang bekerja dengan benar bahkan dengan karakter pengganti (surrogate pairs). Secara teknis disini itu melakukan hal yang sama seperti: ```js run let str = '𝒳😂'; let chars = []; // Array.from secara internal melakukan perulangan yang sama for (let char of str) { chars.push(char); } alert(chars); ``` ...Tapi ini lebih pendek. Kita bahkan bisa membangun `slice` pengganti didalamnya: ```js run function slice(str, start, end) { return Array.from(str).slice(start, end).join(''); } let str = '𝒳😂𩷶'; alert( slice(str, 1, 3) ); // 😂𩷶 // metode natif tidak mendukung karakter pengganti alert( str.slice(1, 3) ); // tidak berguna (dua bagian dari karakter pengganti yang berbeda) ``` ## Ringkasan Objek yang bisa digunakan didalam `for..if` dipanggil dengan *iterable*. - Secara teknis, iterables harus mengimplementasi nama metode `Symbol.iterator`. - Hasil dari `obj[Symbol.iterator]` dipanggil dengan sebuah *iterator*. Itu menangani proses iterasi lebih jauh. - Sebuah iterator harus mempunyai nama metode `next()` yang mengembalikan sebuah objek `{done: Boolean, value: any}`, disini `done:true` menandakan akhir dari proses iterasi, sebaliknya `value` adalah nilai selanjutnya. - Metode `Symbol.iterator` dipanggil secara otomatis oleh `for..of`, tapi kita bisa melakukannya secara langsung. - Iterables bawaan seperti string atau array, juga mengimplementasikan `Symbol.iterator`. - Iterator string tahu tentang karakter pengganti (surrogate pairs). Objek yang mempunyai properti indeks dan `length` dipanggil dengan *seperti-array/array-like*. Objek seperti itu mungkin mempunyai properti dan metode lainnya, tapi tidak memiliki metode bawaan dari array. Jika kita melihat kedalam spesifikasinya -- kita akan melihat kebanyakan metode bawaan yang mengasumsikan bahwa mereka bekerja dengan iterables atau seperti-array daripada dengan array "sungguhan", karena hal itu lebih abstrak. `Array.from(obj[, mapFn, thisArg])` membuak `Array` sungguhan dari sebuah iterable atau seperti-array `obj`, dan lalu kita bisa menggunakan metode array didalamnya. Argumen opsional `mapFn` dan `thisArg` memperbolehan kita untuk menerapkan sebuah fungsi kedalam setiap item. ================================================ 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 ================================================ nilai penting: 5 --- # Memfilter item array yang unik Anggaplah `arr` sebagai sebuah array. Ciptakanlah fungsi `unique(arr)` yang harus mengembalikan array yang berisi nilai-nilai unik dari `arr`. Sebagai contoh: ```js function unique(arr) { /* kodemu */ } let values = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; alert( unique(values) ); // Hare, Krishna, :-O ``` P.S. Disini string dipakai sebagai contoh, tetapi nilai dengan tipe apa saja bisa dipakai. P.P.S. Pakailah `Set` untuk menyimpan nilai-nilai yang unik. ================================================ 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 ================================================ Untuk menemukan semua anagram, mari kita pecahkan setiap kata menjadi huruf-huruf dan urutkanlah. Ketika huruf-huruf terurut, semua anagram adalah sama. Sebagai contoh: ``` nap, pan -> anp ear, era, are -> aer cheaters, hectares, teachers -> aceehrst ... ``` Kita akan menggunakan varian yang diurutkan berdasarkan huruf sebagai kunci map untuk menyimpan hanya satu nilai untuk setiap kunci: ```js run function aclean(arr) { let map = new Map(); for (let word of arr) { // split the word by letters, sort them and join back *!* 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) ); ``` Penyortiran huruf dilakukan oleh deretan panggilan di baris `(*)`. Untuk kenyamanan marilah kita pecahkan menjadi beberapa baris: ```js let sorted = word // PAN .toLowerCase() // pan .split('') // ['p','a','n'] .sort() // ['a','n','p'] .join(''); // anp ``` Dua kata berbeda `'PAN'` dan `'nap'` mendapatkan form urutan huruf yang sama `'anp'`. Baris berikutnya menempatkan kata tersebut ke dalam map: ```js map.set(sorted, word); ``` Jika kita pernah bertemu kata dengan urutan huruf yang sama lagi, maka kata itu akan menggantikan nilai sebelumnya dengan kunci yang sama di dalam map. Maka dari itu kita akan selalu mempunyai maksimum satu kata untuk setiap form huruf. Akhirnya `Array.from(map.values())` mengambil iterable atas nilai-nilai map (kita tidak memperlukan kunci-kunci dalam hasilnya) dan mengembalikan array dengan isi tersebut. Disini kita juga bisa menggunakan objek biasa daripada `Map`, karena kunci adalah string. Solusinya bisa terlihat seperti ini: ```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 ================================================ nilai penting: 4 --- # Filter anagram [Anagram](https://en.wikipedia.org/wiki/Anagram) adalah kata-kata yang mempunyai jumlah huruf-huruf yang sama, tetapi dengan susunan yang berbeda. Sebagai contoh: ``` nap - pan ear - are - era cheaters - hectares - teachers ``` Ciptakanlah fungsi `aclean(arr)` yang mengembalikan array yang bersih dari anagram. Sebagai contoh: ```js let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era" ``` Dari setiap grup anagram hanya harus tersisa satu kata, boleh yang mana saja. ================================================ FILE: 1-js/05-data-types/07-map-set/03-iterable-keys/solution.md ================================================ Itu karena `map.keys()` mengembalikan iterable, tetapi bukan sebuah array. Kita bisa mengubahnya ke sebuah array menggunakan `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 ================================================ nilai penting: 5 --- # Kunci-kunci iterable Kami ingin mendapatkan array daripada `map.keys()` dalam satu variabel lalu mengaplikasikan metode yang array spesifik kepadanya, contoh `.push`. Tapi itu tidak berhasil: ```js run let map = new Map(); map.set("name", "John"); let keys = map.keys(); *!* // Error: keys.push is not a function keys.push("more"); */!* ``` Mengapa? Bagaimana kita bisa membenarkan kode ini untuk membuat `keys.push` berhasil? ================================================ FILE: 1-js/05-data-types/07-map-set/article.md ================================================ # Map dan Set Sekarang kita telah membelajari struktur data compleks berikut: - Objek untuk menyimpan koleksi kunci. - Array untuk menyimpan koleksi berurut. Tapi itu tidak cukup dalam kehidupan nyata. Itu sebabnya `Map` dan` Set` juga ada. ## Map [Map](mdn:js/Map) adalah kumpulan item data yang berkunci, seperti `Object`. Tetapi perbedaan utama adalah `Map` membolehkan kunci jenis apa pun. Metode dan properti: - `new Map()` -- menciptakan map. - `map.set(key, value)` -- menyimpan nilai dengan kunci. - `map.get(key)` -- mengembalikan nilai dengan kunci, `undefined` jika` key` tidak ada di map. - `map.has(key)` -- mengembalikan `true` jika` key` ada, `false` sebaliknya. - `map.delete(key)` -- menghapus nilai dengan kunci. - `map.clear()` -- menghapus semua isi dari map. - `map.size` -- mengembalikan jumlah elemen saat ini. Misalnya: ```js run let map = new Map(); map.set('1', 'str1'); // kunci string map.set(1, 'num1'); // kunci nomor map.set(true, 'bool1'); // kunci boolean //ingat Object biasa? ia akan mengkonversi kunci menjadi string //Map menyimpan tipenya, jadi kedua berikut tidaklah sama: alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3 ``` Seperti yang dapat kita lihat, lain dari objek, kunci tidak dikonversi ke string. Jenis kunci apa pun dimungkinkan. ```smart header="`map[key]` bukan cara yang baik untuk menggunakan `Map`" Meski `map[key]` juga bekerja, misal kita bisa mengeset `map[key] = 2`, ini memperlakukan `map` sebagai objek JavaScript biasa, berimplikasi pada semua limitasi yang sesuai (tak ada kunci objek dan lain-lain). Jadi kita sebaiknya memakai metode `map`: `set`, `get` dan seterusnya. ``` **Map juga dapat menggunakan objek sebagai kunci.** Misalnya: ```js run let john = { name: "John" }; // Untuk setiap pengguna, mari kita simpan jumlah kunjungan mereka let visitsCountMap = new Map(); // john adalah kunci bagi mapnya visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` Menggunakan objek sebagai kunci adalah salah satu fitur `Map` yang paling terkenal dan penting. Untuk kunci string, `Object` bisa dipakai, tetapi tidak untuk kunci objek. Mari kita coba: ```js run let john = { name: "John" }; let ben = { name: "Ben" }; let visitsCountObj = {}; // cobalah memakai objek visitsCountObj[ben] = 234; // cobalah memakai ben sebagai kunci visitsCountObj[john] = 123; // cobalah memakai john sebagai kunci *!* // Inilah yang tertulis! alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` Karena `visitsCountObj` adalah sebuah objek, ia mengubah semua kunci, seperti `john` menjadi string, jadi kita mendapatkan kunci string `"[object Object]"`. Jelas bukan yang kita inginkan. ```smart header="Cara `Map` membandingkan kunci" Untuk mengetes kesamaan kunci, `Map` menggunakan algoritma [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). Ini kira-kira sama dengan kesetaraan ketat `===`, tetapi perbedaannya adalah `NaN` dianggap sama dengan `NaN`. Jadi `NaN` bisa digunakan sebagai kunci juga. Algoritma ini tidak dapat diubah atau dikustomisasi. ``` ````smart header="Chaining" Setiap panggilan `map.set` mengembalikan map itu sendiri, sehingga kami dapat "mem-chain" panggilan-panggilan: ```js map.set('1', 'str1') .set(1, 'num1') .set(true, 'bool1'); ``` ```` ## Iterasi Atas Map Untuk looping atas `map`, ada 3 method: - `map.keys()` -- mengembalikan iterable untuk kunci, - `map.values()` -- mengembalikan iterable untuk nilai, - `map.entries()` -- mengembalikan iterable untuk entri `[key, value]`, ini digunakan dengan standar di `for..of`. Misalnya: ```js run let recipeMap = new Map([ ['cucumber', 500], ['tomatoes', 350], ['onion', 50] ]); // iterasi atas kunci (vegetables) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } // iterasi atas nilai (amounts) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } // iterasi atas entri-entri [key, value] for (let entry of recipeMap) { // the same as of recipeMap.entries() alert(entry); // cucumber,500 (and so on) } ``` ```smart header="Urutan insersi digunakan" Iterasi berjalan dalam urutan yang sama dengan nilai yang dimasukkan. `Map` mempertahankan urutan ini, tidak seperti `Object` biasa. ``` Selain itu, `Map` memilike method `forEach`, mirip dengan `Array`: ```js // menjalankan fungsi untuk setiap pasangan (kunci, nilai) recipeMap.forEach( (value, key, map) => { alert(`${key}: ${value}`); // cucumber: 500 etc }); ``` ## Object.entries: Map dari Object Ketika `Map` diciptakan, kita bisa memberi array (atau iterabel lainnya) pasangan kunci/nilai untuk inisialisasi, seperti ini: ```js run // array berisi pasangan [kunci, nilai] let map = new Map([ ['1', 'str1'], [1, 'num1'], [true, 'bool1'] ]); alert( map.get('1') ); // str1 ``` Jika kita memiliki objek biasa, dan kita mau menciptakan sebuah `Map` darinya, kita bisa menggunakan method built-in [Object.entries(obj)](mdn:js/Object/entries) yang mengembalikan array daripada pasangan-pasangan kunci/nilai untuk satu objek yang berformat persis sama. Jadi kita bisa menciptakan map dari objek seperti ini: ```js run let obj = { name: "John", age: 30 }; *!* let map = new Map(Object.entries(obj)); */!* alert( map.get('name') ); // John ``` Disini, `Object.entries` mengembalikan array daripada pasangan-pasangan kunci/nilai: `[ ["name","John"], ["age", 30] ]`. Itu yang `Map` perlukan. ## Object.fromEntries: Object dari Map Kita baru saja menyaksikan cara menciptakan `Map` dari objek biasa dengan `Object.entries(obj)`. Ada juga method `Object.fromEntries` yang melakukan kebalikkannya: jika diberi array berisi pasangan `[kunci, nilai]`, ia menciptakan objek darinya: ```js run let prices = Object.fromEntries([ ['banana', 1], ['orange', 2], ['meat', 4] ]); // now prices = { banana: 1, orange: 2, meat: 4 } alert(prices.orange); // 2 ``` Kita bisa menggunakan `Object.fromEntries` untuk mendapatkan objek polos dari `Map`. Contoh: Kita menyimpan data di dalam `Map`, tapi kita perlu mengirimnya ke kode pihak ketiga yang mengharapkan objek biasa. Kita mulai: ```js run let map = new Map(); map.set('banana', 1); map.set('orange', 2); map.set('meat', 4); *!* let obj = Object.fromEntries(map.entries()); // ciptakan objek biasa (*) */!* // selesai! // obj = { banana: 1, orange: 2, meat: 4 } alert(obj.orange); // 2 ``` Pemanggilan `map.entries()` mengembalikan sebuah iterable dari pasangan key/value, persis didalam format dari `Object.fromEntries`. Kita juga bisa membuat barisan `(*)` lebih pendek: ```js let obj = Object.fromEntries(map); // hilangkan .entries() ``` Itu sama, karena `Object.fromEntries` mengharapkan objek iterabel sebagai argumen. Tidak harus sesuatu array. Dan iterasi standar untuk `map` mengembalikan pasangan kunci/nilai yang sama dengan `map.entries()`. Jadi kita mendapatkan objek biasa dengan kunci/nilai yang sama dengan `map`. ## Set `Set` adalah tipe koleksi spesial - "set nilai-nilai" (tanpa kunci), dimana setiap nilai hanya dapat terjadi sekali. Method utamanya adalah: - `new Set(iterable)` -- menciptakan set, dan jika objek `iterable` disediakan (biasanya array), menyalin nilai darinya ke set. - `set.add(value)` -- menambahkan nilai, mengembalikan set itu sendiri. - `set.delete(value)` -- menghapus nilai, mengembalikan `true` jika `value` ada pada saat panggilan berlangsung, jika tidak `false`. - `set.has(value)` -- mengembalikan `true` jika nilai ada di set, jika tidak `false`. - `set.clear()` -- menghapus semuanya dari set. - `set.size` -- adalah hitungan elemen. Fitur utamanya adalah panggilan berulang `set.add(value)` dengan nilai yang sama tidak melakukan apa-apa. Itulah alasan mengapa setiap nilai hanya muncul dalam `Set` sekali. Misalnya, ada pengunjung yang datang, dan kami ingin mengingat semua orang. Tetapi kunjungan berulang tidak harus menyebabkan duplikasi. Pengunjung harus "dihitung" hanya sekali. `Set` adalah hal yang tepat untuk itu: ```js run let set = new Set(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; // kunjungan-kunjungan, beberapa pengguna datang berkali-kali set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary); // set hanya menyimpan nilai-nilai unik alert( set.size ); // 3 for (let user of set) { alert(user.name); // John (lalu Pete dan Mary) } ``` Alternatif untuk `Set` dapat berupa array pengguna, dan kode untuk memeriksa duplikat pada setiap insersi menggunakan [arr.find](mdn:js/Array/find). Tetapi kinerjanya akan jauh lebih buruk, karena metode ini menjalani seluruh array memeriksa setiap elemen. `Set` jauh lebih baik dioptimalkan secara internal untuk pemeriksaan keunikan. ## Iteration atas Set Kita bisa meng-loop atas set dengan `for..of` atau menggunakan `forEach`: ```js run let set = new Set(["oranges", "apples", "bananas"]); for (let value of set) alert(value); // sama untuk forEach: set.forEach((value, valueAgain, set) => { alert(value); }); ``` Ingat keanehannya. Fungsi callback yang dilewatkan dalam `forEach` memiliki 3 argumen: satu `value`, kemudian *nilai yang sama* `valueAgain`, dan kemudian objek target. Memang, nilai yang sama muncul dalam argumen dua kali. Itu untuk kompatibilitas dengan `Map` di mana callback yang dilewati `forEach` memiliki tiga argumen. Terlihat agak aneh, memang. Tetapi dapat membantu mengganti `Map` dengan` Set` dalam kasus-kasus tertentu dengan mudah, dan sebaliknya. Metode yang sama yang dimiliki `Map` untuk iterator juga didukung: - `set.keys()` -- mengembalikan objek iterable untuk nilai, - `set.values()` -- sama dengan `set.keys()`, untuk kompatibilitas dengan `Map`, - `set.entries()` -- mengembalikan objek iterable untuk entri `[nilai, nilai]`, ada untuk kompatibilitas dengan `Map`. ## Ringkasan `Map` -- adalah kumpulan nilai-nilai berkunci. Metode dan properti: - `new Map([iterable])` -- membuat map, dengan `iterable` opsional (mis. array) dari pasangan `[key, value]` untuk inisialisasi. - `map.set(key, value)` -- menyimpan nilai dengan kunci. - `map.get(key)` -- mengembalikan nilai dengan kunci, `undefined` jika `key` tidak ada di map. - `map.has(key)` -- mengembalikan `true` jika `key` ada, `false` sebaliknya. - `map.delete(key)` -- menghapus nilai dengan kunci. - `map.clear()` -- menghapus semuanya dari peta. - `map.size` -- mengembalikan jumlah elemen saat ini. Perbedaan dari `Object` biasa: - Kunci apa saja, objek bisa dijadikan kunci. - Metode-metode tambahan untuk kenyamanan, properti `size`. `Set` -- adalah kumpulan nilai-nilai unik. Metode dan properti: - `new Set([iterable])` -- membuat set, dengan nilai opsional `iterable` (mis. array) untuk inisialisasi. - `set.add(value)` -- menambahkan nilai (tidak melakukan apa-apa jika `value` ada), mengembalikan set itu sendiri. - `set.delete(value)` -- menghapus nilai, mengembalikan `true` jika `value` ada pada saat panggilan berlangsung, jika tidak `false`. - `set.has(value)` -- mengembalikan `true` jika nilai ada di set, jika tidak `false`. - `set.clear()` -- menghapus semuanya dari set. - `set.size` -- adalah hitungan elemen. Iterasi atas `Map` dan` Set` selalu dalam urutan insersi, jadi kami tidak dapat mengatakan bahwa koleksi ini tidak berurut, tetapi kami tidak dapat menyusun ulang elemen atau secara langsung mendapatkan elemen dengan nomornya. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md ================================================ Ayo kita simpan pesan yang dibaca didalam `WeakSet`: ```js run let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, {text: "See you soon", from: "Alice"} ]; let readMessages = new WeakSet(); // dua pesan telah dibaca readMessages.add(messages[0]); readMessages.add(messages[1]); // readMessages mempunyai 2 elemen // ...sekarang baca pesan pertama lagi! readMessages.add(messages[0]); // readMessages masih memiliki 2 elemen yang unik // jawaban: apakah message[0] telah dibaca? alert("Read message 0: " + readMessages.has(messages[0])); // true messages.shift(); // sekarang readMessages mempunyai 1 elemen (secara teknis memory mungkin akan dibersihkan nanti) ``` `WeakSet` membolehkan untuk menyimpan satu set dari messages dan dengan mudah memeriksa apakah sebuah pesan ada didalamnya. Itu akan membersihkan dirinya sendiri secara otomatis. Timbal baliknya adalah kita tidak bisa melakukan iterasi didalamnya, tidak bisa mendapatkan "semua pesan yang telah dibaca" darinya secara langsung. Tapi kita bisa melakukan iterasi kepada seluruh pesan dan memfilter semuanya yang ada didalam set. Hal lainnya, solusi berbeda bisa saja seperti menambahkan properti seperti `message.isRead=true` kepada pesan setelah pesannya dibaca. Seperti objek pesan dikelola oleh kode lain, hal itu tidak direkomendasikan, tapi kita bisa menggunakan properti simbol untuk menghindari konflik. Seperti ini: ```js // properti simbol yang hanya diketahui kode kita let isRead = Symbol("isRead"); messages[0][isRead] = true; ``` Sekarang kode dari pihak-ketiga kemungkinan tidak akan melihat properti tambahan kita. walaupun simbol membolehkan kita untuk mengecilkan kemunculan dari masalah, menggunakan `WeakSet` lebih baik dari sisi arsitektural. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md ================================================ nilai penting: 5 --- # Menyimpan tanda "unread" Terdapat beberapa pesan dari array" ```js let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, {text: "See you soon", from: "Alice"} ]; ``` Kode kamu bisa mengaksesnya, tapi pesannya di kelola oleh kode orang lain. Pesan baru ditambahkan, pesan lama dihilangkan secara secara teratur oleh kode itu, dan kamu tidak tahu persis saat ketika itu terjadi. Sekarang, struktur data mana yang harus kamu gunakan untuk menyimpan informasi tentang pesannya apakah "telah dibaca"? Strukturnya haruslah tepat untuk memberikan jawaban "apakah telah dibaca"? untuk pesan objek yang diberikan. Catatan. Ketika sebuah pesan dihilangkan dari `messages`, pesan itu harus menghilang dari strukturnya juga. Catatan tambahan. Kita seharusnya tidak memodifikasi objek message, tambahkan properti kita kedalamnya. Seperti mereka di kelola oleh kode orang lain, itu mungkin akan mengarah ke hasil akhir yang tidak diinginkan. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md ================================================ Untuk menyimpan tanggal, kita bisa menggunakan `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)); // Kita akan belajar objek Date nanti ``` ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md ================================================ nilai penting: 5 --- # Menyimpan tanggal Terdapat sebuah array dari pesan sama seperti di [previous task](info:task/recipients-read).Situasinya sama. ```js let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, {text: "See you soon", from: "Alice"} ]; ``` Pertanyaannya: struktur data mana yang kamu gunakan untuk menyimpan informasinya: "ketika pesannya dibaca?". Di tugas sebelumnya kita hanya menyimpan "yes/no". Sekarang kita butuh untuk menyimpan tanggal, dan itu harus tetap berada di memori simpan sampai pesannya dibuang. Catatan. Tanggal bisa disimpan sebagai objek dengan kelas bawaan `Date, kita akan mempelajarinya nanti. ================================================ FILE: 1-js/05-data-types/08-weakmap-weakset/article.md ================================================ # WeakMap dan WeakSet Seperti yang kita tahu dari bab , Mesin Javascript menyimpan sebuah nilai didalam memori selama itu bisa terjangkau (dan secara potensial bisa digunakan). Contoh: ```js let john = { name: "John" }; // Objeknya bisa diakses, john mereferensi kedalamnya. // tulis urang referensinya john = null; *!* // objeknya akan dihilangkan dari memori */!* ``` Biasanya. properti dari sebuah objek atau elemen dari array atau struktur data lainnya bisa dianggap bisa dijangkau dan tetap berada dimemori selama struktur datanya masih didalam memori. Contoh, jika kita memasukan objek kedalam sebuah array, lalu selama arraynya ada, objeknya akan tetap ada juga, bahkan jika disana sudah tidaka ada yang mereferensi kedalamnya. Seperti ini: ```js let john = { name: "John" }; let array = [ john ]; john = null; // tulis ulang referensinya *!* // the object previously referenced by john is stored inside the array // therefore it won't be garbage-collected // we can get it as array[0] */!* ``` Sama seperti itu, jika kita bisa menggunakan sebuah objek sebagai sebuah kunci/key didalam `Map` biasa, lalu selama `Map`nya ada, objeknya akan selalu ada juga. Itu akan menempati memori dan mungkin tidak akan dibuang. Contoh: ```js let john = { name: "John" }; let map = new Map(); map.set(john, "..."); john = null; // tulis ulang referensinya *!* // john disimpan didalam map, // kita bisa mendapatkannya dengan menggunakan map.keys() */!* ``` `WeakMap` secara dasar berbeda didalam aspek ini. Itu tidak akan mencegah pembuangan dari objek kunci. Ayo kita lihat didalam contoh. ## WeakMap Perbedaan pertama dari `Map` adalah kunci `WeakMap` haruslah objek, bukan nilai primitif: ```js run let weakMap = new WeakMap(); let obj = {}; weakMap.set(obj, "ok"); // bekerja dengan benar (object kunci/key) *!* // tidak bisa menggunakan string sebagai kunci weakMap.set("test", "Whoops"); // Error, karena "test" bukanlah sebuah objek */!* ``` Sekarang, jika kita menggunakan sebuah objek sebagai kunci didalamnya, dan disana tidak terdapat referensi lain ke objeknya -- itu akan dihilangkan dari memori (dan juga dari map) secara otomatis. ```js let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); john = null; // tulis ulang referensinya // jogn telah dihilangkan dari memori! ``` Bandingkan itu dengan `Map` biasa dicontoh diatas. Sekarang jika `john` hanya ada jika sebagai kunci dari `WeakMap` -- itu akan secara otomatis dihapus dari map (dan memori). `WeakMap` tidak mendukung iterasi dan metode `keys()`, `nilai()`, `entries()`, jadi tidak ada cara untuk mendapatkan semua kunci atau nilai darinya. `WeakMap` hanya mempunyai metode berikut: - `weakMap.get(key)` - `weakMap.set(key, value)` - `weakMap.delete(key)` - `weakMap.has(key)` Kenapa terdapat batasan seperti itu? Itu hanyalah untuk alasan teknis. Jika sebuah objek kehilangan semua referensi lainnya (seperti `john` didalam kode diatas), lalu itu akan dibuang secara otomatis. Tapi secara teknis itu tidak benar-benar di spesifikasikan *ketika pembersihan terjadi*. Mesin Javascript yang memilih hal itu. Itu mungkin akan memilih untuk melakukan pembersihan memori seketika atau menunggu dan melakukan pembersihan nanti ketika penghapusan lainnya terjadi. Jadi, secara teknis elemen count yang sekarang dari `WeakMap` tidak diketahui. Mesinnya mungkin sudah menghapusnya atau belum, atau sudah dihapus sebagian. Untuk alasan itu, metode yang mengakses seluruh key/nilai tidak didukung. Sekarang dimana kita butuh struktur data seperti itu? ## Kasus: tambahan data Bagian utama dari penggunaan `WeakMap` adalah sebuah *penambahan penyimpanan data*. Jika kita bekerja dengan sebuah objek yang "dimiliki" kode yang lain, bahkan mungkin sebuah librari pihak-ketiga, dan harus menyimpan beberapa data yang terkait dengannya, itu harus ada selama objeknya ada - lalu `WeakMap` adalah sesuatu yang dibutuhkan. Kita menyimpan datanya kedalam `WeakMap`, menggunakan objek sebagai kunci, dan ketika objeknya dihapus, datanya akan secara otomatis menghilang juga. ```js weakMap.set(john, "secret documents"); // jika john meninggal, secret documents-nya akan dihapus secara otomatis ``` Kita lihat didalam contoh. Contoh, kita mempunyai kode yang menyimpan hitungan kunjungan untuk pengguna. Informasinya disimpan didalam map: sebuah objek user adalah kunci dan hitungan kunjungan adalah nilainya. Ketika pengguna pergi (objeknya akan dihapus), kita tidak ingin kunjungan mereka dihitung lagi. Ini adalah contoh dari fungsi penghitung dengan `Map`: ```js // 📁 visitsCount.js let visitsCountMap = new Map(); // map: user => kunjungan dihitung // naikan hitungan kunjungan function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` Dan disini bagian kode lainnya, mungkin file lainnya akan menggunakannya: ```js // 📁 main.js let john = { name: "John" }; countUser(john); // hitung kunjungannya // lalu john pergi john = null; ``` Sekarang objek `john` harusnya dihapus, tapi tetap berada di memori, itu sebagai kunci didalam `visitsCountMap`. Kita perlu membersihkan `visitsCountMap` ketika kita menghapus pengguna, sebaliknya itu akan tetap didalam memori terus-menerus. Pembersihan seperti itu akan menjadi pekerjaan yang membosankan didalam arsitektur yang rumit. Malahan kita bisa menghindarinya dengan berpindah ke `WeakMap`: ```js // 📁 visitsCount.js let visitsCountMap = new WeakMap(); // weakmap: user => kunjungan dihitung // naikan hitungan kunjungan function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` Sekarang kita tidak harus membersihkan `visitsCountMap`. Setelah objek `john` menjadi tidak terjangkau lagi kecuali sebagai kunci dari `WeakMap`, itu akan dihilangkan dari memori, bersamaan dengan informasi kuncinya dari `WeakMap`. ## Kasus: penyimpanan cache Contoh biasa lainnya adalah penyimpanan cache: ketika sebuah hasil dari fungsi harus diingat ("di cache"), jadi didalam pemanggilan selanjutnya didalam objek yang sama bisa menggunakannya. Kita bisa menggunakan `Map` untuk menyimpan hasil, seperti ini: ```js run // 📁 cache.js let cache = new Map(); // hitung dan ingat hasilnya function process(obj) { if (!cache.has(obj)) { let result = /* kalkulasi hasil */ obj; cache.set(obj, result); } return cache.get(obj); } *!* // sekarang kita gunakan process() didalam file lainnya: */!* // 📁 main.js let obj = {/* katakan kita mempunyai objek */}; let result1 = process(obj); // dihitung // ...selanjutnya, dari bagian kode lainnya... let result2 = process(obj); // ingat hasil yang diambil dari cache // ...nanti, ketika objek tidak dibutuhkan lagi: obj = null; alert(cache.size); // 1 (Ouch! Objeknya masih didalam cache, menggunakan memori) ``` Untuk banyak pemanggilan dari `process(obj)` dengan objek yang sama, itu akan mengkalkulasikan hasilnya pertama kali, dan lalu hanya mengambilnya dari `cache`. kekurangannya adalah kita perlu membersihkan `cache` ketika objeknya tidak dibutuhkan lagi. Jika kita mengganti `Map` dengan `WeakMap`, kemudian masalah ini menghilang: hasil yang di cache akan dihapus dari memori secara otomatis setelah objeknya dihapus. ```js run // 📁 cache.js *!* let cache = new WeakMap(); */!* // hitung dan ingat hasilnya function process(obj) { if (!cache.has(obj)) { let result = /* perhitungan hasil */ obj; cache.set(obj, result); } return cache.get(obj); } // 📁 main.js let obj = {/* objek */}; let result1 = process(obj); let result2 = process(obj); // ...nanti, ketika objeknya tidak dibutuhkan lagi: obj = null; // tidak bisa mendapatkan cache.size, karena itu WeakMap, // tapi itu 0 atau nanti akan jadi 0 // Ketika obj dihapus, data cache akan dihapus juga ``` ## WeakSet `WeakSet` memiliki perilaku yang sama: - Analoginya adalah untuk meng-`Set`, tapi mungkin kita hanya butuh menambahkan objek kedalam `WeakSet` (bukan primitif). - Sebuah objek ada didalam set selama itu bisa dijangkau dari tempat lain. - Seperti `Set`, itu mendukung `add`, `has` dan `delete`, tapi tidak `size`, `keys()` dan tidak ada iterasi menjadi "weak", itu juga menyediakan penyimpanan tambahan. Tapi tidak untuk data yang asal-asalan, tapi untuk "yes/no". Keanggotaan dari `WeakSet` mungkin berarti sesuatu tentang objeknya. Contoh, kita bisa menambahkan user kedalam `WeakSet` untuk mengetahui dari siapa saja yang mengunjungi website kita: ```js run let visitedSet = new WeakSet(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; visitedSet.add(john); // John mengunjungi website visitedSet.add(pete); // lalu Pete visitedSet.add(john); // John lagi // visitedSet sekarang memiliki 2 user // periksa jika John telah berkunjung? alert(visitedSet.has(john)); // true // periksa jika Mary telah berkunjung? alert(visitedSet.has(mary)); // false john = null; // visitedSet akan dibersihkan secara otomatis ``` Hal yang paling bisa diingat adalah batasan dari `WeakMap` dan `WeakSet` adalah tidak adanya iterasi, dan ketidak mampuan untuk mendapatkan seluruh konten saat ini. Itu mungkin akan merepotkan, tapi tidak mencegah `WeakMap/WeakSet` untuk melakukan tugas utama mereka -- menjadi "tambahan" penyimpanan data dari objek yang disimpan/dikelola di tempat lain. ## Ringkasan `WeakMap` adalah koleksi seperti-`Map` yang mengijinkan hanya objek sebagai kunci dan menghapus mereka bersama dengan nilai yang terkait sekalinya mereka menjadi tidak terjangkau. `WeakSet` adalah koleksi seperti-`Set` yang hanya menyimpan objek dan menghapus mereka sekalinya mereka menjadi tidak bisa diakses. Keduanya tidak mendukung metode dan properti yang mengacu pada seluruh kunci atau jumlah mereka. Hanya operasi individual yang diperbolehkan. `WeakMap` dan `WeakSet` digunakan sebagai struktur data "kedua" sebagai tambahan kepada penyimpanan objek "utama". Sekalinya objeknya dihapus dari penyimpanan utama, jika itu hanya ditemukan sebagai kunci dari `WeakMap` atau didalam `WeakSet`, itu akan dihapus secara otomatis. ================================================ 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 ``` Atau kita juga bisa mendapatkan jumlah total dengan menggunakan `Object.values` dan `reduce`: ```js // reduce meng-loop atas array gaji, // menambahkannya // dan mengembalikan hasilnya 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 ================================================ nilai penting: 5 --- # Tambahkan propertinya Ada objek `salaries` berisi beberapa gaji orang-orang. Tulis fungsi `sumSalaries(salaries)` yang mengembalikan jumlah total semua gaji menggunakan `Object.values` dan loop `for..of`. Jika `salaries` kosong, lalu hasilnya akan `0`. Contohnya: ```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 ================================================ nilai penting: 5 --- # Count properties Tulis fungsi `count(obj)` yang mengembalikan jumlah properti di dalam objek: ```js let user = { name: 'John', age: 30 }; alert( count(user) ); // 2 ``` Coba buat kodenya sependek mungkin. P.S. Jangan pedulikan properti simbolik, hanya hitung yang "reguler". ================================================ FILE: 1-js/05-data-types/09-keys-values-entries/article.md ================================================ # Objek.kunci, nilai, entri Mari kita berpaling dari struktur data individual dan membahas iterasi mereka. Di bab sebelumnya kita telah melihat method `map.keys()`, `map.values()`, `map.entries()`. Method ini generik, ada persetujuan umum untuk menggunakan mereka untuk struktur data. Jika kita pernah menciptakan struktur data sendiri, kita harus mengimplementasikannya juga. Mereka tersedia untuk: - `Map` - `Set` - `Array` Objek biasa juga menghadapi method yang mirip, tapi sintaksisnya sedikit berbeda. ## Objek.kunci, nilai-nilai, entri-entri Untuk objek biasa, method berikut tersedia: - [Object.keys(obj)](mdn:js/Object/keys) -- mengembalikan array kunci. - [Object.values(obj)](mdn:js/Object/values) -- mengembalikan array nilai. - [Object.entries(obj)](mdn:js/Object/entries) -- mengembalikan array pasangan `[key, value]`. Perhatikanlah perbedaannya(dibanding map contohnya): | | Map | Object | |-------------|------------------|--------------| | Call syntax | `map.keys()` | `Object.keys(obj)`, but not `obj.keys()` | | Returns | iterable | "real" Array | Perbedaan pertama adalah kita harus memanggil `Object.keys(obj)`, bukan `obj.keys()`. Mengapa? Alasan pertama adalah fleksibilitas. Ingat, objek adalah dasar dari struktur kompleks di Javascript. Jadi kita mungkin mempunyai objek seperti `data` yang mengimplemen method `data.values()` sendirinya. Dan kita masih bisa memanggil `Object.values(data)` atasnya. Alasan kedua adalah method `Object.*` mengembalikan objek array "betulan", bukan hanya iterable. Itu terutama untuk alasan-alasan historis. Contohnya: ```js let user = { name: "John", age: 30 }; ``` - `Object.keys(user) = ["name", "age"]` - `Object.values(user) = ["John", 30]` - `Object.entries(user) = [ ["name","John"], ["age",30] ]` Ini adalah contoh pengunaan `Object.values` untuk meng-loop atas nilai-nilai properti: ```js run let user = { name: "John", age: 30 }; // loop atas nilai for (let value of Object.values(user)) { alert(value); // John, then 30 } ``` ```warn header="Object.keys/values/entries abaikan properti simbolis" Seperti `for..in` loop, method ini mengabaikan properti yang menggunakan `Symbol(...)` as keys. Biasanya itu mudah. Tapi jika kita mau kunci simbolis juga, ada method lain [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) yang mengembalikan array berisi kunci simbolis saja. Ada juga method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) yang mengembalikan semua kunci. ``` ## Mengubah objek Objek kekurangan banyak method yang ada untuk arrays, contoh `map`, `filter` dan yang lainnya. Jika kita ingin mengapplikasikan method-method tersebut, kita bisa menggunakan `Object.entries` diikuti oleh `Object.fromEntries`: 1. Gunakan `Object.entries(obj)` untuk mendapatkan array pasangan kunci/nilai dari `obj`. 2. Gunakan method array di array tersebut, contoh `map`. 3. Gunakan `Object.fromEntries(array)` di array hasil untuk mengubahnya kembali menjadi objek. Sebagai contoh, kita mempunyai objek dengan harga-harga, dan mau melipat duakan harga-harganya: ```js run let prices = { banana: 1, orange: 2, meat: 4, }; *!* let doublePrices = Object.fromEntries( // ubah menjadi array, map, lalu fromEntries mengembalikan objeknya Object.entries(prices).map(([key, value]) => [key, value * 2]) ); */!* alert(doublePrices.meat); // 8 ``` Mungkin ini terlihat susah pertama kalinya, tetapi ini akan menjadi mudah untuk di mengerti setelah kamu menggunakannya beberapa kali. Kita bisa membuat perantaian hebat dengan cara ini. ================================================ 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 ================================================ nilai penting: 5 --- # Destrukturisasi penugasan Kita mempunyai sebuah objek: ```js let user = { name: "John", years: 30 }; ``` Tulis destrukturisasi penugasan yang terbaca: - `name` properti menjadi variabel `name`. - `years` properti menjadi variabel `age`. - `isAdmin` properti menjadi variabel `isAdmin` (false, jika tidak ada properti seperti itu) Berikut adalah contoh nilai setelah penugasan Anda: ```js let user = { name: "John", years: 30 }; // kode Anda ke sisi kiri: // ... = 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 ================================================ nilai penting: 5 --- # Gaji maksimal Ada objek `salaries`: ```js let salaries = { "John": 100, "Pete": 300, "Mary": 250 }; ``` Buatlah fungsi `topSalary(salaries)` yang mengembalikan nama orang dengan bayaran tertinggi. - Jika `gaji` kosong, itu harus mengembalikan` null`. - Jika ada beberapa orang bergaji tinggi, kembalikan salah satu dari mereka. N.B. Gunakan `Object.entries` dan destrukturisasi untuk meng-iterasi lewat pasangan kunci/nilai. ================================================ FILE: 1-js/05-data-types/10-destructuring-assignment/article.md ================================================ # Destrukturisasi Penugasan Dua stuktur data yang paling banyak digunakan di Javascript adalah `Object` dan `Array` Objek memungkinkan kita untuk membuat entitas tunggal yang menyimpan data item berdasarkan kunci, dan array memungkinkan kita untuk mengumpulkan data item menjadi koleksi yang terurut. Tetapi ketika kita meneruskannya ke suatu fungsi, itu mungkin tidak perlu objek / array secara keseluruhan, melainkan potongan individual. *Destructuring assignment* adalah sebuah sintaks spesial yang memungkinkan kita untuk "membongkar" array atau objek menjadi variabel yang banyak, kadang-kadang itu memang lebih nyaman. Destrukturisasi juga berfungsi baik dengan fungsi-fungsi kompleks yang mempunyai banyak parameter, nilai default, dan sebagainya. ## Destrukturisasi Array Contoh bagaimana array di-destrukturisasi menjadi variabel: ```js // kita mempunyai array dengan nama, dan nama keluarga let arr = ["John", "Smith"] *!* // destructuring assignment // atur firstName = arr[0] // dan surname = arr[1] let [firstName, surname] = arr; */!* alert(firstName); // John alert(surname); // Smith ``` Sekarang kita bisa bekerja dengan variabel bukan anggota array. Ini terlihat hebat ketika dikombinasikan dengan `split` atau metode pengembalian array lainnya: ```js run let [firstName, surname] = "John Smith".split(' '); alert(firstName); // John alert(surname); // Smith ``` ````smart header="\"Destructuring\" bukan berarti \"destructive\"." Ini disebut "destructuring assignment," karena "destructurizes" dengan menyalin item kedalam variabel. Tetapi array itu sendiri tidak dimodifikasi. Ini hanya cara singkat untuk menulis: ```js // let [firstName, surname] = arr; let firstName = arr[0]; let surname = arr[1]; ``` ```` ````smart header="Hindari elemen menggunakan koma" Elemen yang tidak diinginkan di array juga bisa di buang dengan sebuah koma tambahan: ```js run *!* // elemen kedua tidak dibutuhkan let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; */!* alert( title ); // Consul ``` Pada kode diatas, elemen kedua dari array dilewati, yang ketiga ditetapkan untuk `title`, dan sisa item array juga dilewati (karena tidak ada variabel untuknya). ```` ````smart header="Bekerja dengan iterabel apapun di sisi kanan" ... Sebenarnya, kita bisa mengggunakan itu untuk iterasi apapun, bukan hanya array: ```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 kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values. ```` ````smart header="Menetapkan ke apa saja pada sisi kiri" Kita bisa menggunakan "penetapan" apa saja pada sisi kiri. Misalnya, sebuah properti objek: ```js run let user = {}; [user.name, user.surname] = "John Smith".split(' '); alert(user.name); // John alert(user.surname); // Smith ``` ```` ````smart header="Pengulangan dengan .entries()" Di bagian sebelumnya kita melihat metode [Object.entries(obj)](mdn:js/Object/entries). Kita bisa menggunakan itu untuk destrukturisasi untuk melompati kunci-dan-nilai sebuah objek: ```js run let user = { name: "John", age: 30 }; // loop over keys-and-values *!* for (let [key, value] of Object.entries(user)) { */!* alert(`${key}:${value}`); // name:John, then age:30 } ``` Kodingan yang sama untuk sebuah `Map` lebih sederhana, dan juga bisa diiterasi: ```js run let user = new Map(); user.set("name", "John"); user.set("age", "30"); *!* // Map iterates as [key, value] pairs, very convenient for destructuring for (let [key, value] of user) { */!* alert(`${key}:${value}`); // name:John, then age:30 } ``` ```` ```smart header="Trik menukar variabel" Trik yang paling diketahui untuk menukar nilai dari dua variabel: ```js run let guest = "Jane"; let admin = "Pete"; // Tukar nilai: buat guest=Pete, admin=Jane [guest, admin] = [admin, guest]; */!* alert(`${guest} ${admin}`); // Pete Jane (penukaran berhasil!) ``` Disini kita membuat array sementara untuk dua variabel dan langsung memisahkannya dengan urutan penukaran. Kita bisa menukar lebih daripada dua variabel dengan cara ini. ### Sisanya '...' Jika kita ingin tidak hanya mendapatkan nilai pertama, tetapi juga untuk mengumpulkan semua yang mengikuti -- kita dapat menambahkan satu parameter lagi dan mendapat "the rest" menggunakan tiga titik `"..."`: ```js run let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert(name1); // Julius alert(name2); // Caesar // Further items aren't assigned anywhere ``` If we'd like also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: ```js run let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* // Catatan bahwa tipe dari `rest` adalah Array. alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 */!* ``` Nilai dari `rest` adalah array dari elemen array yang tersisa. Kita bisa menggunakan variabel lain apapun pada `rest`, hanya pastikan memiliki tiga titik sebelum itu dan pergi terakhir di penetapan destrukturisasi. ```js run let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; // now titles = ["Consul", "of the Roman Republic"] ``` ### Nilai default Jika ada lebih sedikit nilai dalam array daripada variabel dalam penugasan, tidak akan ada kesalahan. Nilai absen dianggap undefined: ```js run *!* let [firstName, surname] = []; */!* alert(firstName); // undefined alert(surname); // undefined ``` Jika kita ingin sebuah nilai "default" untuk mengganti yang hilang, kita bisa menyediakan menggunakan `=`: ```js run *!* // nilai default let [name = "Guest", surname = "Anonymous"] = ["Julius"]; */!* alert(name); // Julius (dari array) alert(surname); // Anonymous (digunakan default) ``` Nilai default bisa berupa ekspresi yang lebih kompleks atau bahkan panggilan fungsi Default values can be more complex expressions or even function calls. Mereka dievaluasi hanya jika nilainya tidak diberikan. Sebagai contoh, di sini kita menggunakan fungsi `prompt` untuk dua default. Tapi itu hanya akan berjalan untuk yang hilang: ```js run // prompt hanya berjalan untuk nama keluarga (surname) let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"]; alert(name); // Julius (dari array) alert(surname); // apapun yang prompt dapatkan ``` Please note: the `prompt` will run only for the missing value (`surname`). ## Destrukturisasi objek Penugasan destrukturisasi juga bekerja dengan objek. Sintaks dasarnya adalah: ```js let {var1, var2} = {var1:…, var2:…} ``` Kita memiliki objek yang ada di sisi kanan, yang ingin kita pisah menjadi beberapa variabel. Sisi kiri berisi "pola" untuk properti yang sesuai. Dalam kasus sederhana, itu adalah daftar nama variabel di `{...}`. Contohnya: ```js run let options = { title: "Menu", width: 100, height: 200 }; *!* let {title, width, height} = options; */!* alert(title); // Menu alert(width); // 100 alert(height); // 200 ``` Properti `options.title`, `options.width` dan `options.height` ditugaskan ke variabel yang sesuai. Urutannya tidak masalah. Ini juga berfungsi: ```js // mengganti urutan di let {...} let {height, width, title} = { title: "Menu", height: 200, width: 100 } ``` Pola di sisi kiri mungkin lebih kompleks dan menentukan pemetaan antara properti dan variabel. Jika kita ingin menetapkan properti ke variabel dengan nama lain, misalnya, `options.width` untuk masuk ke variabel bernama` w`, maka kita dapat mengaturnya menggunakan tanda titik dua: ```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 ``` Kolon menunjukkan "apa : pergi kemana". Dalam contoh di atas properti `width` pergi ke` w`, properti `height` pergi ke` h`, dan `title` ditugaskan ke nama yang sama. Untuk properti yang berpotensi hilang, kita dapat menetapkan nilai default menggunakan `" = "`, seperti ini: ```js run let options = { title: "Menu" }; *!* let {width = 100, height = 200, title} = options; */!* alert(title); // Menu alert(width); // 100 alert(height); // 200 ``` Sama seperti dengan array atau parameter fungsi, nilai default dapat berupa ekspresi atau bahkan panggilan fungsi. Mereka akan dievaluasi jika nilainya tidak diberikan. Dalam kode di bawah ini `prompt` meminta` width`, tetapi tidak untuk `title`: ```js run let options = { title: "Menu" }; *!* let {width = prompt("width?"), title = prompt("title?")} = options; */!* alert(title); // Menu alert(width); // (apapun hasil dari prompt) ``` Kita juga dapat menggabungkan titik dua dan persamaan: ```js run let options = { title: "Menu" }; *!* let {width: w = 100, height: h = 200, title} = options; */!* alert(title); // Menu alert(w); // 100 alert(h); // 200 ``` Jika kita memiliki objek yang kompleks dengan banyak properti, kita hanya dapat mengekstrak apa yang kita butuhkan: ```js run let options = { title: "Menu", width: 100, height: 200 }; // hanya ekstrak judul sebagai variabel let { title } = options; alert(title); // Menu ``` ### Pola sisanya "..." Bagaimana jika objek memiliki lebih banyak properti daripada variabel yang kita miliki? Bisakah kita mengambil beberapa dan kemudian menetapkan "sisanya" di suatu tempat? Kita bisa menggunakan pola 'rest', seperti yang kita lakukan dengan array. Itu tidak didukung oleh beberapa browser tua (IE, gunakan Babel untuk mem-polyfill itu) tapi berjalan di yang modern. Terlihat seperti ini: ```js run let options = { title: "Menu", height: 200, width: 100 }; *!* // title = properti bernama judul // rest = objek dengan sisa properti let {title, ...rest} = options; */!* // sekarang title="Menu", rest={height: 200, width: 100} alert(rest.height); // 200 alert(rest.width); // 100 ``` ````smart header="Gotcha jika tidak ada `let`" Dalam contoh-contoh di atas, variabel dinyatakan tepat dalam penugasan: `let {...} = {...}`. Tentu saja, kita bisa menggunakan variabel yang ada juga, tanpa `let`. Tapi ada tangkapan. Ini tidak akan berfungsi: ```js run let title, width, height; // kesalahan di baris ini {title, width, height} = {title: "Menu", width: 200, height: 100}; ``` Masalahnya adalah bahwa JavaScript memperlakukan `{...}` dalam aliran kode utama (tidak di dalam ekspresi lain) sebagai blok kode. Blok kode seperti itu dapat digunakan untuk pernyataan grup, seperti ini: ```js run { // sebuah kode blok let message = "Hello"; // ... alert( message ); } ``` Jadi di sini JavaScript mengasumsikan bahwa kita memiliki blok kode, itu sebabnya ada kesalahan. Kita ingin mendekstukturisasi. Untuk memperlihatkan JavaScript bahwa itu bukan blok kode, kita dapat membungkus ekspresi dalam tanda kurung `(...)`: ```js run let title, width, height; // oke sekarang *!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; alert( title ); // Menu ``` ```` ## Destrukturisasi bersarang Jika suatu objek atau array berisi objek dan array bersarang lainnya, kita dapat menggunakan pola sisi kiri yang lebih kompleks untuk mengekstraksi bagian yang lebih dalam. Dalam kode di bawah ini `options` memiliki objek lain di properti` size` dan sebuah array di properti `items`. Pola di sisi kiri penugasan memiliki struktur yang sama untuk mengekstrak nilai dari mereka: ```js run let options = { size: { width: 100, height: 200 }, items: ["Cake", "Donut"], extra: true }; // tugas dekstukturisasi dibagi dalam beberapa baris untuk kejelasan let { size: { // letakkan ukuran di sini width, height }, items: [item1, item2], // tetapkan item di sini title = "Menu" // tidak ada dalam objek (nilai default digunakan) } = options; alert(title); // Menu alert(width); // 100 alert(height); // 200 alert(item1); // Cake alert(item2); // Donut ``` Semua properti objek `options` kecuali` extra` yang tidak ada di bagian kiri, ditetapkan ke variabel yang sesuai: ![](destructuring-complex.svg) Akhirnya, kita memiliki `width`,` height`, `item1`,` item2` dan `title` dari nilai default. Perhatikan bahwa tidak ada variabel untuk `size` dan` item`, karena kita mengambil kontennya. ## Parameter fungsi cerdas Ada kalanya suatu fungsi memiliki banyak parameter, yang sebagian besar bersifat opsional. Itu terutama berlaku untuk antarmuka pengguna. Bayangkan sebuah fungsi yang menciptakan menu. Mungkin memiliki lebar, tinggi, judul, daftar item dan sebagainya. Berikut cara yang buruk untuk menulis fungsi tersebut: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { // ... } ``` Dalam kehidupan nyata, masalahnya adalah bagaimana cara mengingat urutan argumen. Biasanya IDE mencoba membantu kita, terutama jika kodenya didokumentasikan dengan baik, tetapi masih ... Masalah lain adalah bagaimana memanggil fungsi ketika sebagian besar parameter ok secara default. Soperti ini? ```js // tidak ditentukan dimana nilai default baik-baik saja showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` Itu jelek. Dan menjadi tidak dapat dibaca ketika kita berurusan dengan lebih banyak parameter. Destrukturisasi datang untuk menyelamatkan! Kita dapat melewatkan parameter sebagai objek, dan fungsinya segera merusaknya menjadi variabel: ```js run // kita meneruskan objek ke fungsi let options = { title: "My menu", items: ["Item1", "Item2"] }; // ...dan segera memperluasnya ke variabel function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []}*/!*) { // title, items – diambil dari options, // width, height – standar yang digunakan alert( `${title} ${width} ${height}` ); // My Menu 200 100 alert( items ); // Item1, Item2 } showMenu(options); ``` Kita juga dapat menggunakan perusakan yang lebih kompleks dengan objek bersarang dan pemetaan titik dua: ```js run let options = { title: "My menu", items: ["Item1", "Item2"] }; *!* function showMenu({ title = "Untitled", width: w = 100, // width menjadi w height: h = 200, // height menjadi h items: [item1, item2] // element pertama items menjadi item1, kedua menjadi item2 }) { */!* alert( `${title} ${w} ${h}` ); // My Menu 100 200 alert( item1 ); // Item1 alert( item2 ); // Item2 } showMenu(options); ``` Sintaks lengkapnya sama dengan untuk tugas penataan: ```js function({ incomingProperty: varName = defaultValue ... }) ``` Kemudian, untuk objek parameter, akan ada variabel `varName` untuk properti` incomingProperty`, dengan `defaultValue` secara default. Harap perhatikan bahwa destrukturisasi seperti itu mengasumsikan bahwa `showMenu ()` memang memiliki argumen. Jika kita menginginkan semua nilai secara default, maka kita harus menentukan objek kosong: ```js showMenu({}); // ok, semua nilai adalah default showMenu(); // ini akan memberikan kesalahan ``` Kita dapat memperbaikinya dengan menjadikan `{}` nilai default untuk seluruh objek parameter: ```js run function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { alert( `${title} ${width} ${height}` ); } showMenu(); // Menu 100 200 ``` Dalam kode di atas, objek argumen keseluruhan adalah `{}` secara default, jadi selalu ada sesuatu untuk distrukturisasi. ## Ringkasan - Penugasan destrukturisasi memungkinkan untuk memetakan objek atau array secara instan ke banyak variabel. - Sintaks lengkap objek: ```js let {prop : varName = default, ...rest} = object ``` Ini berarti properti `prop` harus masuk ke variabel` varName` dan, jika tidak ada properti seperti itu, maka nilai `default` harus digunakan. Properti objek yang tidak memiliki pemetaan disalin ke objek `rest`. - Sintaks lengkap array: ```js let [item1 = default, item2, ...rest] = array ``` Item pertama masuk ke `item1`; yang kedua masuk ke `item2`, sisanya membuat array `rest`. - Dimungkinkan untuk mengekstraksi data dari array / objek bersarang, untuk itu sisi kiri harus memiliki struktur yang sama dengan yang benar. ================================================ FILE: 1-js/05-data-types/11-date/1-new-date/solution.md ================================================ Konstruktor `new Date` menggunakan zona waktu lokal. Sehingga hal penting yang harus diingat adalah bulan dimulai dari angka 0. Jadi Februari mempunyai angka 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("February 20, 2012 03:12:00"); alert( d2 ); ``` ================================================ FILE: 1-js/05-data-types/11-date/1-new-date/task.md ================================================ Tingkat kepentingan: 5 --- # Buat sebuah tanggal Buat sebuah objek `Date` untuk tanggal: Feb 20, 2012, 3:12am. Zona waktu lokal. Tampilkan menggunakan `alert`. ================================================ FILE: 1-js/05-data-types/11-date/2-get-week-day/_js.view/solution.js ================================================ function getWeekDay(date) { let days = ['MIN', 'SEN', 'SEL', 'RAB', 'KAM', 'JUM', 'SAB']; 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)), 'JUM'); }); it("4 January 2014 - saturday", function() { assert.equal(getWeekDay(new Date(2014, 0, 4)), 'SAB'); }); it("5 January 2014 - sunday", function() { assert.equal(getWeekDay(new Date(2014, 0, 5)), 'MIN'); }); it("6 January 2014 - monday", function() { assert.equal(getWeekDay(new Date(2014, 0, 6)), 'SEN'); }); it("7 January 2014 - tuesday", function() { assert.equal(getWeekDay(new Date(2014, 0, 7)), 'SEL'); }); it("8 January 2014 - wednesday", function() { assert.equal(getWeekDay(new Date(2014, 0, 8)), 'RAB'); }); it("9 January 2014 - thursday", function() { assert.equal(getWeekDay(new Date(2014, 0, 9)), 'KAM'); }); }); ================================================ FILE: 1-js/05-data-types/11-date/2-get-week-day/solution.md ================================================ Metode `date.getDay()` mengembalikan angka dari hari dalam satu minggu, dimulai dari Minggu. Buat array hari dalam seminggu, sehingga kita bisa mendapatkan nama yang sesuai dengan angkanya: ```js run demo function getWeekDay(date) { let days = ['MIN', 'SEN', 'SEL', 'RAB', 'KAM', 'JUM', 'SAB']; return days[date.getDay()]; } let date = new Date(2014, 0, 3); // 3 Jan 2014 alert( getWeekDay(date) ); // JUM ``` ================================================ FILE: 1-js/05-data-types/11-date/2-get-week-day/task.md ================================================ Tingkat kepentingan: 5 --- # Tampilkan hari dalam satu minggu Tulis sebuah fungsi `getWeekDay(tanggal)` untuk menunjukkan hari dalam format: 'SEN', 'SEL', 'RAB', 'KAM', 'JUM', 'SAB', 'MIN'. Sebagai contoh: ```js no-beautify let tanggal = new Date(2012, 0, 3); // 3 Jan 2012 alert( getWeekDay(tanggal) ); // harus mengeluarkan "SEL" ``` ================================================ FILE: 1-js/05-data-types/11-date/3-weekday/_js.view/solution.js ================================================ function getLocalDay(date) { let days = date.getDay(); if (days == 0) { // hari ke-0 (Minggu) adalah hari ke-7 di Eropa days = 7; } return days; } ================================================ FILE: 1-js/05-data-types/11-date/3-weekday/_js.view/test.js ================================================ describe("getLocalDay mengembalikan hari sesuai dengan standar di \"eropa\" ", function() { it("3 January 2014 - Jumat", function() { assert.equal(getLocalDay(new Date(2014, 0, 3)), 5); }); it("4 January 2014 - Sabtu", function() { assert.equal(getLocalDay(new Date(2014, 0, 4)), 6); }); it("5 January 2014 - Minggu", function() { assert.equal(getLocalDay(new Date(2014, 0, 5)), 7); }); it("6 January 2014 - Senin", function() { assert.equal(getLocalDay(new Date(2014, 0, 6)), 1); }); it("7 January 2014 - Selasa", function() { assert.equal(getLocalDay(new Date(2014, 0, 7)), 2); }); it("8 January 2014 - Rabu", function() { assert.equal(getLocalDay(new Date(2014, 0, 8)), 3); }); it("9 January 2014 - Kamis", 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 ================================================ Tingkat kepentingan: 5 --- # Hari di Eropa Negara-negara di Eropa mempunyai hari yang dimulai dari Senin (angka 1), kemudian Selasa (angka 2) sampai dengan Minggu (angka 7). Buatlah sebuah fungsi `getLocalDay(date)` yang mengembalikan hari sesuai dengan standar tanggal di Eropa. ```js no-beautify let date = new Date(2012, 0, 3); // 3 Jan 2012 alert( getLocalDay(date) ); // Selasa, seharusnya menunjukkan angka 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 hari sebelum 02.01.2015 -> tanggal 1", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 1), 1); }); it("2 hari sebelum 02.01.2015 -> tanggal 31", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 2), 31); }); it("100 hari sebelum 02.01.2015 -> tanggal 24", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 100), 24); }); it("365 hari sebelum 02.01.2015 -> tanggal 2", function() { assert.equal(getDateAgo(new Date(2015, 0, 2), 365), 2); }); it("tidak mengubah tanggal yang diberikan", function() { let tanggal = new Date(2015, 0, 2); let tanggalCopy = new Date(tanggal); getDateAgo(tanggalCopy, 100); assert.equal(tanggal.getTime(), tanggalCopy.getTime()); }); }); ================================================ FILE: 1-js/05-data-types/11-date/4-get-date-ago/solution.md ================================================ Idenya mudah: Kurangi `tanggal` dengan jumlah hari yang diberikan. ```js function getDateAgo(tanggal, hari) { tanggal.setDate(tanggal.getDate() - hari); return tanggal.getDate(); } ``` ...Namun fungsi tersebut tidak boleh mengubah `tanggal` yang diberikan. Ini adalah yang terpenting, karena kode di luar yang memberikan kita tanggal tidak mengira tanggal tersebut akan berubah. Untuk mengimplementasikannya kita akan menduplikasi tanggal tersebut, seperti ini: ```js run demo function getDateAgo(tanggal, hari) { let tanggalCopy = new Date(tanggal); tanggalCopy.setDate(tanggal.getDate() - hari); return tanggalCopy.getDate(); } let tanggal = new Date(2015, 0, 2); alert( getDateAgo(tanggal, 1) ); // 1, (1 Jan 2015) alert( getDateAgo(tanggal, 2) ); // 31, (31 Des 2014) alert( getDateAgo(tanggal, 365) ); // 2, (2 Jan 2014) ``` ================================================ FILE: 1-js/05-data-types/11-date/4-get-date-ago/task.md ================================================ Tingkat kepentingan: 4 --- # Tanggal berapakah beberapa hari yang lalu? Buatlah sebuah fungsi `getDateAgo(tanggal, hari)` yang mengembalikan beberapa `hari` yang telah berlalu dari sebuah `tanggal`. Sebagai contoh, apabila hari ini tanggal 20, maka `getDateAgo(new Date(), 1)` harus mengembalikan tanggal 19 dan `getDateAgo(new Date(), 2)` harus mengembalikan tanggal 18. Harus bekerja dengan baik dan dapat diandalkan untuk `hari=365` atau lebih: ```js let tanggal = new Date(2015, 0, 2); alert( getDateAgo(tanggal, 1) ); // 1, (1 Jan 2015) alert( getDateAgo(tanggal, 2) ); // 31, (31 Des 2014) alert( getDateAgo(tanggal, 365) ); // 2, (2 Jan 2014) ``` P.S. Fungsi tidak boleh mengubah `tanggal` yang diberikan. ================================================ FILE: 1-js/05-data-types/11-date/5-last-day-of-month/_js.view/solution.js ================================================ function getLastDayOfMonth(tahun, bulan) { let tanggal = new Date(tahun, bulan + 1, 0); return tanggal.getDate(); } ================================================ FILE: 1-js/05-data-types/11-date/5-last-day-of-month/_js.view/test.js ================================================ describe("getLastDayOfMonth", function() { it("tanggal terakhir dari 01.01.2012 - 31", function() { assert.equal(getLastDayOfMonth(2012, 0), 31); }); it("tanggal terakhir dari 01.02.2012 - 29 (tahun kabisat)", function() { assert.equal(getLastDayOfMonth(2012, 1), 29); }); it("tanggal terakhir dari 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 ================================================ Buat sebuah tanggal menggunakan bulan berikutnya, namun berikan 0 sebagai tanggalnya: ```js run demo function getLastDayOfMonth(tahun, bulan) { let tanggal = new Date(tahun, bulan + 1, 0); return tanggal.getDate(); } alert( getLastDayOfMonth(2012, 0) ); // 31 alert( getLastDayOfMonth(2012, 1) ); // 29 alert( getLastDayOfMonth(2013, 1) ); // 28 ``` Normalnya, tanggal dimulai dari 1, namun secara teknis kita bisa berikan angka apapun, dan tanggal akan otomatis menyesuaikan. Sehingga ketika kita berikan 0, itu berarti "satu hari sebelum tanggal pertama untuk sebuah bulan", dengan kata lain: "hari terakhir pada bulan sebelumnya". ================================================ FILE: 1-js/05-data-types/11-date/5-last-day-of-month/task.md ================================================ Tingkat kepentingan: 5 --- # Tanggal terakhir dari sebuah bulan? Tulis sebuah fungsi `getLastDayOfMonth(tahun, bulan)` yang mengembalikan tanggal terakhir dari sebuah bulan. Terkadang 30, 31, atau bahkan 28/29 untuk Februari. Parameter: - `tahun` -- tahun dalam empat-digit, sebagai contoh 2012. - `bulan` -- bulan, dari 0 sampai 11. Sebagai contoh, `getLastDayOfMonth(2012, 1) = 29` (tahun kabisat, Feb). ================================================ FILE: 1-js/05-data-types/11-date/6-get-seconds-today/solution.md ================================================ Untuk mendapatkan jumlah detik, kita harus membuat sebuah tanggal menggunakan hari yang sedang berlangsung dan waktu 00:00:00, dan mengurangi waktu "saat ini" dengannya. Perbedaan yang didapat adalah angka dalam milidetik sejak permulaan hari, yang harus dibagi dengan 1000 agar menjadi detik: ```js run function getSecondsToday() { let sekarang = new Date(); // Buat sebuah objek menggunakan hari/bulan/tahun saat ini let hariIni = new Date(now.getFullYear(), now.getMonth(), now.getDate()); let beda = sekarang - hariIni; // beda dalam milidetik return Math.round(beda / 1000); // ubah menjadi detik } alert( getSecondsToday() ); ``` Solusi alternatif adalah cari jam/menit/detik dan ubah menjadi detik: ```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 ================================================ Tingkat kepentingan: 5 --- # Berapa detik yang telah berlalu untuk hari ini? Tulis sebuah fungsi `getSecondsToday()` yang mengembalikan angka dari detik yang telah berlalu dari sejak permulaan hari ini. Sebagai contoh, sekarang pukul `10:00 am`, dan tidak ada daylight savings shift, maka: ```js getSecondsToday() == 36000 // (3600 * 10) ``` Fungsi haruslah berjalan untuk hari apapun. sehingga, tidak boleh ada nilai "hari ini" yang ditulis secara hard-code. ================================================ FILE: 1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md ================================================ Untuk mendapatkan jumlah milidetik hingga besok, kita bisa mendapatkannya melalui pengurangan tanggal hari ini dengan "besok 00:00:00" Pertama, kita tentukan "besok", dan kemudian lakukan perhitungan: ```js run function getSecondsToTomorrow() { let sekarang = new Date(); // besok let besok = new Date(now.getFullYear(), now.getMonth(), *!*now.getDate()+1*/!*); let beda = besok - sekarang; // beda dalam milidetik return Math.round(beda / 1000); // ubah ke detik } ``` Solusi alternatif: ```js run function getSecondsToTomorrow() { let sekarang = new Date(); let jam = sekarang.getHours(); let menit = sekarang.getMinutes(); let detik = sekarang.getSeconds(); let detikTotalHariIni = (jam * 60 + menit) * 60 + detik; let detikTotalDalamSatuHari = 86400; return detikTotalDalamSatuHari - detikTotalHariIni; } ``` Harap diingat bahwa banyak negara menerapkan Daylight Savings Time (DST), sehingga memungkinkan ada hari dengan 23 atau 25 jam. Kita mungkin ingin melakukan perhitungan dengan cara yang berbeda untuk mereka. ================================================ FILE: 1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md ================================================ Tingkat kepentingan: 5 --- # Berapa detik lagi sampai besok? Buat sebuah fungsi `getSecondsToTomorrow()` yang mengembalikan jumlah detik yang tersisa hingga esok hari. Sebagai contoh, jika sekarang `23:00`, maka: ```js getSecondsToTomorrow() == 3600 ``` P.S. Fungsi haruslah berjalan untuk semua hari, sehingga "hari ini" tidak bisa dalam bentuk hard-code. ================================================ FILE: 1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js ================================================ function formatDate(date) { let diff = new Date() - date; // perbedaan dalam mili-detik if (diff < 1000) { // kurang dari 1 detik return 'right now'; } let sec = Math.floor(diff / 1000); // ubah diff menjadi detik if (sec < 60) { return sec + ' sec. ago'; } let min = Math.floor(diff / 60000); // ubah diff menjadi menit if (min < 60) { return min + ' min. ago'; } // format tanggalnya // tambah awalan nol ke satu-digit hari/bulan/jam/menit let d = date; d = [ '0' + d.getDate(), '0' + (d.getMonth() + 1), '' + d.getFullYear(), '0' + d.getHours(), '0' + d.getMinutes() ].map(component => component.slice(-2)); // ambil 2 dijit dari setiap komponen // satukan komponen menjadi tanggal 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 ================================================ Untuk mendapatkan waktu dari `date` sampai sekarang -- kita kurangi tanggalnya. ```js run demo function formatDate(date) { let diff = new Date() - date; // perbedaan dalam mili-detik if (diff < 1000) { // kurang dari 1 detik return 'right now'; } let sec = Math.floor(diff / 1000); // ubah diff menjadi detik if (sec < 60) { return sec + ' sec. ago'; } let min = Math.floor(diff / 60000); // ubah diff menjadi menit if (min < 60) { return min + ' min. ago'; } // format tanggalnya // tambahkan awalan nol ke dijit-tunggal hari/bulan/jam/menit let d = date; d = [ '0' + d.getDate(), '0' + (d.getMonth() + 1), '' + d.getFullYear(), '0' + d.getHours(), '0' + d.getMinutes() ].map(component => component.slice(-2)); // ambil setidaknya 2 dijit dari setiap komponen // satukan komponen menjadi tanggal return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); } alert( formatDate(new Date(new Date - 1)) ); // "sekarang" alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 detik yang lalu" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 menit yang lalu" // tanggal kemarin seperti 31.12.2016 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` Alternative solution: ```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; // formatting 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 ================================================ nilai penting: 4 --- # Ubah menjadi tanggal yang berhubungan Tulis sebuah fungsi `formatDate(date)` yang harus memformat `date` seperti berikut: - Jika sejak `date` lewat kurang dari 1 detik, lalu `"sekarang"`. - Sebaliknya, jika sejak `date` lewat kurang dari satu menit, lalu `"n detik yang lalu"`. - Sebaliknya, jika kurang dari satu jam, lalu `"m menit yang lalu"`. - Sebaliknya, tanggal dalam format penuh `"DD.MM.YY HH:mm"`. Itu adalah: `"hari.bulan.tahun jam:menit"`, semua dalam format 2 angka, contoh. `31.12.16 10:00`. Contoh: ```js alert( formatDate(new Date(new Date - 1)) ); // "sekarang" alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 detik yang lalu" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 menit yang lalu" // tanggal kemarin seperti 31.12.16 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` ================================================ FILE: 1-js/05-data-types/11-date/article.md ================================================ # Tanggal dan waktu Ayo kita bertemu dengan objek bawaan baru: [Date](mdn:js/Date). yang akan menyimpan tanggal, waktu dan menyediakan metode untuk manajemen tanggal/waktu. Contoh, kita bisa menggunakan itu untuk menyimpan pembuatan/modifikasi waktu, untuk menghitung waktu atau hanya untuk melihat tanggal sekarang. ## Pembuatan Untuk membuat objek `Date` baru panggil `new Date()` dengan salah satu dari argumen dibawah: `new Date()` : Tanpa argumen -- membuat sebuah objek `Date` untuk tanggal dan waktu sekarang: ```js run let now = new Date(); alert( now ); // tampilkan tanggal/waktu sekarang ``` `new Date(milliseconds)` : Membuat sebuah objek `Date` dengan waktu yang sama dengan mili-detik (1/1000 dari satu detik) lewat dari Januari 1 1970 UTC+0. ```js run // 0 berarti 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 ); // sekarang tambahkan 24 jam, ambil 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 ); ``` Sebuah angka integer merepresentasikan angka dari milidetik yang telah lewat sejak awal dari 1970 dipanggil dengan *timestamp*. Ini adalah angka numerik ringan yang merepresentasikan sebuah tanggal. Kita akan selalu bisa membuat tanggal dari timestamp menggunakan `new Date(timestamp)` dan mengubah objek `Date` yang ada ke sebuah timestamp dengan menggunakan metode `date.getTime()` (lihat dibawah). Tanggal sebelum 01.01.1970 mempunyai timestamp yang negatif, contoh.: ```js run // 31 Dec 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 ); ``` `new Date(datestring)` : Jika terdapat sebuah argumen tunggal, dan itu adalah sebuah string, lalu itu akan diurai secara otomatis. Algoritmanya sama dengan yang digunakan `Date.parse`, kita akan pelajari itu nanti. ```js run let date = new Date("2017-01-26"); alert(date); // Waktunya belum di set, jadi itu diasumsikan tengah malam GMT dan // disesuaikan menurut zona waktu dimana kodenya berjalan // Jadi hasilnya mungkin bisa // Kamis Jan 26 2017 11:00:00 GMT+1100 (Waktu timur siang hari Australia ) // atau // Rabu Jan 25 2017 16:00:00 GMT-0800 (Waktu standar pasifik) ``` `new Date(year, month, date, hours, minutes, seconds, ms)` : Membuat waktu dengan komponen yang diberikan dari zona waktu lokal. Hanya dua argument pertama yang wajib. - `Tahun`nya harus mempunyai 4 angka: `2013` boleh, `98` tidak boleh. - Perhitungan `Bulan`nya dimulai dari `0` (Jan), sampai `11` (Des). - Parameter `date` sebenarnya adalah hari dari bulan, jika tidak ada maka akan diasumsikan `1`. - Jika `jam/menit/detik/milidetik` tidak ada, mereka akan diasumsikan sama dengan `0`. Contoh: ```js new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00 new Date(2011, 0, 1); // sama, jam dan yang lainnya secara default adalah 0 ``` Presisi paling minimal adalah 1ms (1/1000 detik): ```js run let date = new Date(2011, 0, 1, 2, 3, 4, 567); alert( date ); // 1.01.2011, 02:03:04.567 ``` ## Mengakses komponen tanggal Terdapat beberapa metode untuk mengakses tahun, bulan dan lainnya dari objek `Date`: [getFullYear()](mdn:js/Date/getFullYear) : Mendapatkan tahun (4 angka) [getMonth()](mdn:js/Date/getMonth) : Mendapatkan bulan, **dari 0 sampai 11**. [getDate()](mdn:js/Date/getDate) : mendapatkan hari dari bulan, dari 1 sampai 31, nama dari metodenya sedikit terlihat aneh. [getHours()](mdn:js/Date/getHours), [getMinutes()](mdn:js/Date/getMinutes), [getSeconds()](mdn:js/Date/getSeconds), [getMilliseconds()](mdn:js/Date/getMilliseconds) : Mendapatkan komponen-komponen yang bersangkutan. ```warn header="Bukan `getYear()`, Tapi `getFullYear()`" Banyak mesin Javascript mengimplementasikan metode yang tidak-standar `getYear()`. Metode ini sudah usang. Itu terkadang mengembalikan tahun dengan 2-angka. Tolong jangan gunakan itu. Gunakan `getFullYear()` untuk tahun. ``` Sebagai tambahan, kita bisa mendapatkan hari dari minggu: [getDay()](mdn:js/Date/getDay) : Dapatkan hari dari minggu, dimulai dari `0` (Minggu) to `6` (Sabtu). Hari pertama akan selalu Minggu, di beberapa negara bukanlah minggu, dan tidak bisa diubah. **Semua metode diatas mengembalikan komponen yang bersangkutan dengan zona waktu lokal.** Juga terdapat pasangan-UTC, yang mengembalikan hari bulan, tahun dan lainnya untuk zona waktu UTC+0: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay). Hanya dengan memasukan `"UTC"` tepat setelah `"get"`. Jika zona waktu lokal kamu diubah menjadi zona yang berhubungan dengan UTC, maka kode dibawah akan menunjukan waktu yang berbeda. ```js run // tanggal sekarang let date = new Date(); // jam didalam zona waktu kamu sekarang alert( date.getHours() ); // jam di zona waktu UTC+0 (waktu london tanpa waktu musim panas) alert( date.getUTCHours() ); ``` Disamping metode yang diberikan, disana terdapat dua yang spesial yang tidak memiliki variasi waktu UTC: [getTime()](mdn:js/Date/getTime) : Mengembalikan timestamp untuk tanggal -- sebuah angka dari milidetik yang telah terlewat sejak 1 Januari 1970 UTC+0 [getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) : Mengembalikan perbedaan diantara UTC dan zona waktu lokal, dalam menit: ```js run // jika kamu berada didalam zona waktu UTC-1, mengeluarkan 60 // jika kamu berada di zona waktu UTC+3, mengeluarkan -180 alert( new Date().getTimezoneOffset() ); ``` ## Menyetel komponen tanggal Metode berikut memperbolehkan kita untuk menyetel komponen tanggal/waktu: - [`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) (setel seluruh tanggal dengan milidetik sejak 01.01.1970 UTC) Semuanya kecuali salah satunya yaitu `setTime()` mempunyai varian-UTC, contoh: `setUTCHours()`. Seperti yang bisa kita lihat, beberapa metode bisa menyetel beberapa komponen sekaligus, untuk contoh `setHours`. Komponen yang tidak disebutkan tidak akan diubah. Contoh: ```js run let today = new Date(); today.setHours(0); alert(today); // masih hari ini, tapi jamnya diubah menjadi 0 today.setHours(0, 0, 0, 0); alert(today); // masih hari ini, tapi tepat 00:00:00 ``` ## Koreksi otomatis *Koreksi otomatis* adalah fitur yang sangat berguna dari objek `Date`. Kita bisa menyetel nilai yang diluar jangkauan, dan itu akan menyesuaikan dirinya sendiri. Contoh: ```js run let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? alert(date); // ...adalah 1st Feb 2013! ``` komponen tanggal yang diluar jangkauan akan diganti secara otomatis. Kita bisa berkata untuk menambah tanggal "28 feb 2016" dengan 2 hari. Itu mungkin akan "2 maret" atau "1 maret" didalam kasus tahun kabisat. Kita tidak perlu memikirkan hal itu. Tinggal tambah 2 hari. Objek `Date` akan melakukan sisanya: ```js run let date = new Date(2016, 1, 28); *!* date.setDate(date.getDate() + 2); */!* alert( date ); // 1 Mar 2016 ``` Fitur itu sering digunakan untuk mendapatkan tanggal setelah diberikan waktu yang ditentukan, coba dapatkan tanggal "70 detik setelah saat ini": ```js run let date = new Date(); date.setSeconds(date.getSeconds() + 70); alert( date ); // menampilkan tanggal yang benar ``` Kita juga bisa menyetel nol atau bahkan nilai negatif. Contoh: ```js run let date = new Date(2016, 0, 2); // 2 Jan 2016 date.setDate(1); // setel hari pertama dari bulan alert( date ); date.setDate(0); // kurangi 1 hari, jadi diasumsikan hari terakhir di bulan sebelumnya alert( date ); // 31 Desember 2015 ``` ## Tanggal menjadi angka, perbedaan tanggal Ketika sebuah objek `Date` diubah menjadi angka, itu menjadi timestamp sama seperti `date.getTime()`: ```js run let date = new Date(); alert(+date); // angka dari milidetik, sama seperti date.getTime() ``` Efek yang perlu diperhatikan: tanggal bisa dikurangi, hasilnya adalah perbedaan dalam milidetik. Hal itu bisa gunakan untuk mengukur waktu: ```js run let start = new Date(); // mulai pengukuran waktu // lakukan perhitungannya for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } let end = new Date(); // akhiri pengukuran waktu alert( `The loop took ${end - start} ms` ); ``` ## Date.now() Jika kita ingin mengukur waktu, kita tidak butuh objek `Date`. Terdapat metode spesial `Date.now()` yang mengembalikan timestamp saat ini. Itu secara semaktik sama dengan `new Date().getTime()`, tapi itu tidak menciptakan sebuah perantara objek `Date`. Jadi itu lebih cepat dan tidak menambah beban pembuangan sampah. Kebanyakan itu digunakan untuk kenyamanan atau ketika performansi menjadi hal yang diperhatikan, seperti permainan didalam Javascript atau aplikasi yang terspesialisasi lainnya. Jadi ini mungkin lebih baik: ```js run *!* let start = Date.now(); // milidetik dihitung dari 1 Januari 1970 */!* // lakukan perhitungannya for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } *!* let end = Date.now(); // selesai */!* alert( `The loop took ${end - start} ms` ); // kurangi angka, bukan tanggal ``` ## Menguji kemampuan / Benchmarking Jika kita ingin kemampuan yang dapat diandalkan dari fungsi yang haus akan sumberdaya CPU, kita harus hati-hati. Contoh, coba kita bandingkan dua fungsi yang mengkalkulasikan perbedaan diantara dua tanggal: yang mana yang lebih cepat? Pengukurang performa seperti itu sering disebut dengan "benchmarks". ```js // kita punya date1 dan date2, fungsi yang mana yang lebih cepat mengembalikan perbedaannya dalam milidetik? function diffSubtract(date1, date2) { return date2 - date1; } // atau function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } ``` kedua fungsi itu melakukan hal yang sama persis, tapi satu dari mereka menggunakan `date.getTime()` secara eksplisit untuk mendapatkan tanggal dalam milidetik, dan lainnya menggunakan perubahan tanggal-ke-angka. Hasil mereka akan selalu sama. Jadi, yang mana yang lebih cepat? Cara sederhananya mungkin menjalankan mereka beberapa kali dan menghitung perbedaan waktunya. Untuk kasus ini, fungsi sangatlah sederhana, jadi kita hanya harus melakukannya setidaknya 100000 kali. Ayo kita hitung: ```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! Menggunakan `getTime()` lebih cepat! itu karena disana tidak terdapat perubahan tipe, itu akan membuat mesinnya lebih mudah dalam mengoptimasi. Oke, kita punya sesuatu. Tapi itu bukanlah sebuah pengujian kemampuan yang bagus. Bayangkan itu pada saat menjalankan `bench(diffSubtract)` CPU nya sedang melakukan sesuatu yang lain, dan itu mengambil sumberdaya nya. Dan pada saat menjalankan `bench(diffGetTime)` pekerjaanya telah selesai. Sebuah skenario nyata untuk Sistem Operasi multi-proses yang modern. Sebagai sebuah hasil, pengujian kemampuan pertama mempunyai sedikit sumberdaya CPU daripada yang kedua. Itu mungkin akan mengakibatkan hasil menjadi keliru. **Untuk pengujian yang lebih dapat diandalkan, seluruh pengujian harus dijalankan beberapa kali.** Contoh, seperti ini: ```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; *!* // jalankan bench(upperSlice) dan bench(upperLoop) setiap 10 kali bergantian 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 ); ``` Mesin Javascript yang modern mulai menggunakan optimasi yang tinggi hanya untuk "hot code" yang dieksekusi beberapa kali (tidak butuh untuk optimasi hal yang jarang dieksekusi). Jadi, dalam contoh diatas, eksekusi pertama tidak benar-benar di optimasi. Kita mungkin butuh menambah sebuah pemanasan: ```js // ditambahkan untuk "memanaskan" terlebih dahulu perulangan utama bench(diffSubtract); bench(diffGetTime); // sekarang benchmark for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } ``` ```warn header="Berhati-hati saat melakukan microbenchmarking/pengujian kemampuan micro" Mesin Javascript modern melakukan banyak optimasi. mereka mungkin merekayasa hasil dari "test buatan" dibandingkan dengan "pemakaian normal", terutama ketika kita mengukur kemampuan sesuatu yang sangat kecil, seperti bagaimana operator bekerja, atau fungsi bawaan. Jadi jika kamu sangat serius ingin mengerti tentang performansi, maka pelajarilah bagaiman mesin Javascript bekerja. dan maka kamu mungkin tidak butuh microbenchmarking sama sekali Kumpulan artikel yang bagus tentang V8 bisa ditemukan di . ``` ## Date.parse dari sebuah string Metode [Date.parse(str)](mdn:js/Date/parse) bisa membaca tanggal dari sebuah string. Bentuk dari string haruslah: `YYYY-MM-DDTHH:mm:ss.sssZ`, dimana: - `YYYY-MM-DD` -- adalah tanggal: tahun-bulan-hari. - Karakter dari `"T"` digunakan sebagai pembatas. - `HH:mm:ss.sss` -- adalah waktu: jam, menit, detik dan milidetik. - Bagian opsional `'Z'` menandakan zona waktu dalam format `+-hh:mm`. Huruf tunggal `Z` menandakan UTC+0. Varian yang lebih pendek juga bisa, seperti `YYYY-MM-DD` atau `YYYY-MM` atau bahkan `YYYY`. Pemanggilan `Date.parse(str)` mengolah string dalam bentuk yang diberikan dan mengembalikan timestamp (angka dalam milidetik dari 1 Januari 1970 UTC+0). Jika formatnya tidak valid, akan mengembalikan `NaN`. Contoh: ```js run let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); alert(ms); // 1327611110417 (timestamp) ``` Kita bisa secara instan membuat sebuah objek `new Date` dari timestamp: ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); alert(date); ``` ## Ringkasan - Tanggal dan waktu dalam Javascript direpresentasikan oleh objek [Date](mdn:js/Date). Kita tidak bisa membuat "hanya tanggal" atau "hanya waktu": objek `Date` selalu membawa keduanya. - Bulan dihitung dari nol (ya, Januari adalah bulan ke-nol) - Hari-hari di minggu di `getDay()` juga dihitung dari nol (yang mana adalah minggu). - `Date` mengkoreksi sendiri secara otomatis ketika komponen diluar jangkauan di-set. Bagus unduk menambahkan/mengurangi hari/bulan/jam. - Tanggal bisa dikurangi, memberikan perbedaannya dalam milidetik. Itu karena `Date` menjadi timestamp ketika diubah menjadi angka. - Gunakan `Date.now()` untuk mendapatkan timestamp dengan cepat. Perhatikan tidak seperti sistem lainnya, timestamp didalam Javascript adalah dalam milidetik, bukan dalam detik. Terkadang kita ingin pengukuran yang lebih teliti. Javascript sendiri tidak mendukung cara untuk mengukur waktu didalam microdetik (1 juta dalam satu detik), tapi kebanyakan lingkungan menyediakannya. Contoh, peramban punya [performance.now()](mdn:api/Performance/now) yang memberikan angka milidetik dari awal halaman dimuat dengan ketepatan microdetik (3 angka setelah titik): ```js run alert(`Loading started ${performance.now()}ms ago`); // Sesuatu seperti: "Loading started 34731.26000000001ms ago" // .26 adalah microdetik (260 microdetik) // lebih dari 3 angka setelah titik desimal adalah presisi error, tapi hanya 3 yang benar ``` Node.js punya modul `microtime` dan cara lainnya. Secara teknis, hampir kebanyakan perangkat dan environment memperbolehkan untuk mendapatkan presisi, itu hanya bukan didalam `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 ================================================ nilai penting: 5 --- # Ubah objek menjadi JSON dan sebaliknya Ubahlah `user` menjadi JSON dan kemudian baca kembali menjadi variabel lain. ```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} } */ ``` Disini kita juga perlu untuk menguji `key==""` untuk tidak memasukkan panggilan pertama dimana properti tersebut normal ketika `value` adalah `meetup`. ================================================ FILE: 1-js/05-data-types/12-json/2-serialize-event-circular/task.md ================================================ nilai penting: 5 --- # Tidak memasukkan referensi balik Dalam kasus-kasus sederhana tentang referensi sirkular, kita bisa tidak memasukkan sebuah properti tertentu dari proses serialisasi berdasarkan namanya. Namun terkadang kita tidak bisa menggunakan nama saja, sebagaimana bisa saja properti tersebut menggunakan referensi sirkular dan (berfungsi sebagai) properti normal. Jadi kita bisa memeriksa properti berdasarkan nilainya. Tulis fungsi `replacer` untuk me-*stringify* semuanya, tetapi menghilangkan propertii yang me-referensi ke `meetup`: ```js run let room = { number: 23 }; let meetup = { title: "Conference", occupiedBy: [{name: "John"}, {name: "Alice"}], place: room }; *!* // referensi-referensi sirkular room.occupiedBy = meetup; meetup.self = meetup; */!* alert( JSON.stringify(meetup, function replacer(key, value) { /* kodemu */ })); /* hasil yang diharapkan yakni: { "title":"Conference", "occupiedBy":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */ ``` ================================================ FILE: 1-js/05-data-types/12-json/article.md ================================================ # Metode JSON, toJSON Anggap saja kita memiliki sebuah objek yang kompleks, dan kita ingin mengonversinya menjadi sebuah *string*, mengirimnya melalui sebuah jaringan atau hanya menghasilkan *string* tersebut untuk tujuan pencatatan. Secara alami, *string* seperti contoh di atas seharusnya sudah termasuk semua properti-properti penting di dalamnya. Kita bisa mengimplementasikan konversi tersebut seperti ini: ```js run let user = { name: "John", age: 30, *!* toString() { return `{name: "${this.name}", age: ${this.age}}`; } */!* }; alert(user); // {name: "John", age: 30} ``` ...Tetapi dalam proses pengembangan, properti-properti baru ditambahkan, properti-properti yang lama diganti nama baru dan dipindahkan. Memperbarui seperti `toStrong` setiap saat bisa jadi sangat menyebalkan. Kita bisa coba untuk memberi properti-properti tersebut perulangan dalam proses pengembangan, tetapi apa yang terjadi jika objeknya kompleks dan memiliki objek yang bersarang (*nested*) dalam propertinya? Kita pastinya perlu untuk mengimplementasikan konversinya juga. Untungnya, (kita) tak perlu untuk menulis kode untuk menangani semua hal ini. Tugas tersebut sudah terpecahkan solusinya. ## JSON.stringify [JSON](http://en.wikipedia.org/wiki/JSON) (*JavaScript Object Notation*) adalah sebuah format umum yang merepresentasikan nilai-nilai dan objek. JSON dideskripsikan sebagaimana dalam standar [RFC 4627](http://tools.ietf.org/html/rfc4627). Awalnya JSON dibuat untuk JavaScript, tapi banyak bahasa pemrograman lain memiliki *library* untuk menangani JSON juga. Oleh karena itu, kini jadi mudah untuk menggunakan JSON untuk tujuan pertukaran data ketika klien menggunakan JavaScript dan server ditulis menggunakan bahasa pemrograman Ruby/PHP/Java/apapun itu. JavaScript menyediakan metode-metode seperti: - `JSON.stringify` untuk mengoversi objek menjadi JSON. - `JSON.parse` untuk mengonversi balik JSON menjadi sebuah objek. For instance, here we `JSON.stringify` a student: ```js run let student = { name: 'John', age: 30, isAdmin: false, courses: ['html', 'css', 'js'], wife: null }; *!* let json = JSON.stringify(student); */!* alert(typeof json); // we've got a string! alert(json); *!* /* JSON-encoded object: { "name": "John", "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], "wife": null } */ */!* ``` Metode `JSON.stringify(student)` mengambil objek dan mengonversikan objek tersebut menjadi sebuah *string*. Hasil *string* `json` disebut sebagai sebuah objek *JSON-encoded* atau *serialized* atau *stringified* atau *marshalled*. Kita kini siap utnuk mengirimnya melalui jaringan atau menyimpannya ke sebuah penyimpanan data. Mohon diingat bahwa sebuah objek *JSON-encoded* memiliki beberapa perbedaan penting dari objek secara harfiah: - *String* menggunakan tanda kutip. Dalam JSON tidak menggunakan tanda petik atau *backtick*. Jadi `'John'` menjadi `"John"`. - Nama-nama properti objek diberi tanda kutip juga. Hal ini wajib dilakukan. Jadi `age:30` menjadi `"age":30`. `JSON.stringify` bisa juga bisa diterapkan ke (tipe data) *primitive*. JSON mendukung tipe-tipe data berikut ini: - Objek `{ ... }` - *Array* `[ ... ]` - *Primitive*: - *string*, - angka (*number*), - nilai-nilai *boolean* `true/false`, - `null`. Sebagai contoh: ```js run // sebuah number(angka) dalam JSON hanyalah sebuah number alert( JSON.stringify(1) ) // 1 // sebuah string dalam JSON tetaplah sebuah string, namun diberi tanda kutip alert( JSON.stringify('test') ) // "test" alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] ``` JSON adalah spessifikasi yang hanya terdiri dari data dan tidak terlekat bahasa pemrograman tertentu (*data-only language-independent*), jadi beberapa properti objek yang spesifik pada JavaScript akan dilewati oleh `JSON.stringify`. Properti-properti objek yang spesifik pada JavaScript tersebut yakni: - Properti fungsi (metode-metode). - Properti simbolis. - Propert yang menyimpan `undefined`. ```js run let user = { sayHi() { // diabaikan alert("Hello"); }, [Symbol("id")]: 123, // ignored something: undefined // diabaikan }; alert( JSON.stringify(user) ); // {} (objek kosong) ``` Biasanya hal tersebut tidak masalah. Jika itu tidak kita inginkan, kita akan melihat bagaimana cara untuk meng-kustomisasi proses tersebut. Hal bagusnya adalah objek-objek yang *nested* secara otomatis didukung dan dikonversikan. Contohnya: ```js run let meetup = { title: "Conference", *!* room: { number: 23, participants: ["john", "ann"] } */!* }; alert( JSON.stringify(meetup) ); /* Keseluruhan struktur di-stringify: { "title":"Conference", "room":{"number":23,"participants":["john","ann"]}, } */ ``` Batasan penting: tidak boleh ada rujukan/referensi yang sirkular/berputar-putar. Contohnya: ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: ["john", "ann"] }; meetup.place = room; // meetup mereferensikan room room.occupiedBy = meetup; // room mereferensikan meetup *!* JSON.stringify(meetup); // Error: Converting circular structure to JSON */!* ``` Disini, konversi gagal, karena adanya referensi yang memutar/sirkular: `room.occupiedBy` mereferensikan ke `meetup`, dan `meetup.place` mereferensikan ke `room`: ![](json-meetup.svg) ## Mengecualikan dan mengubah: *replacer* Sintaks lengkap dari `JSON.stringify` adalah: ```js let json = JSON.stringify(value[, replacer, space]) ``` *value* : Sebuah nilai untuk di-*encode*. *replacer* : *Array* properti untuk di-*encode* atau sebuah fungsi pemetaan `function(key, value)`. *space* : Jumlah ruang yang digunakan untuk proses *formatting*. Seringkali, `JSON.stringify` digunakan dengan hanya sebuah argumen pertama. Tapi jika kita perlu untuk menyetel dengan baik proses pergantian tersebut, seperti menyaring referensi-referensi sirkular, kita dapat menggunakan argumen kedua dari `JSON.stringify`. Jika kita mengoper sebuah *array* properti ke proses tersebut, hanya properti-properti berikut ini yang akan di-*encode*. Sebagai contoh: ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup mereferensikan room }; room.occupiedBy = meetup; // room mereferensikan meetup alert( JSON.stringify(meetup, *!*['title', 'participants']*/!*) ); // {"title":"Conference","participants":[{},{}]} ``` Kini kita bisa jadi terlalu ketat (dalam mendeklarasikan). Daftar properti tersbeut diterapkan ke keseluruhan struktur objek. Jadi objek-objek dalam `participants` kosong, karena `name` tidak ada dalam daftar. Mari memasukkan semua properti ke dalam daftar kecuali properti `room.occupiedBy` yang dapat menyebabkan referensi sirkular: ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup mereferensikan room }; room.occupiedBy = meetup; // room mereferensikan meetup alert( JSON.stringify(meetup, *!*['title', 'participants', 'place', 'name', 'number']*/!*) ); /* { "title":"Conference", "participants":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */ ``` Kini semuanya kecuali `occupiedBy` sudah di-serialisasi. Tapi daftar propertinya masih cukup panjang. Untungnya, kita bisa menggunakan sebuah fungsi ketimbang sebuah *array* sebagai `replacer`. Fungsi tersebut akan dipanggil pada setiap pasangan `(key, value)` dan mengembalikan nilai "replaced", yang mana akan digunakan dan bukan nilai aslinya. Atau `undefined` jika nilai tersebut diatur agar dilewatkan. Dalam kasus kita, kita bisa mengembalikan `value` "as is" untuk semua hal kecuali `occupiedBy`. Untuk mengabaikan `occupiedBy`, kode berikut ini mengembalikan `undefined`: ```js run let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup mereferensikan room }; room.occupiedBy = meetup; // room mereferensikan meetup alert( JSON.stringify(meetup, function replacer(key, value) { alert(`${key}: ${value}`); return (key == 'occupiedBy') ? undefined : value; })); /* pasangan key:value yang menuju ke 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] */ ``` Mohon diingat, bahwa fungsi `replacer` mendapatkan setiap pasang *key/value* termasuk objek-objek *nested* dan *item* dalam *array*. Hal tersebut dapat diterapakn berulang (*recursive*). Nilai dari `this` dalam `replacer` adalah objek yang mengendung properti yang sekarang. Panggilan pertama itu khusus. Panggilan pertama tersebut dibuat menggunakan sebuah "wrapper object" yang khusus: `{"": meetup}`. Dengan kata lain, pasangan `(key, value)` pertama memiliki sebuah kunci kosong, dan nilainya adalah objek sasaran seutuhnya. Itulah mengapa baris pertama dalam contoh di atas adalah `":[object Object]"`. Ide tersebut adalah untuk menyediakan sebanyak mungkin kemampuan pada `replacer`: ide tersebut punya sebuah kesempatan untuk menganalisis dan menggantikan/melewatkan hingga keseluruhan objek jika perlu. ## Proses *Formatting*: *space* Argumen ke-tiga dari `JSON.stringify(value, replacer, space)` adalah jumlah ruang (*space*) yang digunakan untuk *formatting* yang apik. Sebelumnya, semua objek yang di-*stringify* tidak memiliki ruang tambahan. Hal tersebut tidak masalah jika kita ingin mengirim sebuah objek melalui sebuah jaringan. Argumen `space` digunakan secara ekslusif demi sebuah hasil yang apik. Di sini `space = 2` memberitahukan JavaScript untuk menunjukkan objek-objek *nested* pada beberapa baris, dengan kedalaman (*indentation*) sebanyak 2 ruang (*space*) di dalam sebuah objek: ```js run let user = { name: "John", age: 25, roles: { isAdmin: false, isEditor: true } }; alert(JSON.stringify(user, null, 2)); /* indent dengan dua spasi: { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */ /* untuk JSON.stringify(user, null, 4) hasilnya kan lebih menjorok ke dalam: { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */ ``` Parameter `space` digunakan hanya untuk pencatatan dan tujuan-tujuan yang bertujuan menghasilkan *output* yang apik. ## "toJSON" khusus Seperti `toString` untuk konversi *string*, sebuah objek dapat menyediakan metode `toJSON` untuk konversi ke JSON. `JSON.stringify` secara otomatis akan memanggil metode tersebut jika tersedia. Contohnya: ```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) } */ ``` Di sini kita bisa lihat bahwa `date` `(1)` menjadi sebuah *string*. Itu karena semua tanggal memiliki sebuah metode `toJSON` yang sudah *built-in* yang mana mengembalikan *string* seperti itu. Kini mari menambahkan sebuah `toJSON` khusus untuk objek kita `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 */!* } */ ``` Seperti yang kita lihat, `toJSON` digunakan untuk memanggil langsung `JSON.stringify(room)` serta ketikan `room` bersarang (*nested*) dalam objek lain yang ter-*encode*. ## JSON.parse Untuk men-*decode* sebuah *string* JSON, kita memerlukan sebuah metode lain bernama [JSON.parse](mdn:js/JSON/parse). Sintaksnya: ```js let value = JSON.parse(str, [reviver]); ``` *str* : *string* JSON untuk di-*parse*. *reviver* : Fungsi opsional (*key,value*) yang akan dipanggil setiap pasang `(key, value)` dan dapat mengubah nilai. Sebagai contoh: ```js run // array yang di-stringify let numbers = "[0, 1, 2, 3]"; numbers = JSON.parse(numbers); alert( numbers[1] ); // 1 ``` Atau untuk objek-objek yang *nested*: ```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 ``` JSON bisa saja menjadi kompleks seiring jika perlu, objek-objek dan *array* bisa sudah termasuk objek-objek lain serta *array* lain. Namun, mereka (objek dan *array* lain tersebut) harus mematuhi format JSON yang sama. Berikut ini adalah beberapa kesalahan umum saat penulisan langsung JSON (terkadang kita harus menuliskannya untuk tujuan *debugging*): ```js let json = `{ *!*name*/!*: "John", // kesalahan: nama properti tanpa tanda kutip "surname": *!*'Smith'*/!*, // kesalahan: nilai menggunakan tanda petik (harus tanda kutip) *!*'isAdmin'*/!*: false // kesalahan: menggunakan tanda petik pada key (harus tanda kutip) "birthday": *!*new Date(2000, 2, 3)*/!*, // kesalahan: tidak boleh ada "new", hanya berupa nilai saja "friends": [0,1,2,3] // tidak ada kesalahan }`; ``` Selaain itu semua, JSON tidak mendukung komentar. Menambahkan sebuah komentar ke JSON akan membuat JSON tersebut tidak valid. Terdapat format lain yang dinamakan [JSON5](http://json5.org/), yang mengizinkan *key* tanpa tanda kutip, adanya komentar dan lain-lain. Tapi ini adalah *library* yang berdiri sendiri, tidak terdapat dalam spesifikasi bahasa pemrograman. JSON biasa memang seketat itu bukan karena para pengembangnya malas, tetapi agar implementasinya mudah, dapat diandalkan dan cepat saat proses *parsing* algoritma. ## Menggunakan *reviver* Bayangkan, kita mendapat sebuah objek `meetup` yang telah di-*stringify* dari server. Objek tersebut akan terlihat seperti ini: ```js // title: (judul meetup), date: (tanggal meetup) let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; ``` ...Dan sekarang kita perlu untuk men-deserialisasi (*deserialize*) objek itu, untuk mengembalikannya menjadi objek JavaScript. Mari lakukan dengan memamnggil `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! */!* ``` Upps! Ada error! Nilai dari `meetup.date` adalah sebuah *string*, bukan sebuah objek `Date`. Bagaimana cara `JSON.parse` tahu bahwa ia harus mengubah *string* itu menjadi sebuah `Date`? Mari oper ke `JSON.parse` fungsi yang digunakan lagi sebagai argumen kedua, yang mengembalikan semua nilai "as is", tetapi `date` akan menjadi sebuah objek `Date` dengan format yang benar: ```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() ); // kini bekerja! ``` By the way, that works for nested objects as well: ```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() ); // berhasil bekerja! */!* ``` ## Kesimpulan - JSON adalah sebuah format data yang memiliki standar dan *library*-nya sendiri untuk sebagian besar bahasa-bahasa pemrograman. - JSON mendukung objek-objek polos, *array*, *string*, angka, *boolean*, dan `null`. - JavaScript menyediakan metode-metode [JSON.stringify](mdn:js/JSON/stringify) untuk men-serialisasi objek menjadi JSON serta [JSON.parse](mdn:js/JSON/parse) untuk menbaca objek dari JSON. - Kedua metode tersebut mendukung fungsi-fungsi pengubah untuk proses pembacaan (*reading*)/penulisan (*writing*) yang cerdas. - Jika sebuah objek memiliki `toJSON`, lalu metode tersebut akan dipanggil oleh `JSON.stringify`. ================================================ FILE: 1-js/05-data-types/index.md ================================================ # Tipe data Struktur data lainnya dan pembelajaran lebih dalam tentang tipe-tipe. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md ================================================ Solusi menggunakan perulangan: ```js run function sumTo(n) { let sum = 0; for (let i = 1; i <= n; i++) { sum += i; } return sum; } alert( sumTo(100) ); ``` Solusi menggunakan rekursi: ```js run function sumTo(n) { if (n == 1) return 1; return n + sumTo(n - 1); } alert( sumTo(100) ); ``` Solusi menggunakan rumus: `sumTo(n) = n*(n+1)/2`: ```js run function sumTo(n) { return n * (n + 1) / 2; } alert( sumTo(100) ); ``` Catatan. Biasanya, rumus adalah solusi tercepat. Itu hanya menggunakan 3 operasi untuk angka `n` apapun. Matematika mambantu! varian perulangan adalah yang kedua dalam hal waktu. Di varian rekusif dan perulangan kita menambahkan angka yang sama. Tapi rekursi melibatkan pemanggilan bercabang dan manajemen tumpukan eksekusi. Itu juga memakan sumberdaya, jadi itu lebih lambat. Catatan+. Beberapa mesin mendukung optimasi "tail call": jika sebuah pemanggilan rekursi adalah yang paling terakhir didalam fungsi (seperti dalam `sumTo` diatas), maka fungsi terluar tidak butuh untuk melanjutkan eksekusi, jadi mesinnya tidak akan mengingat konteks dari eksekusi. Itu akan menghilangkan beban didalam memori, jadi menghitung `sumTo(100000)` menjadi mungkin. Tapi jika mesin Javascript tidak mendukung optimasi tail call (kebanyakan tidak), disana akan terdapat error: "maximum stack size exceeded", karena disana biasanya terdapat batasan dalam total ukuran stack/penumpukan. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/01-sum-to/task.md ================================================ nilai penting: 5 --- # Tambahkan seluruh angka sampai angka yang diberikan Tulis sebuah fungsi `sumTo(n)` yang mengkalkulasikan penambahan dari angka `1 + 2 + ... + n`. Contoh: ```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 ``` Buatlah jawaban dengan 3 varian: 1. Gunakan perulangan 2. Gunakan rekursi, karena `sumTo(n) = n + sumTo(n-1)` untuk `n > 1`. 3. Gunakan rumus [arithmetic progression](https://en.wikipedia.org/wiki/Arithmetic_progression). Contoh dari hasil: ```js function sumTo(n) { /*... kodemu ... */ } alert( sumTo(100) ); // 5050 ``` Catatan. Varian solusi mana yang lebih cepat? yang lebih lambat? kenapa? Catatan+. Bisakah kita gunakan rekursi untuk menghitung `sumTo(100000)`? ================================================ FILE: 1-js/06-advanced-functions/01-recursion/02-factorial/solution.md ================================================ Secara definisi, sebuah faktorial adalah `n!` bisa ditulis juga sebagai `n * (n-1)`. Dengan kata lain, hasil dari `factorial(n)` bisa juga dikalkulasikan sebagai `n` dikalikan dengan hasil dari `factorial(n-1)`. Dan pemanggilan untuk `n-1` bisa secara rekursi menurun, dan terus menurun sampai `1`. ```js run function factorial(n) { return (n != 1) ? n * factorial(n - 1) : 1; } alert( factorial(5) ); // 120 ``` Basis dari rekursi adalah nilai `1`. Kita jika bisa membuat basis `0` disini, tidak akan berpengaruh banyak, tapi memberikan satu lagi tingkat rekursi: ```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 ================================================ nilai penting: 4 --- # Kalkulasikan faktorial [factorial](https://en.wikipedia.org/wiki/Factorial) dari sebuah angka natural adalah sebuah angka yang dikalikan dengan `"angka minus satu"`, lalu dengan `"angka minus dua"`, dan terus sampai `1`. Faktorial dari `n` di notasikan sebagai `n!`. Kita bisa menulis definisi dari faktorial seperti ini: ```js n! = n * (n - 1) * (n - 2) * ...*1 ``` Nilai dari faktorial untuk `n` yang berbeda: ```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 ``` Tugasnya adalah untuk menulis sebuah fungsi `factorial(n)` yang mengkalkulasikan `n!` menggunakan pemanggilan rekursi. ```js alert( factorial(5) ); // 120 ``` Catatan. Petunjuk: `n!` bisa juga ditulis sebagai `n * (n-1)!`, Contoh: `3! = 3*2! = 3*2*1! = 6` ================================================ FILE: 1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md ================================================ Solusi pertama kita harus coba adalah rekursi. Angka fibonacci adalah rekursi dari definisinya: ```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); // akan sangat lambat! ``` ...Tapi untuk nilai besar dari `n` akanlah sangat lambat. Contoh, `fib(77)` mungkin akan memberatkan mesinnya dan untuk beberapa saat akan menggunakan seluruh sumberdaya CPU. Itu karena fungsinya memanggil terlalu banyak pemanggilan. Nilai yang sama akan terus di evaluasi lagi dan lagi. Contoh, kita lihat satu potongan dari kalkulasi untuk `fib(5)`: ```js no-beautify ... fib(5) = fib(4) + fib(3) fib(4) = fib(3) + fib(2) ... ``` Disini kita bisa melihat nilai dari `fib(3)` dibutuhkan untuk `fib(5)` dan `fib(4)`. Jadi `fib(3)` akan dipanggil dan dievaluasi dua kali secara bergantian. Ini adalah pohon rekursi penuh: ![fibonacci recursion tree](fibonacci-recursion-tree.svg) Kita dengan jelas bisa memperhatikan bahwa `fib(3)` dievaluasi dua kali dan `fib(2)` di evaluasi tiga kali. Total dari komputasi akan terus membesar secara cepat lebih dari `n`, membuatnya besar sekali bahkan untuk `n=77`. Kita bisa mengoptimasinya dengan mengingat nilai yang telah dievaluasi: jika sebuah nilai katakan `fib(3)` dikalkulasi sekali, lalu kita bisa menggunakan nilainya lagi di komputasi selanjutnya. Varian lainnya haruslah menyerah dengan rekursi dan menggunakan algoritma lain yang benar-benar berbeda. Daripada memulai dari `n` ke nilai yang dibawahnya, kota bisa membuat perulangan dimulai dari `1` dan `2`, lalu mendapatkan `fib(3)` sebagai nilai penambahan mereka, lalu `fib(4)` sebagai nilai penambahan dua bilangan sebelumnya, lalu `fib(5)` dan terus naik, sampai itu mencapai nilai yang dibutuhkan. Untuk setiap langkah kita hanya membutuhkan nilai dari kedua bilangan sebelumnya. Ini adalah detail langkah dari algoritma baru. Mulai: ```js // a = fib(1), b = fib(2), nilai-nilai ini adalah definisi dari 1 let a = 1, b = 1; // get c = fib(3) sebagai penambahan mereka let c = a + b; /* sekarang kita punya fib(1), fib(2), fib(3) a b c 1, 1, 2 */ ``` sekarang kita ingin mendapatkan `fib(4) = fib(2) + fib(3)`. Lalu kita ubah variabelnya: `a,b` akan mendapatkan `fib(2),fib(3)`, dan `c` akan mendapatkan penambahan mereka: ```js no-beautify a = b; // now a = fib(2) b = c; // now b = fib(3) c = a + b; // c = fib(4) /* sekarang kita punya urutannya: a b c 1, 1, 2, 3 */ ``` Langkah selanjutnya akan memberikan urutan angka lainnya: ```js no-beautify a = b; // now a = fib(3) b = c; // now b = fib(4) c = a + b; // c = fib(5) /* sekarang urutannya adalah (satu angka lagi): a b c 1, 1, 2, 3, 5 */ ``` ...Dan seterusnya sampai kita mendapatkan nilai yang dibutuhkan. Itu lebih cepat daripada rekursi dan tidak melibatkan duplikasi komputasi Semua kodenya: ```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 ``` Perulangak dimulai dari `i=3`, karena yang urutan nilai pertama dan kedua sudah dikode (hard-coded) kedalam variabel `a=1`, `b=1`. Pendekatan ini dipanggil dengan [dynamic programming bottom-up](https://en.wikipedia.org/wiki/Dynamic_programming). ================================================ FILE: 1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md ================================================ nilai penting: 5 --- # Angka fibonacci Urutan dari [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) mempunyai rumus Fn = Fn-1 + Fn-2. Dengan kata lain, angka selanjutnya adalah penambahan dari dua angka sebelumnya. Dua angka pertama adalah `1`, lalu `2(1+1)`, lalu `3(1+2)`, `5(2+3)` dan seterusnya: `1, 1, 2, 3, 5, 8, 13, 21...`. Angka fibonnaci adalah angka yang terkait kedalam [Golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) dan fenomena natural lainnya disekitar kita. Buat sebuah fungsi `fib(n)` yang mengembalikan angka fibonacci ke-`n-th`. Contoh hasil: ```js function fib(n) { /* kodemu */ } alert(fib(3)); // 2 alert(fib(7)); // 13 alert(fib(77)); // 5527939700884757 ``` Catatan. Fungsinya haruslah cepat. Pemanggilan ke `fib(77)` haruslah tidak memakan lebih dari beberapa detik. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md ================================================ # solusi berbasis perulangan Varian solusi berbasis perulangan: ```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); ``` Perhatikan bahwa kita menggunakan variabel semebtara `tmp` untuk menyusuri daftarnya. Secara teknis, malah kita bisa menggunakan parameter fungsi `list`: ```js function printList(list) { while(*!*list*/!*) { alert(list.value); list = list.next; } } ``` ...Tapi itu kurang tepat. Nanti mungkin kita ingin memperbesar fungsinya, melakukan sesuatu dengan daftarnya. Jika kita merubah `list`, maka kita akan kehilangan kemampuannya. Berbicara tentang nama variabel yang bagus, `list` disini adalah menandakan bahwa dirinya sendiri adalah list/daftar. Elemen pertama dari itu. Dan itu harus tetap seperti itu. Itu jelas dan dapat diandalkan. Dari sisi lainnya, peran dari `tmp` sendiri secara eksklusif adalah daftar traversal, seperti `i` didalam perulangan `for`. # Solusi rekursif Varian rekursif dari `printList(list)` mengikuti logika yang sederhana: untuk mengeluarkan sebuah daftar kita harus mengeluatkan elemen saat ini dari `list`, lalu lakukan hal yang sama untuk `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); // keluarkan item yang sekarang if (list.next) { printList(list.next); // lakukan hal yang sama dengan sisa item dalam list } } printList(list); ``` Sekarang mana yang lebih baik? Secara teknis, perulanganlah yang lebih efektif. Kedua varian itu melakukan hal yang sama, tapi perulangan tidak menghabiskan sumberdaya untuk pemanggilan fungsi yang bercabang. Disisi lainnya, varian rekursi lebih pendek dan terkadang lebih mudah untuk dimengerti. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md ================================================ nilai penting: 5 --- # Keluarkan sebuah daftar single-linked Katakan kita punya sebuah daftar single-linked (seperti yang dideskripsikan pada bab ); ```js let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; ``` Tulis sebuah fungsi `printList(list)` yang mengeluarkan item dalam daftar satu per satu. Buatlah dua varian dari solusinya: gunakan perulangan dan gunakan rekursi. Mana yang lebih baik: dengan rekursi atau tanpa rekursi? ================================================ FILE: 1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md ================================================ # Menggunakan rekursi Logika rekursi sedikit lebih rumit disini. Pertama kita harus mengeluarkan sisa item di daftarnya dan *lalu* mengeluarkan item yang sekarang dipilih. ```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); ``` # Menggunakan perulangan Varian perulangan juga sedikit lebih rumit daripada mengeluarkannya secara langsung. Tidak ada cara untuk mendapatkan nilai terakhir didalam `list` kita. Kita juga tidak bisa "berjalan mundur". Jadi apa yang kita bisa lakukan adalah pertama susuri seluruh item secara langsung dan mengingat mereka didalam array, dan lalu mengeluarkan apa yang diingat dengan urutan terbalik: ```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); ``` Perhatikan baik-baik bahwa solusi rekursi melakukan hal yang sama persis: itu akan menyusuri daftar, mengingat item-itemnya didalam rantai dari pemanggulan bercabang (dalam konteks penumpukan eksekusi), dan lalu mengeluarkan hasilnya. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md ================================================ nilai penting: 5 --- # keluarkan item single-linked dari list dengan urutan terbalik Keluarkan item single-linked dari daftar seperti tugas sebelumnya namun secara terbalik. Buatlah dua solusi: menggunakan perulangan dan menggunakan rekursi. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/article.md ================================================ # Rekursi dan tumpukan (Recursion and stack) Ayo kita kembali ke fungsi dan belajar tentanya lebih dalam lagi. Topik pertama kita adalah *rekursi*. Jika programming bukanlah hal baru untukmu, maka kamu mungkin familiar dan kamu bisa melewatkan bab ini. Rekursi adalah pola programming yang sangat berguna didalam situasi dimana sebuah task bisa secara natural dibagi menjadi beberapa task yang memiliki jenis yang sama, tapi lebih sederhana. Atau ketika task bisa dibuat lebih sederhana menjadi sebuah aksi ditambah varian yang lebih sederhana dari beberapa task yang serupa. ketika sebuah fungsi menyelesaikan sebuah task, didalam proses itu bisa memanggila beberapa fungsi lainnya. Sebagian kasus dari ini ketika sebuah fungsi memanggil *dirinya sendiri*. Itulah yang disebut rekursi. ## Cara berfikir dua arah Untuk sesuatu yang sederhana dimulai dengan -- ayo kita buat sebuah fungsi `pow(x, n)` yang menaikan `x` dengan pangkat dari `n`. Dengan kata lain, mengkalikan `x` dengan dirinya sendiri sebanyak `n` kali. ```js pow(2, 2) = 4 pow(2, 3) = 8 pow(2, 4) = 16 ``` Terdapat dua cara untuk mengimplementasikan hal itu. 1. Pemikiran interaktif: perulangan `for`: ```js run function pow(x, n) { let result = 1; // kalikan hasil dari x n kali didalam perulangan for (let i = 0; i < n; i++) { result *= x; } return result; } alert( pow(2, 3) ); // 8 ``` 2. Pemikiran rekursif: sederhanakan tugasnya dan panggil diri-sendiri: ```js run function pow(x, n) { if (n == 1) { return x; } else { return x * pow(x, n - 1); } } alert( pow(2, 3) ); // 8 ``` Ingat baik-baik bagaimana varian rekursif secara dasar berbeda. Ketika `pow(x, n)` dipanggil, eksekusinya dibagi menjadi dua cabang: ```js if n==1 = x / pow(x, n) = \ else = x * pow(x, n - 1) ``` 1. Jika `n == 1`, maka semuanya menjadi tidak penting. Itulah yang dipanggil dengan *dasar* dari rekursi, karena itu langsung menghasilkan nilai yang jelas `pow(x, 1)` sama dengan `x`. 2. Sebaliknya, kita bisa merepresentasikan `pow(x, n)` sebagai `x * pow(x, n - 1)`. Didalam matematika, satu akan ditulis xn = x * xn-1. Ini yang dipanggil dengan *Langkah rekursif*: kita mengubah tugas/task menjadi aksi yang lebih sederhana (perkalian dengan `x`) dan sebuah pemanggilan yang lebih sederhana dari tugas/task yang sama (`pow` dengan menurunkan `n`). Langkah selanjutnya menyederhanakan itu lebih jauh sampai `n` mencapai `1`. Kita bisa berkata bahwa `pow` *secara rekursif memanggil dirinya sendiri sampai `n == 1`. ![recursive diagram of pow](recursion-pow.svg) Contoh, untuk mengkalkulasi `pow(2, 4)` varian rekursi melakukan langkah-langkah ini: 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` Jadi, rekursi mengurangi sebuah pemanggilan fungsi menjadi yang lebih sederhana, dan lalu -- bahkan lebih sederhana, dan seterusnya, sampai hasilnya menjadi jelas. ````smart header="Rekursi biasanya lebih pendek" Solusi rekursi biasanya lebih pendek daripada sebuah iterasi. Disini kita bisa menulis ulang menggunakan operator konsional `?` daripada `if` untuk membuat `pow(x, n)` lebih pendek dan tetap mudah dibaca: ```js run function pow(x, n) { return (n == 1) ? x : (x * pow(x, n - 1)); } ``` ```` Angka maksimal dari pemanggilan bercabang (termasuk yang pertama) dipanggil dengan *kedalaman rekursi/recursion depth*. Di kasus kita, itu akan persis `n`. Maksimal kedalaman rekursi dibatasi oleh mesin Javascript. Kita bisa berkata bahwa itu mungkin 10000, beberapa mesin bisa lebih tapi 100000 mungkin sudah diluar batas dari kebanyakan mesin. Terdapat optimasi otomatis yang membantu meringankan ini("optimasi tail calls"), tapi mereka belum sepenuhnya didukung di semuanya dan hanya bekerja pada kasus yang sederhana. Itu membatasi aplikasi dari rekursi, tapi itu tetaplah cukup besar. Disana terdapat task dimana cara berfikir rekursif membuat kode lebih sederhana, dan lebih mudah diperlihara. ## Konteks eksekusi dan tumpukan Sekarang ayo kita membahas bagaimana pemanggilan rekursi bekerja. Untuk itu kita akan melihat isi dari fungsi. Informasi tentang proses dari eksekusi dari sebuah fungsi yang berjalan disimpan didalam *konteks eksekusi*nya. [Execution context](https://tc39.github.io/ecma262/#sec-execution-contexts) adalah sebuah struktur data internal yang mengandung detail tentang eksekusinya dari sebuah fungsi: dimana alur kontrolnya adalah sekarang, variabel yang sekarang, nilai dari `this` (kita tidak akan mengunakan ini disini) dan beberapa detail internal lainnya. Satu pemanggilan fungsi mempunyai tepat satu konteks eksekusi yang terkait dengannya. Ketika sebuah fungsi melakukan pemanggilan bercabang, Hal berikut terjadi: - Fungsi yang sekarang dihentikan sementara -- paused. - Konteks eksekusi yang terkait dengannya diingat dalam sebuah struktur data spesial dipanggil dengan *tumpukan konteks eksekusi*. - Pemanggilan bercabang dieksekusi. - Setelah itu selesai, eksekusi konteks yang lama diterima dari tumpukan, dan fungsi terluar dilanjutkan dari mana itu berhenti. Ayo kita lihat apa yang terjadi selama pemanggilan `pow(2, 3)`. ### pow(2, 3) Di awal pemanggilan `pow(2, 3)` konteks eksekusi akan menyimpan variabel: `x = 2, n = 3`, alur eksekusi berada pada baris `1` dari fungsi. Kita bisa menggambarkannya seperti:
  • Context: { x: 2, n: 3, at line 1 } pow(2, 3)
Itu ketika fungsi mulai dieksekusi. Kondisinya `n == 1` adalah false, jadi alurnya berlanjut ke cabang kedua dari `if`: ```js run function pow(x, n) { if (n == 1) { return x; } else { *!* return x * pow(x, n - 1); */!* } } alert( pow(2, 3) ); ``` Variabelnya juga sama, tapi barisnya berubah, jadi konteksnya sekarang:
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
Untuk mengkalkulasikan `x * pow(x, n -1)`, kita perlu membuat subcall dari `pow` dengan argumen baru `pow(2, 2)`. ### pow(2, 2) Untuk melakukan pemanggilan bercabang, javascript mengingat konteks eksekusi yang sekarang didalam *tumpukan konteks eksekusi*. Disini kita memanggil fungsi yang sama `pow`, tapi itu tidaklah penting. Prosesnya sama untuk semua fungsi: 1. Konteks yang sekarang telah "mengingat" tumpukan teratas. 2. Konteks baru dibuat untuk subcall. 3. Ketika subcall telah selesai -- konteks sebelumnya dikeluarkan dari tumpukan, dan eksekusinya dilanjutkan. Here's the context stack when we entered the subcall `pow(2, 2)`: Disini tumpukan konteks ketika kita memasuki subcallnya `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)
Konteks eksekusi baru yang sekarang berada di atas (dan jelas), dan konteks yang sebelumnya berada dibawah. Ketika kita menyelesaikan subcall -- itu akan mudah untuk melanjutkan konteks sebelumnya, karena itu tetap menyimpan kedua variabel dan tempat yang tepat dimana kode itu berhenti. ```smart Disini dialam gambar kita gunakan kata "line", sebagai contoh disana terdapat satu subcall didalam baris, tapi secara umum sebuah baris dari kode mungkin mengandung subcall ganda, seperti `pow(…) + pow(…) + somethingElse(…)`. Jadi itu harus menjadi lebih presisi untuk dikatakan eksekusi berlanjut "langsung seterlah subcall". ``` ### pow(2, 1) Prosesnya diulangi: subcall baru dibuat pada baris `5`, sekarang dengan argumen `x=2`, `n=1`. Sebuah konteks eksekusi baru dibuat, yang sebelumnya didorong dari atas tumpukan:
  • 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)
Disana terdapat 2 konteks lama sekarang dan 1 sedang berjalan untuk `pow(2, 1)`. ### Keluar Selama eksekusi dari `pow(2, 1)`, tidak seperti sebelumnya, kondisi `n == 1` sekarang bernilai true, jadi cabang pertama dari `if` bekerja: ```js function pow(x, n) { if (n == 1) { *!* return x; */!* } else { return x * pow(x, n - 1); } } ``` Disana sekarang tidak ada lagi pemanggilan bercabang, jadi fungsinya selesai, mengembalikan `2`. Setelah fungsinya selesai, konteks eksekusinya tidak dibutuhkan lagi, jadi itu akan dihilangkan dari memori. Satu yang sebelumnya dikembalikan dari atas tumpukan:
  • Context: { x: 2, n: 2, at line 5 } pow(2, 2)
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
Eksekusi dari `pow(2, 2)` dilanjutkan. Itu telah mempunyai hasil dari subcall `pow(2, 1)`, jadi itu bisa menyelesaikan evaluasi dari `x * pow(x, n - 1)`, mengembalikan `4`. Konteks sebelumnya dikembalikan:
  • Context: { x: 2, n: 3, at line 5 } pow(2, 3)
Ketika itu selesai, kita mempunyai hasil dari `pow(2, 3) = 8`. Dalam kasus ini kedalaman rekursinya adalah: **3**. Seperti yang bisa kita lihat dari ilustrasi diatas, kedalaman rekursi sama dengan nilai maksimal dari konteks didalam tumpukan. Catatan kebutuhan memori. Konteks memakan memori. Didalam kasus ini, menaikan dengan pangkat dari `n` sebenarnya membutuhkan memori sebanyak `n` konteks, untuk semua nilai terendah dari `n`. Algoritma berbasis perulangan lebih menghemat memori: ```js function pow(x, n) { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } ``` Interatif `pow` menggunakan konteks tunggal mengganti `i` dan `result` didalam prosesnya. Kebutuhan memorinya kecil, tidak berubah-ubah dan tidak tergantung kepada `n`. **Rekursi apapun bisa ditulis ulang sebagai perulangan. Varian perulangan biasanya bisa dibuat lebih efektif.** ...Tapi terkadang menulis ulang bukanlah hal yang sepele, terutama ketika fungsi menggunakan pemanggilan rekursif yang berbeda tergantung dari kondisi dan menyatukan hasil mereka atau cabangnya lebih rumit. Dan optimasinya mungkin tidak dibutuhkan dan benar-benar menghabiskan tenaga. Rekursi bisa memberikan kode yang lebih pendek, lebih mudah dimengerti dan didukung. Optimasi tidak dibutuhkan di setiap tempat, kebanyakan kita butuh kode yang bagus, itulah kenapa itu digunakan. ## Rekursif traversal Penerapan bagus lainnya dari rekursi adalah rekursif traversal. Bayangkan, kita punya sebuah perusahaan. Struktur karyawannya bisa dipresentasikan sebagai sebuah objek: ```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 }] } }; ``` Dengan kata lain, perusahaannya mempunyai departemen. - Sebuah departemen mungkin mempunyai sebuah array untuk staf. Contoh, departemen `sales` mempunyai dua karyawan John dan Alice. - Atau sebuah departemen mungkin dibagi menjadi sub-departemen, like `development` mempunyai dua cabang: `sites` dan `internals`. Untuk masin-masing memiliki stafnya masing-masing. - Hal yang mungkin terjadi adalah ketika sub-departemennya berkembang, itu akan terbagi menjadi sub-sub-departemen (tau tim). Contoh, departemen `sites` di masa depan mungkin akan terbagi menjadi tim `siteA` dan `siteB`. Dan mereka, memiliki kemungkinan, terbagi lagi. Itu bukanlah sebuah gambaran, hanya sesuatu yang bisa terfikirkan. Sekarang kita ingin sebuah fungsi untuk mendapatkan jumlah dari seluruh gaji. Bagaiman kita melakukannya? Sebuah pendekatan iteratif tidaklah mudah, karena strukturnya tidak sederhana. Pertama mungkin untuk membuat perulangan `for`didalam `company` dengan sub-perulangan bercabang didalam departemen level 1. Tapi kita butuh lebih banyak sub-perulangan untuk mengiterasi staf didalam departemen level 2 seperti `sites`... Dan lalu sub-perulangan lainnya didalam departemen level 3 yang mungkin muncul di masa mendatang? Jika kita menggunakan 3-4 sub-perulangan didalam kode untuk menjelajahi objek tunggal, itu akan terlihat jelek. Ayo kita coba rekursi. Seperti yang bisa kita lihat, ketika fungsi mendapatkan departemen untuk dijumlahkan, disana terdapat dua kemungkinan: 1. Antara itu adalah sebuah departemen "simpel" dengan sebuah array dari orang -- lalu kita bisa menjumlahkan gajinya dengan perulangan yang sederhana. 2. Atau itu adalah *sebuah objek* dengan `N` sub-departemen -- maka kita bisa membuat pemanggilan rekursif `N` untuk mendapatkan jumlah untuk setiap sub-departemen dan menjumlahkan hasilnya. Dalam kasus pertama adalah dasar dari rekursi, kasus biasa, ketika kita mendapatkan sebuah array. Kasus kedua ketika kita mendapatkan sebuah objek adalah langkah rekursif. Sebuah task yang kompleks dibagi menjadi sub-task untuk departemen yang lebih kecil. Mereka mungkin nanti akan terbagi lagi, tapi cepat atau lambat pembagiannya akan selesai pada (1). x Algoritmanya mungkin lebih mudah untuk dibaca dari kodenya: ```js run let company = { // objek yang sama, dikompresi untuk keringkasan sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }], development: { sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }], internals: [{name: 'Jack', salary: 1300}] } }; // Fungsinya melakukan pekerjaannya *!* function sumSalaries(department) { if (Array.isArray(department)) { // kasus (1) return department.reduce((prev, current) => prev + current.salary, 0); // jumlahkan arraynya } else { // kasus (2) let sum = 0; for (let subdep of Object.values(department)) { sum += sumSalaries(subdep); // secara rekursif memanggil sub-departemen, jumlahkan hasilnya } return sum; } } */!* alert(sumSalaries(company)); // 7700 ``` Kodenya pendek dan mudah untuk dimengerti (semoga?). Itulah kemampuan dari rekursi. Itu juga bekerja untuk level apapun dari sub-departemen bercabang. Ini adalah diagram dari pemanggilannya: ![recursive salaries](recursive-salaries.svg) Kita bisa dengan mudah melihat prinsipnya: untuk sebuah objek sub-pemanggilan `{...}` dibuat, semerata array `[...]` adalah daun dari pohon rekursi, mereka memberikan hasil secara langsung. Ingat bahwa kodenya menggunakan fitur pintar yang sudah kita bahas sebelumnya: - Metode `arr.reduce` menjelaskan didalam bab untuk mendapatkan jumlah dari array. - Perulangan `for(val of Object.values(obj))` untuk mengiterasi nilai didalam objek: `Object.values` mengembalikan sebuah array darinya. ## Struktur rekursif Sebuah struktur data rekursif (ditetapkan secara rekursif) adalah sebuah struktur yang mengulangi dirinya-sendiri dalam beberapa bagian. Kita juga telah melihatnya didalam contoh struktur perusahaan diatas. Sebuah *departemen* perusahaan adalah: - Diantara sebuah array dari orang-orang - Atau sebuah objek dengan *departemen*. Untuk seorang pengembang-web disana terdapat contoh yang lebih baik: HTML dan dokumen XML. Didalam dokumen HTML, sebuah *tag-HTML* mungkin mengandung daftar dari: - Potongan-potongan text. - Komentar-komentar HTML. - *Tag-HTML* Lainnya (itu mungkin saja mengandung potongan text/komentar atau tag lainnya). Itu sekali lagi adalah definisi rekursif. Untuk pemahaman lebih baik, kita akan memperlajari satu lagi struktur rekursif bernama "Linked list" itu mungkin sebuah alternatif yang bagus untuk array dalam beberapa kasus. ### Linked list Bayangkan, kita ingin menyimpan sebuah daftar terstruktur didalam sebuah objek. Pilihan naturalnya mungkin sebuah array: ```js let arr = [obj1, obj2, obj3]; ``` ...Tapi disana terdapat sebuah masalah dengan array. Operasi "delete element" dan "insert elemen" sangatlah mahal. Contoh, operasi `arr.unshift(obj)` harus memberikan nomor baru untuk membuat ruang untuk `obj` baru, dan jika arraynya sangat besar, itu akan memakan waktu. Sama dengan `arr.shift()`. Satu-satunya modifikasi struktural yang tidak membutuhkan penomoran secara besar-besaran adalah itu yang beroperasi dengan akhiran dari array: `arr.push/pop`. Jadi sebuah array bisa menjadi cukup lambat untuk antrian yang panjang, ketika kita harus bekerja dengan awalannya. Alternatifnya, jika kita benar-benar membutuhkan memasukan/menghapus dengan cepat, kiat bisa memilih data struktur lainnya bernama [linked list](https://en.wikipedia.org/wiki/Linked_list). *Element linked list* didefinisikan secara rekursif sebagai sebuah objek dengan: - `value`. - `next` properti yang mereferensi *elemen linked list* selanjutnya atau `null` jika sudah mencapai akhir. Contoh: ```js let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; ``` Representasi grafikal dari sebuah list: ![linked list](linked-list.svg) Alternatif kode untuk pembuatan: ```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; ``` Disini kita bisa melihat lebih jelas bahwa disana terdapat beberapa objek, masing-masing memiliki `value` dan `next` mengarah ke objek disisinya. Variabel `list` adalah objek pertama didalam rantainya, jadi pointer `next` selanjutnya dari itu bisa kita dapat dari elemen apapun. List-nya bisa dengan mudah dibagi menjadi beberapa bagian dan lalu disatukan kembali: ```js let secondList = list.next.next; list.next.next = null; ``` ![linked list split](linked-list-split.svg) Untuk menyatukan: ```js list.next.next = secondList; ``` Dan tentu saja kita bisa memasukan atau menghapus item dari manapun. Contoh, untuk memasukan nilai baru, kita harus memperbaharui awalan dari list-nya: ```js let list = { value: 1 }; list.next = { value: 2 }; list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; *!* // memasukan nilai baru kedalam list-nya list = { value: "new item", next: list }; */!* ``` ![linked list](linked-list-0.svg) Untuk menghapus nilai dari tengah, ganti `next` dengan yang sebelumnya: ```js list.next = list.next.next; ``` ![linked list](linked-list-remove-1.svg) Kita membuat `list.next` melompati `1` menuju nilai `2`. Nilai `1` sekarang tidak termasuk dari rentetannya. Jika itu tidak tersimpan dimanapun, itu akan secara otomatis dihapus dari memori. Tidak seperti array, disana tidak terdapat penomoran urang secara besar-besaran, kita bisa dengan mudah menyusun kembali elemen-elemennya. Umumnya, list tidak selalu lebih baik daripada array. Sebaliknya semua orang harusnya hanya menggunakan list. Kekurangannya adalah kita tidak bisa dengan mudah mengakses sebuah elemen dengan nomornya. Didalam sebuah array kita bisa dengan mudah: `arr[n]` adalah sebuah referensi langsung. Tapi didalam list kita harus memulai dari item pertama dan maju `next` `N` kali untuk mendapatkan elemen ke Nth. ...Tapi kita tidak selalu butuh operasi seperti itu. Contoh, ketika kita membutuhkan sebuah antrian atau bahkan [deque](https://en.wikipedia.org/wiki/Double-ended_queue) -- struktur urutannya harus mengijinkan penambahan/penghapusan elemen dengan cepat dari kedua sisi tapi akses kedalam bagian tengah tidak dibutuhkan. List bisa ditingkatkan: - Kita bisa menambahkan properti `prev` didalam penambahan untuk `next` untuk mereferensikan elemen sebelumnya, untuk berpindah mundur dengan mudah. - Kita juga bisa menambahkan sebuah variabel bernama `tail` mereferensi elemen terakhir dari list (dan memperbaharuinya ketika menambahkan/menghapus elemen dari ujung terakhir). - Struktur data mungkin bervariasi tergantung dari kebutuhan kita. ## Rangkuman Istilah: - *Rekursi* adalah sebuah istilah programming yang berarti memanggil fungsi dari dirinya sendiri. Fungsi rekursi bisa digunakan untuk memecahkan tugas dengan cara yang elegan. Ketika sebuah fungsi memanggil dirinya sendiri, itulah yang disebut dengan *langkah rekursi*. *Dasar* dari rekursi adalah sebuah argumen fungsi yang membuat task menjadi lebih sederhana yang dimana fungsinya tidak membuat pemanggilan lebih jauh. - Sebuah struktur data [didefinisikan secara rekursif](https://en.wikipedia.org/wiki/Recursive_data_type) adalah struktur data yang bisa mendefinisikan menggunakan dirinya sendiri. Contoh, linked list bisa didefinisikan sebagai sebuah struktur data yang terdiri dari sebuah objek yang mereferensi sebuah list (atau null). ```js list = { value, next -> list } ``` Pohon seperti pohon elemen HTML atau pohon departemen dari bab ini juga secara natural rekursif: cabang mereka dan setuap cabang mempunyai cabang lainnya. Fungsi rekursif bisa digunakan untuk menyusurinya seperti yang telah kita lihat didalam contoh `sumSalary`. Fungsi rekursif manapun bisa ditulis ulang menggunakan iterasi. Dan itu terkadang membutuhkan hal-hal optimasi. Tapi untuk kebanyakan task sebuah solusi rekursif cukup cepat dan mudah untuk ditulis dan didukung. ================================================ FILE: 1-js/06-advanced-functions/01-recursion/head.html ================================================ ================================================ FILE: 1-js/06-advanced-functions/02-rest-parameters-spread/article.md ================================================ # Parameter rest dan sintaks spread Banyak fungsi bawaan Javascript yang mendukung argumen dengan angka yang panjang. Contoh: - `Math.max(arg1, arg2, ..., argN)` -- mengembalikan nilai terbesar dari argumen. - `Object.assign(dest, src1, ..., srcN)` -- menyalin properti dari `src1..N` kedalam `dest`. - ...dan lainnya. Didalam bab ini kita akan belajar bagaimana cara untuk melakukan hal yang sama, bagaimana cara untuk mengirim array kepada fungsi seperti itu sebagai parameter. ## Parameter rest `...` Sebuah fungsi dapat dipanggil dengan jumlah argumen berapapun, tidak peduli bagaimana itu didefinisikan. Seperti ini: ```js run function sum(a, b) { return a + b; } alert( sum(1, 2, 3, 4, 5) ); ``` Disana tidak akan terdapat error karena argumen "berlebihan". Tapi tentu saja hasilnya hanya dua angka pertama yang dihitung. Sisa parameternya bisa digunakan didalam fungsi dengan menggunakan tiga titik `...` diikuti nama dari array yang akan berisi mereka. Titik secara harfiah berarti "kumpulkan sisa parameter didalam array". Contoh, untuk mengumpulkan seluruh argumen menjadi array `args`: ```js run function sumAll(...args) { // args adalah nama dari arraynya 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 ``` Kita bisa memilih untuk mendapatkan parameter pertama sebagai variabel, dan sisanya dikumpulkan. Disini dua parameter pertama akan dimasukan kedalam variabel dan sisanya akan masuk kedalam array `titles`: ```js run function showName(firstName, lastName, ...titles) { alert( firstName + ' ' + lastName ); // Julius Caesar // sisanya masuk kedalam array 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="Parameter rest harus berada di akhir" Parameter rest mengumpulkan seluruh sisa argumen, jadi contoh dibawah tidak dapat dimengerti dan akan menyebabkan error: ```js function f(arg1, ...rest, arg2) { // arg2 after ...rest ?! // error } ``` `...rest` harus selalu berada di akhir. ```` ## Variable "arguments" Juga terdapat objek spesial seperti array bernama `arguments` yang mengandung seluruh argumen dengan indeksnya. Contoh: ```js run function showName() { alert( arguments.length ); alert( arguments[0] ); alert( arguments[1] ); // dapat diiterasi // for(let arg of arguments) alert(arg); } // menampilkan: 2, Julius, Caesar showName("Julius", "Caesar"); // menampilkan: 1, Ilya, undefined (tidak ada argumen kedua) showName("Ilya"); ``` Dahulu, parameter rest tidak ada didalam bahasa pemrograman, dan menggunakan `arguments` hanyalah satu-satunya cara untuk mendapatkan seluruh argumen dari sebuah fungsi. Dan itu tetap bekerja, kita bisa menemukannya di kode-kode jadul. Tapi kekurangannya adalah walaupun `arguments` seperti array dan bisa diiterasi, itu bukanlah sebuah array. Itu tidak mendukung metode-metode array, jadi kita tidak bisa memanggil untuk contoh `arguments.map(...)`. Juga, itu selalu mengandung seluruh argumen. Kita tidak bisa menangkapnya dalam beberapa bagian, seperti yang kita lakukan dengan parameter rest. Jadi ketika kita membutuhkan fiturnya, parameter rest lebih disukai. ````smart header="Arrow function tidak memiliki `\"arguments\"`" Jika kita ingin mengakses objek `arguments`dari sebuah fungsi panah/arrow function, itu akan mengambilnya dari fungsi "normal" terluar. Contoh: ```js run function f() { let showArg = () => alert(arguments[0]); showArg(); } f(1); // 1 ``` Seperti yang kita ingat, arrow function tidak memiliki `this` mereka sendiri. Sekarang kita tahu mereka tidak memiliki objek `arguments` yang spesial juga. ```` ## Sintaks spread [#spread-syntax] Kita baru saja melihat bagaimana cara untuk mendapatkan sebuah array dari daftar dari sebuah parameter-parameter. Tapi terkadang kita perlu untuk melakukan hal yang sama dengan terbalik. Contoh, terdapat fungsi bawaan [Math.max](mdn:js/Math/max) yang mengembalikan angka terbesar dari list: ```js run alert( Math.max(3, 5, 1) ); // 5 ``` Sekarang kita bayangkan kita mempunyai sebuah array `[3, 5, 1]. Bagaimana caranya kita memanggil `Math.max` dengan itu? Berikan itu "kedalamnya" tidak akan bekerja, karena `Math.max` mengharapkan sebuah daftar dari argumen numerik, bukan dari array tunggal: ```js run let arr = [3, 5, 1]; *!* alert( Math.max(arr) ); // NaN */!* ``` Dan tentu saja kita tidak bisa secara manual memasukan itemnya kedalam kode `Math.max(arr[0], arr[1], arr[2])`, karena kita mungkin tidak yakin ada berapa elemen didalamnya. Lalu saat skripnya dieksekusi, disana mungkin terdapat banyak, atau mungkin tidak ada. Dan itu bukanlah hal yang bagus. *Sintaks spread* datang untuk membantu! Itu terlihat sama dengan parameter rest, juga menggunakan `...`, tapi itu melakukan yang sebaliknya. Ketika `...arr` digunakan didalam pemanggilan fungsi, itu "memperluas" sebuah objek yang bisa diiterasi `arr` kedalam daftar dari argumen. Untuk `Math.max`: ```js run let arr = [3, 5, 1]; alert( Math.max(...arr) ); // 5 (spread mengubah array menjadi daftar dari argumen) ``` Kita juga bisa memberikan beberapa hal yang bisa diiterasi dengan cara ini: ```js run let arr1 = [1, -2, 3, 4]; let arr2 = [8, 3, -8, 1]; alert( Math.max(...arr1, ...arr2) ); // 8 ``` Kita juga bisa mengkombinasikan sintaks spread dengan nilai normal: ```js run let arr1 = [1, -2, 3, 4]; let arr2 = [8, 3, -8, 1]; alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25 ``` Juga, sintaks spread bisa digunakan untuk menyatukan array-array: ```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, lalu arr, lalu 2, lalu arr2) ``` Dalam contoh diatas kita menggunakan sebuah array untuk mendemonstrasikan sintaks spread, tapi hal yang bisa diiterasi apapun bisa digunakan. Contoh, disini kita menggunakan sintaks spread untuk mengubah string menjadi array dari karakter-karakter: ```js run let str = "Hello"; alert( [...str] ); // H,e,l,l,o ``` Sintaks spread secara internal menggunakan iterator untuk menggabungkan elemen-elemen, cara yang sama seperti yang dilakukan `for..of`. Jadi, untuk sebuah string, `for..of` mengembalikan karakter-karakter dan `...str` menjadi `"H","e","l","l","o"`. Daftar dari karakter-karakter diberikan kepada penginisialisasi array `[...str]`. Untuk task tertentu kita bisa juga menggunakan `Array.from`, karena itu akan mengkonversi sebuah hal yang bisa diiterasi (seperti string) menjadi sebuah array: ```js run let str = "Hello"; // Array.from mengubah sebuah iterabel menjadi sebuah array alert( Array.from(str) ); // H,e,l,l,o ``` Hasilnya akan sama seperti `[...str]`. Tapi disana terdapat perbedaan yang tipis diantara `Array.from(obj)` dan `[...obj]`: - `Array.from` dapat dioperasikan di "hal yang seperti array" dan "hal yang bisa diiterasi". - Sintaks spread hanya bekerja dengan hal yang bisa diiterasi. Jadi, task untuk mengubah sesuatu menjadi sebuah array, `Array.from` cenderung lebih banyak digunakan. ## Mendapatkan salinan baru dari sebuah array/objek Inget ketika kita berbicara tentang `Object.assign()` [sebelumnya](info:object-copy#cloning-and-merging-object-assign)? Itu adalah hal yang bisa dilakukan untuk melakukan hal yang sama dengan sintaks spread. ```js run let arr = [1, 2, 3]; let arrCopy = [...arr]; // sebarkan arraynya menjadi list dari parameter // lalu masukan hasilnya kedalam array yang baru // apakah arraynya mempunyai konten yang sama? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true // apakah arraynya sama? alert(arr === arrCopy); // false (bukanlah referensi yang sama) // memodifikasi array awal kita tidak memodifikasi salinannya: arr.push(4); alert(arr); // 1, 2, 3, 4 alert(arrCopy); // 1, 2, 3 ``` Perhatikan bahwa melakukan hal yang sama seperti menyalin sebuah objek adalah hal yang bisa dilakukan: ```js run let obj = { a: 1, b: 2, c: 3 }; let objCopy = { ...obj }; // sebarkan objeknya menjadi daftar dari parameter // lalu kembalikan hasilnya kedalam objek baru. // apakah objeknya memiliki kontent yang sama? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true // apakah objeknya sama? alert(obj === objCopy); // false (not same reference) // memodifikasi objel awal kita tidak berarti memodifikasi salinannya: 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} ``` Menyalin objek dengan cara ini lebih pendek daripada `let objCopy = Object.assign({}, obj);` atau untuk sebuah array `let arrCopy = Objek.assign([], arr);` jadi kita lebih memilih menggunakannya kapanpun bila bisa digunakan. ## Ringkasan ketika kita melihat `"..."` didalam kode, itu adalah parameter rest atau sintaks spread. Terdapat sebuah cara yang mudah untuk membedakan mereka: - Ketika `...` berada di akhiran dari parameter fungsi, itu adalah "parameter rest" dan menggabungkan sisa dari daftar argumen menjadi sebuah array. - Ketika `...` muncul didalam pemanggilan fungsi atau sejenisnya, itu dipanggil dengan "sintaks spread" dan membentangkan array menjadi sebuah list. Penggunaan pola: - Paramter rest digunakan untuk membuat fungsi yang dapat menerima argumen dengan jumlah berapapun. - Sintkas spread digunakan untuk mengirimkan sebuah array kedalam sebuah fungsi yang biasanya membutuhkan daftar dari beberapa argumen. Bersama mereka membantu menggunakan sebuah list dan sebuah array dari parameter dengan mudah. Semua argumen dari sebuah pemanggilan fungsi juga tersedia di argumen dengan "gaya-lama": objek seperti array yang bisa diiterasi. ================================================ FILE: 1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md ================================================ Jawabannya adalah: **Pete**. Sebuah fungsi mendapatkan variabel yang sekarang, nilai paling terbaru akan digunakan. Nilai variabel lama tidak akan tersimpan dimanapun. Ketika sebuah fungsi menginginkan sebuah variabel, nilai terbaru akan diambil dari lingkungan leksikalnya atau dari luar. ================================================ FILE: 1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md ================================================ Nilai penting: 5 --- # Apakah sebuah fungsi akan mengambil perubahan terakhir? Fungsi sayHi menggunakan nama variabel dari luar. Ketika fungsinya berjalan, nilai manakah yang akan digunakan? ```js let name = "John"; function sayHi() { alert("Hi, " + name); } name = "Pete"; sayHi(); // apakah yang akan tampil: "John" atau "Pete"? ``` Situasi seperti itu adalah hal yang biasa didalam peramban dan pengembangan di bagian server. Sebuah fungsi mungkin sudah dijadwalkan untuk dieksekusi nanti daripada saat dibuat, untuk contoh setelah sebuah aksi user atau setelah me-request ke jaringan. Jadi, pertanyaannya adalah: apakah nilai terakhir akan diambil? ================================================ 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() { // fungsi shooter alert( i ); // harus menampilkan angkanya }; 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() { // fungsi shooter alert( i ); // harus menampilkan angkanya }; shooters.push(shooter); i++; } return shooters; } /* let army = makeArmy(); army[0](); // angka shooter 0 menampilkan 10 army[5](); // dan angka 5 juga mengeluarkan 10... // ... seluruh shooter menampilkan 10 daripada nilai 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 ================================================ Let's examine what exactly happens inside `makeArmy`, and the solution will become obvious. 1. It creates an empty array `shooters`: ```js let shooters = []; ``` 2. Fills it with functions via `shooters.push(function)` in the loop. Every element is a function, so the resulting array looks like this: ```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. The array is returned from the function. Then, later, the call to any member, e.g. `army[5]()` will get the element `army[5]` from the array (which is a function) and calls it. Now why do all such functions show the same value, `10`? That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment. Then, what will be the value of `i`? If we look at the source: ```js function makeArmy() { ... let i = 0; while (i < 10) { let shooter = function() { // shooter function alert( i ); // should show its number }; shooters.push(shooter); // add function to the array i++; } ... } ``` We can see that all `shooter` functions are created in the lexical environment of `makeArmy()` function. But when `army[5]()` is called, `makeArmy` has already finished its job, and the final value of `i` is `10` (`while` stops at `i=10`). As the result, all `shooter` functions get the same value from the outer lexical environment and that is, the last value, `i=10`. ![](lexenv-makearmy-empty.svg) As you can see above, on each iteration of a `while {...}` block, a new lexical environment is created. So, to fix this, we can copy the value of `i` into a variable within the `while {...}` block, like this: ```js run function makeArmy() { let shooters = []; let i = 0; while (i < 10) { *!* let j = i; */!* let shooter = function() { // shooter function alert( *!*j*/!* ); // should show its number }; shooters.push(shooter); i++; } return shooters; } let army = makeArmy(); // Now the code works correctly army[0](); // 0 army[5](); // 5 ``` Here `let j = i` declares an "iteration-local" variable `j` and copies `i` into it. Primitives are copied "by value", so we actually get an independent copy of `i`, belonging to the current loop iteration. The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration: ![](lexenv-makearmy-while-fixed.svg) Such a problem could also be avoided if we used `for` in the beginning, like this: ```js run demo 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; } let army = makeArmy(); army[0](); // 0 army[5](); // 5 ``` That's essentially the same, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration. ![](lexenv-makearmy-for-fixed.svg) Now, as you've put so much effort into reading this, and the final recipe is so simple - just use `for`, you may wonder -- was it worth that? Well, if you could easily answer the question, you wouldn't read the solution. So, hopefully this task must have helped you to understand things a bit better. Besides, there are indeed cases when one prefers `while` to `for`, and other scenarios, where such problems are real. ================================================ FILE: 1-js/06-advanced-functions/03-closure/10-make-army/task.md ================================================ nilai penting: 5 --- # Pasukan-pasukan fungsi Kode berikut membuat array dari `shooters`. Setiap fungsi diinginkan untuk mengeluarkan angkanya sendiri. Tetapi ada yang salah... ```js run function makeArmy() { let shooters = []; let i = 0; while (i < 10) { let shooter = function() { // fungsi shooter alert( i ); // seharusnya mengeluarkan angkanya sendiri }; shooters.push(shooter); // and add it to the array i++; } // ...and return the array of shooters return shooters; } let army = makeArmy(); *!* // semua penembak menunjukkan 10 bukannya angka mereka 0, 1, 2, 3... army[0](); // 10 dari nomor penembak 0 army[1](); // 10 dari penembak nomor 1 army[2](); // 10 ...dan seterusnya. */!* ``` Mengapa semua penembak menunjukkan nilai yang sama? Perbaiki kode agar berfungsi sebagaimana mestinya. ================================================ FILE: 1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md ================================================ Jawabannya adalah: **Pete**. Fungsi `work()` didalam kode mendapatkan `name` dari tempat dimana itu dibuat daripada mereferensi dari luar lingkungannya : ![](lexenv-nested-work.svg) jadi, hasilnya adalah `"Pete"` disini. Tapi jika disana tidak ada `let name` didalam `makeWorker()`, maka pencarian akan berlanjut ke luar dan mengambil variabel global seperti yang bisa kita lihat diatas. Di kasus ini hasilnya akan menjadi `"John"`. ================================================ FILE: 1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md ================================================ nilai penting: 5 --- # Variabel manakah yang tersedia? Fungsi `makeWorker` dibawah membuat fungsi lainnya dan mengembalikannya. Fungsi baru itu bisa dipanggil dari manapun. Akankah itu mempunyai akses ke variabel luar dari tempat pembuatannya, atau dari tempat pemanggilannya, atau keduanya? ```js function makeWorker() { let name = "Pete"; return function() { alert(name); }; } let name = "John"; // pembuatan fungsi let work = makeWorker(); // dipanggil work(); // apakah yang akan tampil? ``` Nilai manakah yang akan muncul? "Pete" atau "John"? ================================================ FILE: 1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md ================================================ Jawaban: **0,1.** Fungsi `counter` dan `counter2` dibuat dengan panggilan fungsi `makeCounter` yang berbeda. Jadi mereka memiliki lingkungan leksikal yang berbeda, dengan `count` mereka masing-masing. ================================================ FILE: 1-js/06-advanced-functions/03-closure/3-counter-independent/task.md ================================================ nilai penting: 5 --- # Apakah para counter independen? Di sini kita membuat dua counter: `counter` dan `counter2` menggunakan fungsi `makeCounter` yang sama. Apakah mereka independen? Apa yang akan counter kedua munculkan? `0,1` atau `2,3` atau yang lainnya? ```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 ================================================ Tentu saja hal tersebut akan bekerja. Kedua fungsi bersarang dibuat dengan lingkungan leksikal yang sama, jadi mereka membagi akses ke variabel `count` yang sama: ```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 ================================================ nilai penting: 5 --- # Objek counter Disini kita memiliki objek counter yang dibuat dengan bantuan fungsi konstruktor. Apakah hal tersebut akan bekerja? Apa yang akan muncul? ```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 ================================================ Hasilnya yaitu **sebuah error**. Fungsi `sayHi` dideklarasikan di dalam `if`, jadi fungsi tersebut hanya hidup di dalamnya. Tidak ada fungsi `sayHi` di luar. ================================================ FILE: 1-js/06-advanced-functions/03-closure/5-function-in-if/task.md ================================================ # Fungsi di dalam if Lihatlah kode di bawah ini. Apa hasil dari panggilan fungsi di baris terakhir? ```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 ================================================ Agar kurung kedua berhasil, yang pertama harus mengembalikan sebuah fungsi. Seperti ini: ```js run function sum(a) { return function(b) { return a + b; // mengambil "a" dari lingkungan leksikal luar }; } alert( sum(1)(2) ); // 3 alert( sum(5)(-1) ); // 4 ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/6-closure-sum/task.md ================================================ nilai penting: 4 --- # Penjumlahan dengan closure Buatlah sebuah fungsi `sum` yang bekerja seperti ini: `sum(a)(b) = a+b`. Ya, seperti ini, dengan kurung ganda (bukan salah ketik). Sebagai contoh: ```js sum(1)(2) = 3 sum(5)(-1) = 4 ``` ================================================ FILE: 1-js/06-advanced-functions/03-closure/7-let-scope/solution.md ================================================ Hasilnya adalah: **error**. Cobalah jalankan ini: ```js run let x = 1; function func() { *!* console.log(x); // ReferenceError: Cannot access 'x' before initialization */!* let x = 2; } func(); ``` Didalam contoh ini kita bisa mengamati perbedaan aneh diantara variabel yang "tidak-ada" dan "belum diinisialisasi". Mungkin seperti yang telah kamu baca didalam artikel [](info:closure), sebuah variabel dimulai didalam state "belum diinisialisasi" sejak saat eksekusinya memasuki blok kode (atau sebuah fungsi). Dan itu akan tetap belum diinisialisasi sampai statemen `let` yang bersangkutan. Dengan kata lain, sebuah variabel secara teknis ada, tapi kita belum bisa menggunakannya sebelum `let`. Kode diatas mendemonstrasikan hal itu. ```js function func() { *!* // variabel lokal x dikenal mesinnya di awal dari fungsinya, // tapi "belum diinisialisasi" (tidak dapat digunakan) sampai let ("zona mati") // karenanya terdapat error */!* console.log(x); // ReferenceError: Cannot access 'x' before initialization let x = 2; } ``` Zona tidak terpakai dari sebuah variabel ini (dari awal blok kode sampai `let`) terkadang dipanggil dengan "zona mati". ================================================ FILE: 1-js/06-advanced-functions/03-closure/7-let-scope/task.md ================================================ nilai penting: 4 --- # Apakah variabelnya terlihat? Apakah hasil dari kode ini? ```js let x = 1; function func() { console.log(x); // ? let x = 2; } func(); ``` P.S. There's a pitfall in this task. The solution is not obvious. Catatan. Terdapat jebakan pada task ini. Solusinya menjadi kurang jelas. ================================================ 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 ================================================ nilai penting: 5 --- # Filter dengan fungsi Kita memiliki method bawaan `arr.filter(f)` untuk array. Method tersebut menyaring seluruh elemen menggunakan fungsi `f`. Apabila mengembalikan `true`, maka elemen tersebut dikembalikan di array hasil. Buatlah filter "yang siap pakai": - `inBetween(a, b)` -- antara `a` dan `b` atau sama dengan (inklusif). - `inArray([...])` -- terkandung di dalam array. Penggunaannya harus seperti ini: - `arr.filter(inBetween(3,6))` -- menyimpan hanya nilai di antara 3 dan 6. - `arr.filter(inArray([1,2,3]))` -- menyimpan elemen apabila sama dengan salah satu dari `[1,2,3]`. Sebagai contoh: ```js /* .. implementasi inBetween dan 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, ageSortedKey); }); 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 ================================================ nilai penting: 5 --- # Urutkan berdasarkan field Kita memiliki array objek untuk diurutkan: ```js let users = [ { name: "John", age: 20, surname: "Johnson" }, { name: "Pete", age: 18, surname: "Peterson" }, { name: "Ann", age: 19, surname: "Hathaway" } ]; ``` Cara yang biasa dilakukan yaitu: ```js // berdasarkan name (Ann, John, Pete) users.sort((a, b) => a.name > b.name ? 1 : -1); // berdasarkan age (Pete, Ann, John) users.sort((a, b) => a.age > b.age ? 1 : -1); ``` Apakah kita dapat membuatnya lebih ringkas, seperti ini? ```js users.sort(byField('name')); users.sort(byField('age')); ``` Jadi, daripada menulis sebuah fungsi, cukup tulis `byField(fieldName)`. Tulislah fungsi `byField` yang dapat digunakan untuk itu. ================================================ FILE: 1-js/06-advanced-functions/03-closure/article.md ================================================ # Lingkup variabel, closure Javascript adalah bahasa yang berorientasi-fungsi. Itu memberikan kita banyak kebebasan. Sebuah fungsi bisa dibuat kapanpun, diberikan sebagai argumen kedalam fungsi lain, dan lalu dipanggil dari kode yang benar-benar berbeda nanti. Kita sudah tahu bahwa sebuah fungsi bisa mengakses variabel diluar dari fungsi tersebut (variabel "luar"). Tapi apa yang terjadi jika variabel luar berubah saat fungsinya dibuat? Akankan fungsinya mendapatkan nilai yang baru atau yang lama? Dan bagaimana jika sebuah fungsi diberikan sebagai paramter dan dipanggil dibagian kode lain, akankah itu mendapatkan akses ke variabel luar ditempat itu? Ayo kita peruas pengetahuan kita untuk mengerti skenario ini dan skenario yang lebih kompleks. ```smart header="Kita akan bahas tentang variabel `let/const` di sini" Di JavaScript, ada 3 cara mendeklarasi variabel: `let`, `const` (cara-cara modern), dan `var` (sisa masa lalu). - Di artikel ini kita akan memakai variabel `let` dalam contoh. - Variabel, yang dideklarasi dengan `const`, bertindak sama, jadi artikel ini juga tentang `const`. - `var` usang punya perbedaan mencolok, mereka akan dibahas di artikel . ``` ## Blok kode Jika variabel dideklarasi di dalam blok kode `{...}`, ia hanya terlihat di dalam blok itu. Misalnya: ```js run { // lakukan pekerjaan dengan variabel lokal yang harusnya tak terlihat dari luar let message = "Hello"; // hanya terlihat dalam blok ini alert(message); // Hello } alert(message); // Error: message is not defined ``` Kita bisa memakai ini untuk mengisolasi potongan kode yang melakukan tugasnya sendiri, dengan variabel yang dia punya sendiri: ```js run { // tampilkan pesan let message = "Hello"; alert(message); } { // tampilkan pesan lain let message = "Goodbye"; alert(message); } ``` ````smart header="Akan muncul galat tanpa blok" Tolong ingat, tanpa blok terpisah akan muncul galat, jika kita memakai `let` dengan nama variabel yang sudah ada: ```js run // tampilkan pesan let message = "Hello"; alert(message); // tampilkan pesan lain *!* let message = "Goodbye"; // Galat: variabel sudah dideklarasi */!* alert(message); ``` ```` Untuk `if`, `for`, `while` dan lain-lain, variabel yang dideklarasi dalam `{...}` juga hanya terlihat di situ saja: ```js run if (true) { let phrase = "Hello!"; alert(phrase); // Hello! } alert(phrase); // Galat, variabel ini tak ada! ``` Di sini, setelah `if` selesai, `alert` di bawah tak akan melihat `phrase`, sehingga terjadi galat. Ini keren, karena ia memperbolehkan kita membuat variabel blok-lokal, yang spesifik ke cabang `if`. Hal serupa juga berlaku untuk loop `for` dan `while`: ```js run for (let i = 0; i < 3; i++) { // variabel i hanya terlihat di dalam for ini alert(i); // 0, lalu 1, lalu 2 } alert(i); // Galat, variabel ini tak ada ``` Visually, `let i` is outside of `{...}`. But the `for` construct is special here: the variable, declared inside it, is considered a part of the block. ## Fungsi bersarang Sebuah fungsi dikatakan "bersarang" apabila fungsi tersebut dibuat di dalam fungsi lainnya. Hal tersebut mudah untuk dilakukan di JavaScript. Kita dapat melakukannya untuk mengatur kode kita, seperti ini: ```js function sayHiBye(firstName, lastName) { // fungsi pembantu untuk digunakan di bawah function getFullName() { return firstName + " " + lastName; } alert( "Hello, " + getFullName() ); alert( "Bye, " + getFullName() ); } ``` Di sini fungsi *bersarang* dibuat untuk kemudahan. Fungsi tersebut bisa mengakses variabel luar sehingga dapat mengembalikan nama lengkap. Fungsi bersarang cukup sering ditemui di JavaScript. Yang lebih menarik yaitu, fungsi bersarang dapat dikembalikan: bisa sebagai properti dari objek baru atau sebagai nilai kembalian itu sendiri. Nilai kembalian tersebut bisa dipakai di tempat lain. Tak peduli di mana, ia masih punya akses ke variabel luar yang sama. Di bawah ini, `makeCounter` membuat fungsi "counter" yang mengembalikan angka berikutnya di tiap invokasi: ```js run function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2 ``` Meski sederhana, varian kode itu yang sedikit dimodifikasi punya kegunaan praktis, misalnya, sebagai [generator angka random](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) untuk menggenerate nilai random untuk tes terotomasi. How does this work? If we create multiple counters, will they be independent? What's going on with the variables here? Memahami hal begini bagus untuk pengetahuan keseluruhan JavaScript dan menguntungkan untuk skenario yang lebih komplex. Jadi ayo kita selami lebih dalam. ## Lingkungan Lexikal ```warn header="Sini jadilah naga!" Penjelasan teknikal mendalam ada di depan. Semakin jauh aku menghindari detil bahasa level-rendah, pemahaman apapun tanpa mereka akan kekurangan dan tak-lengkap, jadi bersiaplah. ``` Supaya jelas, penjelasan dibagi dalam beberapa langkah. ### Langkah 1. Variabel Di JavaScript, setiap fungsi yang berjalan, blok kode `{...}`, dan satu script yang menyeluruh punya objek internal (tersembunyi) yang terasosiasi yang dikenal dengan *Lingkungan Lexikal*. Objek Lingkungan Lexikal punya dua bagian: 1. *Rekaman Lingkungan* -- objek yang menyimpan semua variabel lokal sebagai propertinya (dan beberapa informasi lain seperti nilai `this`). 2. Referensi ke *lingkungan lexikal luar*, yang terasosiasi dengan kode luar. **"Variabel" cuma suatu properti dari objek internal spesial, `Rekaman Lingkungan`. "Untuk memperoleh atau mengganti variabel" berarti "memperoleh atau mengganti properti dari objek itu".** Di kode sederhana tanpa fungsi ini, cuma ada satu Lingkugan Lexikal: ![lingkungan lexikal](lexical-environment-global.svg) Ini yang disebut Lingkungan Lexikal *global*, terasosiasi dengan script keseluruhan. Di gambar di atas, kotak persegi panjang artinya Rekaman Lingkungan (simpanan variabel) dan panah artinya referensi luar. Lingkungan Lexikal global tak punya referensi luar, itulah kenapa panahnya menunjuk ke `null`. Seiring kodenya mulai bereksekusi dan berjalan, Lingkungan Lexikal berganti. Ini kode yang sedikit lebih panjang: ![lingkungan lexikal](closure-variable-phrase.svg) Kotak persegi panjang di sisi kanan mendemonstrasikan bagaimana Lingkungan Lexikal global berganti selama exekusi: 1. Ketika script berjalan, Lingkungan Lexikal di-pre-populasi dengan semua variabel yang terdeklarasi. - Awalnya, mereka di state "Belum terinisialisir". Itu state internal spesial, yang berarti bahwa engine tahu tentang variabelnya, tapi tak akan mengijinkan penggunaan itu sebelum `let`. Ini hampir sama saja dengan variabel itu tak ada. 2. Lalu definisi `let phrase` muncul. Tak ada penetapan dulu, jadi nilainya `undefined`. Kita sudah bisa pakai variabel ini di momen ini. 3. `phrase` diberikan nilai. 4. `phrase` mengganti nilai. Apapun terlihat simpel untuk sekarang, ya kan? - Variabel ialah properti dari objek internal spesial, yang terasosiasi dengan blok/fungsi/script yang sedang berexekusi. - Bekerja dengan variabel sebenarnya bekerja dengan properti objek itu. ```smart header="Lingkungan Lexikal merupakan objek spesifikasi" "Lingkungan Lexikal" ialah objek spesifikasi: ia cuma ada "secara teori" di [spesifikasi bahasa](https://tc39.es/ecma262/#sec-lexical-environments) untuk menjelaskan bagaimana cara ia bekerja. Kita tak bisa memperoleh objek ini di kode kita dan memanipulasinya langsung. Engine JavaScript juga bisa mengoptimisasi itu, menghapus variabel yang tak dipakai untuk menghemat memory dan melakukan trik internal lainnya, selama kelakuan yang terlihat sesuai deskripsi. ``` ### Langkah 2. Deklarasi Fungsi Fungsi juga berupa nilai, seperti variabel. **Bedanya ialah Deklarasi Fungsi terinisialisasi penuh secara instan.** Ketika Lingkungan Lexikal dibuat, Deklarasi Fungsi segera menjadi fungsi siap-pakai (tak seperti `let`, yang tak bisa dipakai hingga deklarasi). Itulah kenapa kita bisa memakai fungsi, yang dideklarasi sebagai Deklarasi Fungsi, bahkan sebelum deklarasinya itu sendiri. Misalnya, ini state awal dari Lingkungan Lexikal global ketika kita tambah satu fungsi: ![](closure-function-declaration.svg) Alaminya, kelakukan ini cuma berlaku pada Deklarasi Fungsi, bukan Expresi Fungsi di mana kita menetapkan fungsi ke variabel, seperti `let say = function(name)...`. ### Langkah 3. Lingkungan Lexikal dalam dan luar Ketika satu fungsi berjalan, di awal panggilan, Lingkungan Lexikal tercipta otomatis untuk menyimpan variabel lokal dan parameter dari panggilannya. Misalnya, untuk `say("John")`, ini seperti (exekusinya ada di baris tersebut, yang diberi label dengan panah): ![](lexical-environment-simple.svg) Selama panggilan fungsi, kita punya dua Lingkungan Lexikal: dalam (untuk panggilan fungsi) dan luar (global): - Lingkungan Lexikal dalam berkorespondensi dengan exekusi `say` yang sedang berlangsung. Ia punya properti tunggal: `name`, argumen fungsi. Kita panggil `say("John")`, jadi nilai `name` adalah `"John"`. - Lingkungan Lexikal luar ialah Lingkungan Lexikal global. Ia punya variabel `phrase` dan fungsinya itu sendiri. Lingkungan Lexikal dalam punya referensi ke `outer`. **Ketika kode ingin mengakses variabel -- Lingkungan Lexikal dalam ditelusuri pertama, lalu terluar, lalu yang lebih terluar dan berikutnya.** Jika variabel tak ditemukan di manapun, itu adalah galat dalam mode ketat (tanpa `use strict`, penetapan ke variabel yang tak pernah ada menciptakan satu variabel global, untuk kompatibilitas dengan kode usang). Di contoh ini penelusuran terjadi seperti berikut: - Untuk variabel `name`, `alert` di dalam `say` mencarinya segera di dalam Lingkungan Lexikal dalam. - Ketika ia ingin mengakses `phrase`, maka tak ada `phrase` secara lokal, jadi ia mengikuti referensi ke Lingkungan Lexikal luar dan menemui itu di sana. ![lexical environment lookup](lexical-environment-simple-lookup.svg) ### Langkah 4. Mengembalikan fungsi Ayo kembali ke contoh `makeCounter`. ```js function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); ``` Di awal tiap panggilan `makeCounter()`, objek Lingkungan Lexikal baru dibuat, untuk menyimpan variabel untuk perjalanan `makeCounter` ini. Jadi kita punya dua Lingkungan Lexikal bersarang, sama seperti contoh di atas: ![](closure-makecounter.svg) Bedanya adalah, selama exekusi dari `makeCounter()`, fungsi kecil bersarang tercipta dari cuma satu baris: `return count++`. Kita tak menjalankan itu sekarang, cuma membuat. Semua fungsi mengingat Lingkungan Lexikal di mana mereka dibuat. Teknisnya, tak ada sihir di sini: semua fungsi punya properti tersembunyi bernama `[[Environment]]`, yang menyimpan referensi ke Lingkungan Lexikal di mana fungsi itu dibuat: ![](closure-makecounter-environment.svg) Jadi, `counter.[[Environment]]` punya referensi ke `{count: 0}` Lingkungan Lexikal. Itulah bagaimana fungsi mengingat di mana ia dibuat, tak peduli di mana ia dipanggil. Referensi `[[Environment]]` diset sekali dan selamanya saat kresi fungsi. Lalu, saat `counter()` dipanggil, Lingkungan Lexikal baru dibuat untuk panggilan, dan referensi Lingkungan Lexikal luar-nya diambil dari `counter.[[Environment]]`: ![](closure-makecounter-nested-call.svg) Sekarang ketika kode di dalam `counter()` mencari variabel `count`, ia pertama memeriksa Lingkungan Lexikal miliknya sendiri (kosong, karena tak ada variabel lokal di sana), lalu Lingkungan Lexikal dari panggilan `makeCounter()` luar, di mana ia ditemukan dan berubah. **Variabel diperbarui di Lingkungan Lexikal di mana ia tinggal.** Ini state setelah exekusi: ![](closure-makecounter-nested-call-2.svg) Jika kita panggil `counter()` beberapa kali, variabel `count` akan meningkat ke `2`, `3`, dan seterusnya, at the same place. ```smart header="Closure" Ada satu istilah pemrograman umum "closure", yang sebaiknya diketahui developer secara umum. [Closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) ialah fungsi yang mengingat variabel luarnya dan bisa mengakses mereka. Di beberapa bahasa, itu tak mungkin, atau satu fungsi harus ditulis dalam cara spesial untuk membuat ini terjadi. Tapi seperti yang dijelaskan di atas, di JavaScript, semua fungsi alaminya adalah closure (cuma ada satu pengecualian, akan dibahas di ). Yaitu: mereka otomatis mengingat di mana mereka dibuat menggunakan property `[[Environment]]` tersembunyi, kemudian kdoe mereka bisa mengakses variabel luar. Ketika dalam interview, frontend developer mendapat pertanyaan tentang "apa itu closure?", jawaban valid yaitu definisi closure dan penjelesan bahwa semua fungsi di JavaScript adalah closure, dan mungkin sedikit kata-kata tentang detil teknis: properti `[[Environment]]` dan bagaimana Lingkungan Lexikal bekerja. ``` ## Koleksi sampah Biasanya, Lingkungan Lexikal dihapus dengan semua variabel setelah panggilan fungsinya selesai. Ini karena tak ada referensi ke situ. Sebagai objek JavaScript apapun, ia cuma ditahan di memory selama ia dapat digapai. ...Tapi jika ada fungsi bersarang yang masih dapat digapai setelah akhir fungsi, maka ia punya properti `[[Environment]]` yang mereferensi lingkungan lexikal. Dalam hal Lingkungan Lexikal masih bisa digapai meski setelah berakhirnya fungsi itu, ia tetap hidup. Misalnya: ```js function f() { let value = 123; return function() { alert(value); } } let g = f(); // g.[[Environment]] menyimpan referensi ke Lingkungan Lexikal // dari panggilan f() yang sesuai ``` Tolong diperhatikan apabila `f()` dipanggil beberapa kali, dan fungsi kembaliannya disimpan, maka seluruh objek lingkungan leksikal akan disimpan di memori. Ketiga-tiganya pada kode di bawah: ```js function f() { let value = Math.random(); return function() { alert(value); }; } // 3 fungsi di array, semuanya terhubung ke lingkungan leksikal // dari setiap f() yang bersangkutan let arr = [f(), f(), f()]; ``` Sebuah objek lingkungan leksikal mati apabila sudah tidak dapat dicapai (sperti objek lainnya). Dengan kata lain, objek tersebut hidup selama masih ada setidaknya satu fungsi bersarang yang mengacunya. Di kode berikut, setelah fungsi bersarang itu dihapus, Lingkungan Lexikal lingkupannya (serta `value`-nya) dibersihkan dari memori; ```js function f() { let value = 123; return function() { alert(value); } } let g = f(); // selama fungsi g tetap ada, nilainya tetap berada di memori g = null; // ...dan sekarang memori dibersihkan ``` ### Optimalisasi kehidupan nyata Seperti yang kita lihat, di teori selama sebuah fungsi masih hidup, seluruh variabel luarnya juga disimpan. Tetapi di praktiknya, mesin JavaScript mencoba untuk mengoptimalkannya. Mereka menganalisis penggunaan variabel dan apabila sudah jelas bahwa variabel luar sudah tidak digunakan -- mereka dihapus. **Sebuah efek samping yang penting di V8 (Chrome, Opera) adalah variabel akan tak dapat diakses saat debugging** Cobalah jalankan contoh di bawah di Chrome dengan Developer Tools. Saat dihentikan, pada console coba ketikkan `alert(value)`. ```js run function f() { let value = Math.random(); function g() { debugger; // di console: ketik alert(value); Variabel tak ditemukan! } return g; } let g = f(); g(); ``` Seperti yang kita lihat -- variabel tersebut tak ditemukan! Secara teori, variabel tersebut masih bisa diakses, tetapi mesin mengoptimalkannya. Hal tersebut mungkin menyebabkan masalah debugging yang aneh (mungkin memakan waktu). Salah satunya -- apabila kita mendapat variabel luar yang tak diharapkan: ```js run global let value = "Surprise!"; function f() { let value = "the closest value"; function g() { debugger; // di console: ketik alert(value); Surprise! } return g; } let g = f(); g(); ``` Fitur V8 ini baik untuk diketahui. Jika kamu melakukan debug memakai Chrome/Opera, cepat atau lambat kamu akan menemuinya. Ini bukan bug di debugger, melainkan fitur spesial dari V8. Mungkin ini akan diganti suatu saat. Kamu bisa mengeceknya dengan menjalankan contoh di laman ini. ``` ================================================ FILE: 1-js/06-advanced-functions/04-var/article.md ================================================ # Si Tua "var" ```smart header="Artikel ini untuk memahami script lama" Informasi yang terdapat di artikel ini berguna untuk memahami script lama. Hal itu bukanlah cara kita menulis kode baru. ``` Di bab paling awal tentang [variabel](info:variables), kami menyebutkan tiga cara untuk deklarasi variabel: 1. `let` 2. `const` 3. `var` Deklarasi dari `var` sama dengan `let`. Kebanyakan kasus kita bisa mengganti `let` dengan `var` atau sebaliknya dan dan mengira itu akan berjalan lancar: ```js run var message = "Hi"; alert(message); // Hi ``` Tapi secara internal `var` adalah monster yang benar-benar berbeda, yang berasal dari masa lalu. `var` biasanya tidak digunakan didalam skrip modern, tapi masih tetap ada didalam skrip-skrip lama. Jika kamu tidak berencana bertemu dengan skrip seperti itu kamu bisa melewati bab ini dan membacanya nanti. Akan tetapi, perbedaan ini sangatlah penting untuk dimengerti apalagi ketika mengubah skrip lama dari `var` menjadi `let`. untuk menghindari error-error yang aneh. Variabel, dideklarasikan dengan `var`, baik function-wide ataupun global. Mereka terlihat melalui blok. Contohnya: ```js run if (true) { var test = true; // gunakan "var" daripada "let" } *!* alert(test); // benar, variabel ada setelah if */!* ``` Karena `var` mengabaikan blok kode , kita mendapatkan global variabel `test`. Jika kita menggunakan `let test` daripada `var test`, maka variabel hanya akan terlihat di dalam `if`: ```js run if (true) { let test = true; // gunakan "let" } *!* alert(test); // Error: test tidak didefinisikan */!* ``` Hal yang sama juga untuk loop: `var` tidak dapat berupa blok atau loop-lokal: ```js for (var i = 0; i < 10; i++) { var one = 1; // ... } *!* alert(i); // 10, "i" terlihat setelah loop, itu adalah global variabel */!* ``` Jika blok kode ada di dalam fungsi, maka `var` menjadi variabel tingkat fungsi: ```js run function sayHi() { if (true) { var phrase = "Hello"; } alert(phrase); // bekerja } sayHi(); alert(phrase); // Error: frasa tidak terdefinisi (periksa Developer Console) ``` Seperti yang bisa kita lihat `var` menembus `if`, `for` atau blok kode lainnya. Itu karena sejak dahulu di blok Javascript tidak memiliki Lingkungan Leksikal. dan `var` adalah sisanya. ## "var" mentoleransi pendeklarasian ulang Jika kita mendeklarasikan variabel yang sama dengan `let` didalam scope yang sama, itu akan menciptakan error: ```js run let user; let user; // SyntaxError: 'user' has already been declared ``` Dengan `var`, kita bisa mendeklarasikan ulang berapa kalipun. Jika kita menggunakan `var` dengan variabel yang telah dideklarasikan, `var` itu akan diabaikan: ```js run var user = "Pete"; var user = "John"; // "var" ini tidak melakukan apapun (sudah dideklarasikan) // ...hal ini tidak akan menciptakan error alert(user); // John ``` ## Variabel "var" bisa dideklarasikan dibawah penggunaan variabel tersebut Deklarasi `var` diproses ketika fungsi dimulai (atau skrip dijalankan untuk global). Dengan kata lain, variabel `var` didefinisikan dari awal fungsi, tidak peduli di manapun definisi tersebut ( dengan asumsi definisi tidak didalam fungsi bersarang). Jadi kode ini: ```js run function sayHi() { phrase = "Hello"; alert(phrase); *!* var phrase; */!* } sayHi(); ``` ...Secara teknis sama dengan ini (memindahkan `var pharse` di atas): ```js run function sayHi() { *!* var phrase; */!* phrase = "Hello"; alert(phrase); } sayHi(); ``` ...Atau bahkan seperti ini (Ingat, blok kode diabaikan): ```js run function sayHi() { phrase = "Hello"; // (*) *!* if (false) { var phrase; } */!* alert(phrase); } sayHi(); ``` Orang-orang juga menyebut perilaku seperti ini "hoisting", karena semua `var` "hoisted" (diangkat) ke bagian atas fungsi. Sehingga dalam contoh di atas, cabang `if (false)` tidak pernah dijalankan, tetapi itu tidak masalah. `var` di dalamnya diproses di awal fungsi, jadi pada saat `(*)` variabel ada. **Pendeklarasian hoisted, sedangkan penugasan (assigment) tidak.** Lebih baik didemonstrasikan dengan sebuah contoh: ```js run function sayHi() { alert(phrase); *!* var phrase = "Hello"; */!* } sayHi(); ``` Pada baris `var pharse = "Hello"` memiliki dua aksi didalamnya: 1. Deklarasi variabel `var` 2. Penugasan variabel `=`. Deklarasi diproses pada awal pelaksanaan fungsi ("hoisted"), tetapi penugasan selalu bekerja di tempat mucul. sehingga pada dasarnya kode bekerja seperti ini: ```js run function sayHi() { *!* var phrase; // deklarasi bekerja di awal... */!* alert(phrase); // tidak terdefinisi *!* phrase = "Hello"; // ...penugasan - saat penugasan mencapainya. */!* } sayHi(); ``` Karena semua deklarasi `var` diproses pada awal fungsi, kita dapat mendeferensikanya dimana saja. tetapi variabel tidak terdefinisi sampai penugasan. Dalam kedua contoh diatas `alert` bekerja tanpa error, karena ada variabel `pharse`. Tetapi karena nilainya belum ditetapkan, sehingga menampilkan `undefined`. ## IIFE Karena di masa lalu hanya ada `var`, dan ia tidak memiliki visibilitas tingkat blok, programmer menemukan cara untuk menirunya. cara mereka melakukanya dinamakan “immediately-invoked function expressions” (disingkat IIFE). Itu bukanlah sesuatu yang harus kita gunakan saat ini, tetapi anda dapat menemukannya di skip lama Sebuah IIFE terlihat seperti ini: ```js run (function() { var message = "Hello"; alert(message); // Hello })(); ``` Disini ekspresi fungsi dibuat dan segera dipangil. Sehingga kode dieksekusi segera dan memiliki variabel pribadi sendiri. Fungsi ekspresi dibungkus dengan tanda kurung `(function {...})`, karena ketika Javascript bertemu `"function"` dalam aliran kode utama, ia memahaminya sebagai awal dari Deklarasi Fungsi. tetapi sebuah Deklarasi Fungsi harus memiliki nama, sehingga kode seperti ini akan menghasilkan error: ```js run // mencoba untuk mendeklarasikan dan langsung memanggil fungsinya function() { // <-- Error: Statmen Function membutuhkan sebuah nama fungsi var message = "Hello"; alert(message); // Hello }(); ``` Bahkan jika kita mengatakan: "Ok, mari tambahkan nama", hal itu tidak dapat bekerja, karena Javascript tidak mengizinkan Deklarasi Fungsi dipanggil segera: ```js run // syntax error karena frasa dibawah function go() { }(); // <-- tidak dapat segera memanggil deklarasi fungsi ``` Jadi, tanda kurung di sekitar fungsi adalah trik untuk menunjukan Javascript bahwa fungsi dibuat dalam konteks ekxpresi lain, dan karenanya merupakan ekspresi fungsi: tidak memerlukan nama dan segera dipanggil. Ada beberapa cara lain selain tanda kurung untuk memberi tahu Javascript bahwa yang dimaksud adalah Ekspresi fungsi: ```js run // Cara membuat IIFE (function() { alert("kurung disekitar fungsi"); }*!*)*/!*(); (function() { alert("kurung disekitar semuanya"); }()*!*)*/!*; *!*!*/!*function() { alert("Operator Bitwise NOT memulai ekspresi"); }(); *!*+*/!*function() { alert("Unary plus memulai ekspresi"); }(); ``` Dalam semua kasus diatas kami mendeklarasikan sebuah Ekspresi fungsi dan menjalankanya segera. Mari catat kembali: Saat ini tidak ada alasan untuk menulis kode seperti itu. ## Kesimpulan Ada dua perbedaan utama dari `var` dibandingkan dengan `let/const`; 1. `var` variabel tidak memiliki ruang lingkup blok, mereka terlihat minimum pada tingkat fungsi. 2. Deklarasi `var` diproses saat fungsi dimulai (skrip dimulai untuk global). Ada satu perbedaan kecil terkait objek global, yang akan kita bahas pada bab selanjutnya. Perbedaan-perbedaan ini membuat `var` lebih buruk daripada` let` hampir di setiap waktu. Variabel block-level adalah hal yang bagus. Itu sebabnya `let` diperkenalkan dalam standar sejak dahulu, dan sekarang merupakan cara utama (bersama dengan` const`) untuk mendeklarasikan variabel. ================================================ FILE: 1-js/06-advanced-functions/05-global-object/article.md ================================================ # Objek global Objek global menyediakan variabel dan fungsi yang bisa didapatkan dimana saja. Secara default, variabel dan fungsi yang sudah berada didalam bahasanya atau lingkungannya. Di dalam browser ia dinamakan `window`, untuk Node.js `global`, untuk lingkungan lainnya ia mungkin mempunyai nama lain. Akhir-akhir ini, `globalThis` ditambahkan ke bahasanya, sebagai nama standar untuk objek global, yang harus di dukung di semua lingkungan. Di browser tertentu, ya itu non-Chromium Edge, `globalThis` belum didukung, tapi bisa dengan mudah dipolyfill. Kita akan memakai `window` disini, dengan anggapan bahwa lingkungan kita adalah browser. Jika script kamu mungkin digunakan di lingkungan lain, lebih baik menggunakan `globalThis`. Semua properti objek global bisa diakses secara langsung: ```js run alert("Hello"); // sama saja dengan window.alert("Hello"); ``` Di dalam browser, fungsi global dan variabel yang dinyatakan dengan `var` (bukan `let/const`!) menjadi properti global objek: ```js run untrusted refresh var gVar = 5; alert(window.gVar); // 5 (menjadi properti objek global) ``` Mohon jangan bergantung dengan itu! Perilaku ini ada untuk alasan kompatibilitas. Script modern menggunakan [JavaScript modules](info:modules) dimana hal-hal tersebut tidak terjadi. Jika kita menggunakan `let`, hal tersebut tidak akan terjadi: ```js run untrusted refresh let gLet = 5; alert(window.gLet); // undefined (tidak menjadi properti objek global) ``` Jika sesuatu nilai sangat penting sesampai kamu ingin membuatnya tersedia secara global, tulislah langsung sebagai satu properti: ```js run *!* // buat info pengguna saat ini global, supaya semua script bisa mengaksesnya window.currentUser = { name: "John" }; */!* // di tempat lain di kode alert(currentUser.name); // John // atau jika kita mempunyai variabel lokal dengan nama "currentUser" // ambillah secara eksplisit dari window (aman!) alert(window.currentUser.name); // John ``` Meskipun begitu, menggunakan variabel global umumnya tidak dianjurkan. Variabel global harus ada sesedikit mungkin. Desain kode dimana fungsi mendapatkan variabel "input" dan mengeluarkan "outcome" tertentu akan lebih jelas, kurang cenderung menghasilkan eror dan lebih mudah untuk dites dibanding jika ia menggunakan variabel luar atau global. ## Menggunakan polyfills Kita menggunakan objek global untuk mengetes dukungan atas fitur bahasa modern. Contohnya, tes jika objek built-in `Promise` berada (tidak ada di browser yang sangat tua): ```js run if (!window.Promise) { alert("Your browser is really old!"); } ``` Jika tidak ada (anggap kita di browser tua), kita bisa menciptakan "polyfills": tambahkan fungsi yang tidak di dukung oleh lingkungan, tapi ada di standar modern. ```js run if (!window.Promise) { window.Promise = ... // implementasi custom dari fitur bahasa modern } ``` ## Ringkasan - Objek global menyimpan variabel yang harus tersedia dimana saja. Itu termasuk built-in Javascript, seperti `Array` dan nilai-nilai lingkungan-spesifik, seperti `window.innerHeight` -- tinggi window di dalam browser. - Objek global mempunyai nama universal `globalThis`. ...Tapi lebih sering disebut nama lingkungan-spesifik "old-school", seperti `window` (browser) dan `global` (Node.js). Karena `globalThis` adalah usulan baru, ia belum didukung di dalam non-Chromium Edge (tapi bisa dipolyfill). - Kita harus menyimpan nilai di objek global jika kalau ia benar-benar global untuk projek kita. Dan pertahankan jumlah minimum. - Dalam browser, jika kita tidak menggunakan [modules](info:modules), fungsi global dan variabel ternyatakan `var` menjadi properti objek global. - Untuk membuat kode kami future-proof dan lebih mudah dimengerti, kita harus mengakses properti dari global objek secara langsung, sebagain `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; // ... kodemu ... } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 counter.set(10); // setel perhitungan baru alert( counter() ); // 10 counter.decrease(); // kurangi perhitungannya dengan 1 alert( counter() ); // 10 (daripada 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 ================================================ Solusinya adalah menggunakan `count` didalam variabel lokal, tapi metode tambahan ditulis tepat didalam `counter`nya. Mereka membagi lingkungan leksikan luar yang sama dan juga bisa mengakses `count` yang sekarang. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/task.md ================================================ nilai penting: 5 --- # Setel dan kurangi penghitung Modifikasi kode dari `makeCounter()` jadi penghitungnya juga bisa mengurangi dan menyetel ulang angkanya: - `counter()` harus mengembalikan angka selanjutnya (seperti sebelumnya). - `counter.set(value)` harus menyetel ulang penghitungnya jadi `value`. - `counter.decrease()` harus mengurangi angka penghitungnya dengan 1. Lihat kode pada sandbox untuk contoh penggunaan yang lengkap. Catatan. Kamu bisa menggunakan closure atau properti fungsi untuk menyimpan perhitungan yang sekarang. Atau tulis kedua variannya. ================================================ 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){ // Tulis kodemu disini. } /* 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. Untuk membuat semuanya bekerja *entah bagaimana*, hasil dari `sum` haruslah sebuah fungsi. 2. Fungsi harus menyimpan nilai sekarang yang berada diantara pemanggilan didalam memori. 3. Menurut tasknya, fungsinya harus menjadi angka ketika digunakan didalam `==`. Fungsi adalah objek, jadi perubahan terjasi seperti yang dideskripsikan didalam bab , dan kita bisa menyediakan metode milik kita yang mengembalikan angkanya. Sekarang angkanya: ```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 ``` Perhatikan baik-baik bahwa fungsi `sum` sebenarnya hanya bekerja satu kali. Itu mengembalikan fungsi `f`. Lalu, untuk setiap pemanggilan selanjutnya `f` menambahkan parameternya kedlaam `currentSum`, dan mengembalikan dirinya sendiri. **Tidak terdapat rekursi di akhir baris dari `f`.** Ini adalah bagaimana rekursi terlihat: ```js function f(b) { currentSum += b; return f(); // <-- pemanggilan rekursi } ``` Dan didalam kasus kita, kita hanya mengembalikan fungsinya, tanpa memanggilnya: ```js function f(b) { currentSum += b; return f; // <-- tidak memanggil dirinya-sendiri, hanya mengembalikan dirinya } ``` `f` ini akan digunakan didalam pemanggilan selanjutnya, dan lagi akan mengembalikan dirinya-sendiri, berapa kalipun seperti yang dibutuhkan. Lalu, ketika digunakan sebagai angka atau sebuah string -- `toString` mengembalikan `currentSum`. Kita jadi bisa menggunakan `Symbol.toPrimitive` atau `valueOf` disini sebagai perubahan. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/task.md ================================================ nilai penting: 2 --- # Tambahkan dengan jumlah kurung yang banyak Buatlah sebuah fungsi `sum` yang harus bekerja seperti ini: ```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 ``` Catatan. Kamu mungkin perlu untuk mengatur objek kostum menjadi perngubah primitif didalam fungsi kamu. ================================================ FILE: 1-js/06-advanced-functions/06-function-object/article.md ================================================ # Objek fungsi, NFE Seperti yang telah kita tahu, sebuah fungsi didalam Javascript adalah sebuah nilai. Setiap nilai didalam Javascript memiliki sebuah tipe. Apa tipe dari sebuah fungsi? Didalam Javascript, fungsi adalah objek. Sebuah cara yang bagus untuk membayangkan fungsi adalah sebagai "aksi objek" yang dapat dipanggil. Kita tidak hanya memanggil mereka, tapi juga memperlakukannya seperti sebuah objek: menambah/menghapus properti, memberikan referensi dll. ## Properti "name" Objek fungsi mengandung beberapa properti yang dapat digunakan. Contoh, sebuah nama fungsi dapat diakses sebagai properti "name": ```js run function sayHi() { alert("Hi"); } alert(sayHi.name); // sayHi ``` Itu terlihat cukup lucu, logika untuk penggunaan nama cukup pintar. Itu juga menggunakan nama fungsi yang benar bahkan jika tidak terdapat nama sekalipun, dan jika langsung ditempatkan: ```js run let sayHi = function() { alert("Hi"); }; alert(sayHi.name); // sayHi (ada namanya!) ``` Itu juga akan bekerja jika assignmentnya dilakukan dengan menggunakan nilai default: ```js run function f(sayHi = function() {}) { alert(sayHi.name); // sayHi (bekerja!) } f(); ``` Didalam spesifikasinya, fitur ini dinamakan dengan "nama kontekstual/contextual name". Jika sebuah fungsi tidak memiliki nama, maka didalam assignment-nya akan mencarinya didalam konteksnya. Metode objek mempunyai nama juga: ```js run let user = { sayHi() { // ... }, sayBye: function() { // ... } } alert(user.sayHi.name); // sayHi alert(user.sayBye.name); // sayBye ``` Tidak ada sulap disini. Terdapat kasus dimana tidak ada cara untuk mengetahui namanya. Didalam kasus itu, properti namanya akan kosong, seperti disini: ```js run // fungsi dibuat didalam array let arr = [function() {}]; alert( arr[0].name ); // // mesinnya tidak memiliki cara untuk mengetahui namanya, maka isinya akan menjadi kosong ``` Didalam penerapannya, entah bagaimana, kebanyakan fungsi akan memiliki nama. ## Properti "length" Juga terdapat properti bawaan "length" yang mengembalikan jumlah dari parameter sebuah fungsi, contoh: ```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 ``` Disini kita bisa melihat parameter rest tidak dihitung. Properti `length` terkadang digunakan untuk [introspeksi](https://en.wikipedia.org/wiki/Type_introspection) didalam fungsi yang mengoperasikan fungsi lainnya. Contoh, didalam kode dibawah fungsi `ask` menerima sebuah `question` untuk ditanyakan dan sebuah angka yang panjang dari fungsi `handler` untuk dipanggil. Sekalinya pengguna memberikan jawaban mereka, pemanggilan fungsi akan memanggil handler-nya. Kita bisa memberikan dua macan handler: - Fungsi dengan jumlah argumen nol, yang mana hanya dipanggil ketika pengguna memberikan jawaban yang positif. - Fungsi dengan argumen, yang mana akan dipanggil diantara salah satu kasusnya dan mengembalikan sebuah jawaban. Untuk memanggil `handler` dengan cara yang tepat, kita memeriksa properti `handler.length`. Idenya adalah kita mempunyai handler yang simpel tanpa argumen untuk kasus yang positif (pada kasus yang sering terjadi), tapi yang tentunya tidak mendukung handler yang universal: ```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); } } } // untuk jawaban yang positif, kedua handler-nya akan dipanggil // untuk jawaban yang negatif, hanya yang kedua ask("Question?", () => alert('You said yes'), result => alert(result)); ``` Ini hanyalah kasus tertentu yang dipanggil dengan [polimorfisme](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)) -- memperlakukan argumen berbeda-beda tergantung dari tipenya atau, didalam kasus kita tergantung dari `length`. Idenya adalah didalam penggunakan librari Javascript. ## Properti-properti kustom/Custom properties Kita juga bisa menambahkan properti-properti milik kita sendiri. Disini kita menambahkan properti `counter` untuk melacar total dari pemanggilan: ```js run function sayHi() { alert("Hi"); *!* // hitung berapa kali kita berjalan sayHi.counter++; */!* } sayHi.counter = 0; // nilai awal sayHi(); // Hi sayHi(); // Hi alert( `Called ${sayHi.counter} times` ); // dipanggil dua kali ``` ```warn header="Sebuah properti bukanlah sebuah variabel" Sebuah properti dimasukan kedalam fungsi seperti `sayHi.counter = 0` tidak mendefinisikan variabel lokal `counter` didalamnya. Dengan kata lain, sebuah properti `counter` dan sebuah variabel `let counter` adalah dua hal yang tidak memiliki hubungam sama sekali. Kita bisa memperlakukan sebuah fungsi seperti sebuah objek, menyimpan properti didalamnya, tapi itu tidak akan mempengaruhi eksekusinya sendiri. Variabel bukanlah sebuah properti fungsi dan sebaliknya. Keduanya hanyalah dua hal yang berbeda. ``` Terkadang properti fungsi bisa menggantikan closure. Contoh, kita bisa menulis ulang contoh fungsi counter dari bab untuk menggunakan properti fungsi: ```js run function makeCounter() { // daripada: // let count = 0 function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 ``` `count`nya sekarang tersimpan didalam fungsinya langsung, bukan diluar dari lingkungan leksikalnya. Apakah lebih baik atau tidak menggunakan closure? Perbedaan utamanya adalah jika nilai dari `count` berada didalam variabel luar, maka kode eksternal tidak dapat mengaksesnya. Hanya fungsi bercabang yang bisa memodifikasinya. Dan jika itu terikat dengan sebuah fungsi, maka kode eksternal dapat mengaksesnya. ```js run function makeCounter() { function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); *!* counter.count = 10; alert( counter() ); // 10 */!* ``` Jadi pilihannya implementasinya adalah tergantung pada kebutuhan kita. ## Ekspresi fungsi yang mempunyai nama Ekspresi fungsi yang mempunyai nama, atau NFE(Named Function Expression) adalah istilah untuk ekspresi fungsi yang memiliki nama. Contoh, ayo kita lihat ekspresi fungsi yang biasa: ```js let sayHi = function(who) { alert(`Hello, ${who}`); }; ``` Dan tambahkan nama: ```js let sayHi = function *!*func*/!*(who) { alert(`Hello, ${who}`); }; ``` Apakah kita meraih sesuatu disini? Apa tujuan dari penambahan nama `"func"`? pertama kita perhatikan, bahwa kita masih memiliki ekspresi fungsi. Menambahkan nama `"func"` setelah `function` tidaklah membuatnya menjadi deklarasi fungsi, karena itu masih dibuat sebagai bagian dari sebuah assignment ekspresi. Menambahkan nama seperti itu tidak akan merusak apapun. Fungsinya masih ada sebagai `sayHi()`: ```js run let sayHi = function *!*func*/!*(who) { alert(`Hello, ${who}`); }; sayHi("John"); // Hello, John ``` Terdapat dua hal yang spesial tentang nama `func`, hal itu adalah: 1. Itu mengijinkan fungsinya untuk mereferensi dirinya sendiri secara internal. 2. Fungsinya tidak akan terlihat diluar fungsi tersebut. Untuk contoh, fungsi `sayHi` dibawah memanggil dirinya-sendiri lagi dengan `"Guest"` jika `who` tidak ada: ```js run let sayHi = function *!*func*/!*(who) { if (who) { alert(`Hello, ${who}`); } else { *!* func("Guest"); // gunakan func untuk memanggil dirinya sendiri */!* } }; sayHi(); // Hello, Guest // Tapi hal ini tidak akan bekerja func(); // Error, func is not defined (tidak terliha diluar fungsinya sendiri) ``` Kenapa kita menggunakan `func`? Mungkin cukup gunakan `sayHi` untuk pemanggilan bercabang? Sebenarnya, dalam kebanyakan kasus kita bisa: ```js let sayHi = function(who) { if (who) { alert(`Hello, ${who}`); } else { *!* sayHi("Guest"); */!* } }; ``` Masalahnya dengan kode itu adalah `sayHi` mungkin akan berubah di kode terluar. Malah jika fungsinya dimasukan kedalam variabel lain, kodenya akan mulai memberikan error: ```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, pemanggilan bercabang sayHi tidak akan bekerja lagi! ``` Hal itu terjadi karena fungsinya menggunakan `sayHi` dari luar lingkungan leksikalnya. Disana tidak ada `sayHi` lokal, jadi variabel terluar digunakan. Dan pada saat pemanggilannya terjadi `sayHi` terluar adalah `null`. Nama opsional dimana kita bisa memasukannya kedalam ekspresi fungsi diartikan untuk menyelesaikan masalah yang tepat seperti ini. Ayo kita gunakan itu untuk membetulkan masalah pada kode kita: ```js run let sayHi = function *!*func*/!*(who) { if (who) { alert(`Hello, ${who}`); } else { *!* func("Guest"); // Sekarang semuanya mantap */!* } }; let welcome = sayHi; sayHi = null; welcome(); // Hello, Guest (pemanggilan bercabang bekerja) ``` Sekarang hal itu bekerja karena nama `"func"` adalah fungsi-lokal. Fungsi itu tidak diambil dari luar (dan tidak terlihat dari luar). Spesifikasinya menjamin itu akan selalu mereferensi fungsi saat ini. Fungsi dari luar kode mempunyai variabel `sayHi` atau `welcome`nya sendiri. Dan `func` adalah sebuah "nama fungsi internal", bagaimana fungsi bisa memanggil dirinya sendiri secara internal. ```smart header="Tidak ada hal semacam itu untuk deklarasi fungsi" Fitur "nama internal" dideskripsikan disini hanya tersedia untuk ekspresi fungsi, bukan deklarasi fungsi. Untuk deklarasi fungsi, tidak terdapat sintaks untuk menambahkan sebuah nama "internal". Terkadang, ketika kita membutuhkan nama internal yang dapat diandalkan, itulah alasan yang tepat untuk menulis ulang sebuah deklarasi fungsi kedalam bentuk ekspresi fungsi. ``` ## Ringkasan Fungsi adalah objek. Disini kita memperlajari properti-propertinya: - `name` -- nama dari fungsinya. Biasanya diambil dari definisi fungsinya, tapi jika disana tidak ada, Javascript akan mencoba mencarinya dari konteksnya (contoh. dari assignment-nya). - `length` -- jumlah dari argumen didalam definisi dari fungsi. Parameter rest tidak dihitung. Jika fungsinya di deklarasikan sebagai ekspresi fungsi (tidak didalam alur kode utama), dan itu memiliki nama, maka itu dipanggil dengan ekspresi fungsi yang memiliki nama. Namanya bisa digunakan didalam fungsinya untuk mereferensi dirinya sendiri, untuk pemanggilan rekursif atau sejenisnya. Juga, fungsi mungkin memiliki properti tambahan. Beberapa librari Javascript yang cukup terkenal banyak menggunakan fitur ini. Mereka membuat sebuah fungsi "utama" dan mengkaitkannya dengan fungsi "pembantu". Contoh librari [jQuery](https://jquery.com) menciptakan fungsi bernama `$`. Librari The [lodash](https://lodash.com) membuat sebuah fungsi `_` dan lalu menambahkan `_.clone`, `_.keyBy`dan properti lainnya kedalamnya (lihat [dokumentasinya](https://lodash.com/docs) ketika kamu mau tau lebih dalam). Sebenarnya, mereka melakukannya untuk mengurangi penggunaan dari ruang global, jadi librari tunggal itu hanya menggunakan satu variabel global. Itu mengurangi kemungkinan dari konflik penamaan variabel. Jadi, sebuah fungsi bisa melakukan hal-hal yang berguna dengan dirinya-sendiri dan juga membawa setumpuk fungsionalitas didalam propertinya sendiri. ================================================ FILE: 1-js/06-advanced-functions/07-new-function/article.md ================================================ # Sintaks "new Function" Terdapat satu lagi cara untuk membuat fungsi. Ini sangat jarang digunakan, tapi terkadang tidak ada alternatif lain. ## Sintaks Sintaks untuk membuat sebuah fungsi: ```js let func = new Function ([arg1, arg2, ...argN], functionBody); ``` Fungsinya dibuat dengan argumen-argumen `arg1...argN` dan diberikan `functionBody`. Ini sangat mudah dimengerti hanya dengan melihat contohnya. Disini sebuah fungsi dengan dua argumen: ```js run let sum = new Function('a', 'b', 'return a + b'); alert( sum(1, 2) ); // 3 ``` Dan ini fungsi tanpa argumen, hanya ada body dari fungsinya: ```js run let sayHi = new Function('alert("Hello")'); sayHi(); // Hello ``` Perbedaan utama dari cara lain yang pernah kita lihat adalah fungsinya dibuat secara harfiah dari sebuah string, yang dilanjutkan ke run-time. Seluruh deklarasi sebelumnya membutuhkan kita, programmer, untuk menulis kode fungsi didalam skrip. Tapi `new Function` mengijinkan kita untuk mengubah string apapun menjadi fungsi. Contoh, kita bisa menerima sebuah fungsi baru dari server dan mengeksekusinya: ```js let str = ... receive the code from a server dynamically ... let func = new Function(str); func(); ``` Itu digunakan dalam beberapa kasus yang spesifik, seperti ketika kita menerima kode dari server, atau secara dinamis mengkompilasikan sebuah fungsi dari sebuah template didalam aplikasi-web yang kompleks. ## Closure Biasanya, sebuah fungsi mengingat dimana dirinya dibuat didalam properti spesial `[[Environtment]]. Fungsi itu akan mereferensi lingkungan leksikal dari dimana ia dibuat (kita telah membahasnya didalam bab ). Tapi ketika sebuah fungsi dibuat menggunakan `new Function`, `[[Environment]]` miliknya disetel bukan pada lingkungan leksikal saat ini, tapi pada yang global. Jadi, fungsi seperti itu tidak memiliki akses kepada variabel luar, hanya pada yang global saja. ```js run function getFunc() { let value = "test"; *!* let func = new Function('alert(value)'); */!* return func; } getFunc()(); // error: value is not defined ``` Bandingkan dengan yang biasa: ```js run function getFunc() { let value = "test"; *!* let func = function() { alert(value); }; */!* return func; } getFunc()(); // *!*"test"*/!*, berasal dari lingkungan leksikal dari getFung ``` Fitur spesial dari `new Function` ini terlihat aneh, tapi akan sangat berguna didalam penerapannya. Bayangkan kalau kita harus membuat sebuah fungsi dari string. Kode dari fungsinya tidak diketahui saat penulisan skrip (itulah kenapa kita tidak menggunakan fungsi yang biasa), tapi akan diketahui saat proses dari eksekusinya. Kita mungkin menerima kodenya itu dari server atau sumber lainnya. Fungsi baru kita membutuhkan interaksi dengan skrip utamanya. Bagaimana jika itu bisa mengakses variabel luar? Masalahnya adalah saat Javascript belum dipublikasikan untuk produksi, itu akan dikompresi menggunakan *minifier* -- sebuah program spesial yang mengecilkan ukuran kode dengan menghapus komentar-komentar, spasi dan -- yang paling penting, menamai variabel lokal menjadi lebih pendek. Contoh, jika sebuah fungsi mempunyai `let userName`, minifier akan mengganti itu dengan `let a` (atau huruf lainnya jika tidak hurufnya tidak tersedia), dan melakukannya dimanapun. Sebenarnya itu adalah yang yang aman untuk dilakukan, karena variabelnya lokal, tidak ada sesuatu dari luar fungsinya yang bisa mengaksesnya. Dan didalam fungsinya, minifier mengganti seluruh penamaan variabelnya. Minifier cukup pintar, mereka menganalisa struktur kodenya, jadi mereka tidak akan merusak apapun. Minifier bukanlah hal bodoh yang hanya akan mencari-dan-mengganti. Jadi jika `new Function` mempunyai akses ke variabel luar, itu tidak akan bisa menemukan `userName` yang telah dinamai ulang. **Jika `new Function` mempunyai akses ke variabel luar, itu akan membuat masalah dengan minifiernya..** Selain itu, kode seperti itu secara arsitekturnya jelek dan rentan terhadap error. Untuk memberikan sesuatu kepada fungsi, dibuat sebagai `new Function`, kita seharusnya menggunakan argumennya. ## Ringkasan Sintaks: ```js let func = new Function ([arg1, arg2, ...argN], functionBody); ``` Untuk sebuah alasan lama, argumen-argumen bisa diberikan sebagai daftar dengan koma. Ketiga deklarasi ini melakukan hal yang sama: ```js new Function('a', 'b', 'return a + b'); // sintaks dasar new Function('a,b', 'return a + b'); // dipisahkan dengan kona new Function('a , b', 'return a + b'); // dipisahkan dengan koma dan ditambah spasi ``` Fungsi yang dibuat dengan `new Function`, memiliki `[[Environment]]` mereferensi kepada lingkungan leksikal global, bukan bagian luarnya. Karenanya, mereka tidak bisa menggunakan variabel di bagian luarnya. Tapi sebenarnya itu bagus karena itu memastikan kita menjauh dari error. Memberikan parameter secara jelas adalah metode yang lebih baik secara arsitektur dan tidak akan menyebabkan error pada minifier. ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/solution.md ================================================ Using `setInterval`: ```js run function printNumbers(from, to) { let current = from; let timerId = setInterval(function() { alert(current); if (current == to) { clearInterval(timerId); } current++; }, 1000); } // penggunaan: printNumbers(5, 10); ``` Using nested `setTimeout`: ```js run function printNumbers(from, to) { let current = from; setTimeout(function go() { alert(current); if (current < to) { setTimeout(go, 1000); } current++; }, 1000); } // penggunaan: printNumbers(5, 10); ``` Perhatikan dikedua solusinya, disana terdapat penundaan awal sebelum keluaran pertamanya. Fungsinya dipanggil setelah `1000ms` saat pertama kali. Jika kita juga ingin fungsinya untuk berjalan langsung, maka kita bisa menambahkan pemanggilan di baris yang berbeda, seperti ini: ```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 ================================================ nilai penting: 5 --- # Membuat keluaran setiap detik Tulis sebuah fungsi `printNumbers(from, to)` yang mengeluarkan angka setiap detik, dimulai dari `from` dan berakhir sampai `to`. Buatlah dua varian solusinya. 1. Menggunakan `setInterval`. 2. Gunakan `setTimeout` bercabang. ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/4-settimeout-result/solution.md ================================================ `setTimeout` apapun akan berjalan hanya setelah kode yang sedang berjalan saat ini telah selesai. `i`nya akan menjadi yang terakhir: `100000000`. ```js run let i = 0; setTimeout(() => alert(i), 100); // 100000000 // asumsikan waktu untuk mengeksekusi fungsi ini lebih dari 100ms for(let j = 0; j < 100000000; j++) { i++; } ``` ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/4-settimeout-result/task.md ================================================ nilai penting: 5 --- # Apa yang akan ditampilkan setTimeout? Di kode dibawah disana terdapat pemanggilan `setTimeout` yang sudah terjadwal, lalu kalkulasi yang cukup berat berjalan, yang memakan waktu lebih dari 100ms untuk selesai. Kapankan fungsi yang sudah dijadwal akan berjalan? 1. Setelah perulangannya. 2. Sebelum perulangannya. 3. Di awal dari perulangannya. Apakan yang akan `alert` tampilkan? ```js let i = 0; setTimeout(() => alert(i), 100); // ? // asumsikan waktu untuk mengeksekusi fungsi ini lebih dari 100ms for(let j = 0; j < 100000000; j++) { i++; } ``` ================================================ FILE: 1-js/06-advanced-functions/08-settimeout-setinterval/article.md ================================================ # Pendadwalan: setTimeout dan setInterval Kita mungkin memutuskan untuk mengeksekusi fungsinya bukanlah sekarang, tapi pada waktu-waktu tertentu nanti. Itu dipanggil dengan "penjadwalan pemanggilan". Terdapat dua metode untuk itu: - `setTimeout` mengijinkan kita untuk menjalankan fungsinya setelah interval dari waktu. - `setInterval` mengijinkan kita untuk menjalankan fungsinya berulang-ulang, dimulai seterlah interval dari waktu yang diberikan, lalu akan terus berulang pada interval waktu yang diberikan. Metode-metode ini bukanlah bagian dari spesifikasi Javascript. Tapi kebanyakan lingkungan memiliki penjadwalan internal dan menyediakan metode-metode ini. Khususnya, mereka didukung didalam semua peramban dan Node.js. ## setTimeout Sintaksnya: ```js let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) ``` Parameters: `func|code` : Fungsi atau sebuah string dari kode untuk dieksekusi. Biasanya, adalah sebuah fungsi. Untuk beberapa alasan, sebuah string dari kode bisa diberikan, tapi hal itu tidak direkomendasikan. `delay` : Penundaannya sebelum berjalan, didalam milidetik (1000 milidetik = 1 detik), secara default 0. `arg1`, `arg2`... : Argumen-argumen untuk fungsinya (tidak didukung didalam IE9-). Contoh, kode ini memanggil `sayHi()` setelah satu detik: ```js run function sayHi() { alert('Hello'); } *!* setTimeout(sayHi, 1000); */!* ``` Dengan argumen: ```js run function sayHi(phrase, who) { alert( phrase + ', ' + who ); } *!* setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John */!* ``` Jika argumen pertama adalah sebuah string, maka Javascript akan membuatkan fungsi untuk itu. Jadi, seperti ini juga akan bekerja: ```js run no-beautify setTimeout("alert('Hello')", 1000); ``` Tapi menggunakan string tidak direkomendasikan, lebih baik gunakan fungsi arrow, seperti ini: ```js run no-beautify setTimeout(() => alert('Hello'), 1000); ``` ````smart header="Berikan sebuah fungsi, tapi jangan dijalankan" Developer pemula terkadang membuat sebuah kesalahan dengan menambahkan kurung `()` setelah fungsinya: ```js // wrong! setTimeout(sayHi(), 1000); ``` Itu tidak akan bekerja, karena `setTimeout` mengharapkan sebuah referensi kepada fungsi. Dan disini `sayHi()` akan menjalankan fungsinya, dan *hasil dari eksekusinya* akan diberikan kepada `setTimeout`. Didalam kasus kita hasil dari `sayHi()` adalah `undefined` (fungsinya tidak mengembalikan apapun), jadi tidak ada hal yang dijadwalkan. ```` ### Pembatalan menggunakan clearTimeout Pemanggilan `setTimeout` mengembalikan sebuah "identifier waktu" `timerId` yang bisa kita gunakan untuk membatalkan eksekusinya. Sintaks untuk membatalkan: ```js let timerId = setTimeout(...); clearTimeout(timerId); ``` Di kode dibawah, kita menjadwalkan fungsinya dan membatalkannya (berubah pikiran). Sebagai hasilnya, tidak ada yang terjadi: ```js run no-beautify let timerId = setTimeout(() => alert("never happens"), 1000); alert(timerId); // identifier waktu clearTimeout(timerId); alert(timerId); // identifier yang sama (tidak akan menjadi null setelah dibatalkan) ``` Seperti yang bisa kita lihat dari keluaran `alert`, didalam peramban identifier timernya adalah sebuah angka. Didalam lingkungan pengembangan lainnya, identifiernya bisa saja sesuatu yang lain. Contoh, Node.js mengembalikan objek timer dengan metode tambahan. Lainnya, tidak terdapat spesifikasi universal untuk metode-metode ini, jadi tidak ada masalah. Untuk peramban, timer dideskripsikan didalam [bagian timer](https://www.w3.org/TR/html5/webappapis.html#timers) dari standar HTML5. ## setInterval Metode `setInterval` mempunyai sintaks yang sama seperti `setTimeout`: ```js let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...) ``` Seluruh argumennya mempunyai arti yang sama. Tapi tidak seperti `setTimeout` fungsinya akan berjalan tidak sekali, tapi akan berjalan secara teratur dengan interval waktu yang diberikan. Untuk menghentikan pemanggilan selanjutnya, kita harus memanggil `clearInterval(timerId)`. Contoh berikut akan memperlihatkan pesan setiap 2 detik. Setelah 5 detik, keluarannya akan dihentikan: ```js run // Ulangi dengan interval 2 detik let timerId = setInterval(() => alert('tick'), 2000); // setelah 5 detik berhenti setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000); ``` ```smart header="Waktu terus berjalan sementara `alert` ditampilkan" Di kebanyakan peramban, termasuk Chrome dan Firefox penghitung waktu internal berlanjut "berdetik" selagi menampilkan `alert/confirm/prompt`. Jadi jika kamu menjalankan kode diatas dan tidak menyingkirkan jendela `alert` untuk beberapa saat, maka `alert` selanjutnya akan langsung muncul. Interval sebenarnya diantara alert lebih pendek dari 2 detik. ``` ## setTimeout bercabang Terdapat dua cara untuk menjalankan sesuatu secara terus-menerus. Satu adalah `setInterval`. Dan satunya lagi adalah `setTimeout` bercabang, seperti ini: ```js /** daripada menggunakan: let timerId = setInterval(() => alert('tick'), 2000); */ let timerId = setTimeout(function tick() { alert('tick'); *!* timerId = setTimeout(tick, 2000); // (*) */!* }, 2000); ``` `setTimeout` diatas menjadwalkan pemanggilan selanjutnya tepat setelah akhir `(*)`. `setTimeout` bercabang metode yang lebih fleksibel daripada `setInterval`. Dengan cara ini pemanggilan mungkun dapat dijadwalkan dengan interval yang berbeda, tergantung dari hasil sebelumnya. Contoh, kita perlu menulis sebuah service yang mengirim sebuah request kepada server setiap 5 detik untuk menanyakan data, tapi di kasus ini servernya sedang sibuk, maka intervalnya harus dinaikan menjadi 10, 20, 40 detik... Ini adalah pseukodenya: ```js let delay = 5000; let timerId = setTimeout(function request() { ...send request... if (request failed due to server overload) { // interval dinaikan untuk pemanggilan selanjutnya delay *= 2; } timerId = setTimeout(request, delay); }, delay); ``` Dan jika fungsi yang sudah kita jadwalkan ternyata membutuhkan sumber-daya yang besar, maka kita bisa mengukur waktu yang diabmbil oleh eksekusinya dan mengatur ulang eksekusi selanjutnya agar lebih cepat atau lebih lambat. **`setTimeout` membolehkan untuk menyetel penundaan eksekusi-eksekusinya lebih presisi daripada `setInterval`.** Ayo kita bandingkan dua pecahan kode berikut. Yang pertama menggunakan `setInterval`: ```js let i = 1; setInterval(function() { func(i++); }, 100); ``` Yang kedua menggunakan `setTimeout` bercabang: ```js let i = 1; setTimeout(function run() { func(i++); setTimeout(run, 100); }, 100); ``` Untuk `setInterval` penjadwal internalnya akan berjalan `func(i++)` setiap 100ms: ![](setinterval-interval.svg) Apakah kamu memperhatikannya? **penundaan sebenarnya diantara pemanggilan `func` pada `setInterval` lebih cepat daripada apa yang ada pada kodenya!** Itu adalah hal yang normal, karena waktu yang diambil oleh eksekusi `func` "mengambil" bagian dari intervalnya. Adalah hal yang mungkin jika eksekusi `func` ternyata lebih lama daripada yang kita harapkan dan memakan lebih dari 100ms. Didalam kasus ini mesinnya menunggu `func` untuk selesai, lalu memeriksa penjadwalnya dan jika waktunya sudah berakhir, maka akan *langsung* dieksekusi lagi. Didalam kasus yang jarang, jika fungsinya selalu mengeksekusi lebih lama daripada `penundaan` ms, maka pemanggilannya akan terjadi tanpa berhenti sama sekali. Dan ini adalah gambaran dari `setTimeoute` bercabang: ![](settimeout-interval.svg) **`setTimeout` bercabang menjamin penundaan yang tepar (disini 100ms).** Itu karena pemanggilan baru sudah direncanakan pada akhir dari pemanggilan sebelumnya. ````smart header="Garbage collection and callback pada setInterval/setTimeout" Ketika sebuah fungsi dimasukan kedalam `setInterval/setTimeout`, sebuah referensi interval dibuat kedalamnya dan disimpan didalam penjadwal. Itu akan mencegah fungsinya dari pembuangan (dihilangkan dari memori), bahkan jika disana sudah tidak ada yang mereferensi kedalam fungsinya lagi. ```js // fungsinya tetap berada di memori sampai penjadwalnya memanggil lagi setTimeout(function() {...}, 100); ``` Untuk `setInterval` fungsinya akan tetap didalam memori sampai `clearInterval` dipanggil. Tidak terdapat efek-samping pada hal itu. Sebuah fungsi mereferensi lingkungan leksikal luar, jadi, selama itu masih ada, variabel luar pun akan tetap ada. Hal itu mungkin akan memakan memori daripada fungsinya sendiri. Jadi ketika kita tidak butuh fungsi yang sudah dijadwalkan lagi, akan lebih baik untuk dibatalkan/diberhentikan, bahkan jika itu sebuah kode yang sangat pendek/kecil. ```` ## setTimeout dengan penundaan nol Terdapat sebuah kasus spesial: `setTimeout(func, 0)`, atau hanya `setTimeout(func)`. Penjadwalan eksekusi dari `func` akan dilakukan secepat mungkin. Tapi penjadwal akan memanggilnya hanya setelah skrip yang sedang berjalan selesai dieksekusi. Jadi fungsinya dijadwalkan untuk berjalan "tepat setelah" skrip yang sedang berjalan. Contoh, dibawah akan mengeluarkan "Hello", lalu langsung "World": ```js run setTimeout(() => alert("World")); alert("Hello"); ``` Pada baris pertama "akan memasukan pemanggilan kedalam urutan pemanggilan setelah 0ms". Tapi penjadwal hanya akan "memeriksa urutanya" setelah skrip yang sedang berjalan selesai, jadi `"Hello"` adalah pertama, dan `"World"` -- setelahnya. Juga terdapat kasus yang berhubungan dengan peramban, kita akan membahasnya didalam bab . ````smart header="Penundaan dengan nol faktanya tidaklah nol (didalam peramban)" Didalam peramban, terdapat sebuah batasan seberapa seringnya timer bercabang bisa berjalan. [standar HTML5](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) mengatakan: "setelah lima timer bercabang, intervalnya dipaksa untuk berjalan setidaknya 4 milidetik.". Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see: Ayo kita prakterkan apa artinya itu dengan contoh dibawah. Pemanggilan `setTimeout` menjadwalkan ulang dengan penundaan 0. Setiap pemanggilan mengingat waktu yang asli dari pemanggilan sebelumnya didalam array `times`. Seperti apa penundaan sesungguhnya terlihat? Lihat dibawah: ```js run let start = Date.now(); let times = []; setTimeout(function run() { times.push(Date.now() - start); // mengingat penundaan dari pemanggilan sebelumnya if (start + 100 < Date.now()) alert(times); // tampilkan penundaanya setelah 100ms else setTimeout(run); // atau akan dijadwalkan ulang }); // contoh dari keluarannya: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100 ``` Pertama timer akan berjalan secara langsung (seperti yang tertulis dalam spesifikasinya), dan lalu kita melihat `9, 15, 20, 24...`. Penundaan wajib 4+ms diantara pemanggilan akan berjalan. Hal yang sama akan terjadi jika kita menggunakan `setInterval` daripada `setTimeout`: `setInterval(f)` menjalankan `f` beberapa kali dengan tanpa delay, dan setelahnya dengan 4+ms delay. Batasan itu sudah ada sejak lama dan beberapa skrip mengandalkan hal itu, jadi itu ada untuk beberapa alasan. Untuk Javascript dibagian server, batasan itu tidaklah ada, dan disana terdapat cara lain untuk menjadwalkan sebuah pekerjaan yang asinkronus, seperti [setImmediate](https://nodejs.org/api/timers.html) untuk Node.js. Jadi hal ini merupakan hal yang berada pada peramban. ```` ## Ringkasan - Metode `setTimeout(func,delay, ...args)` dan `setInterval(func, delay, ...args)` mengijinkan kita untuk menjalankan fungsinya sekali atau terus menerus setelah `delay` milidetik. - Untuk membatalkan eksekusinya, kita harus memanggil `clearTimeout/clearInterval` dengan nilai yang dikembalikan oleh `setTimeout/setInterval`. - Pemanggilan `setTimeout` bercabang adalah alternatif yang lebih fleksibel dari `setInterval`, mengijinkan kita untuk menyetel waktu *diantara* eksekusinya dengan lebih presisi. - Penjadwalan dengan delay 0 dengan `setTimeout(func, 0)` (sama seperti `setTimeout(func)`) digunakan untuk menjadwalkan pemanggilan "secepat mungkin, tapi setelah skrip yang sedang berjalan selesai". - Peramban membatasi delay dengan minimal 5 atau lebih pada pemanggilan bercabang dari `setTimeout` atau dari `setInterval` (setelah pemanggilan kelima) menjadi 4ms. Hanyalah untuk alasan-alasan yang sudah lama. Perhatikan bahwa seluruh metode penjadwalan tidak *menjamin* delay yang tepat. Contoh, didalam peramban timer mungkin lebih lambat untuk beberapa alasan: - CPU-nya sedang melakukan banyak pekerjaan. - Ada tab peramban yang sedang berjalan dalam mode background. - Laptopnya sedang menggunakan mode batre. Semua itu mungkin menaikan resolusi timernya (delay minimalnya) menjadi 300ms atau bahkan 1000ms tergantung perambannya dan performasi pada OS-nya. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js ================================================ function spy(func) { function wrapper(...args) { // gunakan ...args daripada argumen untuk menyimpan "array asli" didalam 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) { // Tulis kodemu disini } ================================================ 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 ================================================ Pembungkus yang dikembalikan oleh `spy(f)` harus menyimpan semua argumen dan lalu menggunakan `f.apply` untuk melanjutkan pemanggilannya. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md ================================================ nilai penting: 5 --- # Spy decorator Buatlah sebuah dekorator `spy(func)` yang harus mengembalikan pembungkus yang menyimpan semua pemanggilan kepada fungsinya didalam propertinya sendiri bernama `calls`. Setiap pemanggilan disimpan sebagai sebuah array dari argumen. Contoh: ```js function work(a, b) { alert( a + b ); // bayangkan work adalah sebuah fungsi yang panjang } *!* 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" } ``` Catatan. Dekoratornya harus berguna untuk unit-testing. Bentuk lanjutannya adalah `sinon.spy` didalam librari [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 ================================================ Solusi: ```js run demo function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; } let f1000 = delay(alert, 1000); f1000("test"); // tampilkan "test" setelah 1000ms ``` Perhatikan bagaimana fungsi arrow digunakan disini. Seperti yang kita tahu, fungsi panah tidak memiliki `this` dan `argumen`nya sendiri, jadi `f.apply(this, arguments)` akan mengambil `this` dan `arguments` dari pembungkusnya. Jika kita memasukan fungsi yang biasa, `setTimeout` akan memanggil fungsinya tanpa argumen dan `this=window` (asumsikan kita berada didalam peramban). Kita masih bisa memberikan `this` yang benar dengan menggunakan variabel tambahan, tapi kodenya akan sedikit menjadi lebih rumit: ```js function delay(f, ms) { return function(...args) { let savedThis = this; // simpan this kedalam variabel tambahan setTimeout(function() { f.apply(savedThis, args); // gunakan disini }, ms); }; } ``` ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md ================================================ nilai penting: 5 --- # Dekorator penunda Buatlah sebuah dekorator `delay(f, ms)` yang menunda setiap pemanggilan dari `f` selama `ms` milidetik. Contoh: ```js function f(x) { alert(x); } // create wrappers let f1000 = delay(f, 1000); let f1500 = delay(f, 1500); f1000("test"); // tampilkan "test" setelah 1000ms f1500("test"); // tampilkan "test" setelah 1500ms ``` Dengan kata lain, `delay(f, ms)` mengembalikan sebuah "varian dari `f` yang telah ditunda selama `ms`". Didalam kode diatas, `f` adalah sebuah fungsi dari sebuah argumen tunggal, tapi solusimu harus bisa melewati seluruh argumen dan konteks dari `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); // diabaikan (terlalu dini) setTimeout(() => debounced('c'), 500); // dijalankan (1000 ms telah berlalu) 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); }; } ``` Pemanggilan kepada `debounce` mengembalikan sebuah pembungkus. Ketika dipanggil, `debounce` akan menunggu lalu memanggil fungsi aslinya setelah `ms` milidetik dan membatal kan timeout sebelumnya. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md ================================================ nilai penting: 5 --- # Debounce decorator Hasil dari dekorator `debounce(f, ms)` adalah sebuah pembungkus yang menghentikan pemanggilan `f` selama `ms` milidetik dari ketidakaktifan (tidak ada pemanggilan, "masa menunggu"), lalu memanggil `f` sekali dengan argumen terakhir. Dengan kata lain, `debounce` seperti seorang sekertaris yang menerima "telefon", dan menunggu selama `ms` milidetik dari ketidakaktifan. Dan lalu menyampaikan pemanggilan terakhir kepada "boss" (melakukan pemanggilan `f`). Contoh, jika kita mempunyai sebuah fungsi `f` dan lalu memasukan `f = debounce(f, 1000)`. Maka jika fungsi pembungkus dipanggil pada 0ms, 200ms, dan 500ms, dan lalu tidak ada pemanggilan lainnya, maka fungsi `f` akan dipanggil sekali, pada 1500ms. Itulah: setelah beberapa saat fungsi tidak dipanggil maka fungsinya akan benar-benar dipanggil dengan rentang waktu 1000ms setelah pemanggilan terakhir. ![](debounce.svg) ...Dan itu akan mendapatkan argumen dari pemanggilan yang paling terakhir, pemanggilan lainnya akan diabaikan. Ini adalah kodenya (digunakan untuk dekorator debounce dari [Lodash library](https://lodash.com/docs/4.17.15#debounce)): ```js let f = _.debounce(alert, 1000); f("a"); setTimeout( () => f("b"), 200); setTimeout( () => f("c"), 500); // fungsi debounce menunggu 1000ms setelah pemanggilan terakhir dan lalu menjalankan: alert("c") ``` Sekarang contoh yang lebih praktikal. Katakan, penggunakan mengetik sesuatu, dan kita ingin mengirim request kepada server ketika pengguna telah selesai mengetik. Pada hal ini, sangat tidak berguna untuk mengirim request kepada server untuk setiap huruf yang diketik. Lagipula kita ingin menunggu, dan lalu memproses hasil ketikan pengguna. Didalam peramban, kita bisa menyetel sebuah event handler(penangan event) -- sebuah fungsi yang dipanggi untuk setiap perubahan pada kotak inputan, sebuah penangan event dipanggil sangat sering untuk setiap huruf yang diketik. Tapi jika kita ingin men`debounce`nya selama 1000ms, maka fungsinya akan dipanggil sekali, 1000ms setelah penginputan huruf terakhir. ```online Didalam contoh ini, handlernya memasukan hasilnya kedalam kotak dibawah, cobalah: [iframe border=1 src="debounce" height=200] Lihat? inputan kedua memanggil fungsi debounce, jadi kontennya diproses setelah 1000ms dari inputan terakhir. ``` Jadi, `debounce` adalah cara terbaik untuk memproses event yang terjadi berurutan: bisa tombol yang dipencet berulang-ulang, pergerakan mouse atau lainnya. Fungsinya akan menunggu hingga pemanggilan terakhir, dan lalu menjalankan fungsi aslinya, lalu hasilnya akan diolah. Tugasnya adalah untuk mengimplementasikan dekorator `debounce`. Petunjuk: jika kamu perhatikan, perubahan fungsinya hanya dengan menambahkan beberapa baris :) ================================================ 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) { // mengingat argumen terakhir untuk diingat setelah kondisi tidak aktif savedArgs = arguments; savedThis = this; return; } // sebaliknya, masuk kedalam kondisi tidak aktif func.apply(this, arguments); isThrottled = true; // mereset isThrotthled setelah penundaan setTimeout(function() { isThrottled = false; if (savedArgs) { // jika terdapat sebuah pemanggilan, savedThis/savedArgs harusnya memiliki nilai terakhir // pemanggilan rekursif menjalankan fungsinya dan menyetel ulang kondisi tidak aktifnya. 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); // (ditunda - kurang dari 1000ms sejak pemanggilan terakhir) f1000(3); // (ditunda - kurang dari 1000ms sejak pemanggilan terakhir) // after 1000 ms f(3) call is scheduled // setelah 1000ms pemanggilan f(3) dilakukan assert.equal(log, "1"); // sampai sekarang hanya pemanggilan pertama yang pernah dilakukan this.clock.tick(1000); // setelah 1000ms assert.equal(log, "13"); // log==13, pemanggilan kepada f1000(3) telah dibuat }); it("the third call waits 1000ms after the second call", function() { this.clock.tick(100); f1000(4); // (ditunda - kurang dari 1000ms sejak pemanggilan terakhir) this.clock.tick(100); f1000(5); // (ditunda - kurang dari 1000ms sejak pemanggilan terakhir) this.clock.tick(700); f1000(6); // (ditunda - kurang dari 1000ms sejak pemanggilan terakhir) this.clock.tick(100); // sekarang 100 + 100 + 700 + 100 = 1000ms telah berlalu assert.equal(log, "136"); // pemanggilan terakhir adalah 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; } ``` Pemanggilan kepada `throttle(func, ms)` mengembalikan `wrapper`. 1. Selama pemanggilan pertama, `wrapper`nya hanya menjalankan `func` dan menyetel kondisi tidak aktif (`isThrottled = true`). 2. Didalam kondisi ini semua pemanggilan akan diingat/disimpan didalam `savedArgs/savedThis`. Ingat baik-baik bahwa konteks dan argumennya sama-sama penting dan harus diingat/disimpan. Kita akan membutuhkannya untuk membuat panggilannya. 3. Setelah `ms` milidetik berlalu, `setTimeout` akan berjalan. Kondisi tidak aktif dihilangkan (`isThrottled = false`) dan, jika kita memiliki daftar panggilan yang diabaikan, `wrapper` akan dieksekusi dengan argumen dan konteks yang terakhir diingat/disimpan. Langkah ketika yang berjalan bukanlah `func`, tapi `wrapper`, karena kita tidak hanya perlu mengeksekusi `func`, tapi sekali-lagi kita memasuki kondisi tidak aktif dan perlu menyetel ulang timeout. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md ================================================ nilai penting: 5 --- # Dekorator penutup Buatlah sebuah dekorator "penutup" `throttle(f, ms)` -- yang mengembalikan sebuah pembungkus. Ketika fungsinya dipanggil beberapa kali, fungsinya akan melakukan pemanggilan kepada `f` maksimal sekali per `ms` milidetik. Perbedaannya dengan dekorator debounce adalah keduanya benar-benar dekorator berbeda: - `debounce` menjalankan fungsinya sekali setelah masa "tidak aktif". Bagus untuk memproses hasil akhir. - `throttle` menjalankan fungsinya tidak lebih banyak dari waktu `ms` yang diberikan. Bagus untuk update tersusun yang tidak terlalu sering dipanggil. Dengan kata lain, `throttle` seperti seorang sekertaris yang menerima panggilan telefon, tapi menggangu bos nya (memanggil fungsi `f` asli) tidak lebih sering dari sekali per `ms` milidetik. Ayo kita lihat contoh pengaplikasiannya langsung untuk mengerti lebih dalam tentang kebutuhannya dan dimana digunakannya. **Contoh, kita ingin mengetahui posisis dari pergerakan mouse.** Didalam peramban kita bisa menyetel sebuah fungsi yang berjalan untuk setiap pergerakan mouse dan mendapatkan lokasi pointernya selama mouse-nya bergerak. Selama mouse-nya bergerak terus-menerus, fungsi ini biasanya berjalan sangat sering, bisa menjadi seperti 100 kali per-detik (setiap 10ms). **Kota ingin meng-update beberapa informasi didalam halaman webnya ketika pointernya bergerak.** ...Akan tetapi meng-update fungsi `update()` terlalu berat dilakukan untuk dijalankan terus menerus mengikuti pergerakan mouse. Tidak ada alasan yang bagus untuk meng-update lebih sering daripada sekali per 100ms. Jadi kita akan membungkusnya dengan dekorator: gunakan `throttle(update, 100)` sebagai fungsi untuk berjalan setiap pergerakan mouse daripada secara langsung menggunakan `update()`. Dekoratornya akan sering dipanggil, tapi untuk pemanggilan kepada `update` akan dilakukan maksimal sekali per 100ms. Secara visual, langkah-langkahnya akan seperti ini: 1. Untuk pergerakan mouse pertama dekoratornya langsung memanggil fungsi `update`. Itu penting, untuk penggunanya melihat reaksi sistemnya ketika mereka baru saja bergerak. 2. Lalu selama mousenya bergerak, sampai `100ms` tidak akan terjadi apa-apa. Dekoratornya akan mengabaikan pemanggilannya. 3. Setelah melewati `100ms` -- satu pemanggilan fungsi `update `terjadi dengan kondisi paling terakhir. 4. Lalu, pada akhirnya, mousenya berhenti disuatu tempat. Dekoratornya menunggu sampai melewati `100ms` dan lalu menjalankan `update` dengan kondisi terakhir. Jadi, cukup penting, pergerakan mouse terakhir akan diproses. Contoh kode: ```js function f(a) { console.log(a); } // f1000 mengirimkan pemanggilan kepada f maksimal sekali per 1000ms let f1000 = throttle(f, 1000); f1000(1); // tampilkan 1 f1000(2); // (ditahan, belum melewati 1000ms) f1000(3); // (ditahan, belum melewati 1000ms) // ketika 1000ms terlewati... // ...menampilkan 3, nilai sebelum tiga yaitu 2 akan diabaikan ``` Catatan. Argumen dan konteks `this` yang dikirimkan kepada `f1000` harus bisa dikirimkan kepada fungsi asli `f`. ================================================ FILE: 1-js/06-advanced-functions/09-call-apply-decorators/article.md ================================================ # Decorators dan forwarding, call/apply Javascript memberikan fleksibilitas yang istimewa ketika harus berurusan dengan fungsi. Mereka bisa dikirim, digunakan sebagai objek, dan sekarang kita akan melihat bagaimana *penerusan/forward* panggilan diantara mereka dan *mendekorasi/decorate* mereka. ## Cache transparan Katakan kita mempunyai sebuah fungsi `slow(x)` yang mana adalah fungsi berat saat diolah pada CPU, tapi hasil dari fungsi tersebut stabil. Dengan kata lain, untuk `x` yang sama fungsi itu selalu mengembalikan hasil yang sama. Jika fungsinya sering dipanggil, kita mungkin ingin meng-cache (mengingat) hasilnya untuk menghindari pembuangan waktu saat kalkulasi-ulang. Tapi sebagai gantinya daripada menambahkan fungsionalitas lain kedalam `slow()` kita akan membuat sebuah fungsi pembungkus/wrapper, yang menambahkan cache. Seperti yang akan kita lihat, terdapat beberapa keuntungan untuk melakukan cache. Ini kodenya, dan penjelasannya: ```js run function slow(x) { // disini terdapat task berat yang menggunakan sumberdaya CPU alert(`Called with ${x}`); return x; } function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { // jika terdapat kunci "x" pada cache return cache.get(x); // baca hasil dari cache } let result = func(x); // jika tidak, panggil fungsi cache.set(x, result); // dan cache (ingat) hasilnya return result; }; } slow = cachingDecorator(slow); alert( slow(1) ); // slow(1) telah dimasukan kedalam cache alert( "Again: " + slow(1) ); // sama seperti baris sebelumnya alert( slow(2) ); // slow(2) telah dimasukan kedalam cache alert( "Again: " + slow(2) ); // sama seperti baris sebelumnya ``` Didalam kode diatas `cachingDecorator` adalah sebuah *decorator/dekorator*: sebuah fungsi spesial yang menerima fungsi dan mengubah tingkah lakunya. Idenya adalah kita bisa memanggil `cachingDecorator` dari fungsi manapun, dan itu akan mengembalikan pembungkus caching. Itu bagus, karena kita bisa mempunyai banyak fungsi yang dapat menggunakan fitur itu, dan semua yang kita butuhkan adalah menerapkan `cachingDecorator` kedalam fungsinya. Dengan memisahkan caching dari kode fungsi utama kita juga bisa tetap membuat kode utama tetap sederhana. Hasil dari `cachingDecorator(func)` adalah sebuah "pembungkus/wrapper": `function(x)` yang "membungkus" pemanggilan dari `func(x)` kedalam logika penyimpanan cache. ![](decorator-makecaching-wrapper.svg) Dari kode luar, fungsi yang dibungkus `slow` akan melakukan tetap hal yang sama. Fungsinya hanya akan menambahkan aspek caching kedalam prilakunya. Untuk meringkaskan, terdapat beberapa keuntungan untuk menggunakan `cachingDecorator` secara terpisah daripada dimasukan kedalam kode `slow` itu sendiri: - `cachingDecorator` dapat digunakan lagi. Kita bisa menerapkannya kedalam fungsi lainnnya. - Logika dari penyimpanan kedalam cache dipisahkan, itu tidak akan menambah kompleksitas dari `slow` sendiri. - Kita bisa menggunakan beberapa dekorator jika dibutuhkan. ## Menggunakan "func.call" untuk konteksnya Dekorator penyimpanan kedalam cache diatas tidak cokok untuk bekerja dengan metode objek. Contoh, didalam kode dibawah `worker.slow()` akan berhenti bekerja setelah decoration: ```js run // disini membuat worker.slow menyimpan kedalam cache let worker = { someMethod() { return 1; }, slow(x) { // task yang benar-benar menggunakan banyak sumber daya CPU disini alert("Called with " + x); return x * this.someMethod(); // (*) } }; // same code as before 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) ); // metode aslinya bekerja worker.slow = cachingDecorator(worker.slow); // sekarang simpan kedalam cache *!* alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined */!* ``` Errornya muncul pada baris `(*)` yang mencoba untuk mengakses `this.someMethod` dan gagal. Apakah kamu bisa lihat kenapa? Alasannya adalah karena pembungkusnya memanggil fungsi aslinya sebagai `func(x)` pada baris `(**)`. Dan, ketika dipanggil seperti itu, fungsinya mendapatkan `this = undefined`. Kita harusnya bisa melihat kasus yang serupa jika kita mencoba menjalankan: ```js let func = worker.slow; func(2); ``` Jadi, pembungkusnya mengirimkan pemanggilan pada metode aslinya, tapi tanpa konteks dari `this`. Karenanya akan terjadi error. Coba kita perbaiki. Terdapat sebuah metode bawaan yang spesial [func.call(context, ...args)](mdn:js/Function/call) yang mengijinkan untuk melakukan pemanggilan fungsi menyetel nilai dari `this`. Sintaksnya adalah: ```js func.call(context, arg1, arg2, ...) ``` Itu akan menjalankan `func` yang menyediakan argumen pertama sebagai `this`, dan sisanya sebagai argumen-argumennya. Untuk menyederhanakannya, kedua pemanggilan dibawah hampir melakukan hal yang serupa: ```js func(1, 2, 3); func.call(obj, 1, 2, 3) ``` Keduanya memanggil `func` dengan argumen `1`, `2`, dan `3`. Perbedaannya adalah `func.call` juga menyetel `this` menjadi `obj`. Sebagai sebuah contoh, didalam kode dibawah kita memanggil `sayHi` didalam konteks pada objek yang berbeda: `sayHi.call(user)` menjalankan `sayHi` menyediakan `this=user`, dan baris selanjutnya menyetel `this=admin`: ```js run function sayHi() { alert(this.name); } let user = { name: "John" }; let admin = { name: "Admin" }; // lakukan pemanggilan untuk memberikan objek yang berbeda sebagai "this" sayHi.call( user ); // John sayHi.call( admin ); // Admin ``` Dan disini kita menggunakan `call` untuk memanggil `say` dengan konteks dan *phrase* yang diberikan: ```js run function say(phrase) { alert(this.name + ': ' + phrase); } let user = { name: "John" }; // user menjadi this, dan "Hello" menjadi argumen pertama say.call( user, "Hello" ); // John: Hello ``` Didalam kasus kita, kita bisa menggunakan `call` didalam pembungkus untuk memberikan konteks kedalam fungsi aslinya: ```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" diberikan dengan benar sekarang */!* cache.set(x, result); return result; }; } worker.slow = cachingDecorator(worker.slow); // sekarang disimpan kedalam cache alert( worker.slow(2) ); // bekerja alert( worker.slow(2) ); // bekerja, tidak memanggil yang aslinya (dari cache) ``` Sekarang semuanya berjalan. Untuk memperjelas, kita akan melihat lebih dalam bagaimana `this` diberikan: 1. Setelah dekorasi dari `worker.slow` sekarang menjadi pembungkusnya `function (x) { ... }`. 2. Jadi ketika `worker.slow(2)` dieksekusi, pembungkusnya mendapatkan `2` sebagai sebuah argumen dan `this=worker` (sebuah objek sebelum titik). 3. Didalam pembungkusnya, asumsikan hasilnya belum disimpan didalam cache, `func.call(this, x)` diberikan kepada `this` (`=worker`) dan argumennya (`=2`) kepada metode aslinya. ## Menjadi multi-argument Sekarang kita buat `cachingDecorator` menjadi lebih universal. Sampai sekarang fungsi itu hanya bekerja dengan satu-argumen. Sekarang bagaimana untuk menyimpan multi-argumen metode `worker.slow` kedalam cache? ```js let worker = { slow(min, max) { return min + max; // asumsikan sebuah fungsi yang sangat berat } }; // harus mengingat pemanggilan dengan argument-yang-sama worker.slow = cachingDecorator(worker.slow); ``` Sebelumnya, untuk argumen tunggal `x` kita bisa dengan melakukan `cache.set(x, result)` untuk menyimpan result-nya dan `cache.get(x)` untuk mengambilnya. Tapi kita harus mengingat hasil dari sebuah *kombinasi dari argumen-argumen* `(min, max)`. `Map` yang asli mengambil nilai tunggal sebagai kuncinya. Terdapat beberapa solusi yang bisa dilakukan: 1. Implementasikan struktur data seperti-map baru yang lebih serba guna dan mengijinkan menggunakan banyak-kunci (atau gunakan third-party). 2. Gunakan maps bercabang: `cache.set(min)` akan menjadi sebuah `Map` yang menyimpan pasangan `(max, result)`. Jadi kita bisa mendapatkan `result` sebagai `cache.get(min).get(max)`. 3. Gabungkan kedua nilai menjadi satu. Didalam kasus tertentu kita bisa menggunakna sebuah string `"min,max"` sebagai kunci `Map`. Untuk fleksibilitas, kita bisa mengijinkan untuk menyediakan sebuah *fungsi hashing* untuk dekoratornya, yang mengetahui bagaimana cara membuat nilai tunggal dari banyak nilai. Untuk kebanykan penggunaan yang praktikal, varian ketiga sudahlah cukup, jadi kita akan menggunakannya. Juga kita harus memberikan bukan hanya `x`, tapi seluruh argumen-argumen didalam `func.call`. Kita panggil ulang didalam sebuah `function()` kita bisa mendapatkan pseudo-array dari argumennya sebagai `arguments`, jadi `func.call(this, x)` harus diganti dengan `func.call(this, ...arguments)`. Ini adalah `cachingDecorator` yang lebih powerful: ```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) ); // bekerja alert( "Again " + worker.slow(3, 5) ); // sama (dari cache) ``` Sekarang itu bekerja dengan berapapun jumlah argumen (walaupun fungsi hash harusnya disesuaikan untuk menerima argumen dengan jumlah berapapun. Cara yang menarik untuk menangani ini akan dijelaskan dibawah). Terdapat dua perubahan: - Didalam baris `(*)` memanggil `hash` untuk membuat sebuah kunci tunggal dari `arguments`. Disini kita menggunakan fungsi "joining" yang sederhana yang mengubah argument `(3, 5)` menjadi kunci `"3,5"`. Kasus kompleks yang lain mungkin membutuhkan fungsi-fungsi hashing lainnya. - Lalu `(**)` menggunakan `func.call(this, ...arguments)` untuk memberikan konteks dan seluruh argumen yang pembungkusnya dapatkan (tidak hanya yang pertama) dari fungsi aslinya. ## func.apply Daripada `func.call(this, ...arguments)` kita bisa gunakan `func.apply(this, arguments)`. Sintaks dari metode bawaannya [func.apply](mdn:js/Function/apply) adalah: ```js func.apply(context, args) ``` Kode diatas menjalankan `func` dan menyetel `this=context` dan menggunakan objek yang seperti array `args` sebagai daftar dari argumen-argumen. Perbedaan sintaks antara `call` dan `apply` adalah bahwa `call` mengharapkan sebuah daftar dari argumen-argumen, sementara `apply` menerima objek yang seperti-array didalamnya. Jadi kedua pemanggilan dibawah hampir sama: ```js func.call(context, ...args); // mengirimkan sebuah array sebagai daftar dengan sintaks spread func.apply(context, args); // sama seperti pemanggilan call ``` Hanya terdapat perbedaan yang tipis: - Sintaks spread `...` mengijinkan untuk mengirimkan *iterable* `args` sebagai list untuk `call`. - `apply` hanya menerima `args` yang *seperti-array*. Jadi, dimana kita mengharapkan sebuah iterasi, gunakan `call`, dan dimana kita menggunakan seperti-array, gunakan `apply`. Dan untuk objek yang bisa diiterasi dan seperti-array, seperti array yang asli, kita bisa gunakan keduanya, tapi `apply` akan lebih cepat, karena kebanyakan mesin Javascript secara internal mengoptimasi `apply` lebih baik. Mengirimkan seluruh argumen bersamaan dengan konteks ke fungsi lainnya dipanggil dengan *call forwarding*. Ini adalah contoh paling sederhana dari *call forwarding*: ```js let wrapper = function() { return func.apply(this, arguments); }; ``` Ketika sebuah kode eksternal memanggil `wrapper` yang seperti diatas, pemanggilan itu tidak bisa dibedakan dengan pemanggilan dari fungsi asli `func`. ## Meminjam sebuah metode [#method-borrowing] Sekarang kita buat satu perubahan minor didalam fungsi hashing: ```js function hash(args) { return args[0] + ',' + args[1]; } ``` Seperti yang sekarang, fungsi diatas hanya akan bekerja dengan dua argumen. Fungsi diatas akan lebih baik jika dapat menerima berapapun jumlah dari `args`. Solusi naturalnya harusnya dengan menggunakan metode [arr.join](mdn:js/Array/join): ```js function hash(args) { return args.join(); } ``` ...Sayangnya, hal diatas tidak akan bekerja. karena kita memanggil `hash(arguments)`, dan objek `arguments` adalah hal yang bisa diiterasi dan hal yang seperti array, tapi bukanlah array asli. jadi memanggil `join` tentu tidak akan bekerja, seperti yang bisa kita lihat dibawah: ```js run function hash() { *!* alert( arguments.join() ); // Error: arguments.join is not a function */!* } hash(1, 2); ``` Tetap, terdapat sebuah cara yang mudah untuk menggunakan array join: ```js run function hash() { *!* alert( [].join.call(arguments) ); // 1,2 */!* } hash(1, 2); ``` Caranya bernama *method borrowing*. Kita menggunakan (borrow/meminjam) metode join dari array biasa (`[].join`) dan gunakan `[].join.call` untuk menjalankannya didalam konteks dari `arguments`. Kenapa hal itu bisa bekerja? Itu karena algoritma internal dari metode native `arr.join(glue)` sangatlah sederhana. Diambil dari spesifikasi hampir "as-is(apa adanya)": 1. Biarkan `glue` menjadi argumen pertama atau, jika tidak ada argumen, maka sebuah koma `","`. 2. Biarkan `result` menjadi sebuah string kosong. 3. Masukan `this[0]` kedalam `result`. 4. Masukan `glue` dan `this[1]`. 5. Masukan `glue` dan `this[2]`. 6. ...lakukan terus sampai item dari `this.length` ditempel. 7. Kembalikan `result`. Jadi, secara tekniks itu akan menggunakan `this` dan menggabungkan `this[0]`, `this[1]` ...lainnya bersama. Itu secara sengaja ditulis dengan cara yang mengijinkan hal yang seperti array `this` (bukan kebetulan, banyak metode lainnya mengikuti cara ini). Itulah kenapa hal ini bekerja juga dengan `this=arguments`. ## Decorators and properti fungsi Secara umum mengganti sebuah fungsi atau metode dengan yang telah diubah adalah hal yang aman, kecuali untuk satu hal kecil. Jika fungsi aslinya memiliki properti didalamnya `func.calledCount` atau apapun, maka fungsi yang telah diubah tidak akan memilikinya. Karena itu adalah sebuah pembungkus. Jadi haruslah hati-hati saat menggunakannya. Contoh, didalam contoh diatas jika fungsi `slow` memiliki properti apapun didalamnya, maka `cachingDecorator(slow)` adalah sebuah pembungkus tanpa properti itu. Beberapa dekorator mungkin menyediakan propertinya sendiri. Misalnya sebuah dekorator mungkin menghitung berapa kali fungsinya dipanggil dan berapa lama pemanggilannya, dan mengetahui informasi ini lewat pembungkus properti. Terdapat sebuah cara untuk membuat dekorator yang tetap menyimpan akses kepada properti fungsi, tapi hal ini membutuhkan objek spesial `Proxy` untuk membungkus fungsinya. Kita akan pelajari nanti dalam artikel . ## Ringkasan *Dekorator* adalah sebuah pembungkus fungsi yang mengubah prilaku fungsi tersebut. Pekerjaan utamanya tetap untuk membawa fungsinya. Dekorator bisa dilihat sebagai "fitur" atau "aspek" yang bisa ditambahkan kedalam fungsi. Kita bisa menambahkan satu atau banyak. Dan semuanya tanpa mengubah kode dari fungsinya sendiri. Untuk mengimplementasikan `cachingDecorator`, kita telah mempelajari metode: - [func.call(context, arg1, arg2...)](mdn:js/Function/call) -- memanggil `func` dengan konteks dan argumen yang diberikan. - [func.apply(context, args)](mdn:js/Function/apply) -- memanggil `func` mengirimkan `context` sebagai this dan hal yang seperti array `args` kedalam sebuah daftar dari argumen. *call forwarding* biasanya digunakan dengan `apply`: ```js let wrapper = function() { return original.apply(this, arguments); }; ``` Kita juga melihat contoh dari *method borrowing* ketika kita mengambil metode dari sebuah objek dan `call/memanggil`nya didalam konteks dari objek lain. Hal itu cukup umum untuk mengambil metode array dan mengaplikasikannya kepada `arguments`. Alternatif lainnya adalah untuk menggukanan objek parameter rest yang mana adalah sebuah array asli. Terdapat beberapa dekorator yang tersedia. Pecahkan seluruh task untuk mengetahui seberapa paham kamu tentang dekorator tersebut didalam bab ini. ================================================ FILE: 1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md ================================================ Jawabannya: `null`. ```js run function f() { alert( this ); // null } let user = { g: f.bind(null) }; user.g(); ``` Konteks dari pengikatan fungsi sangat sulit diperbaiki. Tidak ada cara untuk merubahnya dilain waktu. Jadi bahkan ketika kita menjalankan `user.g()`, fungsi aslinya dipanggil dengan `this=null`. ================================================ FILE: 1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md ================================================ nilai penting: 5 --- # Ikat fungsi sebagai sebuah metode Apakah keluarannya? ```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 ================================================ Jawabannya: **John**. ```js run no-beautify function f() { alert(this.name); } f = f.bind( {name: "John"} ).bind( {name: "Pete"} ); f(); // John ``` Objek eksotis [bound function](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects) yang dikembalikan oleh `f.bind(...)` mengingat konteksnya (dan argumen jika ada) hanya pada waktu pembuatan. Sebuah fungsi tidak bisa diikat-ulang. ================================================ FILE: 1-js/06-advanced-functions/10-bind/3-second-bind/task.md ================================================ nilai penting: 5 --- # Pengikatan kedua Bisakah kita merubah`this` dengan pengikatan tambahan? Apakah yang akan menjadi keluarannya? ```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 ================================================ Jawabannya: `undefined`. Hasil dari `bind` adalah objek lainnya. Objek tersebut tidak memiliki properti `test`. ================================================ FILE: 1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md ================================================ nilai penting: 5 --- # Properti fungsi setelah pengikatan Terdapat sebuah nilai didalam properti dari sebuah fungsi. Apakah properti tersebut akan berubah setelah `bind`? Kenapa, atau kenapa tidak? ```js run function sayHi() { alert( this.name ); } sayHi.test = 5; *!* let bound = sayHi.bind({ name: "John" }); alert( bound.test ); // apakah keluarannya? kenapa? */!* ``` ================================================ FILE: 1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md ================================================ Errornya muncul karena `ask` mendapatkan fungsi `loginOk/loginFail` tanpa objeknya. ketika `ask` memanggil, `loginOk/loginFail` mengasumsikan bahwa `this=undefined`. Ayo kita coba `bind` konteksnya: ```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)); */!* ``` Sekarang kodenya berjalan. Alternatif lainnya bisa menggunakan: ```js //... askPassword(() => user.loginOk(), () => user.loginFail()); ``` Contoh diatas biasanya bekerja dan terlihat lebih rapih. Cara ini sedikit kurang bisa digunakan didalam situasi yang lebih kompleks dimana variabel `user` mungkin berubah *setelah* `askPassword` dipanggil, tapi *sebelum* pengguna menjawab dan memanggil `() => user.loginOk()`. ================================================ FILE: 1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md ================================================ nilai penting: 5 --- # Perbaiki sebuah fungsi yang telah kehilangan "this" Pemanggilan kepada `askPassword()` didalam kode dibawah harus memeriksa passwordnya dan lalu memanggil `user.loginOk/loginFail` tergantung dari jawabannya. Tapi pemanggilan itu mengembalikan sebuah error. kenapa? Perbaiki baris yang di tandai agar kodenya dapat berjalan dengan benar (baris lainnya tidak perlu diubah). ```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. Funakan fungsi pembungkus, lebih jelasnya gunakanlah fungsi arrow: ```js askPassword(() => user.login(true), () => user.login(false)); ``` Sekarang fungsi `user dapat diambil dari variabel luar dan dijalankan dengan normal. 2. Atau buat sebuah fungsi mini dari `user.login` yang menggunakan `user` sebagai konteksnya dan yang mana mempunyai argumen pertama yang benar: ```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 ================================================ nilai penting: 5 --- # Pengaplikasian parsial untuk login Tugas yang ini adalah varian yang sedikit lebih sulit daripada . Objek `user`nya telah dimodifikasi. Sekarang daripada menggunakan dua fungsi `loginOk/loginFail`, sekarang hanya memiliki satu fungsi `user.login(true/false)`. Apa yang harus kita kirimkan kedalam `askPassword` di kode dibawah, apakah harus memanggul `user.login(true)` sebagai `ok` dan `user.login(false)` sebagai `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(?, ?); // ? */!* ``` Ubahlah kodenya hanya pada bagian yang ditandai. ================================================ FILE: 1-js/06-advanced-functions/10-bind/article.md ================================================ libs: - lodash --- # Function binding Ketika mengirimkan metode objek sebagai callback, seperti `setTimeout`, terdapat sebuah masalah: "kehilangan `this`". Didalam chapter ini kita akan belajar cara memperbaikinya. ## Kehilangan "this" Kita sudah melihat beberapa contoh saat kehilangan `this`. Sekalinya sebuah metode dikirim kebagian kode lain dengan terpisah dari objeknya -- `this` akan menghilang dari metodenya. Ini adalah bagaimana hal itu terjadi dengan `setTimeout`: ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; *!* setTimeout(user.sayHi, 1000); // Hello, undefined! */!* ``` Seperti yang bisa kita lihat, keluarannya tidak menampilkan "John" sebagai `this.firstName`, tapi menampilkan `undefined`! Itu karena `setTimeout` mendapatkan fungsi `user.sayHi`, terpisah dari objeknya. Baris terakhir bisa ditulis ulang sebagai: ```js let f = user.sayHi; setTimeout(f, 1000); // kehilangan konteks dari user ``` Metode `setTimeout` didalam peramban sedikit spesial: metode tersebut menyetel `this=window` untuk pemanggilan fungsi (untuk Node.js, `this` menjadi objek timer, tapi tidak terlalu penting disini). Jadi untuk `this.firstName` metodenya jadi mendapatkan `window.firstName`, yang mana tidak ada. Dalam kasus serupa lainnya `this` akan menjadi `undefined`. Tugasnya cukup tipikal -- kita ingin mengirim metode objek ke bagian kode lainnya (disini -- kepada penjadwal/setTimeout) dimana metodenya akan dipanggil. Bagaimana cara untuk memeriksa konteksnya dipanggil dengan benar? ## Solusi 1: pembungkus Solusi sederhananya adalah untuk menggunakan fungsi pembungkus: ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; *!* setTimeout(function() { user.sayHi(); // Hello, John! }, 1000); */!* ``` Kode diatas bekerja, karena `user` didapatkan dari lingkungan leksikal terluar, dan lalu memanggil metodenya secara normal. Solusi yang sama, tapi lebih pendek: ```js setTimeout(() => user.sayHi(), 1000); // Hello, John! ``` Terlihat bagus, tapi sedikit memiliki kerentanan yang akan muncul pada struktur kodenya. Bagaimana jika sebelum `setTimeout` berjalan (terdapat penundaan selama satu detik!) nilai `user` untuk berubah? Maka, tiba-tiba,fungsinya akan memanggil objek yang salah. ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(() => user.sayHi(), 1000); // ...nilai dari user berubah sebelum 1 detik! user = { sayHi() { alert("Another user in setTimeout!"); } }; // setTimeout menggunakan user yang berbeda! ``` Solusi selanjutnya akan menjamin hal seperti diatas tidak akan terjadi. ## Solusi 2: bind Fungsi menyediakan sebuah metode bawaan [bind](mdn:js/Function/bind) yang mengijinkan untuk membernarkan `this`. Sintaks dasarnya adalah: ```js // contoh sintaks yang lebih kompleks akan kita segera lihat let boundFunc = func.bind(context); ``` hasil dari `func.bind(contenxt)` adalah sesuatu yang terlihat seperti fungsi spesial atau bisa disebut dengan "objek eksotik", yang dapat dipanggil sebagai fungsi dan dapat melanjutkan pemanggilan kepada `func` sambil menyetel `this=context`. Dengan kata lain, memanggil `boundFunc` sama seperti `func` dengan nilai `this` yang tetap. Contoh, disini `funcUser` mengirimkan sebuah panggilan kepada `func` dengan `this=user`: ```js run let user = { firstName: "John" }; function func() { alert(this.firstName); } *!* let funcUser = func.bind(user); funcUser(); // John */!* ``` Disini `func.bin(user)` sebagai sebuah varian dari `func`, dengan nilai tetap `this=user`. Seluruh argumen dikirim kepada `func` asli "sebagaimana adanya", contoh: ```js run let user = { firstName: "John" }; function func(phrase) { alert(phrase + ', ' + this.firstName); } // bind this to user let funcUser = func.bind(user); *!* funcUser("Hello"); // Hello, John (argumen "Hello" dikirim, dan this=user) */!* ``` Sekarang kita coba dengan menggunakan metode objek: ```js run let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; *!* let sayHi = user.sayHi.bind(user); // (*) */!* // bisa dijalankan tanpa objek sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! // bahkan jika nilai dari user berubah sebelum 1 detik // sayHi menggunakan nilai yang telah diikat, yang mana telah mereferensi kepada objek yang lama user = { sayHi() { alert("Another user in setTimeout!"); } }; ``` Didalam baris `(*)` kita menggunakan metode `user.sayHi` dan mengikatkannta kepada `user`. `sayHi` adalah sebuah fungsi "terikat", yang bisa dipanggil sendiri atau dikirimkan kepada `setTimeout` -- itu tidaklah penting, yang penting adalah konteksnya tepat. Disini kita bisa melihat argumen yang dikirimkan "seperti adanya", hanya saja `this` nilainya menjadi tetap oleh `bind`: ```js run let user = { firstName: "John", say(phrase) { alert(`${phrase}, ${this.firstName}!`); } }; let say = user.say.bind(user); say("Hello"); // Hello, John (argumen "Hello" dikirim untuk digunakan) say("Bye"); // Bye, John ("Bye" dikirim untuk digunakan) ``` ````smart header="Metode yang bermanfaat: `bindAll`" Jika sebuah objek mempunyai beberapa metode dan kita berencana untuk mengirimkannya kebagian kode lain secara terus-menerus, kita bisa mengikatkannya didalam sebuah perulangan: ```js for (let key in user) { if (typeof user[key] == 'function') { user[key] = user[key].bind(user); } } ``` Librari Javascript juga menyediakan fungsi untuk memudahkan pengikatan/binding masal, contoh [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) didalam lodash. ## Partial functions/Fungsi sebagian Sampai sekarang kita hanya berbicara tentang binding/pengikatan `this`. Ayo kita lihat lebih dalam. Kita bisa mengikat bukan hanya `this`, tapi juga argumen. Yang mana sangat jarang digunakan, tapi terkadang cukup mudah digunakan. Sintaks penuh dari `bind`: ```js let bound = func.bind(context, [arg1], [arg2], ...); ``` Yang mana mengijinkan kita untuk mengikat konteks sebagai `this` dan memulai argumen dari sebuah fungsi. Contoh, kita mempunyai sebuah fungsi perkalian `mul(a, b)`: ```js function mul(a, b) { return a * b; } ``` Kita gunakan `bind` untuk membuat sebuah fungsi `double` didalamnya: ```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 ``` Panggilan pada `mul.bind(null, 2)` membuat function `double` baru yang memberikan panggilan terhadap `mul`, memperbaiki `null` sebagai konteksnya dan `2` sebagai argumen pertamanya. Argumen-argumen lebih lanjut yang diberikan "as is/sebagaimana adanya". Itu dipanggil [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- kita membuat sebuah fungsi baru dengan memperbaiki beberapa parameter dari yang sudah ada. Harap dicatat bahwa disini kita tidak menggunakan `this`. Tapi `bind` memerlukannya, jadi kita harus meletakkan di dalam sesuatu seperti `null`. Fungsi `triple` di dalam kode dibawah ini melipatkan tiga kali lipat nilai tersebut: ```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 ``` Kenapa kita umumnya membuat fungsi parsial? Manfaatnya bahwa kita dapat membuat sebuah fungsi independen dengan nama yang dapat dibaca (`double`, `triple`). Kita bisa menggunakannya dan tidak menyediakan argumen pertamanya setiap saat karena sudah diperbaiki dengan `bind`. Dalam kasus lain, aplikasi parsial berguna saat kita punya sebuah fungsi generik dan menginginkan varian yang kurang universal untuk kenyamanan. Contoh, kita punya sebuah fungsi `send(from, to, text)`. Kemudian, di dalam objek `user` kita mungkin ingin menggunakan varian parsial darinya: `sendTo(to, text)` yang dikirim dari user saat ini. ## Menjadi parsial tanpa konteks Bagaimana jika kita ingin memperbaiki beberapa argumen, tetapi bukan konteks `this`? Contoh, untuk sebuah method objek. `bind` yang asli tidak mengizinkan itu. Kita tidak bisa begitu saja mengabaikan konteks dan lompat ke argumen. Untungnya, fungsi `partial` untuk mengikat argumen saja dapat dengan mudah diterapkan. Seperti ini: ```js run *!* function partial(func, ...argsBound) { return function(...args) { // (*) return func.call(this, ...argsBound, ...args); } } */!* // Usage: let user = { firstName: "John", say(time, phrase) { alert(`[${time}] ${this.firstName}: ${phrase}!`); } }; // tambahkan method parsial dengan waktu tetap user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Something like: // [10:00] John: Hello! ``` Hasil dari panggilan `partial(func[, arg1, arg2...])` yaitu sebuah pembungkus `(*)` yang memanggil `func` dengan: - `this` sama seperti yang didapat (`user.sayNow` menyebutnya `user`) - Lalu berikan `...argsBound` -- argumen dari panggilan `partial` yaitu (`"10:00"`) - Lalu berikan `...args` -- argumen yang diberikan ke pembungkus (`" Hello "`) Sangat mudah melakukannya dengan sintaks penyebaran, bukan? Juga ada implementasi [_.partial](https://lodash.com/docs#pihak) yang siap dari perpustakaan lodash. ## Kesimpulan Method `func.bind(context, ...args)` mengembalikan sebuah "varian terikat" dari function `func` yang memperbaiki konteks` this` dan argumen pertama jika diberikan. Biasanya kita menerapkan `bind` untuk memperbaiki `this` untuk sebuah method objek, sehingga kita bisa memberikannya ke suatu tempat. Misalnya, ke `setTimeout`. Ketika kita memperbaiki beberapa argumen dari function yang ada, fungsi yang dihasilkan (less universal) disebut *partially applied* atau *partial*. Parsial lebih mudah digunakan ketika kita tidak ingin mengulangi argumen yang sama berulang kali. Seperti jika kita memiliki fungsi `send (from, to)`, dan `from` harus selalu sama untuk tugas kita, kita bisa mendapatkan sebuah partial dan melanjutkannya. ================================================ FILE: 1-js/06-advanced-functions/10-bind/head.html ================================================ ================================================ FILE: 1-js/06-advanced-functions/12-arrow-functions/article.md ================================================ # Membahas Kembali Fungsi Arrow Ayo kita kunjungi kembali Fungsi Arrow. Fungsi arrow bukanlah cuma bertujuan untuk menyingkat penulisan fungsi. Namun mereka memiliki fitur yang berguna dalam kondisi tertentu. Javascript memiliki banyak kondisi yang dimana kita membutuhkan penulisan fungsi kecil yang yang dijalankan disuatu tempat. Sebagai contoh: - `arr.forEach(func)` -- `func` dijalankan oleh `forEach` untuk setiap item pada array. - `setTimeout(func)` -- `func` dijalankan oleh penjadwal bawaan. - ...dan ada banyak contoh lain. Sudah menjadi jiwa dasar javascript untuk membuat fungsi dan menjalankannya disuatu tempat. Dan ada kalanya pada suatu fungsi, kita biasanya tidak ingin meninggalkan konteks dimana fungsi itu berjalan. dan situlah dimana penggunaan fungsi arrow menjadi berguna. ## Fungsi Arrow tidak memiliki "this" Seperti yang kita ingat pada chapter , fungsi arrow tidak memiliki `this`. jika `this` diakses, itu diambil dari fungsi normal diluar. (bukan dari fungsi arrow) Sebagai contoh, kita bisa menggunakannya untuk mengiterasi apa yang ada didalam objek method: ```js run let group = { title: "Our Group", students: ["John", "Pete", "Alice"], showList() { *!* this.students.forEach( student => alert(this.title + ': ' + student) ); */!* } }; group.showList(); ``` Pada `forEach` disini, fungsi arrow digunakan, jadi `this.title` hasilnya akan sama persis dengan fungsi diluar `showList`. Yaitu : `group.title`. Jika kita menggunakan fungsi "normal", maka akan terjadi eror: ```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(); ``` Error tersebut terjadi karena `forEach` berjalan dengan fungsi `this=undefined` sebagai bawaannya, jadi upaya untuk mengakses `undefined.title` terjadi. Itu tidak mempengaruhi fungsi arrow, karena mereka tidak memiliki `this`. ```warn header="Fungsi arrow tidak bisa berjalan dengan `new`" Tidak memiliki `this` sebagai bawaannya, berarti memiliki keterbatasan lainnya: fungsi arrow tidak bisa digunakan sebagai fungsi constructors. Mereka tidak bisa dipanggil dengan `new`. ``` ```smart header="Arrow functions VS bind" Terdapat sedikit perbedaan antara fungsi arrow '=>' dan sebuah fungsi normal yang dipanggil dengan `.bind(this`): - `.bind(this)` membuat sebuah "versi terikat" dari fungsi itu. - The arrow `=>` tidak membuat keterikatan. Fungsi itu secara dasar tidak memiliki `this`. Pencarian dari `this` dibuat sama persis dengan sebuah pencarian variabel normal: yaitu pada luar lexical environment. ``` ## Fungsi arrow tidak memiliki "arguments" Fungsi arrow juga tidak memiliki variabel `arguments` . Itu bagus untuk decorators, ketika kita butuh untuk meneruskan panggilan dengan `this` dan `arguments` yang sekarang. Sebagai contoh, `defer(f, ms)` mendapatkan sebuah fungsi dan mengembalikan sebuah wrapper disekitarnya yang menunda panggilan selama `ms` milisekon: ```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 Setelah 2 detik ``` Sama juga tanpa menggunakan fungsi arrow berupa: ```js function defer(f, ms) { return function(...args) { let ctx = this; setTimeout(function() { return f.apply(ctx, args); }, ms); }; } ``` Disini kita butuh untuk membuat variabel tambahan `args` dan `ctx` agar fungsi didalam `setTimeout` bisa mengambilnya. ## Kesimpulan Fungsi arrow: - Tidak memiliki `this` - Tidak memiliki `arguments` - Tidak bisa dipanggil dengan `new` - Dia juga tidak memiliki `super`, kita belum mempelajarinya. Tapi kita akan mempelajarinya di Itu dikarenakan fungsi arrow dibuat untuk pembentukan fungsi kode pendek yang tidak memiliki "konteks" pribadi, melainkan bekerja pada konteks dimana fungsi itu bekerja. Dan mereka bekerja sangat baik pada kasus tersebut. ================================================ FILE: 1-js/06-advanced-functions/index.md ================================================ # Penggunaan lanjutan fungsi ================================================ FILE: 1-js/07-object-properties/01-property-descriptors/article.md ================================================ # Properti flag dan Deskriptor Seperti yang kita ketahui, objek dapat menyimpan banyak properti. Sampai sekarang, sebuah properti hanyalah pasangan nilai dan kunci bagi kita. Tapi, sebuah properti objek sebenarnya lebih fleksibel dan memiliki banyak kegunaan. Pada bab ini kita akan mempelajari konfigurasi tambahan dan pada bab selanjutnya kita akan melihat bagaimana secara samar mengubahnya menjadi fungsi getter atau setter. ## Properti Flag Properti Objek, selain sebuah **`Nilai`**, memiliki tiga atribut spesial (yang dinamakan "flags"): - **`writable`** -- jika `benar`, maka nilai nya bisa diubah, jika tidak maka hanya bsa dibaca. - **`enumerable`** -- jika `benar`, maka akan dicantumkan pada daftar perulangan, jika tidak maka tidak akan dicantumkan. (Menentukan apakah bisa melakukan perulangan dengan properti objek tersebut) - **`configurable`** -- jika `benar`, properti itu dapat dihapus dan attribute-attributenya bisa diubah-ubah, jika tidak maka tidak bisa dihapus dan diubah. **catatan:** *`benar` disini biasanya di gambarkan sebagai boolean `true` dan `salah` digambarkan sebagai boolean `false` pada javascript.* Kita belum melihat mereka, karena biasanya mereka tidak muncul. Ketika kita membuat sebuah properti "dengan cara biasa", Semua dari tiga attribut diatas biasanya bernilai `benar`.namun, kita juga bisa mengubahnya kapan pun kita mau. Pertama, mari kita lihat bagaimana cara mendapatkan properti flag tersebut. Pada Method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) memperbolehkan kita untuk melakukan query terhadap informasi *komplit* dari sebuah properti. Sintaksnya adalah: ```js let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); ``` `obj` : Objek yang akan kita ambil informasinya. `propertyName` : Nama dari properti tersebut. Nilai yang akan dikembalikan disebut sebagai "properti deskriptor" dari objek: didalamnya mengandung nilai dan semua flag dari properti tersebut. Sebagai contoh: ```js run let user = { name: "John" }; let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* property descriptor: { "Nilai": "John", "writable": true, "enumerable": true, "configurable": true } */ ``` Untuk mengganti flag tersebut, kita dapat menggunakan [Object.defineProperty](mdn:js/Object/defineProperty). Sintaksnya adalah: ```js Object.defineProperty(obj, propertyName, descriptor) ``` `obj`, `propertyName` : Objek dan nama properti yang akan diterapkan deskriptornya. `descriptor` : Properti deskriptor objek yang akan digunakan. Jika properti tersebut ada, `defineProperty` akan memperbarui flagnya. namun, itu akan membuat properti dengan nilai yang diberikan dan flag properti tersebut; pada kasus itu, jika sebuah flag tidak disediakan, maka itu akan diasumsikan dengan nilai `false` (salah). Sebagai contoh, sebuah properti `name` dibuat dengan semua nilai flag yang salah: ```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 */!* } */ ``` bandingkan dengan "pembuatan cara biasa" `user.name` diatas: sekarang semua nilai flagnya berbentuk salah. Jika itu yang bukan kita mau maka kita sebaiknya merubahnya menjadi `true` pada `deskriptor`nya. Sekarang mari kita lihat efek flag tersebut dengan contoh. ## Non-writable Ayo kita buat `user.name` menjadi non-writable (tidak bisa diatur dan ditetapkan kembali) dengan mengubah flag `writable` nya : ```js run let user = { name: "John" }; Object.defineProperty(user, "name", { *!* writable: false */!* }); *!* user.name = "Pete"; // Error: Cannot assign to read only property 'name' */!* ``` Sekarang tidak ada yang dapat mengubah nama dari user kita, kecuali mereka menetapkan `defineProperty`nya sendiri untuk menimpa konfigurasi kita. ```smart header="Errors appear only in strict mode" Pada mode non-strict, tidak ada eror yang terjadi ketika menulis pada properti non-writable dan sejenisnya. tapi operasi itu tetap tidak akan berhasil. Aksi pelanggaran flag hanya saja diabaikan pada mode non-strict. ``` Berikut contoh kasus yang sama, tapi properti itu dibuat dari awal: ```js run let user = { }; Object.defineProperty(user, "name", { *!* value: "John", // untuk properti baru kita butuh untuk memberi tau apa saja yang benar/true enumerable: true, configurable: true */!* }); alert(user.name); // John user.name = "Pete"; // Error ``` ## Non-enumerable Sekarang mari kita tambahkan `toString` yang sudah disesuaikan pada `user`. Secara normal, bawaan `toString` untuk objek merupakan non-enumerable (tidak bisa diiterasi), itu tidak akan muncul pada `for..in`. Tapi apabila kita tambahkan`toString` yang kita buat sendiri, maka secara default akan muncul pada `for..in`, contohnya seperti ini: ```js run let user = { name: "John", toString() { return this.name; } }; // secara default, kedua properti tersebut akan terdaftar: for (let key in user) alert(key); // name, toString ``` Jika kita tidak menyukainya, maka kita bisa mengatur menjadi `enumerable:false`. yang kemudian itu tidak akan tampil pada `for..in` loop, seperti pada bawaannya: ```js run let user = { name: "John", toString() { return this.name; } }; Object.defineProperty(user, "toString", { *!* enumerable: false */!* }); *!* // sekarang toString kita tidak akan muncul: */!* for (let key in user) alert(key); // name ``` Properti non-enumerable juga tidak termasuk dari `Object.keys`: ```js alert(Object.keys(user)); // name ``` ## Non-configurable Flag non-configurable (`configurable:false`) terkadang sudah diatur sebelumnya untuk objek dan properti bawaan. Sebuah properti non-configurable tidak bisa di hapus. Sebagai contoh, `Math.PI` adalah non-writable, non-enumerable and 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 } */ ``` Jadi, seorang programer tidak akan bisa mengganti nilai dari sebuah `Math.PI` atau juga menimpanya. ```js run Math.PI = 3; // Error // menghapus Math.PI juga tidak akan bekerja ``` Membuat sebuah properti non-configurable adalah jalan satu arah. kita tidak bisa mengubahnya kembali dengan `defineProperty`. Tepatnya, non-configurable memberlakukan beberapa pembatasan pada `defineProperty`: 1. tidak bisa mengubah flag `configurable` . 2. tidak bisa mengubah flag `enumerable` . 3. tidak bisa mengubah `writable: false` menjadi `true` (kebalikannya masih bisa bekerja). 4. tidak bisa mengubah `get/set` untuk sebuah properti aksesor (tapi bisa menetapkannya jika kosong). Disini kita membuat `user.name` menjadi sebuah konstant "yang selamanya tersegel": ```js run let user = { name: "John" }; Object.defineProperty(user, "name", { configurable: false }); user.name = "Pete"; // works fine delete user.name; // Error ``` And here we make `user.name` a "forever sealed" constant: ```js run let user = { name: "John" }; Object.defineProperty(user, "name", { writable: false, configurable: false }); *!* // tidak bisa mengubah user.name atau flag nya // semua dibawah ini tidak akan bekerja: // user.name = "Pete" // delete user.name // Object.defineProperty(user, "name", { value: "Pete" }) Object.defineProperty(user, "name", {writable: true}); // Error */!* ``` ```smart header="\"Non-configurable\" doesn't mean \"non-writable\"" Catatan pengecualian: sebuah nilai dari non-configurable, tapi writable properti masih bisa diubah. Ide dari `configurable: false` adalah untuk mencegah perubahan properti flag dan penghapusannya, bukan perubahan dalam nilainya. ``` ## Object.defineProperties Ada sebuah method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) yang memperbolehkan untuk mendefinisikan banyak properti pada satu waktu. Sintaksnya adalah: ```js Object.defineProperties(obj, { prop1: descriptor1, prop2: descriptor2 // ... }); ``` Sebagai contoh: ```js Object.defineProperties(user, { name: { value: "John", writable: false }, surname: { value: "Smith", writable: false }, // ... }); ``` Jadi, kita bisa mengatur banyak properti dalam satu waktu. ## Object.getOwnPropertyDescriptors Untuk mendapat semua properti deskriptor pada satu waktu, kita dapat menggunakan sebuah method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors). Bersamaan dengan `Object.defineProperties` itu dapat digunakan menjadi cara "flags-aware" untuk mengkloning sebuah objek: ```js let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj)); ``` Normalnya ketika kita mengkloning sebuah objek, kita menggunakan penetapan nilai untuk menyalin propertinya, seperti ini: ```js for (let key in user) { clone[key] = user[key] } ``` ...Tapi itu tidak menyalin flagnya. jadi jika kita ingin sesuatu salinan "yang lebih baik" maka `Object.defineProperties` lebih disarankan. Perbedaan lainnya adalah jika `for..in` mengabaikan properti simbolik, tapi `Object.getOwnPropertyDescriptors` mengembalikan *semua* properti deskriptor termasuk properti simboliknya. ## menyegel sebuah objek secara global Properti deskriptor bekerja pada level invidual propertinya. Dan ada juga method yang memberi batasan akses terkait *keselurhan* objek: [Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) : Melarang penambahan pada properti baru dalam objek. [Object.seal(obj)](mdn:js/Object/seal) : Melarang penambahan/penghapusan dari properti. Menetapkan `configurable: false` untuk semua properti yang ada. [Object.freeze(obj)](mdn:js/Object/freeze) : Melarang penambahan/pengurangan/pengubahan pada properti. Menetapkan `configurable: false, writable: false` untuk semua properti yang ada. Dan ada juga test untuk mereka: [Object.isExtensible(obj)](mdn:js/Object/isExtensible) : Mengembalikan `false` jika menambahkan properti itu dilarang, selain itu `true`. [Object.isSealed(obj)](mdn:js/Object/isSealed) : Mengembalikan `true` jika menambahkan/mengurangi properti itu dilarang, dan semua properti yang ada memiliki `configurable: false`. [Object.isFrozen(obj)](mdn:js/Object/isFrozen) : Mengembalikan `true` jika menambahkan/mengurangi/mengubah properti itu dilarang, dan semua properti yang sekarang memiliki `configurable: false, writable: false`. Method diatas biasanya jarang digunakan pada prakteknya. ================================================ FILE: 1-js/07-object-properties/02-property-accessors/article.md ================================================ # Properti getter and setter Terdapat dua jenis properti objek. Yang pertama adalah *properti data*. Kita telah mengetahui bagaimana cara kerja mereka. Semua properti yang kita gunakan sampai sekarang adalah properti data. Yang kedua adalah properti yang bisa dibilang cukup baru. Properti itu adalah *properti aksesor*. Mereka sebenarnya adalah fungsi untuk mendapatkan dan mengatur sebuah nilai, tapi mereka mirip seperti properti biasa pada kode eksternal. ## Getter dan setter Properti aksesor diwakili dengan method "getter" dan "setter". Didalam objek literal mereka dilambangkan dengan `get` dan `set`: ```js let obj = { *!*get propName()*/!* { // getter, kode dijalankan untuk mendapat obj.propName }, *!*set propName(value)*/!* { // setter, kode dijalankan untuk mengatur obj.propName = value } }; ``` Getter bekerja ketika `obj.propName` terbaca, sedangkan setter -- ketika variabel itu ditetapkan. Sebagai contoh, kita memiliki sebuah objek `user` dengan `name` dan `surname` (nama variabel): ```js let user = { name: "John", surname: "Smith" }; ``` Sekarang kita ingin untuk menambahkan sebuah properti `fullName`, yang berisi `"John Smith"`. kita tidak ingin untuk melakukan penyalinan terhadap informasi yang sudah ada, melainkan kita bisa menerapakan sebuah aksesor: ```js run let user = { name: "John", surname: "Smith", *!* get fullName() { return `${this.name} ${this.surname}`; } */!* }; *!* alert(user.fullName); // John Smith */!* ``` Dari luar, properti aksesor tampak seperti variabel pada umumnya. Itulah ide dari properti aksesor. Kita tidak *memanggil* `user.fullName` melaui fungsi, Namun kita *membacanya* secara biasa: properti getter berjalan di belakang layar. Sekarang, `fullName` memiliki sebuah properti getter. Jika kita mencoba untuk menetapkan value lain pada `user.fulName=`, maka akan terjadi eror: ```js run let user = { get fullName() { return `...`; } }; *!* user.fullName = "Test"; // Error (property has only a getter) */!* ``` Mari kita perbaiki dengan menambahkan setter untuk `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(" "); } */!* }; // variabel fullName dijalankan dengan value ditetapkan. user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper ``` Alhasil, kita memiliki sebuah properti virtual `fullname`. Yang bisa di baca dan diatur nilainya. ## Deskriptor aksesor Deskriptor untuk properti aksesor berbeda dengan yang ada di dalam properti data. Untuk properti aksesor, tidak ada `nilai` atau `pengaturan` dalam properti aksesor, melainkan digantikan dengan fungsi `get` dan `set`. Yang berarti, deskriptor aksesor mungkin memiliki: - **`get`** -- sebuah fungsi tanpa argument, yang bekerja ketika properti dibaca, - **`set`** -- sebuah fungsi dengan satu argumen, yang dipanggil ketika properti itu ditetapkan, - **`enumerable`** -- sama seperti pada properti data, - **`configurable`** -- sama seperti pada properti data. Sebagai contoh, untuk membuat sebuah aksesor `fullName` dengan `defineProperty`, kita dapat membawa sebuah deskriptor dengan `get` dan `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 ``` Perlu diperhatikan bahwa sebuah properti bisa jadi adalah sebuah properti aksesor(memiliki method `get/set`) atau sebuah properti data(hanya memiliki sebuah `nilai`), namun tidak keduanya. Jika kita mencoba untuk menyediakan `get` dan `value` pada satu deskriptor yang sama, maka akan terjadi eror. ```js run *!* // Error: Invalid property descriptor. */!* Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2 }); ``` ## getter/setter yang lebih baik Getter/setter dapat digunakan sebagai wrapper pada properti `asli`(bukan aksesor) untuk mendapatkan akses kontrol lebih terkait pengoperasian dengan mereka. Sebagai contoh, jika kita ingin untuk melarang penamaan yang terlalu singkat untuk `user`, kita dapat memiliki sebuah setter `name` dan menjaga nilainya pada properti yang terpisah `_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 = ""; // Name is too short... ``` Jadi, variabel name tersebut disimpan pada properti `_name`, yang aksesnya dapat melalui getter dan setter. Secara teknis, kode eksternal bisa aja mengakses variabel nama secara langsung dengan menggunakan `user._name`. Tapi sudah menjadi rahasia umum bahwa properti yang diawali dengan underscore `"_"` adalah internal variabel yang seharusnya tidak boleh diakses dari luar. ## Penggunaan kompabilitas Salah satu kegunaan besar properti aksesor adalah mereka memperbolehkan kita untuk mengontrol properti data `biasa` untuk menggantinya pada suatu waktu, dengan sebuah setter dan getter, serta mengubah perilakunya. Bayangkan kita mulai dengan implementasi objek user menggunakan properti `name` dan `age`. ```js function User(name, age) { this.name = name; this.age = age; } let john = new User("John", 25); alert( john.age ); // 25 ``` ...Tapi cepat atau lambat, sesuatu mungkin berubah. Alih-alih menggunakan `age` kita mungkin memutuskan untuk menyimpan `birthday`, karena mungkin itu lebih tepat dan sesuai: ```js function User(name, birthday) { this.name = name; this.birthday = birthday; } let john = new User("John", new Date(1992, 6, 1)); ``` Sekarang apa yang akan kita lakukan terhadap kode lama yang masih menggunakan properti`age`? Kita dapat mencoba untuk mencarinya disemua tempat dan memperbaiki nya, tapi itu akan memakan waktu yang lama dan susah jika kode itu digunakan oleh banyak orang. selain itu, properti `age` adalah sesuatu yang bagus dimiliki oleh user, kan ? Tetaplah menjaganya. Menambahkan sebuah getter pada `age` menyelesaikan permasalahan. ```js run no-beautify function User(name, birthday) { this.name = name; this.birthday = birthday; *!* // variabel age dihitung berdasarkan tanggal sekarang dan tanggal lahirnya 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 ); // properti birthday tersedia alert( john.age ); // ...begitu juga dengan age ``` Sekarang kode yang lama bisa bekerja dan kita memiliki tambahan properti yang bagus. ================================================ FILE: 1-js/07-object-properties/index.md ================================================ # Object properties configuration In this section we return to objects and study their properties even more in-depth. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md ================================================ 1. `true`, diambil dari `rabbit`. 2. `null`, diambil dari `animal`. 3. `undefined`, propertinya sudah tidak ada. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md ================================================ Nilai: 5 --- # Bekerja dengan prototype Ini adalah kode yang membuat sepasang objek, lalu dimodifikasi. Nilai manakan yang akan muncul? ```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) ``` Seharusnya ada 3 jawaban. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md ================================================ 1. Let's add `__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. Dalam mesin modern, kinerja yang bagus, tidak ada perbedaan apakan kita mengambil properti dari sebuah objek atau dari *prototype*nya. Mesinnya akan ingat darimana mengambil propertinya dan menggunakannya kembali pada request selanjutnya. Contoh, untuk `pockets.glasses` mereka ingat dimana `glasses` ditemukan (dalam `head`), dan pencarian selanjutnya akan dicari ditempat yang sama. Mesinnya juga cukup pintar untuk memperbaharui *cache internal* jika sesuatu berubah, jadi optimasinya akan aman. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md ================================================ Nilai: 5 --- # Algoritma pencarian Tugasnya memiliki dua bagian. Diberikan objek-objek berikut: ```js let head = { glasses: 1 }; let table = { pen: 3 }; let bed = { sheet: 1, pillow: 2 }; let pockets = { money: 2000 }; ``` 1. Gunakan `__proto__` untuk memasukan *prototype* dengan cara yang mana membuat property yang mencari akan mengikuti *path*: `pockets` -> `bed` -> `table` -> `head`. Contoh, `pockets.pen` haruslah `3` (ditemukan di `table`), dan `bed.glasses` haruslah `1` (ditemukan didalam `head`). 2. Jawab pertanyaan: mana yang lebih cepat didapatkan `glasses` sebagai `pockets.glasses` atau `head.glasses`? Jika diperlukan gunakanlah *benchmark*. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md ================================================ **Jawabannya: `rabbit`.** Karena `this` adalah sebuah objek sebelum titik, jadi `rabbit.eat()` memodifikasi `rabbit`. Pencarian properti dan eksekusi adalah dua hal yang berbeda. The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`. Metode `rabbit.eat` adalah yang pertama ditemukan dalam *prototype*, lalu dieksekusi dengan `this=rabbit`. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md ================================================ Nilai: 5 --- # Dimanakah akan tertulis? Kita memiliki `rabbit` mewarisi dari `animal`. Jika kita memanggil `rabbit.eat()`, yang mana objeknya menerima properti `full`: `animal` atau `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 ================================================ Kita perhatikan baik-baik pada apa yang terjadi dalam pemanggilannya `speedy.eat("apple")`. 1. Metode `speedy.eat` ditemukan dalam prototype (`hamster`), lalu dieksekusi dengan `this=speedy` (objek sebelum titik). 2. Lalu `this.stomach.push()` perlu menemukan properti `stomach` dan panggil `push` didalamnya. Itu akan mencati `stomach` didalam `this` (`=speedy`), tapi tidak menemukan apapun. 3. Lalu akan mengikuti rantai *prototype* dan menemukan `stomach` didalam `hamster`. 4. lalu akan memanggil `push` didalamnya, menambahkan makanan kedalam *stomach dari prototype*. Jadi semua hamster membagi satu *stomach*! Diantara `lazy.stomach.push(...)` dan `speedy.stomach.push()`. properti `stomach` ditemukan didalam *prototype* (sebagaimana tidak didalam objeknya sendiri) , lalu datanya akan dimasukan. Perhatikan bahwa hal tersebut tidak akan terjadi pada *assignment* sederhana `this.stomach=`: ```js run let hamster = { stomach: [], eat(food) { *!* // masukan this.stomach daripada this.stomach.push this.stomach = [food]; */!* } }; let speedy = { __proto__: hamster }; let lazy = { __proto__: hamster }; // Speedy one menemukan makanannya speedy.eat("apple"); alert( speedy.stomach ); // apple // Perut Lazy one kosong alert( lazy.stomach ); // ``` Sekarang semuanya berjalan dengan baik, karena `this.stomach=` tidak melakukan pencarian `stomach`. Nilainya ditulis langsung kedalam objek `this`. Kita juga bisa benar-benar menghindar dari masalah dengan memastikan bahwa setiak hamster memiliki perut mereka masing-masing: ```js run let hamster = { stomach: [], eat(food) { this.stomach.push(food); } }; let speedy = { __proto__: hamster, *!* stomach: [] */!* }; let lazy = { __proto__: hamster, *!* stomach: [] */!* }; // Speedy one menemukan makanan speedy.eat("apple"); alert( speedy.stomach ); // apple // Perut Lazy one kosong alert( lazy.stomach ); // ``` Sebagai solusi umum, seluruh properti yang dideskripsikan dari objek tertentu, seperti `stomach` diatas, seharusnya ditulis kedalam objeknya. Untuk menghindari masalah seperti itu. ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md ================================================ Nilai: 5 --- # Kenapa kedua hamster kenyang? Kita memiliki dua hamster: `speedy` dan `lazy` yang mewarisi objek `hamster`. Ketika kita memberi makan salah satunya, yang satunya lagi akan ikut kenyang. Kenapa? Bagaimana cara memperbaikinya? ```js run let hamster = { stomach: [], eat(food) { this.stomach.push(food); } }; let speedy = { __proto__: hamster }; let lazy = { __proto__: hamster }; // Yang satu ini menemukan makanan speedy.eat("apple"); alert( speedy.stomach ); // apple // Yang ini juga memilikinya, kenapa? perbaikilah. alert( lazy.stomach ); // apple ``` ================================================ FILE: 1-js/08-prototypes/01-prototype-inheritance/article.md ================================================ # Pewarisan *Prototype* (*Prototypal Inheritance*) Dalam *programming*, terkadang kita ingin mengambil sesuatu lalu dikembangkan lagi. Contoh, kita memiliki objek `user` lengkap dengan properti dan metodenya, dan kita ingin membuat `admin` dan `guest` sebagai varian yang sedikit diubah dari objek `user`. Kita ingin menggunakan apa yang dimiliki oleh `user`, bukan menyalin ataupun meimplementasikan ulang metode-metodenya, akan tetapi menciptakan objek baru diatasnya. *Pewarisan Prototype* adalah fitur yang bisa membantu untuk melakukan hal itu. ## [[Prototype]] Didalam Javascript, objek memiliki properti tersembunyi yang spesial `[[Prototype]]` (seperti yang dinamakan didalam spesifikasinya), yang mana dapat mereferensi pada `null` atau mereferensi pada objek lainnya. Objek itu disebut dengan *prototype*: ![prototype](object-prototype-empty.svg) Saat kita membaca properti dari `objek`, dan properti itu hilang, JavaScript secara otomatis mengambilnya dari prototipe. Dalam pemrograman, ini disebut "pewarisan prototipe". Dan segera kita akan mempelajari banyak contoh pewarisan tersebut, serta fitur bahasa yang lebih keren yang dibangun di atasnya. Properti yang dimiliki `[[Prototype]]` bersifat internal dan tersembunyi, tapi ada banyak cara untuk melihat properti tersebut. Salah satunya adalah menggunakan nama spesial `__proto__`, seperti: ```js run let animal = { eats: true }; let rabbit = { jumps: true }; *!* rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal */!* ``` Sekarang jika kita ingin membaca properti dari `rabbit`, dan ternyada tidak ada, Javascript akan mengambilnya dari `animal`. Contoh: ```js let animal = { eats: true }; let rabbit = { jumps: true }; *!* rabbit.__proto__ = animal; // (*) */!* // sekarang kita bisa menggunakan kedua propertinya didalam *rabbit*: *!* alert( rabbit.eats ); // true (**) */!* alert( rabbit.jumps ); // true ``` Pada baris `(*)` menyetel `animal` untuk menjadi prototype dari `rabbit`. Lalu, ketika `alert` mencoba untuk membaca properti `rabbit.eats` `(**)`, ternyata `rabbit` tidak memiliki propertinya, maka Javascript mengikuti referensi `[[Prototype]]`nya dan menemukan `animal` (mencari dari bawah ke atas): ![](proto-animal-rabbit.svg) Disini kita bisa berkata bahwa "`animal` adalah prototype dari `rabbit`" atau "`rabbit` secara prototype mewarisi dari `animal`". Jadi jika `animal` memiliki banyak properti dan metode yang berguna, maka properti dan metode tersebut secara otomatis akan tersedia didalam `rabbit`. Properti tersebut dinamakan "pewarisan". Jika kita memiliki metode didalam `animal`, maka metode tersebut dapat dipanggil didalam `rabbit`: ```js run let animal = { eats: true, *!* walk() { alert("Animal walk"); } */!* }; let rabbit = { jumps: true, __proto__: animal }; // walk diambil dari prototype *!* rabbit.walk(); // Animal walk */!* ``` Metodenya secara otomatis diambil dari *prototype*nya, seperti: ![](proto-animal-rabbit-walk.svg) Rantai *prototype* bisa lebih panjang: ```js run let animal = { eats: true, walk() { alert("Animal walk"); } }; let rabbit = { jumps: true, *!* __proto__: animal */!* }; let longEar = { earLength: 10, *!* __proto__: rabbit */!* }; // walk diambil dari rantai prototype longEar.walk(); // Animal walk alert(longEar.jumps); // true (dari rabbit) ``` ![](proto-animal-rabbit-chain.svg) Sekarang jika kita membaca sesuatu dari `longEar`, dan ternyata tidak ada, Javascript akan mencarinya didalam `rabbit`, dan lalu didalam `animal`. Akan tetapi terdapat dua batasan: 1. Referensinya tidak bisa berputar (seperti lingkaran atau perulangan tak terhingga). Javascript akan mengembalikan error jika kita mencoba untuk membuat `__proto__` berputar. 2. Nilai dari `__proto__` bisa antara sebuah objek atau `null`. Tipe lainnya akan diabaikan. Dan juga tentu saja: hanya terdapat satu `[[Prototype]]`. Sebuah objek tidak bisa mewarisi dari dua objek. ```smart header="`__proto__` adalah asal usul getter/setter untuk `[[Prototype]]`" Biasanya kesalan *developer* pemula adalah tidak mengetahui perbedaan antara keduanya. Perlu diingat bahwa `__proto__` *tidak sama* dengan properti internal `[[Prototype]]`. Itu hanyalah *getter/setter* untuk `[[Prototype]]`. Nanti kita akan melihat situasi dimana hal itu akan digunakan, untuk sekarang kita hanya perlu tahu, kita akan terus bangun pemahaman kita tentang Javascript. Properti `__proto__` sedikit ketinggalan jaman. Properti tersebut ada karena alasan lama, pada Javascript terbaru merekomendasikan kita untuk menggunakan fungsi `Object.getPrototypeOf/Object.setPrototypeOf` daripada *prototype* get/set. Kita akan belajar tentang fungsi ini nanti. Dari spesifikasinya, `__proto__` telah didukung oleh banyak *browser*. Faktanya, seluruh lingkungan termasuk dibagian *server* juga mendukung `__proto__`, jadi kita aman untuk menggunakannya. Karena notasi `__proto__` sedikit lebih jelas, kita akan menggunakannya didalam contoh. ``` ## Menulis tanpa menggunakan *prototype* *Prototype* hanya digunakan untuk membaca properti. Operasi menulis / menghapus bekerja secara langsung dengan objeknya. Didalam contoh dibawah, kita memasukan metode `walk` kedalam `rabbit`: ```js run let animal = { eats: true, walk() { /* metode ini tidak akan digunakan oleh rabbit */ } }; let rabbit = { __proto__: animal }; *!* rabbit.walk = function() { alert("Rabbit! Bounce-bounce!"); }; */!* rabbit.walk(); // Rabbit! Bounce-bounce! ``` Mulai sekarang, pemanggilan `rabbit.walk()` akan menemukan metodenya secara langsung didalam objek dan langsung dieksekusi tanpa menggunakan *prototype*: ![](proto-animal-rabbit-walk-2.svg) Properti pengakses adalah pengecualian, sebagaimana memasukan nilai dipegang oleh fungsi *setter*. Jadi menulis properti seperti itu sebenarnya sama dengan memanggil sebuah fungsi. Untuk alasan itu `admin.fullName` akan bekerja dengan benar pada contoh dibawah: ```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 (*) // memicu setter! admin.fullName = "Alice Cooper"; // (**) alert(admin.fullName); // Alice Cooper, state dari admin diubah alert(user.fullName); // John Smith, state dari user dilindungi / *protected* ``` Disini pada baris `(*)` properti `admin.fullName` memiliki *getter* didalam prototype `user`, jadi itu akan dipanggil. Pada baris `(**)` properti memiliki *setter* didalam *prototype*, jadi itu dipanggil. ## Nilai dari "this" Sebuah pertanyaan menarik mungkin muncul didalam contoh diatas: apa nilai dari `this` didalam `set fullName(value)`? Dimanakah properti dari `this.name` dan `this.surname` ditulis: kedalam `user` atau `admin`? Jawabannya sederhana: `this` sama sekali tidak terkena efek oleh *prototype*. **Tidak peduli dimana metodenya ditemukan: didalam objek atau didalam *prototype*nya. Dalam pemanggilan metode, `this` adalah objeknya sebelum titik.** Jadi, pemanggilan *setter* `admin.fullName=` menggunakan `admin` sebagai `this` dan bukan `user`. Itu sebenarnya adalah sebuah hal yang sangat penting, karena kita mungkin memiliki objek yang besar dengan banyak metode, dan memiliki objek yang mewarisinya. Dan ketika pewarisan objek berjalan metode yang diwariskan, mereka hanya akan memodifikasi bagian / *state* mereka sendiri, bukan bagian dari objek besarnya. Contoh, disini `animal` merepresentasikan sebuah "method storage (penyimpanan metode)", dan `rabbit` menggunakannya. Pemanggilan `rabbit.sleep()` menyetel `this.isSleeping` didalam objek `rabbit`: ```js run // animal memiliki metode let animal = { walk() { if (!this.isSleeping) { alert(`I walk`); } }, sleep() { this.isSleeping = true; } }; let rabbit = { name: "White Rabbit", __proto__: animal }; // memodifikasi rabbit.isSleeping rabbit.sleep(); alert(rabbit.isSleeping); // true alert(animal.isSleeping); // undefined (no such property in the prototype / tidak ada property seperti itu didalam prototype) ``` Hasilnya: ![](proto-animal-rabbit-walk-3.svg) Jika kita memiliki objek lainnya, seperti `bird`, `snake`, dll., Mewarisi dari `animal`, mereka juga akan memiliki kases kepada metode dari `animal`. Tapi `this` didalam setiap pemanggilan metode adalah objeknya itu sendiri, mengevaluasi pada saat pemanggilan (sebelum titik), bukan `animal`. Jadi ketika kita menulis data kedalam `this`, itu akan tersimpan kedalam objeknya. Sebagai hasilnya, metodenya dibagi bersama, tapi *state* dari objeknya tidak. ## Perulangan for..in Perulangan `for..in` mengiterasi properti yang diwariskan juga. Contoh: ```js run let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; *!* // Object.keys hanya mengembalikan kunci / keys miliknya sendiri alert(Object.keys(rabbit)); // jumps */!* *!* // perulangan for..in mengiterasi kunci milik sendiri dan kunci yang diwariskan for(let prop in rabbit) alert(prop); // jumps, lalu eats */!* ``` Jika itu bukanlah hal yang kita inginkanm dan kita ingin untuk mengecualikan properti warisan, terdapat metode bawaan [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): yang mengembalikan `true` jika `obj` memiliki properti bernama `key` (bukan properti warisan). Jadi kita bisa memisahkan properti warisan (atau melakukan sesuatu dengan properti warisan itu): ```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 } } ``` Disini kita memiliki rantai pewarisan: `rabbit` mewarisi dari `animal`, pewarisan itu dari `Object.prototype` (karena `animal` adalah objek literal `{...}`, jadi itu terjadi secara otomatis), dan lalu `null` diatasnya: ![](rabbit-animal-object.svg) Catat bahwa ada satu hal lucu. darimanakah `rabbit.hasOwnProperty` datang? Kita tidak membuatnya. Lihat rantainya dan kita bisa melihat metodenya disediakan oleh `Object.prototype.hasOwnProperty`. Dengan kata lain, itu diwariskan. ...Tapi kenapa `hasOwnProperty` tidak muncul didalam perulangan `for..in` seperti `eats` dan `jumps`, jika `for..in` adalah properti yang diwariskan? Jawabanya sederhana: properti tersebut tidak dapat terhitung(*enumerable*). Sama seperti properti lainnya dari `Object.prototype`, yang mana memiliki tanda `enumerable:false`. Dan `for..in` hanya akan menampilkan properti yang dapat dihitung (*enumerable*). Itulah kenapa properti `Object.prototype`tidak terlihat. ```smart header="Hampir semua metode key/value mengabaikan properti warisan" Hampir semua metode key/value, seperti `Object.keys`, `Object.values` dan lainnya mengabaikan properti warisan. Mereka hanya akan beroperasi pada objeknya sendiri. Properti dari *prototype* *tidak* akan dihitung. ``` ## Ringkasan - Dalam Javascript, seluruh objek memiliki `[[Prototype]]` tersembunyi yang mana bisa objek atau `null`. - Kita bisa menggunakan `obj.__proto__` untuk mengaksesnya (selain getter/setter, terdapat cara lain, yang mana akan dibahas nanti). - Objek yang diferensi oleh `[[Prototype]]` dipanggil dengan sebuah "prototype". - Jika kita ingin membaca sebuah properti dari `obj` atau memanggil metode, dan ternyata tidak ada maka Javascript akan mencoba mencari didalam *prototype*nya - Operasi menulis/menghapus langsung bekerja didalam objeknya, mereka tidak menggunakan *prototype* (asumsikan propertinya adalah data, bukan sebuah *setter*). - Jika kita memanggil `obj.method()`, dan `method`nya diambil dari prototype, `this` akan mereferensi `obj`. Jadi metode selalu bekerja dengan objek yang sedang digunakannya bahkan jika objeknya adalah hasil pewarisan. - Perulangan `for..in` mengiterasi properti asli dan properti warisan. Semua metode key/value hanya akan bekerja pada objeknya sendiri. ================================================ FILE: 1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md ================================================ Jawaban: 1. `true`. Memasukan ke `Rabbit.prototype` menyetel `[[Prototype]]` untuk objek baru, tapi itu tidak memberikan efek pada yang sudah ada. 2. `false`. Objek yang dimasukan dengan menggunakan referensi. Objek dari `Rabbit.prototype` bukanlah di duplikasi, itu masih tetap objek tunggal yang direferensikan dari `Rabbit.prototype` dan dari `[[Prototype]]` dari `rabbit`. Jadi ketika kita mengubah kontennya melalui satu referensi, itu masih terlihat melalui yang lainnya. 3. `true`. Semua operasi `delete` diterapkan langsung ke objeknya. Disini `delete rabbit.eats` mencoba untuk menghapus properti `eats` dari `rabbit`, tapi itu tidak memilikinya. Jadi operasinya tidak akan menghasilkan efek apapun. 4. `undefined`. Properti `eats` dihapus dari *prototype*, itu tidak akan ada lagi. ================================================ FILE: 1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md ================================================ nilai: 5 --- # Merubah "prototype" Di kode dibawah kita membuat `new Rabbit`, dan mencoba memodifikasi prototypenya. Pada awalnya kita memiliki kode ini: ```js run function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); alert( rabbit.eats ); // true ``` 1. Kita menambah satu string (ditekankan). What will `alert` show now? ```js function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); *!* Rabbit.prototype = {}; */!* alert( rabbit.eats ); // ? ``` 2. ...Dan jika kodenya seperti ini (diganti satu baris)? ```js function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); *!* Rabbit.prototype.eats = false; */!* alert( rabbit.eats ); // ? ``` 3. Dan seperti ini (diganti satu baris)? ```js function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); *!* delete rabbit.eats; */!* alert( rabbit.eats ); // ? ``` 4. varian terakhir: ```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 ================================================ Kita bisa menggunakan pendekatan jika kita yakin properti `"constructor"` memiliki nilai yang benar. Contoh, kita tidak ingin menyentuh `"prototype"` bawaan, maka kode ini akan berjalan dengan semestinya: ```js run function User(name) { this.name = name; } let user = new User('John'); let user2 = new user.constructor('Pete'); alert( user2.name ); // Pete (bekerja!) ``` Kode diatas bekerja karena `User.prototype.constructor == User`. Tapi jika seseorang, menimpah `User.prototype` dan lupa untuk membuat ulang `constructor` untuk `User`, maka akan membuat kegagalan. Contoh: ```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 ``` Kenapa `user2.name` menghasilan `undefined`? Ini bagaimana `new user.constructor('Pete')` bekerja: 1. Pertama, mencari `constructor` di `user`. Tidak ada. 2. Kemudian mengikuti rantai prototipe. Prototipe `user` adalah `User.prototype`, dan juga tidak memiliki `constructor` (karena kita "lupa" untuk menyetelnya dengan benar!). 3. Lebih jauh ke atas rantai, `User.prototype` adalah objek biasa, prototipenya adalah `Object.prototype` bawaan. 4. Terakhir, untuk `Object.prototype` bawaan, ada `Object.prototype.constructor == Object` bawaan. Jadi itu digunakan. Akhirnya, pada akhirnya, kita memiliki `let user2 = new Object('Pete')`. Mungkin, bukan itu yang kita inginkan. Kami ingin membuat `Pengguna baru`, bukan `Objek baru`. Itulah hasil dari `konstruktor` yang hilang. (Untuk berjaga-jaga jika Anda penasaran, panggilan `new Object(...)` mengubah argumennya menjadi objek. Itu hal teoretis, dalam praktiknya tidak ada yang memanggil `Objek baru` dengan nilai, dan umumnya kami tidak' t gunakan `objek baru` untuk membuat objek sama sekali). ================================================ FILE: 1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md ================================================ nilai: 5 --- # Buat sebuah objek dengan konstruktor yang sama Bayangkan, kita memiliki objek yang berubah-ubah, dibuat dengan menggunakan fungsi konstruktor -- kita tidak tahu yang mana, tapi kita ingin membuat sebuah objek menggunakannya. Bisakah kita melakukannya? ```js let obj2 = new obj.constructor(); ``` Beri sebuah contoh dari menggunakan fungsi konstruktor untuk `obj` yang mana membiarkan kode seperti itu bekerja. Dan sebuah contoh yang mana membuat kodenya menjadi tidak bekerja semestinya. ================================================ FILE: 1-js/08-prototypes/02-function-prototype/article.md ================================================ # F.prototype Ingat ketika objek baru bisa dibuat dengan menggunakan fungsi konstruktor seperti `new F()`. Jika `F.prototype` adalah sebuah objek, maka operator `new` menggunakannya untuk menyetel `[[Prototype]]` untuk objek barunya. ```smart Javascript memiliki pewarisan *prototype* dari awal. Itu adalah salah satu fitur utama dari bahasanya. Tapi dimasa lalu, hal itu tidak memiliki akses langsung. Hal yang dapat diandalkan adalah properti `"prototype"` dari fungsi konstruktor, yang akan dijelaskan didalam bab ini. Jadi masih banyak skrip yang masih menggunakannya. ``` Catat bahwa `F.prototype` disini berarti properti biasa yang bernama `"prototype"` didalam `F`. Terdengar seperti istilah "prototype", tapi disini kita menunjuk properti biasa dengan nama itu. Contohnya: ```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 ``` Menyetel `Rabbit.prototype = animal` secara literal kita mengartikan: "Ketika sebuah `new Rabbit` dibuat, itu akan memasukan `[[Prototype]]`nya ke `animal`". Hasilnya akan seperti gambar dibawah: ![](proto-constructor-animal-rabbit.svg) Dalam gambar, `"prototype"` adalah panah *horizontal*, menandakan properti *regular*, dan `[[Prototype]]` adalah panah vertikal, menandakan pewarisan `rabbit` dari `animal`. ```smart header="`F.prototype` hanya digunakan pada `new F`" Properti `F.prototype` hanya digunakan ketika `new F` dipanggil, itu memasukan `[[Prototype]]` dari objek barunya. Jika, setelah pembuatan, properti `F.prototype` berubah (`F.prototype = `), maka objek baru yang dibuat menggunakan `new F` akan memiliki objek lain sebagai `[[Prototype]]`, tapi objek yang sudah ada akan menyimpan yang lama. ``` ## F.prototype bawaan, properti konstruktor Setiap fungsi memiliki properti `"prototype"` bahkan jika kita tidak memberikannya. `"prototype"` bawaan adalah sebuah objek dengan properti `constructor` yang menunjuk balik pada fungsinya sendiri. Seperti: ```js function Rabbit() {} /* default prototype Rabbit.prototype = { constructor: Rabbit }; */ ``` ![](function-prototype-constructor.svg) Kita bisa periksa: ```js run function Rabbit() {} // secara *default*: // Rabbit.prototype = { constructor: Rabbit } alert( Rabbit.prototype.constructor == Rabbit ); // true ``` Secara teknis, jika kita tidak melakukan apapun, properti `constructor` akan tersedia untuk semua "rabbits" melalui `[[Prototype]]`: ```js run function Rabbit() {} // secara default: // Rabbit.prototype = { constructor: Rabbit } let rabbit = new Rabbit(); // mewarisi dari {constructor: Rabbit} alert(rabbit.constructor == Rabbit); // true (from prototype) ``` ![](rabbit-prototype-constructor.svg) Kita bisa menggunakan properti `constructor` untuk membuat objek baru menggunakan konstruktor yang sama seperti yang sudah ada. Seperti: ```js run function Rabbit(name) { this.name = name; alert(name); } let rabbit = new Rabbit("White Rabbit"); *!* let rabbit2 = new rabbit.constructor("Black Rabbit"); */!* ``` Hal itu akan mudak ketika kita memiliki sebuah objek, tidak tahu konstruktor yang mana yang menggunakannya (mis. ketika datang dari *library* pihak ketiga), dan kita butuh membuat satu lagi dengan bentuk yang sama. Tapi mungkin hal yang paling penting tentang `"constructor"` adalah... **...Javascript sendiri tidak yakin dengan nilai `"constructor"`.** Ya, terdapat nilai untuk fungsi bawaan `"prototype"`, tapi hanya itu. Apa yang terjadi setelahnya -- semuanya bergantung pada kita. Khususnya, jika kita mengganti seluruh prototype bawaannya, maka disana tidak akan terdapat `"constructor"`. Contoh: ```js run function Rabbit() {} Rabbit.prototype = { jumps: true }; let rabbit = new Rabbit(); *!* alert(rabbit.constructor === Rabbit); // false */!* ``` Jadi, untuk menyimpan `"constructor"` dengan benar kita bisa memilih untuk menambahkan/menghapus properti menjadi `"prototype"` bawaan daripada menimpahnya dengan yang baru: ```js function Rabbit() {} // Tidak menimpah Rabbit.prototype selurunya // hanya menambahkan Rabbit.prototype.jumps = true // Rabbit.prototype.constructor bawaan diamankan ``` Atau, alternatifnya, membuat ulang properti `constructor` secara manual: ```js Rabbit.prototype = { jumps: true, *!* constructor: Rabbit */!* }; // sekarang konstruktor tidak berubah, karena kita menambahkan yang baru ``` ## Ringkasan Didalam chapter ini kita secara jelas mendeskripsikan cara untuk menyetel `[[Prototype]]` untuk objek yang dibuat dengan menggunakan fungsi konstruktor. Nanti kita akan melihat lebih banyak alur *programming* lanjutan yang akan menggunakannya. Semuanya cukup simpel, hanya tinggal mengingat beberapa langkah untuk membuat lebih jelas: - Properti `F.prototype` (jangan keliru tentang `[[Prototype]]`) menyetel `[[Prototype]]` dari objek baru ketika `new F()` dipanggil. - Nilai dari `F.prototype` harusnya antara sebuah objek atau `null`: nilai lainnya tidak akan bekerja. - Properti `"prototype"` hanya memiliki efek spesial ketika menyetel fungsi konstruktor, dan dipanggil dengan `new`. Dalam objek biasa `prototype` tidaklah spesial: ```js let user = { name: "John", prototype: "Bla-bla" // tidak ada yang spesial disini }; ``` Secara teknis semua fungsi memiliki `F.prototype = { constructor: F }`, jadi kita bisa mendapatkan konstruktor dari sebuah objek dengan mengakses properti `"constructor"` miliknya sendiri. ================================================ 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); // menampilkan "Hello!" setelah 1 detik ``` ================================================ FILE: 1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md ================================================ nilai: 5 --- # Menambahkan metode "f.defer(ms)" ke fungsi Tambahkan kepada *prototype* dari semua fungsi metode `defer(ms)`, yang menjalankan fungsinya setelah milidetik `ms`. Setelah kamu melakukannya, kodenya harus berjalan: ```js function f() { alert("Hello!"); } f.defer(1000); // menampilkan "Hello!" setelah 1 detik ``` ================================================ 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); } }; // check it function f(a, b) { alert( a + b ); } f.defer(1000)(1, 2); // menampilkan 3 setelah 1 detik ``` Ingatlah: kita menggunakan `this` didalam `f.apply` untuk membuat dekorasi kita bekerja untuk metode objek. Jadi jika pembungkus fungsinya dipanggil sebagai metode objek, maka `this` diberikan kepada metode asli `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 ================================================ nilai: 4 --- # Menambah dekorasi "defer()" ke fungsi. Tambahkan prototype dari semua fungsi metode` defer(ms)`, yang mengembalikan pembungkus, menunda pemanggilan dengan `ms` milidetik. Contoh: ```js function f(a, b) { alert( a + b ); } f.defer(1000)(1, 2); // tampilkan 3 setelah 1 detik ``` Perhatikan bahwa argumennya harus diberikan ke fungsi aslinya. ================================================ FILE: 1-js/08-prototypes/03-native-prototypes/article.md ================================================ # *Prototype* asli Properti `"prototype"` adalah properti yang banyak digunakan oleh Javascript itu sendiri. Semua konstruktor fungsi menggunakannya. Pertama kita akan melihat lebih lengkapnya dan bagaimana cara menggunakannya untuk menambah kemampuan dari objek-objek bawaan. ## Object.prototype katakan kita mengeluarkan sebuah objek kosong: ```js run let obj = {}; alert( obj ); // "[object Object]" ? ``` Dimanakah kode yang menghasilkan *string* `"[object Object]"`? Itu adalah metode bawaan `toString`, tapi diamanakah itu berara? `obj` tidak berisi apapun! ...Tapi notasi pendek dari `obj = {}` sama seperti `obj = new Object()`, dimana `Object` adalah fungsi konstruktor objek bawaan, dengan properti `prototype`nya sendiri yang mereferensi sebuah objek besar `toString` dan metode lainnya. Inilah yang terjadi: ![](object-prototype.svg) Ketika `new Object()` dipanggil (atau sebuah objek literal `{...}` dibuat), `[[Prototype]]`nya disetel ke `Object.prototype` mengikuti aturan yang telah kita bahas di bab sebelumnya: ![](object-prototype-1.svg) Jadi ketika `obj.toString()` dipanggil metodenya dibawa dari `Object.prototype`. Kita bisa periksa seperti ini: ```js run let obj = {}; alert(obj.__proto__ === Object.prototype); // true alert(obj.toString === obj.__proto__.toString); //true alert(obj.toString === Object.prototype.toString); //true ``` Ingat bahwa disana sudah tidak ada lagi `[[Prototype]]` didalam rantai diatas `Object.prototype`: ```js run alert(Object.prototype.__proto__); // null ``` ## *prototype* bawaan lainnya Objek bawaan lainnya seperti `Array`, `Date`, `Function` dan lainnya juga tetap menyimpan metode didalam *prototype*. Contoh, ketika kita membuat sebuah *array* `[1, 2, 3]`, konstruktor bawaan `new Array()` digunakan secara internal. Jadi `Array.prototype` menjadi *prototype* miliknya dan menyediakan metode-metode. Itu akan membuatnya menjadi efisien dalam penggunaan memori. Sebagaimana spesifikasinya, semua *prototype* bawaan memiliki `Object.prototype` diatasnya. Itulah kenapa beberapa orang berkata bahwa "semuanya diwarisi dari objek". Ini adalah gambar keseluruhan (memasangkan 3 fungsi): ![](native-prototypes-classes.svg) Sekarang kita cek *prototype*nya secara manual: ```js run let arr = [1, 2, 3]; // apakah diwarisi dari Array.prototype? alert( arr.__proto__ === Array.prototype ); // true // maka dari Object.prototype? alert( arr.__proto__.__proto__ === Object.prototype ); // true // dan null diatasnya. alert( arr.__proto__.__proto__.__proto__ ); // null ``` Beberapa metode didalam *prototype* mungkin tumpang tindih, contoh, `Array.prototype` memiliki `toString`nya sendiri yang menyusun elemen yang dipisahkan dengan koma: ```js run let arr = [1, 2, 3] alert(arr); // 1,2,3 <-- hasil dari Array.prototype.toString ``` Seperti yang telah kita lihat, `Object.prototype` memiliki `toString` juga, tapi `Array.prototype` lebih dekat dengan rantainya, jadi varian dari *array* mungkin akan digunakan. ![](native-prototypes-array-tostring.svg) Dialam alat *browser* seperti *Chrome Developer Conolse* juga menunjukan pewarisannya (`console.dir` mungkin butuh untuk digunakan seperti objek bawaan): ![](console_dir_array.png) Objek bawaan lainnya juga mungkin bekerja mirip seperti itu. Bahkan fungsi -- mereka adalah objek dari konstruktor bawaan `Function`, dan metode mereka (`call`/`apply` dan lainnya) juga diambil dari `Function.prototype`. Fungsi juga memiliki `toString` mereka masing-masing. ```js run function f() {} alert(f.__proto__ == Function.prototype); // true alert(f.__proto__.__proto__ == Object.prototype); // true, warisan dari objek ``` ## Primitif-primitif Hal yang paling rumit terjadi dengan *string*, *number* dan *boolean*. Seperti yang kita ingat, mereka bukanlah objek. Tapi jika kita mencoba untuk mengakses propertinya, objek pembungkus sementara menggunakan konstruktor bawaan `String`, `Number` dan `Boolean`. Mereka menyediakan metodenya dan menghilang. Objek-objek ini dibuat tak terlihat untuk kita dan kebanyakan mesin mengoptimalkan mereka, tapi spesifikasinya menjelaskannya juga seperti itu. Metode dari objek ini juga tinggal didalam *prototype*, tersedia sebagai `String.prototype`, `Number.prototype` dan `Boolean.prototype`. ```warn header="Nilai `null` dan `undefined` tidak memiliki objek pembungkus" Nilai spesial `null` dan `undefined` memiliki pendiriannya sendiri. Mereka tidak memiliki objek pembungkus, jadi metode dan properti tidak tersedia untuk mereka. Dan juga tidak terdapat prototypenya. ``` ## Changing native prototypes [#native-prototype-change] Prototipe asli bisa dimodifikasi. Contoh, jika kita menambahkan metode kepada `String.prototype`, itu akan menjadi tersedia untuk selurung *string*. ```js run String.prototype.show = function() { alert(this); }; "BOOM!".show(); // BOOM! ``` Selama proses pembangunan, kita mungkin memiliki ide untuk metode bawaan baru yang kita ingin punya, dan kita mungkin tergoda untuk menambahkannya sebagai *prototype* asli. Tapi itu sebenarnya bukan ide yang bagus. ```warn Prototype terlihat di global, jadi akan mudah membuat konflik. Jika dua library menambahkan sebuah metode `String`prototype.show`, maka salah satu dari mereka akan menimpah yang lainnya. Jadi, umumnya, memodifikasi prototype asli bisa dikatakan bukan ide bagus. ``` **Didalam *programming* modern, terdapat satu kasus dimana memodifikasi *prototype* asli dapat diterima. Disebut dengan *polyfilling*.** *Polyfilling* adalah sebuah istilah untuk membuat sebuah metode pengganti yang ada didalam spesifikasi Javascript, tapi itu tidak didukung oleh mesin Javascript tertentu. Kita mungkin mengimplementasi manual dan mengisi prototype bawaan dengan itu. Contoh: ```js run if (!String.prototype.repeat) { // Jika tidak terdapat metode // tambahkan kedalam prototype String.prototype.repeat = function(n) { // ulangi stringnya n kali // sebenarnya, kodenya haruslah lebih rumit dari itu // (algoritma lengkapnya ada didalam spesifikasinya) // bahkan sebuah polyfill tidak sempurna kadang bisa dikatakan cukup bagus return new Array(n + 1).join(this); }; } alert( "La".repeat(3) ); // LaLaLa ``` ## meminjam dari *prototype* Didalam bab kita berbicara tentang peminjaman metode. Itulah ketika kita mengambil metode dari satu objek dan menyalinnya ke objek lain. Beberapa metode dari *prototype* asli sering dipinjam. Contoh, jika kita membuat objek yang mirip array, kita mungkin ingin menyalin beberapa metode `Array` darinya. E.g. ```js run let obj = { 0: "Hello", 1: "world!", length: 2, }; *!* obj.join = Array.prototype.join; */!* alert( obj.join(',') ); // Hello,world! ``` Contoh diatas bekerja karena algoritma internal bawaan `join` yang memperhatikan tentang indeks yang benar dan `length` dari properti. Itu tidak akan memeriksa apakah objeknya adalah array. Beberapa metode bawaan memang seperti itu. Kemungkinan lainnya adalah pewarisan dari `obj.__proto__` ke `Array.prototype`, jadi seluruh metode `Array` secara otomatis tersedia didalam `obj`. Tapi itu menjadi tidak mungkin jika `obj` sudah mewarisi dari objek lainnya. Ingat, kita hanya bisa mewarisi dari satu objek pada satu waktu. Meminjam metode sebenarnya cukup fleksibel, hal itu memperbolehkan kita untuk mencampur fungsionalitas dari objek yang berbeda-beda jika dibutuhkan. ## Ringkasan - Seluruh objek bawaan mengikuti alur yang sama: - Metode disimpan didalam prototype (`Array.prototype`, `Object.prototype`, `Date.prototype`, etc.) - Objeknya sendiri hanya menyimpan data (item array, properti objek, tanggal) - Prototype Asli menyimpan metode didalam prototype dari objek pembungkus: `Number.prototype`, `String.prototype` dan `Boolean.prototype`. Only `undefined` dan `null` tidak memiliki objek pembungkus. - *Prototype* bawaan bisa dimodifikasi atau diisi ulang dengan metode baru. Tapi tidak direkomendasikan untuk mengubahnya. Hal yang diperbolehkan dalam beberapa kasus mungkun ketika kita menambahkan peraturan baru, tapi itu belum sepenuhnya didukung oleh mesin Javascript. ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md ================================================ Pemanggilan metode bisa mengambil semua kunci yang terhitung menggunakan `Object.keys` dan mengeluarkan daftarnya. Untuk membuat `toString` tidak bisa dihitung, kita bisa mendefinisikannya menggunakan deskriptor properti. Sintaks dari `Object.create` membolehkan kita untuk menyediakan sebuah objek dengan deskriptor properti sebagai argumen kedua. ```js run *!* let dictionary = Object.create(null, { toString: { // definisikan properti tostring value() { // nilainya adalah fungsi return Object.keys(this).join(); } } }); */!* dictionary.apple = "Apple"; dictionary.__proto__ = "test"; // apple dan __proto berada didalam perulangan for(let key in dictionary) { alert(key); // "apple", lalu "__proto__" } // properti dari daftar yang dipisahkan dengan koma oleh toString alert(dictionary); // "apple,__proto__" ``` Ketika kita membuat sebuah properti menggunakan deskriptor, tandanya akan menjadi `false` secara bawaan. Jadi kode diatas, `dictionary.toString` tidak bisa dihitung. Lihat bab [](info:property-descriptors) untuk review. ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md ================================================ nilai: 5 --- # Add toString to the dictionary Terdapat sebuah objek `dictionary`, dibuat sebagai `Object.create(null)`, untuk menyimpan pasangan `key/value`. Tambahkan metode `dictionary.toString()` kedalamnya, yang harus mengembalikan daftar yang dibatasi dengan koma. `toString` milikmu haruslah tidak tampil didalam `for..in` dalam objeknya. Ini adalah contohnya: ```js let dictionary = Object.create(null); *!* // metode yang ditambahkan dictionary.toString */!* // tambahkan beberapa data dictionary.apple = "Apple"; dictionary.__proto__ = "test"; // __proto__ adalah kunci properti biasa disini // hanya apple dan __proto__ yang berada di perulangan for(let key in dictionary) { alert(key); // "apple", lalu "__proto__" } // toString milikmu alert(dictionary); // "apple,__proto__" ``` ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md ================================================ Panggilan pertama memiliki `this == rabbit`, yang lainnya memiliki `this` sama dengan `Rabbit.prototype`, karena itu sebenarnya adalah objek sebelum titiknya. Jadi hanya panggilan pertama yang menampilkan `Rabbit`, lainnya menampilkan `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 ================================================ nilai: 5 --- # Perbedaan diantara pemanggilan Kita buat sebuah objek `rabbit` baru: ```js function Rabbit(name) { this.name = name; } Rabbit.prototype.sayHi = function() { alert(this.name); }; let rabbit = new Rabbit("Rabbit"); ``` Apakah panggilan-panggilan dibawah sama atau tidak? ```js rabbit.sayHi(); Rabbit.prototype.sayHi(); Object.getPrototypeOf(rabbit).sayHi(); rabbit.__proto__.sayHi(); ``` ================================================ FILE: 1-js/08-prototypes/04-prototype-methods/article.md ================================================ # Prototype methods, objects without __proto__ In the first chapter of this section, we mentioned that there are modern methods to setup a prototype. The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard). The modern methods are: - [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`. - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`. These should be used instead of `__proto__`. For instance: ```js run let animal = { eats: true }; // create a new object with animal as a prototype *!* let rabbit = Object.create(animal); */!* alert(rabbit.eats); // true *!* alert(Object.getPrototypeOf(rabbit) === animal); // true */!* *!* Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {} */!* ``` `Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this: ```js run let animal = { eats: true }; let rabbit = Object.create(animal, { jumps: { value: true } }); alert(rabbit.jumps); // true ``` The descriptors are in the same format as described in the chapter . We can use `Object.create` to perform an object cloning more powerful than copying properties in `for..in`: ```js let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` This call makes a truly exact copy of `obj`, including all properties: enumerable and non-enumerable, data properties and setters/getters -- everything, and with the right `[[Prototype]]`. ## Brief history If we count all the ways to manage `[[Prototype]]`, there are a lot! Many ways to do the same thing! Why? That's for historical reasons. - The `"prototype"` property of a constructor function has worked since very ancient times. - Later, in the year 2012, `Object.create` appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. So browsers implemented the non-standard `__proto__` accessor that allowed the user to get/set a prototype at any time. - Later, in the year 2015, `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, to perform the same functionality as `__proto__`. As `__proto__` was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is: optional for non-browser environments. As of now we have all these ways at our disposal. Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? That's an interesting question, requiring us to understand why `__proto__` is bad. Read on to get the answer. ```warn header="Don't change `[[Prototype]]` on existing objects if speed matters" Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time and don't modify it anymore: `rabbit` inherits from `animal`, and that is not going to change. And JavaScript engines are highly optimized for this. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation as it breaks internal optimizations for object property access operations. So avoid it unless you know what you're doing, or JavaScript speed totally doesn't matter for you. ``` ## "Very plain" objects [#very-plain] As we know, objects can be used as associative arrays to store key/value pairs. ...But if we try to store *user-provided* keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except `"__proto__"`. Check out the example: ```js run let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; alert(obj[key]); // [object Object], not "some value"! ``` Here, if the user types in `__proto__`, the assignment is ignored! That shouldn't surprise us. The `__proto__` property is special: it must be either an object or `null`. A string can not become a prototype. But we didn't *intend* to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug! Here the consequences are not terrible. But in other cases we may be assigning object values, and then the prototype may indeed be changed. As a result, the execution will go wrong in totally unexpected ways. What's worse -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side. Unexpected things also may happen when assigning to `toString`, which is a function by default, and to other built-in methods. How can we avoid this problem? First, we can just switch to using `Map` for storage instead of plain objects, then everything's fine. But `Object` can also serve us well here, because language creators gave thought to that problem long ago. `__proto__` is not a property of an object, but an accessor property of `Object.prototype`: ![](object-prototype-2.svg) So, if `obj.__proto__` is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`. As it was said in the beginning of this tutorial section: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself. Now, if we intend to use an object as an associative array and be free of such problems, we can do it with a little trick: ```js run *!* let obj = Object.create(null); */!* let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; alert(obj[key]); // "some value" ``` `Object.create(null)` creates an empty object without a prototype (`[[Prototype]]` is `null`): ![](object-prototype-null.svg) So, there is no inherited getter/setter for `__proto__`. Now it is processed as a regular data property, so the example above works right. We can call such objects "very plain" or "pure dictionary" objects, because they are even simpler than the regular plain object `{...}`. A downside is that such objects lack any built-in object methods, e.g. `toString`: ```js run *!* let obj = Object.create(null); */!* alert(obj); // Error (no toString) ``` ...But that's usually fine for associative arrays. Note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects: ```js run let chineseDictionary = Object.create(null); chineseDictionary.hello = "你好"; chineseDictionary.bye = "再见"; alert(Object.keys(chineseDictionary)); // hello,bye ``` ## Summary Modern methods to set up and directly access the prototype are: - [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with a given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors. - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys into an object. Just because a user may enter `"__proto__"` as the key, and there'll be an error, with hopefully light, but generally unpredictable consequences. So we can either use `Object.create(null)` to create a "very plain" object without `__proto__`, or stick to `Map` objects for that. Also, `Object.create` provides an easy way to shallow-copy an object with all descriptors: ```js let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just like other methods. We can create an object without a prototype by `Object.create(null)`. Such objects are used as "pure dictionaries", they have no issues with `"__proto__"` as the key. Other methods: - [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs. - [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic keys. - [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string keys. - [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own keys. - [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): returns `true` if `obj` has its own (not inherited) key named `key`. All methods that return object properties (like `Object.keys` and others) -- return "own" properties. If we want inherited ones, we can use `for..in`. ================================================ FILE: 1-js/08-prototypes/index.md ================================================ # Prototypes, inheritance ================================================ 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 ================================================ nilai: 5 --- # Menulis ulang ke class Class `Clock` ditulis dalam gaya fungsional. Tulis ulang dengan menggunakan sintaks "class". P.S Class Clock berdetak pada console, buka untuk melihatnya. ================================================ FILE: 1-js/09-classes/01-class/article.md ================================================ # Class basic syntax ```quote author="Wikipedia" In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods). ``` Pada kenyataannya, kita sering membutuhkan sesuatu untuk membuat banyak objek yang memiliki kemiripan, seperti user, atau benda-benda lainnya seperti buah-buahan, binatang, kendaraan dan sesuatu yang biasanya bisa dikelompokan. Seperti yang kita ketahui dari bab , `new function` dapat membantu kita membuatnya. Tapi pada Javascript modern, ada cara pembuatan "class" yang lebih lanjut, Yang akan memperkenalkan fitur-fitur yang sangat berguna untuk pemograman berbasis objek / objek oriented. ## Sintaks "class" Sintaks dasarnya adalah: ```js class MyClass { // class methods constructor() { ... } method1() { ... } method2() { ... } method3() { ... } ... } ``` Kemudian kita gunakan `new MyClass` untuk menciptakan sebuah objek baru dengan semua method yang sudah didaftarkan. Method `constructor()` secara otomatis terpanggil oleh `new`, jadi kita dapat menginisialisasi sebuah objek dengannya. Contohnyya: ```js run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // Cara penggunaan: let user = new User("John"); user.sayHi(); ``` Ketika `new user("john")` dijalankan: 1. Sebuah objek baru terbentuk. 2. method "constructor" berjalan dengan argumen yang dberikan dan menetapkan `this.name` pada nya. ...Lalu kita dapat memanggil method objek, seperti `user.sayHi()`. ```warn header="Tidak boleh ada koma diantara method class" Kesalahan umum bagi pemula biasanya dengan memberikan koma diantara method class, yang nantinya akan menghasilkan sintaks eror. Notasi disini jangan disamakan dengan objek literal. Dalam class, tidak perlu ada koma. ``` ## Apa itu class? Jadi apa sih sebenarnnya `class` itu ? Itu bukan sebuah istilah baru dalam bahasa pemograman. Mari kita uraikan dan lihat apa sebenarnya class itu. Yang nantinya akan membantu untuk memahami berbagai aspek yang lebih rumit. Di Javascript, sebuah class itu merupakan sesuatu yang mirip fungsi. Yuk kita lihat disini: ```js run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // bukti: User adalah sebuah fungsi *!* alert(typeof User); // function */!* ``` Apa yang konstruktor `class User {...}` sebenarnya lakukan: 1. Membuat sebuah fungsi bernama `User`, yang akan menjadi hasil dari deklarasi class. Sebuah fungsi kodingan diambil dari method `constructor` (kita asumsikan kosong jika kita tidak menulis sebuah method). 2. Simpan method-method class didalamnya, seperti `sayHi`, pada `User.prototype`. Setelah objek `new User` terbentuk, ketika kita panggil method tersebut, itu akan mengambil dari prototypenya, seperti yang dijelaskan pada bab . Sehingga objek tersebut memiliki akses pada method class nya. Kita dapat mengilustrasikan hasil dari deklrasi `class User` seperti berikut: ![](class-user.svg) Mari kita bedah kode tersebut: ```js run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } } // class adalah sebuah fungsi alert(typeof User); // function // ...atau, lebih tepatnya, Method constructor alert(User === User.prototype.constructor); // true // Method tersebut berada pada User.prototype, contoh: alert(User.prototype.sayHi); // alert(this.name); // Didalamnya terdapat dua method pada prototipe alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ``` ## Bukan hanya sebuah pemanis sintaks belaka Terkadang orang-orang mengatakan bahwa `class` adalah sebuah `sintaksis sugar` (sintaks yang diciptakan untuk membuat mudah pembacaan, tapi tidak ada sesuatu yang baru didalamnya), karena kita sebenarnya dapat mendeklasikan objek tanpa menggunakan sintaks `class` sama sekali. ```js run // menjalankan Class User hanya dengan fungsi // 1. Membuat fungsi konstruktor function User(name) { this.name = name; } // sebuah fungsi prototipe memiliki properti "constructor" secara bawaannya, // jadi kita tidak perlu membuatnya // 2 menambahkan method pada prototype User.prototype.sayHi = function() { alert(this.name); }; // Penggunaan: let user = new User("John"); user.sayHi(); ``` Hasil dari pendefinisian berikut adalah sama. Jadi memang ada alasan mengapa `class` dapat dianggap sebagai pemanis sintaks untuk pendefinisian konstruktor bersamaan dengan method prototipe-nya. Tetap saja, ada perbedaan penting. 1. Pertama, sebuah fungsi yang dibentuk dengan `class` dilabeli oleh properti internal yang khusus `[[FunctionKind]]:"classConstructor"`. Jadi tidak sepenuhnya sama dengan membuatnya secara manual. Javascript sendiri mengecek properti tersebut diberbagai tempat. sebagai contoh, tidak seperti fungsi biasa, itu harus dijalankan dengan sintaks `new`: ```js run class User { constructor() {} } alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new' ``` Selain itu, representasi string dari konstruktor class di sebagian besar mesin JavaScript dimulai dengan "class ..." ```js run class User { constructor() {} } alert(User); // class User { ... } ``` Terdapat perbedaan lain, kita akan segera melihatnya. 2. Method Class merupakan non-enumerable(tidak bisa melakukan perhitungan dengan method ini) Sebuah pendefinisian class menetapkan flag `enumerable` menjadi `false` untuk semua methods yang ada di `properti` nya. Itu bagus, karena jika kita melakukan `for..in` pada sebuah objek, kita biasanya tidak menginginkan method bawaan class nya. 3. Class selalu menggunakan `use strict`.(cek apa itu [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)) Selain itu, sintaks `class` menghadirkan banyak fitur lain yang akan kita bahas nantinya. ## Class Expression Sama seperti fungsi, kelas dapat didefinisikan denga ekspresi lain, diturunkan, dikembalikan, ditetapkan, dll. Berikut adalah contoh class expression: ```js let User = class { sayHi() { alert("Hello"); } }; ``` Mirip dengan fungsi Expressions yang memiliki nama, class expression juga mungkin memiliki nama. Jika sebuah class expression memiliki nama, itu hanya terlihat di dalam class tersebut: ```js run // "Class Expression yang memiliki nama" // ((tidak ada pembahasan dalam spesifikasinya, tapi itu mirip dengan function expression yang memiliki nama) let User = class *!*MyClass*/!* { sayHi() { alert(MyClass); // Nama MyClass hanya terlihat di dalam kelas } }; new User().sayHi(); //berjalan, menunjukkan definisi dari MyClass alert(MyClass); // error, Nama MyClass tidak bisa dilihat di luar kelas ``` Kita bahkan dapat membuat kelas secara dinamis "sesuai permintaan", seperti berikut: ```js run function makeClass(phrase) { // deklrasikan sebuah class dan kembalikan class trsebut return class { sayHi() { alert(phrase); } }; } // Membuat sebuah class baru let User = makeClass("Hello"); new User().sayHi(); // Hello ``` ## Getter/setter Sama seperti objek literal, class dapat menyertakan getter / setter serta properti yang telah diproses, dll. Berikut adalah sebuah contoh untuk `user.name` dengan implementasi menggunakan `get/set`: ```js run class User { constructor(name) { // memanggil setternya 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(""); // Name is too short. ``` Secara teknis, deklarasi kelas tersebut bekerja dengan membuat getter dan setter didalam `User.prototype`. ## Nama yang telah diproses [...] Berikut adalah sebuah contoh menggunakan method pada nama yang telah diproses pada bracket`[...]`: ```js run class User { *!* ['say' + 'Hi']() { */!* alert("Hello"); } } new User().sayHi(); ``` Fitur seperti itu mudah diingat, karena mirip dengan objek literal. ## Class fields ```warn header="Browser lama mungkin membutuhkan polyfill" Class fields adalah tambahan terbaru pada bahasa ini.. ``` Sebelumnya, kelas ini hanya memiliki method. "Class fields" adalah sebuah sintaks yang memungkinkan untuk menambahkan properti apa pun. Sebagai contoh, mari kita tambahkan properti `name` pada `class User`: ```js run class User { *!* name = "John"; */!* sayHi() { alert(`Hello, ${this.name}!`); } } new User().sayHi(); // Hello, John! ``` Jadi, kita hanya perlu menulis " = " pada saat proses deklarasi. Perbedaan penting pada class field adalah bahwa class field tersebut diatur untuk objek individual, bukan pada `User.prototype`: ```js run class User { *!* name = "John"; */!* } let user = new User(); alert(user.name); // John alert(User.prototype.name); // undefined ``` Kita juga dapat menetapkan nilai menggunakan ekspresi yang lebih kompleks dan memanggil fungsi: ```js run class User { *!* name = prompt("Name, please?", "John"); */!* } let user = new User(); alert(user.name); // John ``` ### Membuat bound method dengan class field Seperti yang ditunjukkan pada bab , fungsi di JavaScript memiliki `this` yang dinamis. Itu tergantung pada konteks pemanggilannya. Jadi, jika method objek diteruskan dan dipanggil pada konteks lain, `this` tidak akan menjadi referensi ke objek itu lagi. Sebagai contoh, kode dibawah ini akan menampilkan output `undefined`: ```js run class Button { constructor(value) { this.value = value; } click() { alert(this.value); } } let button = new Button("hello"); *!* setTimeout(button.click, 1000); // undefined */!* ``` Masalah itu terjadi karena memanggil `this` pada konteks lain. Terdapat dua pendekatan cara untuk memperbaikinya, seperti yang telah kita diskusikan pada bab : 1. Meneruskan sebuah fungsi-wrapper, seperti `setTimeout(() => button.click(), 1000)`. 2. Mengikat method tersebut terhadap objek, seperti pada constructor. Class field menyediakan sintaks lain yang cukup elegan: ```js run class Button { constructor(value) { this.value = value; } *!* click = () => { alert(this.value); } */!* } let button = new Button("hello"); setTimeout(button.click, 1000); // hello ``` Class field ini `click = () => {...}` dibuat pada pada basis tiap objek, ada sebuah fungsi terpisah pada tiap `Button` objek, dengan `this` di dalamnya mereferensikan objek tersebut. Kita dapat meneruskan `button.click` di mana saja, dan nilai` this` akan selalu benar. Itu akan sangat berguna pada lingkungan browser, teruntuk event listener. ## Kesimpulan Sintaks class dasar terlihat seperti ini: ```js class MyClass { prop = value; // property constructor(...) { // constructor // ... } method(...) {} // method get something(...) {} // getter method set something(...) {} // setter method [Symbol.iterator]() {} // method dengan nama yang telah diproses (disini simbol) // ... } ``` `MyClass` secara teknis adalah sebuah fungsi (yang kita sediakan sebagai` konstruktor`), sedangkan method, getter, dan setter ditulis ke `MyClass.prototype`. Pada bab selanjutnya kita akan mempelajari lebih lanjut tentang class, termasuk pewarisan dan fitur lainnya. ================================================ FILE: 1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md ================================================ Itu karena konstruktor turunan harus memanggil `super()`. Berikut kode yang benar: ```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"); // sekarang oke */!* alert(rabbit.name); // White Rabbit ``` ================================================ FILE: 1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md ================================================ importance: 5 --- # Kesalahan saat membuat sebuah _instance_ Berikut kode dengan `Rabbit` _extending_ `Animal`. Sayangnya, objek `Rabbit` tidak dapat dibuat. Apa yang salah? Perbaiki!. ```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 --- # _Extended clock_ Kita punya kelas `Clock`. Sampai sekarang, ini mencetak waktu setiap detik. [js src="source.view/clock.js"] Buat kelas baru `ExtendedClock` yang diturunkan dari` Clock` dan tambahkan parameter `precision` -- jumlah` ms` antara "ticks". Seharusnya `1000` (1 detik) secara _default_. - Kode kamu harus ada di file `extended-clock.js` - Jangan ubah `clock.js`. Perpanjang itu!. ================================================ FILE: 1-js/09-classes/02-class-inheritance/article.md ================================================ # Turunan Kelas Turunan Kelas adalah cara satu kelas untuk memperluas kelas lainnya. Jadi kita bisa membuat fungsionalitas baru di atas yang sudah ada. ## Kata kunci "extends" Katakanlah kita mempunyai kelas `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'); ``` Inilah cara kita mewakili objek `animal` dan kelas `Animal` secara grafis: ![](rabbit-animal-independent-animal.svg) ...Dan kita akan membuat yang lain `class Rabbit`. Karena kelinci adalah binatang, kelas `Rabbit` harus didasarkan pada `Animal`, memiliki akses ke metode _animal_, sehingga _rabbits_ dapat melakukan apa yang dapat dilakukan hewan "pada umumnya". Sintaks untuk memperluas kelas lain adalah: `class Child extends Parent`. Mari buat `class Rabbit` yang diwarisi dari `Animal`: ```js *!* class Rabbit extends Animal { */!* hide() { alert(`${this.name} hides!`); } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` Objek dari kelas `Rabbit` mempunyai akses kedua metode `Rabbit`, seperti `rabbit.hide()`, dan juga untuk metode `Animal`, seperti `rabbit.run()`. Secara internal, kata kunci `extends` bekerja menggunakan mekanik prototipe lama yang bagus. Ini mengatur `Rabbit.prototype.[[Prototype]]` untuk `Animal.prototype`. Jadi, jika metode tidak ditemukan di `Rabbit.prototype`, JavaScript mengambilnya dari `Animal.prototype`. ![](animal-rabbit-extends.svg) Misalnya, untuk menemukan metode `rabbit.run`, mesin mengecek (dari bawah ke atas pada gambar): 1. Objek `rabbit` (tidak punya `run`). 2. Prototipenya, yaitu `Rabbit.prototype` (mempunyai `hide`, tapi tidak `run`). 3. Prototipenya, yaitu (disebabkan oleh `extends`) `Animal.prototype`, yang akhirnya mempunyai metode `run`. Seperti yang bisa kita ingat dari bab , JavaScript sendiri menggunakan pewarisan _prototypal_ untuk objek bawaan. Misalnya. `Date.prototype.[[Prototype]]` adalah `Object.prototype`. Itulah mengapa tanggal memiliki akses ke metode objek umum. ````smart header="Ekspresi apa pun diperbolehkan setelah `extends` "Sintaks kelas memungkinkan untuk menentukan tidak hanya kelas, tetapi ekspresi apa pun setelah `extends`. Misalnya, panggilan fungsi yang menghasilkan kelas induk: ```js run function f(phrase) { return class { sayHi() { alert(phrase); } }; } *!* class User extends f("Hello") {} */!* new User().sayHi(); // Hello ``` Di sini `class User` mewarisi dari hasil `f("Hello")`. Itu mungkin berguna untuk pola pemrograman tingkat lanjut saat kita menggunakan fungsi untuk menghasilkan kelas bergantung pada banyak kondisi dan dapat mewarisinya. ````` ## Mengganti metode Sekarang mari bergerak maju dan mengganti metode. Secara default, semua metode yang tidak dispesifikasikan dalam `class Rabbit` diambil secara langsung "sebagaimana adanya" dari `class Animal`. Tetapi jika kita menentukan metode kita sendiri di `Rabbit`, seperti `stop()` maka itu akan digunakan sebagai gantinya: ```js class Rabbit extends Animal { stop() { // ...sekarang ini akan digunakan untuk rabbit.stop() // Bukan stop() dari kelas Animal } } ``` Biasanya kita tidak ingin sepenuhnya mengganti metode induk, melainkan untuk membangun di atasnya untuk mengubah atau memperluas fungsinya. Kita melakukan sesuatu dalam metode kita, tetapi memanggil metode induk sebelum/sesudahnya atau dalam proses. Kelas menyediakan kata kunci `"super"` untuk itu. - `super.method(...)` untuk memanggil metode induk. - `super(...)` untuk memanggil konstruktor induk (hanya di dalam konstruktor kita). Misalnya, biarkan kelinci kita bersembunyi otomatis saat dihentikan: ```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(); // memanggil stop induk this.hide(); // dan lalu bersembunyi } */!* } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. rabbit.stop(); // White Rabbit stands still. White Rabbit hides! ``` Sekarang `Rabbit` mempunyai metode `stop` yang memanggil induk `super.stop()` di dalam proses. ````smart header="_Arrow functions_ tidak mempunyai `super`" Seperti yang disebutkan di bab , _arrow functions_ tidak memiliki `super`. Jika diakses, itu diambil dari fungsi luar. Misalnya: ```js class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // memanggil stop induk setelah 1 detik } } ``` `super` di _arrow function_ sama dengan di `stop()`, jadi berfungsi seperti yang diinginkan. Jika kita menetapkan fungsi "biasa" di sini, akan ada kesalahan: ```js // Unexpected super setTimeout(function() { super.stop() }, 1000); ``` ````` ## Mengganti konstruktor Dengan konstruktor, ini menjadi sedikit rumit. Sampai sekarang, `Rabbit` tidak mempunyai `constructor` sendiri. Menurut [spesifikasi](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), jika sebuah kelas memperluas kelas lain dan tidak memiliki `constructor`, maka `constructor` "kosong" berikut akan dibuat: ```js class Rabbit extends Animal { // dihasilkan untuk memperluas kelas tanpa konstruktor sendiri *!* constructor(...args) { super(...args); } */!* } ``` Seperti yang bisa kita lihat, ini pada dasarnya memanggil `constructor` induk dengan meneruskan semua argumen. Itu terjadi jika kita tidak menulis konstruktor kita sendiri. Sekarang mari tambahkan konstruktor kustom ke `Rabbit`. Ini akan menentukan `earLength` selain `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; } */!* // ... } *!* // Tidak bekerja! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. */!* ``` Ups! Kita mendapat kesalahan. Sekarang kita tidak bisa membuat _rabbits_. Apa yang salah? Jawaban singkatnya adalah: - **Konstruktor dalam kelas mewarisi harus memanggil `super(...)`, dan (!) lakukan sebelum menggunakan `this`.** ...Tapi kenapa? Apa yang terjadi di sini? Memang, persyaratannya tampak aneh. Tentu saja ada penjelasannya. Mari kita bahas detailnya, jadi kamu akan benar-benar mengerti apa yang terjadi. Dalam JavaScript, ada perbedaan antara fungsi konstruktor dari kelas yang mewarisi (disebut "konstruktor turunan") dan fungsi lainnya. Konstruktor turunan memiliki properti internal khusus `[[ConstructorKind]]:"turunan"`. Itu label internal khusus. Label itu mempengaruhi perilakunya dengan `new`. - Saat fungsi reguler dijalankan dengan `new`, itu membuat objek kosong dan menugaskannya ke `this`. - Tetapi ketika konstruktor turunan berjalan, ia tidak melakukan ini. Ia mengharapkan konstruktor induk untuk melakukan pekerjaan ini. Jadi, konstruktor turunan harus memanggil `super` untuk menjalankan konstruktor induknya, jika tidak, objek untuk `this` tidak akan dibuat. Dan kita akan mendapatkan kesalahan. Agar konstruktor `Rabbit` bekerja, ia perlu memanggil`super()`sebelum menggunakan `this`, seperti di sini: ```js run class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { constructor(name, earLength) { *!* super(name); */!* this.earLength = earLength; } // ... } *!* // sekarang baik-baik saja let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10 */!* ``` ### Mengganti bidang kelas: catatan rumit ```warn header="Advanced note" Catatan ini mengasumsikan kamu memiliki pengalaman tertentu dengan kelas, mungkin dalam bahasa pemrograman lain. Ini memberikan wawasan yang lebih baik tentang bahasa dan juga menjelaskan perilaku yang mungkin menjadi sumber kesalahan (tetapi tidak terlalu sering). Jika kamu merasa kesulitan untuk memahaminya, lanjutkan saja, lanjutkan membaca, kemudian kembali lagi nanti. ``` Kita tidak hanya dapat mengganti metode, tetapi juga bidang kelas. Meskipun, ada perilaku rumit saat kami mengakses kolom yang diganti di konstruktor induk, sangat berbeda dari kebanyakan bahasa pemrograman lainnya. Pertimbangkan contoh ini: ```js run class Animal { name = 'animal'; constructor() { alert(this.name); // (*) } } class Rabbit extends Animal { name = 'rabbit'; } new Animal(); // animal *!* new Rabbit(); // animal */!* ``` Di sini, kelas `Rabbit` memperluas `Animal` dan mengganti bidang `nama` dengan nilainya sendiri. Tidak ada konstruktor sendiri dalam `Rabbit`, jadi konstruktor `Animal` dipanggil. Yang menarik adalah dalam kedua kasus: `new Animal()` dan `new Rabbit()`, `alert` di baris `(*)` menampilkan `animal`. **Dengan kata lain, konstruktor induk selalu menggunakan nilai bidangnya sendiri, bukan yang diganti.** Apa yang aneh tentang itu? Jika masih belum jelas silahkan bandingkan dengan metodenya. Berikut kode yang sama, tetapi alih-alih bidang `this.name` kita memanggil metode `this.showName()`: ```js run class Animal { showName() { // sebagai ganti this.name = 'animal' alert('animal'); } constructor() { this.showName(); // sebagai ganti alert(this.name); } } class Rabbit extends Animal { showName() { alert('rabbit'); } } new Animal(); // animal *!* new Rabbit(); // rabbit */!* ``` Harap diperhatikan: sekarang hasilnya berbeda. Dan itulah yang secara natural kita harapkan. Ketika konstruktor induk dipanggil di kelas turunan, ia menggunakan metode yang diganti. ...Tetapi untuk bidang kelas tidak demikian. Seperti yang dikatakan, konstruktor induk selalu menggunakan bidang induk. Mengapa ada bedanya? Nah, alasannya ada di urutan bidang inisialisasi. Bidang kelas diinisialisasi: - Sebelum konstruktor untuk kelas dasar (yang tidak memperluas apa pun), - Langsung setelah `super()` untuk kelas turunan. Dalam kasus kita, `Rabbit` adalah kelas turunannya. Tidak ada `constructor()` di dalamnya. Seperti yang dikatakan sebelumnya, itu sama seperti jika ada konstruktor kosong hanya dengan `super(...args)`. Jadi, `new Rabbit()` memanggil `super()`, sehingga mengeksekusi konstruktor induk, dan (sesuai aturan untuk kelas turunan) hanya setelah bidang kelasnya diinisialisasi. Pada saat eksekusi induk konstruktor, belum ada bidang kelas `Rabbit`, itulah mengapa bidang `Animal` digunakan. Perbedaan halus antara bidang dan metode ini khusus untuk JavaScript Untungnya, perilaku ini hanya muncul dengan sendirinya jika bidang yang diganti digunakan di konstruktor induk. Maka mungkin sulit untuk memahami apa yang sedang terjadi, jadi kita menjelaskannya di sini. Jika ini menjadi masalah, seseorang dapat memperbaikinya dengan menggunakan metode atau _getter_/_setter_ sebagai ganti bidang. ## _Super: internals, [[HomeObject]]_ ```warn header="Advanced information" Jika Anda membaca tutorial untuk pertama kali - bagian ini mungkin dilewati. Ini tentang mekanisme internal di balik pewarisan dan `super`. ``` Mari kita selami lebih dalam di balik tudung `super`. Kita akan melihat beberapa hal menarik di sepanjang jalan. Pertama untuk mengatakan, dari semua yang telah kita pelajari sampai sekarang, mustahil bagi `super` untuk bekerja sama sekali! Ya, memang, mari kita tanyakan pada diri kita sendiri, bagaimana seharusnya secara teknis bekerja? Ketika sebuah metode objek dijalankan, ia mendapatkan objek saat ini sebagai `this`. Jika kita memanggil `super.method()` maka, mesin perlu mendapatkan `metode` dari prototipe objek saat ini. Tapi bagaimana caranya? Tugasnya mungkin tampak sederhana, tetapi sebenarnya tidak. Mesin mengetahui objek saat ini `this`, sehingga bisa mendapatkan `metode` induk sebagai `this.__proto__.method`. Sayangnya, solusi "naif" seperti itu tidak akan berhasil. Mari kita tunjukkan masalahnya. Tanpa kelas, menggunakan objek biasa demi kesederhanaan. Kamu dapat melewati bagian ini dan melanjutkan ke subbagian `[[HomeObject]]` jika kamu tidak ingin mengetahui detailnya. Itu tidak akan merugikan. Atau baca terus jika kamu tertarik untuk memahami berbagai hal secara mendalam. Pada contoh di bawah, `rabbit.__ proto__ = animal`. Sekarang mari kita coba: di `rabbit.eat()` kita akan memanggil `animal.eat() `, menggunakan` this.__ proto__`: ```js run let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { *!* // begitulah kemungkinan super.eat() bisa bekerja this.__proto__.eat.call(this); // (*) */!* } }; rabbit.eat(); // Rabbit eats. ``` Pada baris `(*)` kita mengambil `eat` dari prototipe (`animal`) dan memanggilnya dalam konteks objek saat ini. Harap dicatat bahwa `.call(this)` penting di sini, karena sesimpel `this.__proto__.Eat()` akan mengeksekusi `eat` induk dalam konteks prototipe, bukan objek saat ini. Dan pada kode di atas itu benar-benar berfungsi sebagaimana mestinya: kita memiliki `alert` yang benar. Sekarang mari tambahkan satu objek lagi ke rantai. Kita akan melihat bagaimana hal-hal rusak: ```js run let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, eat() { // ...melambung di sekitar rabbit-style dan panggil metode induk (animal) this.__proto__.eat.call(this); // (*) } }; let longEar = { __proto__: rabbit, eat() { // ...lakukan sesuatu dengan long ears dan panggil metode induk (rabbit) this.__proto__.eat.call(this); // (**) } }; *!* longEar.eat(); // Error: Maximum call stack size exceeded */!* ``` Kode tidak berfungsi lagi! Kita dapat melihat kesalahan saat mencoba memanggil `longEar.eat()`. Mungkin tidak begitu jelas, tetapi jika kita melacak panggilan `longEar.eat()`, maka kita bisa melihat alasannya. Di kedua baris `(*)` dan `(**)` nilai `this` adalah objek saat ini (`longEar`). Itu penting: semua metode objek mendapatkan objek saat ini sebagai `this`, bukan prototipe atau semacamnya. Jadi, di kedua baris `(*)` dan `(**)` nilai dari `this.__ proto__` sama persis: `rabbit`. Mereka keduanya memanggil `rabbit.eat` tanpa naik rantai dalam perulangan tak berujung. Berikut gambaran tentang apa yang terjadi: ![](this-super-loop.svg) 1. Di dalam `longEar.eat()`, baris `(**)` memanggil `rabbit.eat` dengan menyediakan `this=longEar`. ```js // di dalam longEar.eat() kita punya this = longEar this.__proto__.eat.call(this); // (**) // menjadi longEar.__proto__.eat.call(this); // itu adalah rabbit.eat.call(this); ``` 2. Lalu di baris `(*)` dari `rabbit.eat`, kita ingin meneruskan panggilan lebih tinggi lagi dalam rantai, tapi `this=longEar`, jadi `this.__proto__.eat` lagi-lagi `rabbit.eat`! ```js // di dalam rabbit.eat() kita juga punya this = longEar this.__proto__.eat.call(this); // (*) // menjadi longEar.__proto__.eat.call(this); // atau (lagi) rabbit.eat.call(this); ``` 3. ...Jadi `rabbit.eat` menyebut dirinya dalam perulangan tak berujung, karena tidak bisa naik lebih jauh. Masalahnya tidak dapat diselesaikan hanya dengan menggunakan `this`. ### _`[[HomeObject]]`_ Untuk memberikan solusi, JavaScript menambahkan satu lagi properti internal khusus untuk fungsi: `[[HomeObject]]`. Ketika sebuah fungsi ditetapkan sebagai metode kelas atau objek, properti `[[HomeObject]]` -nya menjadi objek itu. Lalu `super` menggunakannya untuk menyelesaikan prototipe induk dan metodenya. Mari kita lihat cara kerjanya, pertama dengan objek biasa: ```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(); } }; *!* // bekerja dengan benar longEar.eat(); // Long Ear eats. */!* ``` Ini berfungsi sebagaimana mestinya, karena mekanisme `[[HomeObject]]`. Metode, seperti `longEar.eat`, tahu itu `[[HomeObject]]` dan mengambil metode induk dari prototipe-nya. Tanpa menggunakan`this`. ### Metode tidak "gratis" Seperti yang kita ketahui sebelumnya, umumnya fungsi adalah "gratis", tidak terikat ke objek di JavaScript. Jadi mereka dapat disalin di antara objek dan dipanggil dengan `this` lainnya. Keberadaan `[[HomeObject]]` melanggar prinsip itu, karena metode mengingat objeknya. `[[HomeObject]]` tidak bisa diubah, jadi ikatan ini selamanya. Satu-satunya tempat dalam bahasa di mana `[[HomeObject]]` digunakan -- adalah `super`. Jadi, jika suatu metode tidak menggunakan `super`, maka kita masih bisa menganggapnya gratis dan menyalin antar objek. Tetapi dengan `super` mungkin ada yang salah. Berikut demo hasil `super` yang salah setelah menyalin: ```js run let animal = { sayHi() { alert(`I'm an animal`); } }; // rabbit mewarisi dari animal let rabbit = { __proto__: animal, sayHi() { super.sayHi(); } }; let plant = { sayHi() { alert("I'm a plant"); } }; // tree mewarisi dari plant let tree = { __proto__: plant, *!* sayHi: rabbit.sayHi // (*) */!* }; *!* tree.sayHi(); // I'm an animal (?!?) */!* ``` Panggilan ke `tree.sayHi()` menunjukkan "I'm an animal". Benar-benar salah. Alasannya sederhana: - Di baris `(*)`, metode `tree.sayHi` telah disalin dari`rabbit`. Mungkin kita hanya ingin menghindari duplikasi kode? - `[[HomeObject]]` -nya adalah `rabbit`, seperti yang dibuat di`rabbit`. Tidak ada cara untuk mengubah `[[HomeObject]]`. - Kode `tree.sayHi()` memiliki `super.sayHi()` di dalamnya. Ini naik dari `rabbit` dan mengambil metode dari`animal`. Berikut diagram dari apa yang terjadi: ![](super-homeobject-wrong.svg) ### Metode, bukan properti fungsi `[[HomeObject]]` didefinisikan untuk metode baik di kelas maupun di objek biasa. Tetapi untuk objek, metode harus ditentukan persis sebagai `method()`, bukan sebagai `"method: function()"`. Perbedaannya mungkin tidak terlalu penting bagi kita, tetapi penting untuk JavaScript. Dalam contoh di bawah ini, sintaks non-metode digunakan untuk perbandingan. Properti `[[HomeObject]]` tidak diatur dan warisan tidak berfungsi: ```js run let animal = { eat: function() { // sengaja menulis seperti ini, bukan eat() {... // ... } }; let rabbit = { __proto__: animal, eat: function() { super.eat(); } }; *!* rabbit.eat(); // Kesalahan memanggil super (karena tidak ada [[HomeObject]]) */!* ``` ## Ringkasan 1. Untuk memperluas kelas: `class Child extends Parent`: - Itu berarti `Child.prototype.__proto__` akan menjadi `Parent.prototype`, jadi metode diwariskan/diturunkan. 2. Saat mengganti konstruktor: - Kita harus memanggil konstruktor induk sebagai `super()` di konstruktor `Child` sebelum menggunakan `this`. 3. Saat mengganti metode lain: - Kita dapat menggunakan `super.method()` di metode `Child` untuk memanggil metode `Parent`. 4. Internal: - Metode mengingat kelas/objek mereka di internal properti `[[HomeObject]]`. Begitulah caranya `super` menyelesaikan metode induk. - Jadi tidak aman untuk menyalin metode dengan `super` dari satu objek ke objek lainnya. Juga: - _Arrow functions_ tidak memiliki `this` atau `super` sendiri, sehingga secara transparan sesuai dengan konteks sekitarnya. ================================================ FILE: 1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md ================================================ Pertama, mari kita lihat mengapa kode terakhir tidak berfungsi. Alasannya menjadi jelas jika kita mencoba menjalankannya. Konstruktor kelas yang mewarisi harus memanggil `super()`. Jika tidak, `"this"` tidak akan "defined". Jadi, inilah perbaikannya: ```js run class Rabbit extends Object { constructor(name) { *!* super(); // perlu memanggil konstruktor induk saat mewarisi */!* this.name = name; } } let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // true ``` Tapi itu belum semuanya. Bahkan setelah perbaikan, masih ada perbedaan penting dalam `"class Rabbit extends Object"` versus `class Rabbit`. Seperti yang kita tahu, sintaks "extends" menyiapkan dua prototipe: 1. Antara `"prototype"` dari fungsi konstruktor (untuk metode). 2. Antara konstruktor berfungsi sendiri (untuk metode statis). Dalam kasus kita, untuk `class Rabbit extends Object` itu berarti: ```js run class Rabbit extends Object {} alert(Rabbit.prototype.__proto__ === Object.prototype); // (1) true alert(Rabbit.__proto__ === Object); // (2) true ``` Jadi `Rabbit` sekarang menyediakan akses ke metode statis `Object` melalui `Rabbit`, seperti ini: ```js run class Rabbit extends Object {} *!* // biasanya kita sebut Object.getOwnPropertyNames alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b */!* ``` Tetapi jika kita tidak punya `extends Object`, lalu `Rabbit.__proto__` tidak diatur ke `Object`. Berikut demo nya: ```js run class Rabbit {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) alert( Rabbit.__proto__ === Function.prototype ); // sebagai fungsi apa pun secara default *!* // error, tidak ada fungsi seperti itu di Rabbit alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error */!* ``` Jadi `Rabbit` tidak menyediakan akses ke metode statis `Object` dalam hal itu. Ngomong-ngomong, `Function.prototype` mempunyai fungsi metode "generic", seperti `call`, `bind` dll. Mereka terakhir tersedia dalam kedua kasus, karena untuk konstruktor `Object` bawaan, `Object.__proto__ === Function.prototype`. Berikut gambarnya: ![](rabbit-extends-object.svg) Jadi, sederhananya, ada dua perbedaan: | class Rabbit | class Rabbit extends Object | | ----------------------------------------- | -------------------------------------- | | -- | needs to call `super()` in constructor | | `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?_ Seperti yang kita ketahui, semua objek biasanya diwarisi dari `Object.prototype` dan mendapatkan akses ke metode objek "generic" seperti `hasOwnProperty` dll. Misalnya: ```js run class Rabbit { constructor(name) { this.name = name; } } let rabbit = new Rabbit("Rab"); *!* // metode hasOwnProperty dari Object.prototype alert( rabbit.hasOwnProperty('name') ); // true */!* ``` Tapi jika kita mengejanya secara eksplisit seperti `"class Rabbit extends Object"`, maka hasilnya akan berbeda dari `"class Rabbit"`? Apa perbedaannya? Berikut contoh kodenya (tidak berhasil -- mengapa? memperbaikinya?): ```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 ================================================ # Properti dan metode statis Kita juga dapat menetapkan metode ke fungsi kelas itu sendiri, bukan ke `" prototipe "`-nya. Metode seperti itu disebut _static_. Di dalam kelas, mereka ditambahkan oleh kata kunci `static`, seperti ini: ```js run class User { *!* static staticMethod() { */!* alert(this === User); } } User.staticMethod(); // true ``` Itu sebenarnya sama dengan menetapkannya sebagai properti secara langsung: ```js run class User {} User.staticMethod = function () { alert(this === User); }; User.staticMethod(); // true ``` Nilai `this` dalam panggilan `User.staticMethod()` adalah konstruktor kelas `User` itu sendiri (aturan "object before dot"). Biasanya, metode statis digunakan untuk mengimplementasikan fungsi yang dimiliki kelas, tetapi tidak untuk objek tertentu darinya. Misalnya, kita punya objek `Article` dan membutuhkan sebuah fungsi untuk membandingkan mereka. Solusi natural adalah menambahkan metode `Article.compare`, seperti ini: ```js run class Article { constructor(title, date) { this.title = title; this.date = date; } *!* static compare(articleA, articleB) { return articleA.date - articleB.date; } */!* } // penggunaan 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 ``` Di sini `Article.compare` berdiri "di atas" _articles_, sebagai alat untuk membandingkannya. Ini bukan metode _article_, melainkan seluruh kelas. Contoh lain adalah apa yang disebut metode "factory". Bayangkan, kita butuh beberapa cara untuk membuat _article_: 1. Buat dengan parameter yang diberikan (`title`, `date` dsb). 2. Buat _article_ kosong dengan tanggal hari ini. 3. ...atau yang lainnya. Cara pertama dapat diterapkan oleh konstruktor. Dan untuk yang kedua kita bisa membuat metode statis kelas. Seperti `Article.createTodays()` di sini: ```js run class Article { constructor(title, date) { this.title = title; this.date = date; } *!* static createTodays() { // ingat, this = Article return new this("Today's digest", new Date()); } */!* } let article = Article.createTodays(); alert( article.title ); // Today's digest ``` Sekarang setiap kali kita perlu membuat _today's digest_, kita dapat memanggil `Article.createTodays()`. Sekali lagi, itu bukan metode _article_, tapi metode seluruh kelas. Metode statis juga digunakan dalam kelas terkait basis data untuk mencari/menyimpan/menghapus entri dari basis data, seperti ini: ```js // dengan asumsi Article adalah kelas khusus untuk mengelola articles // metode statis untuk menghapus article: Article.remove({ id: 12345 }); ``` ## Properti Statis [recent browser=Chrome] Properti statis juga dimungkinkan, mereka terlihat seperti properti kelas biasa, tetapi diawali dengan `static`: ```js run class Article { static publisher = 'Ilya Kantor'; } alert(Article.publisher); // Ilya Kantor ``` Itu sama dengan penugasan langsung ke `Article`: ```js Article.publisher = 'Ilya Kantor'; ``` ## Pewarisan properti dan metode statis [#statics-and-inheritance] Properti dan metode statis diwarisi. Misalnya, `Animal.compare` dan `Animal.planet` dalam kode di bawah ini diwariskan dan dapat diakses sebagai `Rabbit.compare` dan `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; } */!* } // Mewarisi dari 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 ``` Sekarang kita dapat memanggil `Rabbit.compare`, yang diwariskan `Animal.compare` akan dipanggil. Bagaimana cara kerjanya? Sekali lagi, menggunakan prototipe. Seperti yang mungkin sudah kamu duga, `extends` memberi `Rabbit` sebagai `[[Prototype]]` mengacu kepada `Animal`. ![](animal-rabbit-static.svg) Jadi, `Rabbit extends Animal` membuat dua acuan `[[Prototype]]`: 1. `Rabbit` fungsi _prototypally_ mewarisi dari fungsi `Animal`. 2. `Rabbit.prototype` _prototypally_ mewarisi dari `Animal.prototype`. Hasilnya, pewarisan berfungsi baik untuk metode reguler dan statis. Di sini, mari kita periksa dengan kode: ```js run class Animal {} class Rabbit extends Animal {} // untuk statis alert(Rabbit.__proto__ === Animal); // true // untuk metode reguler alert(Rabbit.prototype.__proto__ === Animal.prototype); // true ``` ## Ringkasan Metode statis digunakan untuk fungsionalitas yang termasuk dalam kelas "secara keseluruhan". Ini tidak terkait dengan instance kelas konkret. Sebagai contoh, metode perbandingan `Article.compare(article1, article2)` atau metode _factory_ `Article.createTodays()`. Mereka diberi label dengan kata `static` dalam deklarasi kelas. Properti statis digunakan ketika kita ingin menyimpan data tingkat kelas, juga tidak terikat pada sebuah _instance_. Sintaksnya adalah: ```js class MyClass { static property = ...; static method() { ... } } ``` Secara teknis, deklarasi statis sama dengan menetapkan ke kelas itu sendiri: ```js MyClass.property = ... MyClass.method = ... ``` Properti dan metode statis diwarisi. Untuk `class B extends A` prototipe dari kelas `B` itu sendiri menunjuk ke `A`: `B.[[Prototype]] = A`. Jadi jika bidang tidak ditemukan di `B`, pencarian dilanjutkan di `A`. ================================================ FILE: 1-js/09-classes/04-private-protected-properties-methods/article.md ================================================ # Properti dan metode _private_ dan _protected_ Salah satu prinsip terpenting dari pemrograman berorientasi objek -- membatasi antarmuka internal dari antarmuka eksternal. Itu adalah praktik yang "harus" dilakukan dalam mengembangkan sesuatu yang lebih rumit daripada aplikasi "hello world". Untuk memahami ini, mari kita melepaskan diri dari pengembangan dan mengalihkan pandangan kita ke dunia nyata. Biasanya, perangkat yang kita gunakan cukup rumit. Tetapi membatasi antarmuka internal dari antarmuka eksternal memungkinkan untuk menggunakannya tanpa masalah. ## Contoh kehidupan nyata Misalnya, mesin kopi. Simpelnya dari luar: tombol, layar, beberapa lubang ... dan tentunya, hasilnya -- kopi yang enak! :) ![](coffee.jpg) Tetapi di dalamnya... (gambar dari panduan perbaikan) ![](coffee-inside.jpg) Banyak detail. Tetapi kita bisa menggunakannya tanpa mengetahui apapun. Mesin kopi cukup andal, bukan? Kita dapat menggunakannya selama bertahun-tahun, dan jika hanya terjadi kesalahan -- diperbaiki. Rahasia keandalan dan kesederhanaan mesin kopi -- semua detail diatur dengan baik dan _tersembunyi_ di dalamnya. Jika kita melepas tutup pelindung dari mesin kopi, maka menggunakannya akan jauh lebih rumit (di mana harus menekan?), dan berbahaya (dapat menyetrum). Seperti yang akan kita lihat, dalam pemrograman objek itu seperti mesin kopi. Tetapi untuk menyembunyikan detail bagian dalam, kita tidak akan menggunakan tutup pelindung, melainkan sintaks khusus bahasa dan konvensi. ## Antarmuka internal dan eksternal Dalam pemrograman berorientasi objek, properti dan metode dibagi menjadi 2 kelompok: - _Antarmuka internal_ -- metode dan properti, dapat diakses dari metode kelas lainnya, tapi tidak dari luar. - _Antarmuka eksternal_ -- metode dan properti, dapat diakses juga dari luar kelas. Jika kita melanjutkan analogi dengan mesin kopi -- apa yang tersembunyi di dalam: tabung, elemen pemanas, dan sebagainya -- adalah antarmuka internalnya. Antarmuka internal digunakan agar objek berfungsi, detailnya saling menggunakan. Misalnya, tabung dipasang ke elemen pemanas. Tetapi dari luar mesin pembuat kopi ditutup oleh tutup pelindungnya, sehingga tidak ada yang bisa menjangkau itu. Detail disembunyikan dan tidak dapat diakses. Kita dapat menggunakan fitur-fiturnya melalui antarmuka eksternal. Jadi, yang kita butuhkan untuk menggunakan sebuah objek adalah mengetahui antarmuka eksternalnya. Kita mungkin sama sekali tidak menyadari cara kerjanya di dalam, dan itu bagus. Itu adalah perkenalan umum. Di JavaScript, ada dua jenis bidang objek (properti dan metode): - _Public_: dapat diakses dari mana saja. Mereka terdiri dari antarmuka eksternal. Sampai sekarang kita hanya menggunakan properti dan metode _public_. - _Private_: dapat diakses hanya dari dalam kelas. Ini untuk antarmuka internal. Di banyak bahasa lain juga ada bidang "protected": dapat diakses hanya dari dalam kelas dan mereka yang memperluasnya (seperti _private_, tetapi ditambah akses dari kelas yang diwariskan). Mereka juga berguna untuk antarmuka internal. Mereka dalam arti lebih luas daripada yang _private_, karena kita biasanya ingin mewarisi kelas untuk mendapatkan akses ke sana. Bidang _protected_ tidak diterapkan dalam JavaScript pada tingkat bahasa, tetapi dalam praktiknya mereka(_protected_) sangat nyaman, jadi mereka(_protected_) ditiru. Sekarang kita akan membuat mesin kopi di JavaScript dengan semua jenis properti ini. Mesin kopi memiliki banyak detail, kita tidak akan memodelkannya agar tetap sederhana (meskipun kita bisa). ## Melindungi "waterAmount" Mari kita buat kelas mesin kopi sederhana dulu: ```js run class CoffeeMachine { waterAmount = 0; // jumlah air di dalamnya constructor(power) { this.power = power; alert(`Created a coffee-machine, power: ${power}`); } } // membuat mesin kopi let coffeeMachine = new CoffeeMachine(100); // tambahkan air coffeeMachine.waterAmount = 200; ``` Sekarang properti `waterAmount` dan `power` adalah publik. Kita dapat dengan mudah mendapatkan/mengatur dari luar ke nilai apa pun. Mari ganti properti `waterAmount` menjadi _protected_ untuk lebih mengontrolnya. Misalnya, kita tidak ingin siapa pun mengaturnya di bawah nol. **Properti _protected_ biasanya diawali dengan garis bawah `_`.** Itu tidak diberlakukan pada level bahasa, tetapi ada konvensi terkenal antara pemrogram bahwa properti dan metode seperti itu tidak boleh diakses dari luar. Jadi properti kita akan dipanggil `_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; } } // membuat mesin kopi let coffeeMachine = new CoffeeMachine(100); // tambahkan air coffeeMachine.waterAmount = -10; // Error: Negative water ``` Sekarang aksesnya terkendali, jadi pengaturan air di bawah nol gagal. ## _Read-only_ "power" Untuk properti `power`, mari kita membuatnya menjadi _read-only_. Kadang-kadang terjadi bahwa properti harus diatur hanya pada waktu pembuatan, lalu tidak pernah diubah. Persis seperti itulah kasus mesin kopi: tenaga tidak pernah berubah. Untuk melakukannya, kita hanya perlu membuat pengambil(_getter_), bukan pengatur(_setter_): ```js run class CoffeeMachine { // ... constructor(power) { this._power = power; } get power() { return this._power; } } // membuat mesin kopi let coffeeMachine = new CoffeeMachine(100); alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W coffeeMachine.power = 25; // Error (no setter) ``` ````smart header="Getter/setter functions" Di sini kita menggunakan sintaks pengambil/pengatur. Tetapi seringkali fungsi `get.../set...` lebih disukai, seperti ini: ```js class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { if (value < 0) value = 0; this._waterAmount = value; } *!*getWaterAmount()*/!* { return this._waterAmount; } } new CoffeeMachine().setWaterAmount(100); ``` Itu terlihat sedikit lebih lama, tetapi fungsinya lebih fleksibel. Mereka dapat menerima banyak argumen (bahkan jika kita tidak membutuhkannya sekarang). Di sisi lain, sintaks get/set lebih pendek, jadi pada akhirnya tidak ada aturan ketat, terserah kamu yang memutuskan. ```` ```smart header="Protected fields are inherited" Jika kita mewarisi `class MegaMachine extends CoffeeMachine`, maka tidak ada yang menghalangi kita untuk mengakses` this._waterAmount` atau `this._power` dari metode kelas baru. Jadi bidang yang dilindungi tentu saja dapat diwariskan. Tidak seperti privat yang akan kita lihat di bawah. ``` ## Privat "#waterLimit" [recent browser=none] Ada proposal JavaScript yang sudah selesai, hampir dalam standar, yang memberikan dukungan tingkat bahasa untuk properti dan metode privat. Privat harus dimulai dengan `#`. Mereka hanya dapat diakses dari dalam kelas. Misalnya, ada properti privat `#waterLimit` dan metode privat pemeriksaan air `#checkWater`: ```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(); *!* // tidak dapat mengakses privat dari luar kelas coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error */!* ``` Pada level bahasa, `#` adalah tanda khusus bahwa bidang tersebut bersifat privat. Kita tidak dapat mengaksesnya dari luar atau dari kelas yang diwariskan. Bidang privat tidak bertentangan dengan bidang publik. Kita bisa memiliki bidang privat `#waterAmount` dan publik `waterAmount` pada saat yang sama. Misalnya, mari jadikan `waterAmount` sebagai pengakses untuk `#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 ``` Tidak seperti _protected_, bidang privat diberlakukan oleh bahasa itu sendiri. Itu hal yang bagus. Tetapi jika kita mewarisi dari `CoffeeMachine`, maka kita tidak akan memiliki akses langsung ke`#waterAmount`. Kita harus mengandalkan pengambil/pengatur `waterAmount`: ```js class MegaCoffeeMachine extends CoffeeMachine { method() { *!* alert( this.#waterAmount ); // Error: can only access from CoffeeMachine */!* } } ``` Dalam banyak skenario, batasan seperti itu terlalu parah. Jika kita memperluas `CoffeeMachine`, kita mungkin memiliki alasan yang sah untuk mengakses internalnya. Itulah mengapa bidang protected lebih sering digunakan, meskipun tidak didukung oleh sintaks bahasa. ````warn header="Private fields are not available as this[name]" Bidang privat itu istimewa. Seperti yang kita ketahui, biasanya kita bisa mengakses bidang menggunakan `this[name]`: ```js class User { ... sayHi() { let fieldName = "name"; alert(`Hello, ${*!*this[fieldName]*/!*}`); } } ``` Dengan bidang privat itu tidak mungkin: `this['#name']` tidak berfungsi. Itu adalah batasan sintaks untuk memastikan privasi. ```` ## Ringkasan Dalam istilah PBO (Pemrograman Berorientasi Objek), membatasi antarmuka internal dari antarmuka eksternal disebut [enkapsulasi](). Itu memberi manfaat sebagai berikut: Perlindungan bagi pengguna, agar mereka tidak menembak diri sendiri : Bayangkan, ada tim pengembang yang menggunakan mesin kopi. Itu dibuat oleh perusahaan "Mesin Kopi Terbaik", dan berfungsi dengan baik, tetapi penutup pelindungnya dilepas. Jadi antarmuka internal terekspos. Semua pengembang sopan -- mereka menggunakan mesin kopi sebagaimana mestinya. Tapi salah satu dari mereka, John, memutuskan bahwa dialah yang paling pintar, dan membuat beberapa perubahan di internal mesin kopi. Jadi mesin kopi mati dua hari kemudian. Itu jelas bukan salah John, melainkan orang yang melepas penutup pelindung dan membiarkan John melakukan manipulasinya. Hal yang sama dalam pemrograman. Jika pengguna kelas akan mengubah hal-hal yang tidak dimaksudkan untuk diubah dari luar -- konsekuensinya tidak dapat diprediksi. Didukung : Situasi dalam pemrograman lebih rumit daripada dengan mesin kopi di kehidupan nyata, karena kita tidak hanya membelinya sekali. Kode terus mengalami pengembangan dan peningkatan. **Jika kita membatasi secara ketat antarmuka internal, maka pengembang kelas dapat dengan bebas mengubah properti dan metode internalnya, bahkan tanpa memberi tahu pengguna.** Jika kamu adalah pengembang dari kelas seperti itu, senang mengetahui bahwa metode privat dapat diubah namanya dengan aman, parameternya dapat diubah, dan bahkan dihapus, karena tidak ada kode eksternal yang bergantung padanya. Untuk pengguna, ketika versi baru keluar, itu mungkin perombakan total secara internal, tetapi masih mudah untuk meningkatkan jika antarmuka eksternal sama. Menyembunyikan kerumitan : Orang suka menggunakan hal-hal yang sederhana. Setidaknya dari luar. Apa yang ada di dalamnya adalah hal yang berbeda. Pemrogram tidak terkecuali. **Selalu nyaman jika detail implementasi disembunyikan, dan tersedia antarmuka eksternal yang sederhana dan terdokumentasi dengan baik.** Untuk menyembunyikan antarmuka internal kita menggunakan properti _protected_ atau privat: - Bidang _protected_ dimulai dengan `_`. Itu konvensi yang terkenal, tidak diberlakukan di tingkat bahasa. Pemrogram hanya boleh mengakses bidang yang dimulai dengan `_` dari kelasnya dan kelas yang mewarisinya. - Bidang privat dimulai dengan `#`. JavaScript memastikan kita hanya dapat mengakses mereka dari dalam kelas. Saat ini, bidang privat tidak didukung dengan baik di antara browser, tetapi dapat di-polyfill. ================================================ FILE: 1-js/09-classes/05-extend-natives/article.md ================================================ # Meng-_extend_ `class` bawaan `class` bawaan seperti Array, Map dan lainnya juga dapat di-_extend_. Contohnya, `PowerArray` disini mewarisi dari _native_ `Array`: ```js run // menambahkan satu method ke dalam `PowerArray` (bisa lebih) 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 ``` Mohon perhatikan hal yang sangat menarik. _Method_ bawaan seperti `filter`, `map`, dan yang lainnya -- mengembalikan objek baru dengan tipe `PowerArray` yang diwariskan. Implementasi didalamnya menggunakan properti `constructor` untuk hal itu. Pada contoh di atas, ```js arr.constructor === PowerArray; ``` Saat `arr.filter()` dipanggil, secara internal itu membuat senarai hasil yang baru menggunakan `arr.constructor`, bukan _basic_ `Array`. Itu sebenarnya sangat menakjubkan, karena kita bisa tetap menggunakan `PowerArray` _method_ lebih jauh. Bahkan, kita bisa menyesuaikan perilaku khusus untuk itu. Kita bisa menambahkan _static getter_ `Symbol.species` ke dalam `class`. Jika ada, ia harus mengembalikan `constructor` yang akan JavaScript gunakan secara internal untuk membuat entitas baru di dalam `map`, `filter`, dan seterusnya. Jika kita ingin _method_ bawaan seperti `map` atau `filter` untuk mengembalikan senarai biasa, kita bisa mengembalikannya di dalam `Symbol.species`, seperti contohnya disini: ```js run class PowerArray extends Array { isEmpty() { return this.length === 0; } // method bawaan menggunakan ini sebagai `constructor` static get [Symbol.species]() { return Array; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false // `filter` membuat senarai baru `arr.constructor[Symbol.species]` sebagai `constructor` let filteredArr = arr.filter((item) => item >= 10); // filteredArr bukan PowerArray, tapi sebuah Array alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function ``` Seperti yang anda lihat, sekarang `.filter` mengembalikan `Array`. Jadi _function_ yang di-_extend_ tidak diteruskan. ```smart header="Other collections work similarly" Koleksi lain, seperti `Map` dan `Set`, berfungsi sama. Mereka juga menggunakan `Symbol.species`. ``` ## Tidak ada _static inheritance_ pada _built-in_ Objek bawaan memiliki _static method_-nya sendiri, misalnya `Object.keys`, `Array.isArray`, dll. Seperti yang kita tahu, _native class_ meng-_extend_ satu sama lain. Misalnya, `Array` meng-_extend_ `Object`. Biasanya, ketika sebuah `class` meng-_extend_ `class` yang lainnya, kedua _method static_ dan _non-static_ akan diwariskan. Itu dijelaskan sepenuhnya dalam artikel [](info:static-properties-methods#statics-and-inheritance). Tapi `class` bawaan adalah pengecualian. Mereka tidak mewarisi satu sama lain. Contohnya, `Array` dan `Date` mewarisi dari `Object`, sehingga _instance_ mereka memiliki _method_ dari `Object.prototype`. Tapi `Array.[[Prototype]]` tidak mereferensikan `Object`, sehingga tidak ada, semisalnya, `Array.keys()` (atau `Date.keys()`) _static method_. Berikut adalah struktur gambar untuk `Date` dan `Object`: ![](object-date-inheritance.svg) Seperti yang Anda lihat, tidak ada hubungan antara `Date` dan `Object`. Mereka independen, hanya `Date.prototype` mewarisi dari `Object.prototype`. Itu adalah perbedaan penting dari warisan antara objek bawaan dibandingkan dengan apa yang kita dapat dengan `extends`. ================================================ FILE: 1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md ================================================ Yah, memang terlihat aneh. Tetapi `instanceof` tidak peduli dengan fungsinya, melainkan tentang `prototype`, yang cocok dengan rantai prototipe. Dan di sini `a.__proto__ == B.prototype`, jadi `instanceof` mengembalikan `true`. Jadi, dengan menggunakan logika `instanceof`, `prototype` sebenarnya mendefinisikan tipe, bukan fungsi konstruktor. ================================================ FILE: 1-js/09-classes/06-instanceof/1-strange-instanceof/task.md ================================================ importance: 5 --- # "instanceof" Aneh Pada kode dibawah, kenapa `instanceof` mengembalikan `true`? Dengan jelas kita dapat melihat `a` tidak dibuat oleh `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 ================================================ # Pengecekan kelas: "instanceof" Operator `instanceof` memungkinkan kita untuk memeriksa apakah suatu _object_ milik _class_ tertentu. `instanceof` juga memperhatikan _inheritance_. Pengecekan seperti itu mungkin diperlukan dalam beberapa kasus. Di sini kita akan menggunakannya untuk membangun fungsi *polymorphic*, yang memperlakukan argumen secara berbeda bergantung pada tipenya. ## Operator instanceof [#ref-instanceof] Sintaksnya adalah: ```js obj instanceof Class ``` Akan mengembalikan `true` jika `obj` dimiliki oleh `Class` atau kelas turunannya. Misalnya: ```js run class Rabbit {} let rabbit = new Rabbit(); // apakah ini merupakan object dari kelas Rabbit? *!* alert( rabbit instanceof Rabbit ); // true */!* ``` Itu juga bekerja dengan fungsi _constructor_: ```js run *!* // dari pada class function Rabbit() {} */!* alert( new Rabbit() instanceof Rabbit ); // true ``` ...Dan kelas bawaan seperti `Array`: ```js run let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true ``` Harap dicatat bahwa `arr` juga termasuk dalam kelas `Object`. Itu karena `Array` secara prototipikal mewarisi dari` Object`. Normalnya, `instanceof` memeriksa rantai _prototype_ untuk pemeriksaan. Kita juga dapat menyetel _custom logic_ dalam _static method_ `Symbol.hasInstance`. Algoritma `obj instanceof Class` bekerja kurang lebih sebgai berikut: 1. Jika terdapat _static method_ `Symbol.hasInstance`, maka panggil saja: `Class[Symbol.hasInstance](obj)`. Itu akan mengembalikan `true` atau `false`, selesai. Begitulah cara kita bisa menyesuaikan perilaku dari `instanceof`. Sebagai contoh: ```js run // menyiapkan instanceOf yang berasumsi // apapun yang memiliki properti canEat adalah binatang class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) dipanggil ``` 2. Kebanyakan kelas tidak memiliki `Symbol.hasInstance`. Dalam kasus ini, logika standar digunakan: `obj instanceOf Class` Memeriksa apakah `Class.prototype` sama dengan salah satu _prototype_ dalam rantai _prototype_`obj` . Dengan kata lain, bandingkan satu sama lain: ```js obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // jika jawabannya true, return true // jika tidak, jika kita mencapai ujung rantai, return false ``` Pada contoh diatas `rabbit.__proto__ === Rabbit.prototype`, sehingga akan memberikan jawaban segera. Dalam kasus _inheritance_, kesamaan akan berada pada langkah kedua: ```js run class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); *!* alert(rabbit instanceof Animal); // true */!* // rabbit.__proto__ === Rabbit.prototype *!* // rabbit.__proto__.__proto__ === Animal.prototype (match!) */!* ``` Berikut ilustrasi tentang perbandingan `rabbit instanceof Animal` dengan `Animal.prototype`: ![](instanceof.svg) Omong-omong, terdapat juga _method_ [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), yang mengembalikan `true` jika `objA` berada di suatu tempat dalam rantai _prototypes_ untuk `objB`. Jadi pengujian `obj instanceof Class` bisa dirumuskan sebagai `Class.prototype.isPrototypeOf(obj)`. Ini lucu, tetapi _constructor_ `Class` itu sendiri tidak ikut dalam pemeriksaan! Hanya rangkaian dari _prototype_ dan `Class.prototype` yang penting. Itu bisa menimbulkan konsekuensi yang menarik ketika properti `prototype` diubah setelah objek dibuat. Seperti: ```js run function Rabbit() {} let rabbit = new Rabbit(); // mengubah prototype Rabbit.prototype = {}; // ...bukan kelinci lagi! *!* alert( rabbit instanceof Rabbit ); // false */!* ``` ## Bonus: Object.prototype.toString untuk tipe Kita tahu bahwa _plain object_ akan diubah menjadi _string_ sebagai `[object Object]`: ```js run let obj = {}; alert(obj); // [object Object] alert(obj.toString()); // sama ``` Itulah implementasi dari `toString`. Tetapi ada fitur tersembunyi yang membuat `toString` menjadi lebih _powerful_ dari itu. Kita bisa menggunakannya sebagai perluasan dari `typeof` dan alternatif untuk `instanceof`. Terdengar aneh? Tentu. Mari kita cari tahu. Dengan [spesifikasi](https://tc39.github.io/ecma262/#sec-object.prototype.tostring), `toString` bawaan dapat diekstrak dari object dan dijalankan dalam konteks nilai lainnya. Dan hasilnya tergantung pada nilai tersebut. - Untuk angka, akan menjadi `[object Number]` - Untuk _boolean_, akan menjadi `[object Boolean]` - Untuk `null`: `[object Null]` - Untuk `undefined`: `[object Undefined]` - Untuk _array_: `[object Array]` - ...dll (dapat disesuaikan). Mari kita tunjukkan: ```js run // copy toString method kedalam sebuah variabel let objectToString = Object.prototype.toString; // tipe apa ini? let arr = []; alert( objectToString.call(arr) ); // [object *!*Array*/!*] ``` Disini kita gunakan [_call_](mdn:js/function/call) seperti dijelaskan dalam bab [](info:call-apply-decorators) untuk menjalankan fungsi `objectToString` dalam konteks `this=arr`. Secara internal, algoritme `toString` memeriksa `this` dan mengembalikan hasil yang sesuai. Contoh lainnya: ```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 Perilaku object `toString` dapat disesuaikan dengan menggunakan properti objek khusus `Symbol.toStringTag`. Sebagai contoh: ```js run let user = { [Symbol.toStringTag]: "User" }; alert( {}.toString.call(user) ); // [object User] ``` Untuk sebagian besar objek yang spesifik pada _environment_, terdapat properti seperti itu. Berikut beberapa contoh untuk browser yang spesifik: ```js run // toStringTag untuk objek dan kelas yang spesifik pada environtment: alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest] ``` Dapat dilihat, hasilnya persis `Symbol.toStringTag` (jika ada), digabungkan ke dalam `[object ...]`. Pada akhirnya kami memiliki _"typeof on steroids"_ yang tidak hanya akan berfungsi untuk tipe data primitif, tetapi juga untuk objek bawaan dan bahkan dapat disesuaikan. Kita dapat menggunakan `{}.toString.call` daripada `instanceof` untuk objek bawaan ketika ingin mendapatkan tipe sebagai string daripada hanya untuk diperiksa. ## Ringkasan Mari kita rangkum metode pengecekan tipe yang kita ketahui: | | bekerja pada | mengembalikan | |---------------|-------------|---------------| | `typeof` | primitif | string | | `{}.toString` | primitif, objek bawaan, objek dengan `Symbol.toStringTag` | string | | `instanceof` | objek | true/false | Dapat kita lihat, `{}.toString` secara teknis "lebih _advanced_" `typeof`. Dan operator `instanceof` akan lebih berguna ketika kita bekerja dengan hirearki kelas dan ingin memeriksa kelas yang memperhatikan _inheritance_. ================================================ FILE: 1-js/09-classes/07-mixins/article.md ================================================ # _Mixins_ Dalam JavaScript kita hanya dapat mewarisi dari satu objek. Hanya ada satu `[[Prototype]]` untuk sebuah objek. Dan sebuah kelas hanya dapat memperluas satu kelas lainnya. Tapi terkadang hal itu terasa membatasi. Misalnya, kita memiliki kelas `StreetSweeper` dan kelas `Bicycle`, dan ingin membuat campurannya: `StreetSweepingBicycle`. Atau kita memiliki kelas `User` dan kelas `EventEmitter` yang mengimplementasikan pembuatan peristiwa, dan kita ingin menambahkan fungsionalitas `EventEmitter` ke `User`, sehingga pengguna kita dapat memancarkan peristiwa. Ada konsep yang bisa membantu di sini, yang disebut "mixin". Seperti yang didefinisikan di Wikipedia, [_mixin_](https://en.wikipedia.org/wiki/Mixin) adalah kelas yang berisi metode yang dapat digunakan oleh kelas lain tanpa perlu mewarisinya. Dengan kata lain, _mixin_ menyediakan metode yang mengimplementasikan perilaku tertentu, tetapi kami tidak menggunakannya sendiri, kita menggunakannya untuk menambahkan perilaku ke kelas lain. ## Contoh _mixin_ Cara termudah untuk mengimplementasikan _mixin_ dalam JavaScript adalah membuat objek dengan metode yang berguna, sehingga kita dapat dengan mudah menggabungkannya menjadi prototipe kelas apa pun. Misalnya di sini mixin `sayHiMixin` digunakan untuk menambahkan beberapa "ucapan" untuk `User`: ```js run *!* // mixin */!* let sayHiMixin = { sayHi() { alert(`Hello ${this.name}`); }, sayBye() { alert(`Bye ${this.name}`); } }; *!* // penggunaan: */!* class User { constructor(name) { this.name = name; } } // salin metodenya Object.assign(User.prototype, sayHiMixin); // sekarang User dapat berkata hi new User("Dude").sayHi(); // Hello Dude! ``` Tidak ada warisan, tetapi penyalinan metode sederhana. Jadi, `User` dapat mewarisi dari kelas lain dan juga menyertakan _mixin_ untuk "mencampur" metode tambahan, seperti ini: ```js class User extends Person { // ... } Object.assign(User.prototype, sayHiMixin); ``` _Mixin_ dapat memanfaatkan warisan di dalam dirinya sendiri. Misalnya, di sini `sayHiMixin` mewarisi dari `sayMixin`: ```js run let sayMixin = { say(phrase) { alert(phrase); } }; let sayHiMixin = { __proto__: sayMixin, // (atau kita bisa gunakan Object.create untuk mengatur prototipe di sini) sayHi() { *!* // panggil metode induk */!* super.say(`Hello ${this.name}`); // (*) }, sayBye() { super.say(`Bye ${this.name}`); // (*) } }; class User { constructor(name) { this.name = name; } } // salin metodenya Object.assign(User.prototype, sayHiMixin); // sekarang User dapat berkata hi new User("Dude").sayHi(); // Hello Dude! ``` Perhatikan bahwa panggilan ke metode induk `super.say()` dari `sayHiMixin` (pada baris berlabel `(*)`) mencari metode dalam prototipe _mixin_ tersebut, bukan kelasnya. Berikut diagramnya (lihat bagian kanan): ![](mixin-inheritance.svg) Itu karena metode `sayHi` dan `sayBye` awalnya dibuat di `sayHiMixin`. Jadi, meskipun disalin, properti internal `[[HomeObject]]` mereferensikan `sayHiMixin`, seperti yang ditunjukkan pada gambar di atas. Karena `super` mencari metode induk di `[[HomeObject]].[[Prototype]]`, itu berarti mencari `sayHiMixin.[[Prototype]]`, bukan `User.[[Prototype]]`. ## Peristiwa _Mixin_ Sekarang mari kita membuat _mixin_ untuk kehidupan nyata. Fitur penting dari banyak objek browser (misalnya) adalah mereka dapat menghasilkan peristiwa. Peristiwa adalah cara terbaik untuk "menyiarkan informasi" kepada siapa pun yang menginginkannya. Jadi mari kita membuat _mixin_ yang memungkinkan kita dengan mudah menambahkan fungsi terkait peristiwa ke kelas/objek apa pun. - _Mixin_ akan menyediakan metode `.trigger(name, [...data])` untuk "menghasilkan peristiwa" ketika sesuatu yang penting terjadi padanya. Argumen `name` adalah nama peristiwa, secara opsional diikuti oleh argumen tambahan dengan data peristiwa. - Juga metode `.on(name, handler)` yang menambahkan fungsi `handler` sebagai pendengar peristiwa dengan nama yang diberikan. Ini akan dipanggil ketika sebuah peristiwa dengan `name` yang diberikan terpicu, dan mendapatkan argumen dari panggilan `.trigger`. - ...Dan metode `.off(name, handler)` yang menghapus pendengar `handler`. Setelah menambahkan _mixin_, sebuah objek `user` akan dapat membuat peristiwa `"login"` saat pengunjung masuk. Dan objek lain, katakanlah, `calendar` mungkin ingin mendengarkan peristiwa seperti itu untuk memuat kalender bagi orang yang masuk. Atau, `menu` dapat menghasilkan peristiwa `"select"` ketika item menu dipilih, dan objek lain dapat menetapkan penangan untuk bereaksi pada peristiwa itu. Dan seterusnya. Berikut kodenya: ```js run let eventMixin = { /** * Berlangganan ke peristiwa, menggunakan: * 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); }, /** * Membatalkan langganan, menggunakan: * 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); } } }, /** * menghasilkan peristiwa dengan nama dan data yang diberikan * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers?.[eventName]) { return; // tidak ada penangan untuk nama peristiwa itu } // panggil penangannya this._eventHandlers[eventName].forEach((handler) => handler.apply(this, args) ); }, }; ``` - `.on(eventName, handler)` -- menetapkan fungsi `handler` untuk dijalankan ketika peristiwa dengan nama itu terjadi. Secara teknis, ada properti `_eventHandlers` yang menyimpan senarai penangan untuk setiap nama peristiwa, dan itu hanya menambahkannya ke daftar. - `.off(eventName, handler)` -- menghapus fungsi dari daftar penangan. - `.trigger(eventName, ...args)` -- menghasilkan peristiwa: semua penangan dari `_eventHandlers[eventName]` dipanggil, dengan daftar argumen `...args`. Penggunaan: ```js run // Membuat kelas class Menu { choose(value) { this.trigger("select", value); } } // Tambahkan mixin dengan metode terkait peristiwa Object.assign(Menu.prototype, eventMixin); let menu = new Menu(); // tambahkan penangan, untuk dipanggil saat seleksi: *!* menu.on("select", value => alert(`Value selected: ${value}`)); */!* // memicu peristiwa => penangan di atas berjalan dan menunjukkan: // Value selected: 123 menu.choose("123"); ``` Sekarang, jika kita ingin kode apa pun bereaksi terhadap pilihan menu, kita dapat mendengarkannya dengan `menu.on(...)`. Dan _mixin_ `eventMixin` membuatnya mudah untuk menambahkan perilaku tersebut ke sebanyak mungkin kelas yang kita inginkan, tanpa mengganggu rantai pewarisan. ## Ringkasan _Mixin_ -- adalah istilah umum pemrograman berorientasi objek: kelas yang berisi metode untuk kelas lain. Beberapa bahasa lain mengizinkan banyak pewarisan. JavaScript tidak mendukung banyak pewarisan, tetapi _mixin_ dapat diimplementasikan dengan menyalin metode ke dalam prototipe. Kita bisa menggunakan _mixin_ sebagai cara untuk menambah kelas dengan menambahkan beberapa perilaku, seperti penanganan peristiwa seperti yang telah kita lihat di atas. _Mixin_ dapat menjadi titik konflik jika secara tidak sengaja menimpa metode kelas yang ada. Jadi umumnya orang harus berpikir dengan baik tentang metode penamaan _mixin_, untuk meminimalkan kemungkinan hal itu terjadi. ================================================ FILE: 1-js/09-classes/07-mixins/head.html ================================================ ================================================ FILE: 1-js/09-classes/index.md ================================================ # Kelas ================================================ FILE: 1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md ================================================ Perbedaannya menjadi jelas ketika kita melihat kode di dalam suatu fungsi. Perilakunya berbeda jika ada lompatan keluar dari `try..catch`. Misalnya, ketika ada `return` di dalam` try..catch`. Klausa `finally` berfungsi jika *ada* ya keluar dari` try..catch`, bahkan melalui pernyataan `return`: tepat setelah` try..catch` selesai, tetapi sebelum kode pemanggil mendapatkan kontrol. ```js run function f() { try { alert('start'); *!* return "result"; */!* } catch (err) { /// ... } finally { alert('cleanup!'); } } f(); // cleanup! ``` .. Atau ketika ada `throw`, seperti di sini: ```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! ``` Bagian `finally` yang menjamin proses pembersihan di sini. Jika kita hanya meletakkan kode di akhir `f`, itu tidak akan berjalan dalam situasi ini. ================================================ FILE: 1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md ================================================ Nilai Penting: 5 --- # Finally atau hanya kode biasa? Bandingkan dua fragmen kode. 1. Yang pertama menggunakan `finally` untuk mengeksekusi kode setelah` try..catch`: ```js try { work work } catch (err) { handle errors } finally { *!* cleanup the working space */!* } ``` 2. Fragmen kedua melakukan pembersihan tepat setelah `try..catch`: ```js try { work work } catch (err) { handle errors } *!* cleanup the working space */!* ``` Kita pasti membutuhkan pembersihan setelah pekerjaan, tidak masalah apakah ada kesalahan atau tidak. Apakah ada keuntungan di sini dalam menggunakan `finally` atau kedua fragmen kode sama? Jika ada keuntungan seperti itu, berikan contoh ketika itu penting. ================================================ FILE: 1-js/10-error-handling/1-try-catch/article.md ================================================ # Penanganan eror, "try..catch" Tidak peduli seberapa hebatnya kita dalam pemograman, terkadang kodingan kita memiliki banyak eror. Mereka mungkin muncul dikarenakan kesalahan kita, input dari user yang tidak terduga, eror respon dari server, dan juga berbagai macam alasan lainnya. Biasanya, sebuah kodingan/scrip "terhenti" (tiba-tiba berhenti) dikarenakan adanya eror, menampilkan erornya pada console. Tapi terdapat sebuah sintaks `try..catch` yang memperbolehkan kita untuk "menangkap" hasil eror sehingga skrip bisa berjalan sesuai dengan arahan kita, dibanding hanya berhenti saja. ## Sintaks "try..catch" Sintaks `try..catch` membentuk dua bagian utama: pertama `try`, dan kemudian `catch`: ```js try { // kodingan disini } catch (err) { // Penanganan jika eror } ``` Mereka akan bekerja seperti ini: 1. Pertama, kodingan pada `try {...}` akan dijalankan. 2. Jika tidak terdapat eror, maka `catch(err)` akan dihiraukan: prosesnya akan mencapai ujung bagian `try` dan kemudian berlanjut, melewati bagian `catch`. 3. Jika terdapat eror, maka bagian `try` akan berhenti berjalan, dan alur prosesnya akan berlanjut pada awal bagian `catch(err)`. Variabel `err` (yang mana kita bisa ganti dengan nama apapun) akan mengandung eror objek dengan keterangan eror didalamnya. ![](try-catch-flow.svg) Jadi, sebuah eror didalam bagian `try {…}` tidak akan memberhentikan kodingan tersebut -- kita memiliki sebuah kesempatan untuk menanganinya pada bagian `catch`. Mari kita lihat contoh lainnya. - Sebuah contoh tanpa eror: menampilkan `alert` `(1)` dan `(2)`: ```js run try { alert('Start of try runs'); // *!*(1) <--*/!* // ...tidak ada eror disini alert('End of try runs'); // *!*(2) <--*/!* } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) } ``` - Sebuah contoh dengan eror: shows `(1)` dan `(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` hanya akan bekerja pada eror runtime" Untuk `try..catch` agar bekerja, kodingan tersebut harus bisa dijalankan. Dengan artian lain, itu harus dalam bahasa javascript yang valid. Mereka tidak akan bekerja jika kodingan tersebut secara sintaks salah, sebagai contoh jika mereka memiliki kurung kurawal yang tidak sama: ```js run try { {{{{{{{{{{{{ } catch (err) { alert("The engine can't understand this code, it's invalid"); } ``` Mesin Javascript pertama membaca kodingan tersebut, dan menjalankannya. eror yang terjadi pada saat proses pembacaan disebut sebagai eror "parse-time" dan tidak dapat dipulihkan (dari dalam kodingan tersebut). Itu dikarenakan mesin javascript tidak mengerti kodingan . Jadi, `try..catch` hanya dapat menangani eror yang terjadi pada kodingan yang valid. eror demikian biasanya dinamakan sebagai "eror runtime" atau terkadang, "exceptions". ```` ````warn header="`try..catch` bekerja secara sinkronis" Jika sebuah exception terjadi pada kodingan yang "terjadwal", seperti pada `setTimeout`, maka `try..catch` tidak akan menangkapnya: ```js run try { setTimeout(function() { noSuchVariable; // kodingan akan berhenti disini }, 1000); } catch (err) { alert( "won't work" ); } ``` Itu dikarenakan fungsi tersebut dijalankan nanti, ketika mesin javascript telah meninggalkan bagian pada `try..catch`. Untuk menangkap sebuah exception didalam sebuah fungsi yang terjadwal, `try..catch` harus terjadi didalam fungsi tersebut: ```js run setTimeout(function() { try { noSuchVariable; // try..catch menangani eror tersebut! } catch { alert( "error is caught here!" ); } }, 1000); ``` ```` ## Eror Objek Ketika sebuah eror terjadi, Javascript menghasilkan sebuah ojek yang berisikan keterangan terkait eror tersebut. Objek itu kemudian dilewatkan sebagai sebuah argumen pada bagian `catch`: ```js try { // ... } catch(err) { // <-- "error object", bisa menggunakan kata lain selain err // ... } ``` Untuk semua eror bawaan, eror objek memiliki dua properti utama: `name` : Nama error. sebagai contoh, untuk variable yang belum terdefinisikan maka itu disebut `"ReferenceError"`. `message` : Pesan yang ada didalam eror tersebut. Terdapat properti non-standard lainnya pada kebanyakan Ada properti non-standar lain yang tersedia di sebagian besar lingkungan. Salah satu yang paling banyak digunakan dan didukung ialah: `stack` : Call stack saat ini: string dengan informasi tentang urutan panggilan bertingkat yang menyebabkan kesalahan. Digunakan untuk tujuan debugging. Sebagai contoh: ```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) // Can also show an error as a whole // The error is converted to string as "name: message" alert(err); // ReferenceError: lalala is not defined } ``` ## "Catch" binding Opsional [recent browser=new] Jika kita tidak butuh detail tentang eror, `catch` mungkin bisa menghilangkannya: ```js try { // ... } catch { // <-- tanpa (err) // ... } ``` ## Menggunakan "try..catch" Mari kita telusuri contoh penggunaan nyata dari `try..catch`. Seperti yang telah kita ketahui, Javascript mendukung method [JSON.parse(str)](mdn:js/JSON/parse) yang membaca dari nilai JSON-encoded. Biasanya digunakan untuk memecahkan kode data yang diterima melalui jaringan, dari server atau sumber lain. Kita menerimanya dan memanggil `JSON.parse` seperti ini: ```js run let json = '{"name":"John", "age": 30}'; // data dari server *!* let user = JSON.parse(json); // mengonversi representasi teks ke objek JS */!* // sekarang pengguna adalah objek dengan properti dari string alert( user.name ); // John alert( user.age ); // 30 ``` Kalian dapat menemukan informasi lebih detail tentang JSON di bab . **Jika format `json` salah,` JSON.parse` menghasilkan error, sehingga skrip "mati".** Cukupkah kita puas dengan itu? Tentu saja tidak! Dengan cara ini, jika ada yang salah dengan datanya, pengunjung tidak akan pernah mengetahuinya (kecuali mereka membuka konsol pengembang). Dan orang biasanya tidak suka ketika sesuatu "berhenti begitu saja" tanpa ada pesan kesalahan. Mari gunakan `try..catch` untuk menangani kesalahan: ```js run let json = "{ bad json }"; try { *!* let user = JSON.parse(json); // <-- ketika terjadi sebuah eror... */!* alert( user.name ); // tidak berjalan } catch (err) { *!* // ...proses eksekusinya akan lompat kesini alert( "Our apologies, the data has errors, we'll try to request it one more time." ); alert( err.name ); alert( err.message ); */!* } ``` Di sini kita menggunakan blok `catch` hanya untuk menampilkan pesan, tetapi kita dapat melakukan lebih banyak lagi: mengirim permintaan jaringan baru, menyarankan alternatif kepada pengunjung, mengirim informasi tentang kesalahan ke fasilitas logging, .... Semuanya jauh lebih baik daripada sekedar mati. ## Melontarkan eror kita sendiri Bagaimana jika `json` secara sintaksis benar, tetapi tidak memiliki properti` name` yang diperlukan? Seperti ini: ```js run let json = '{ "age": 30 }'; // data tidak lengkap try { let user = JSON.parse(json); // <-- tidak ada eror *!* alert( user.name ); // tidak ada nama! */!* } catch (err) { alert( "doesn't execute" ); } ``` Di sini `JSON.parse` berjalan normal, tetapi tidak adanya` nama` sebenarnya merupakan eror bagi kita. Untuk menyatukan penanganan error, kita akan menggunakan operator `throw`. ### Operator "Throw" Operator `throw` menghasilkan sebuah eror. Sintaksnya adalah: ```js throw ``` Secara teknis, kita dapat menggunakan apapun sebagai eror objek. Technically, we can use anything as an error object. Itu bahkan mungkin dengan data primitif, seperti angka atau string, lebih disarankan menggunakan objek, dan juga dengan properti `name` dan` message` (agar tetap kompatibel dengan error bawaan). JavaScript memiliki banyak konstruktor bawaan untuk standar eror: `Error`, `SyntaxError`,` ReferenceError`, `TypeError` dan lain-lain. Kita bisa menggunakannya untuk membuat eror objek juga. Sintaksnya adalah: ```js let error = new Error(message); // or let error = new SyntaxError(message); let error = new ReferenceError(message); // ... ``` Untuk eror bawaan (bukan untuk objek lainnya, hanya untuk eror), properti `name` persis dengan nama konstruktornya. Dan `pesan` diambil dari argumennya. Sebagai contoh: ```js run let error = new Error("Things happen o_O"); alert(error.name); // Error alert(error.message); // Things happen o_O ``` Mari kita lihat jenis kesalahan apa yang dihasilkan `JSON.parse`: ```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 } ``` Seperti yang bisa kita lihat, itu adalah `SyntaxError`. Dan dalam kasus ini, tidak adanya `nama` adalah sebuah eror, karena pengguna harus memiliki` nama`. Jadi mari kita tampung pada bagian throw: ```js run let json = '{ "age": 30 }'; // data tidak lengkap try { let user = JSON.parse(json); // <-- tidak ada eror 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 } ``` Di baris `(*)`, operator `throw` menghasilkan` SyntaxError` dengan `message` yang diberikan, sama seperti JavaScript akan menjalankannya sendiri. Proses eksekusi `try ` langsung berhenti dan alur kontrol melompat ke bagian ` catch`. Sekarang `catch` menjadi satu tempat untuk semua penanganan error: baik untuk` JSON.parse` dan kasus lainnya. ## Rethrowing Pada contoh di atas kami menggunakan `try..catch` untuk menangani data yang salah. Tetapi apakah mungkin terjadi kesalahan tak terduga lainnya dalam blok `try {...}`? Seperti kesalahan pemrograman (variabel tidak terdefinisi) atau sesuatu yang lain, bukan hanya hal terkait "data yang salah " ini. Sebagai contoh: ```js run let json = '{ "age": 30 }'; // data tidak lengkap try { user = JSON.parse(json); // <-- lupa meletakkan "let" sebelum user // ... } catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (no JSON Error actually) } ``` Tentu saja, semuanya bisa! Pemrogram memang membuat kesalahan. Bahkan dalam projek pembantu pada sumber terbuka(open source) yang digunakan oleh jutaan orang selama beberapa dekade - tiba-tiba bug dapat ditemukan yang mengarah ke peretasan yang mengerikan. Dalam kasus kita, `try..catch` ditempatkan untuk menangkap eror `"data yang tidak valid "`. Tetapi pada dasarnya, `catch` mendapatkan *semua* error dari` try`. Di sini ia mendapat eror yang tak terduga, tetapi masih menampilkan pesan `"JSON Error"` yang sama. Itu salah dan juga membuat kode lebih sulit untuk di-debug. Untuk menghindari masalah seperti itu, kita dapat menggunakan teknik "rethrowing". Aturannya sederhana: **Proses throwing hanya akan memproses kesalahan yang diketahui dan "melempar ulang / rethrowing" yang lainnya.** Teknik "rethrowing" dapat dijelaskan lebih detail sebagai: 1. Catch mendapatkan semua eror. 2. Dalam blok `catch (err) {...}` kita menganalisis eror objek `err`. 3. Jika kita tidak tahu bagaimana menanganinya, kita melakukan `throw err`. Biasanya, kita dapat memeriksa jenis erornya menggunakan operator `instanceof`: ```js run try { user = { /*...*/ }; } catch (err) { *!* if (err instanceof ReferenceError) { */!* alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable } } ``` Kita juga bisa mendapatkan nama kelas eror dari properti `err.name`. Semua eror bawaan memilikinya. Pilihan lainnya adalah membaca `err.constructor.name`. Pada kode di bawah ini, kita menggunakan teknik rethrowing sehingga `catch` hanya menangani` SyntaxError`: ```js run let json = '{ "age": 30 }'; // incomplete data try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("Incomplete data: no name"); } *!* blabla(); // unexpected error */!* alert( user.name ); } catch (err) { *!* if (err instanceof SyntaxError) { alert( "JSON Error: " + err.message ); } else { throw err; // rethrow (*) } */!* } ``` Eror saat yang dilempar pada baris `(*)` dari dalam blok `catch` "jatuh" dari` try..catch` dan dapat ditangkap oleh bagian luar `try..catch`(jika ada), atau itu memberhentikan kodingannya. Jadi, blok `catch` sebenarnya hanya menangani error yang tahu cara penanganannya dan "melewatkan" semua error lainnya. Contoh di bawah ini menunjukkan bagaimana eror tersebut dapat ditangkap oleh satu level lagi dari blok `try..catch`: ```js run function readData() { let json = '{ "age": 30 }'; try { // ... *!* blabla(); // error! */!* } catch (err) { // ... if (!(err instanceof SyntaxError)) { *!* throw e; // rethrow (tidak tahu bagaimana cara menanganinya) */!* } } } try { readData(); } catch (err) { *!* alert( "External catch got: " + e ); // menangkapnya! */!* } ``` Di sini `readData` hanya mengetahui cara menangani` SyntaxError`, sedangkan bagian `try..catch` luar mengetahui cara menangani semuanya. ## try...catch...finally Tunggu, itu belum semuanya. Blok `try..catch` mungkin memiliki satu klausa kode lagi yaitu:`finally`. Jika ada, maka itu akan berjalan di semua kasus: - setelah `try`, jika tidak ada eror, - setelah `catch`, jika ada error. Contoh sintaks lengkapnya seperti ini: ```js *!*try*/!* { ... try to execute the code ... } *!*catch*/!* (err) { ... handle errors ... } *!*finally*/!* { ... execute always ... } ``` Try running this code: ```js run try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); } catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); } ``` Kode tersebut memiliki dua cara eksekusi: 1. Jika kalian menjawab "Ya" untuk "Membuat eror?", maka proses eksekusinya akan jadi seperti ini `try -> catch -> finally`. 2. Jika kalian mengatakan "Tidak", maka proses nya akan seperti ini `try -> finally`. Klausa `finally` sering digunakan ketika kita mulai melakukan sesuatu dan ingin menyelesaikannya dalam kasus apa pun hasilnya. Misalnya, kami ingin mengukur waktu yang dibutuhkan oleh fungsi bilangan Fibonacci `fib (n)`. Secara alami, kita dapat mulai mengukur sebelum berlari dan menyelesaikannya setelahnya. Tetapi bagaimana jika ada kesalahan selama pemanggilan fungsi? Secara khusus, implementasi `fib (n)` dalam kode di bawah ini mengembalikan eror untuk bilangan negatif atau non-integer. Klausa `finally` adalah tempat yang tepat untuk menyelesaikan pengukuran apa pun yang terjadi. Di sini `finally` menjamin bahwa waktu akan diukur dengan benar dalam kedua situasi - jika eksekusi` fib` berhasil ataupun jika terjadi kesalahan di dalamnya: ```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` ); ``` Kalian dapat memeriksa dengan menjalankan kode dengan memasukkan `35` ke dalam` prompt` - ini dijalankan secara normal, `finally` setelah` try`. Dan kemudian masukkan `-1` - akan ada eror langsung, dan eksekusi akan memakan waktu` 0ms`. Kedua pengukuran tersebut dilakukan dengan benar. Dengan kata lain, fungsi tersebut mungkin diakhiri dengan `return` atau `throw`, itu tidak masalah. Klausa `finally` dijalankan di kedua kasus. ```smart header="Variables are local inside `try..catch..finally`" Tolong diperhatikan bahwa variabel `result` dan `diff` pada kode di atas dideklarasikan *sebelum* `try..catch`. Sebaliknya, jika kita mendeklarasikan `let` di blok` try`, itu hanya akan terlihat di dalamnya. ``` ````smart header="`finally` and `return`" Klausa `finally` berfungsi untuk *apa saja* yang keluar dari` try..catch`. Itu termasuk `return` eksplisit. Pada contoh di bawah ini, terdapat `return` dalam `try`. Dalam kasus ini, `finally` dijalankan tepat sebelum kontrol kembali ke kode luar. ```js run function func() { try { *!* return 1; */!* } catch (err) { /* ... */ } finally { *!* alert( 'finally' ); */!* } } alert( func() ); //pertama alert bekerja dari finally, dan kemudian yang satu ini ``` ```` ````smart header="`try...finally`" Bagian `try..finally`, tanpa klausa` catch`, juga berguna. Kita menerapkannya ketika kita tidak ingin menangani eror di sini (biarkan eror itu terjadi), tetapi ingin memastikan bahwa proses yang kita mulai sudah selesai. ```js function func() { // mulai melakukan sesuatu yang perlu diselesaikan (seperti pengukuran) try { // ... } finally { // selesaikan itu bahkan jika semuanya berhenti } } ``` Pada kode di atas, eror di dalam `try` selalu terjadi, karena tidak ada `catch`. Tapi `finally` berfungsi sebelum aliran eksekusi meninggalkan fungsinya. ```` ## Catch Global ```warn header="Environment-specific" Informasi dari bagian ini bukan merupakan bagian dari inti JavaScript. ``` Bayangkan kita mendapatkan eror yang fatal di luar `try..catch`, dan kodingannya mati. Seperti eror pemrograman atau hal buruk lainnya. Adakah cara untuk bereaksi atas kejadian seperti itu? kita mungkin ingin mencatat kesalahan, menunjukkan sesuatu kepada pengguna (biasanya mereka tidak melihat pesan eror), dll. Tidak ada dalam spesifikasinya, tapi dilingkungan tempat itu bekerja biasanya menyediakannya, karena sangat berguna. Misalnya, Node.js memiliki [`process.on (" uncaughtException ")`] (https://nodejs.org/api/process.html#process_event_uncaughtexception) untuk itu. Dan pada browser kita dapat menetapkan fungsi ke properti khusus [window.onerror] (mdn: api / GlobalEventHandlers / onerror), yang akan berjalan jika terjadi kesalahan yang tidak tertangkap. Sintaksnya adalah: ```js window.onerror = function(message, url, line, col, error) { // ... }; ``` `message` : Pesan eror. `url` : URL pada kodingan tempat kesalahan terjadi. `line`, `col` : Nomor baris dan kolom ditempat terjadinya kesalahan. `error` : Eror objek Sebagai contoh: ```html run untrusted refresh height=1 ``` Peran dari global handler `window.onerror` biasanya bukan untuk memulihkan eksekusi dari kodingannya - itu biasanya tidak mungkin jika terjadi kesalahan pemrograman, namun tugasnya adalah untuk mengirim pesan eror ke pengembang. Ada juga layanan web yang menyediakan pencatatan eror untuk kasus seperti itu, seperti or . Mereka bekerja seperti ini: 1. Kita mendaftarkannya di layanan dan mendapatkan sepotong JS (atau URL skrip) dari mereka untuk disisipkan di halaman. 2. Skrip JS itu menyetel fungsi `window.onerror` kustom. 3. Ketika terjadi kesalahan, itu mengirimkan permintaan jaringan tentangnya pada layanan itu. 4. Kita dapat masuk ke antarmuka web layanan dan melihat erornya. ## Kesimpulan Bagian `try..catch` memungkinkan untuk menangani error runtime. Secara harfiah memungkinkan untuk "mencoba" menjalankan kode dan "menangkap" kesalahan yang mungkin terjadi di dalamnya. Sintaksnya adalah: ```js try { // Jalankan kode ini } catch(err) { // jika terjadi kesalahan, lompat ke sini // err adalah eror objek } finally { // lakukan dalam hal apa pun setelah try / catch } ``` Mungkin tidak ada bagian `catch` atau `finally`, jadi bagian yang lebih pendek `try..catch` dan` try..finally` juga valid. Eror objek memiliki properti berikut ini: - `message` -- pesan kesalahan yang bisa dibaca manusia. - `name` -- string dengan nama eror (nama konstruktor eror). - `stack` (non-standar, tetapi didukung dengan baik) - tumpukan/stack pada saat pembuatan eror. Jika eror objek tidak diperlukan, kita bisa menghilangkannya dengan menggunakan `catch {` daripada `catch (err) {`. Kita juga bisa menghasilkan error kita sendiri menggunakan operator `throw`. Secara teknis, argumen dari `throw` bisa berupa apa saja, tetapi biasanya itu adalah eror objek yang diturunkan dari kelas ʻError` bawaan. Lebih lanjut tentang memperluas eror objek di bab berikutnya. *Rethrowing* adalah pola yang sangat penting dari penanganan eror: blok `catch` biasanya mengharapkan dan mengetahui bagaimana menangani jenis kesalahan tertentu, jadi blok tersebut harus menampilkan kembali kesalahan yang tidak diketahuinya. Bahkan jika kita tidak memiliki `try..catch`, sebagian besar lingkungan memungkinkan kita menyiapkan penangan eror "global" untuk menangkap eror yang "terjadi". Di dalam browser, itu adalah `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 --- # Mewarisi dari SyntaxError Buat kelas `FormatError` yang diwarisi dari bawaan kelas `SyntaxError`. Ini harus mendukung properti `message`, `name` dan `stack`. Contoh penggunaan: ```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 (karena mewarisi dari SyntaxError) ``` ================================================ FILE: 1-js/10-error-handling/2-custom-errors/article.md ================================================ # Kesalahan khusus, memperluas Kesalahan Saat kita mengembangkan sesuatu, kita sering membutuhkan kelas kesalahan kita sendiri untuk mencerminkan hal-hal spesifik yang mungkin salah dalam tugas kita. Untuk kesalahan dalam operasi jaringan kita mungkin memerlukan `HttpError`, untuk operasi basis data `DbError`, untuk operasi pencarian `NotFoundError` dan seterusnya. Kesalahan kita harus mendukung properti kesalahan dasar seperti `message`, `name` dan, sebaiknya, `stack`. Tetapi mereka juga mungkin memiliki properti lain sendiri, misalnya objek `HttpError` mungkin memiliki properti `statusCode` dengan nilai seperti `404` atau `403` atau `500`. JavaScript memungkinkan untuk menggunakan `throw` dengan argumen apa pun, jadi secara teknis kelas kesalahan khusus kita tidak perlu mewarisi dari `Error`. Tetapi jika kita mewarisi, maka menjadi mungkin untuk menggunakan `obj instanceof Error` untuk mengidentifikasi objek kesalahan. Jadi lebih baik mewarisinya. Saat aplikasi berkembang, kesalahan kita sendiri secara alami membentuk hierarki. Misalnya, `HttpTimeoutError` mungkin mewarisi dari `HttpError`, dan seterusnya. ## Memperluas Kesalahan Sebagai contoh, mari pertimbangkan fungsi `readUser(json)` yang harus membaca JSON dengan data pengguna. Berikut adalah contoh tampilan `json` yang valid: ```js let json = `{ "name": "John", "age": 30 }`; ``` Secara internal, kita akan menggunakan `JSON.parse`. Jika menerima `json` yang salah, maka itu melontarkan `SyntaxError`. Tetapi bahkan jika `json` secara sintaksis benar, itu tidak berarti bahwa itu adalah pengguna yang valid, bukan? Ini mungkin kehilangan data yang diperlukan. Misalnya, mungkin tidak memiliki properti `name` dan `age` yang penting bagi pengguna kita. Fungsi kita `readUser(json)` tidak hanya akan membaca JSON, tetapi juga memeriksa ("memvalidasi") data. Jika tidak ada bidang yang wajib diisi, atau formatnya salah, itu adalah kesalahan. Dan itu bukan `SyntaxError`, karena datanya benar secara sintaksis, tetapi jenis kesalahan lain. Kita akan menyebutnya `ValidationError` dan membuat kelas untuk itu. Kesalahan semacam itu juga harus membawa informasi tentang bidang yang melanggar. Kelas `ValidationError` kita harus mewarisi dari kelas `Error` bawaan. Kelas itu sudah ada di dalamnya, tetapi berikut ini kode perkiraannya sehingga kita dapat memahami apa yang kita perluas: ```js // "Kode semu" untuk kelas Kesalahan bawaan yang ditentukan oleh JavaScript itu sendiri class Error { constructor(message) { this.message = message; this.name = "Error"; // (nama yang berbeda untuk kelas kesalahan bawaan yang berbeda) this.stack = ; // tidak standar, tetapi sebagian besar lingkungan mendukungnya } } ``` Sekarang mari kita mewarisi `ValidationError` darinya dan mencobanya dalam tindakan: ```js run untrusted *!* 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); // daftar panggilan bertingkat dengan masing-masing nomor baris } ``` Harap diperhatikan: pada baris `(1)` kita memanggil konstruktor induk. JavaScript mengharuskan kita memanggil `super` di konstruktor anak, jadi itu wajib. Konstruktor induk mengatur properti `message`. Konstruktor induk juga menyetel properti `name` menjadi `"Error"`, jadi di baris `(2)` kita menyetel ulang ke nilai yang benar. Mari kita coba menggunakannya di `readUser(json)`: ```js run class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } // Penggunaan 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; } // Contoh kerja dengan try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* alert("Invalid data: " + err.message); // Invalid data: No field: name (Tidak ada bidang: name) */!* } else if (err instanceof SyntaxError) { // (*) alert("JSON Syntax Error: " + err.message); } else { throw err; // unknown error (kesalahan yang tidak diketahui), lontarkan kembali (**) } } ``` Blok `try..catch` dalam kode di atas menangani baik `ValidationError` dan `SyntaxError` bawaan dari `JSON.parse`. Silakan lihat bagaimana kita menggunakan `instanceof` untuk memeriksa jenis kesalahan spesifik pada baris `(*)`. Kita juga bisa melihat `err.name`, seperti ini: ```js // ... // daripada (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ... ``` Versi `instanceof` jauh lebih baik, karena di masa mendatang kita akan memperluas `ValidationError`, membuat subtipe darinya, seperti `PropertyRequiredError`. Dan pemeriksaan `instanceof` akan terus berfungsi untuk kelas pewaris baru. Jadi itu bukti masa depan. Juga penting bahwa jika `catch` menemui kesalahan yang tidak diketahui, maka itu akan ditarik kembali di baris `(**)`. Blok `catch` hanya mengetahui cara menangani validasi dan kesalahan sintaksis, jenis lain (karena kesalahan ketik pada kode atau kesalahan lain yang tidak diketahui) akan gagal. ## Warisan lebih lanjut Kelas `ValidationError` sangat umum. Banyak hal mungkin salah. Properti mungkin tidak ada atau mungkin dalam format yang salah (seperti nilai string untuk `age`). Mari kita buat kelas yang lebih konkret `PropertyRequiredError`, tepatnya untuk properti yang tidak ada. Ini akan membawa informasi tambahan tentang properti yang hilang. ```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; } } */!* // Penggunaan function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } return user; } // Contoh kerja dengan try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* alert("Invalid data: " + err.message); // Invalid data: No property: name (Tidak ada properti: name) alert(err.name); // PropertyRequiredError alert(err.property); // name */!* } else if (err instanceof SyntaxError) { alert("JSON Syntax Error: " + err.message); } else { throw err; // unknown error (kesalahan yang tidak diketahui), lontarkan kembali } } ``` Kelas baru `PropertyRequiredError` mudah digunakan: kita hanya perlu meneruskan nama properti: `new PropertyRequiredError(property) `. `Pesan` yang dapat dibaca manusia dihasilkan oleh konstruktor. Harap dicatat bahwa `this.name` dalam konstruktor `PropertyRequiredError` ditetapkan lagi secara manual. Itu mungkin agak membosankan -- untuk menetapkan `this.name = ` di setiap kelas kesalahan kustom. Kita dapat menghindarinya dengan membuat kelas "kesalahan dasar" kita sendiri yang menetapkan `this.name = this.constructor.name`. Dan kemudian mewarisi semua kesalahan khusus kita darinya. Sebut saja `MyError`. Berikut kode dengan `MyError` dan kelas kesalahan khusus lainnya, yang disederhanakan: ```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; } } // name-nya benar alert( new PropertyRequiredError("field").name ); // PropertyRequiredError ``` Sekarang kesalahan khusus jauh lebih pendek, terutama `ValidationError`, karena kita menyingkirkan baris `"this.name = ..."` di konstruktor. ## Pengecualian pembungkusan Tujuan dari fungsi `readUser` pada kode di atas adalah "untuk membaca data pengguna". Mungkin ada berbagai jenis kesalahan dalam prosesnya. Saat ini kita memiliki `SyntaxError` dan `ValidationError`, tetapi di masa mendatang fungsi `readUser` dapat berkembang dan mungkin menghasilkan jenis kesalahan lain. Kode yang memanggil `readUser` harus menangani kesalahan ini. Saat ini ia menggunakan beberapa `if` dalam blok `catch`, yang memeriksa kelas dan menangani kesalahan yang diketahui dan memunculkan kembali yang tidak diketahui. Skemanya seperti ini: ```js try { ... readUser() // potensi sumber kesalahan ... } catch (err) { if (err instanceof ValidationError) { // menangani kesalahan validasi } else if (err instanceof SyntaxError) { // menangani kesalahan sintaks } else { throw err; // unknown error (kesalahan yang tidak diketahui), lontarkan kembali } } ``` Pada kode di atas kita dapat melihat dua jenis kesalahan, tetapi bisa juga lebih. Jika fungsi `readUser` menghasilkan beberapa jenis kesalahan, maka kita harus bertanya pada diri sendiri: apakah kita benar-benar ingin memeriksa semua jenis kesalahan satu per satu setiap saat? Seringkali jawabannya adalah "Tidak": kita ingin menjadi "satu tingkat di atas semua itu". Kita hanya ingin tahu apakah ada "kesalahan membaca data" -- mengapa sebenarnya hal itu terjadi seringkali tidak relevan (pesan kesalahan menjelaskannya). Atau, lebih baik lagi, kita ingin mendapatkan cara untuk mendapatkan detail kesalahan, tetapi hanya jika kita perlu. Teknik yang kita jelaskan di sini disebut "pengecualian pembungkusan". 1. Kita akan membuat kelas baru `ReadError` untuk merepresentasikan kesalahan "membaca data" yang umum. 2. Fungsi `readUser` akan menangkap kesalahan pembacaan data yang terjadi di dalamnya, seperti `ValidationError` dan `SyntaxError`, dan sebagai gantinya menghasilkan `ReadError`. 3. Objek `ReadError` akan menyimpan referensi ke kesalahan asli dalam properti `cause` -nya. Kemudian kode yang memanggil `readUser` hanya perlu memeriksa `ReadError`, bukan untuk setiap jenis kesalahan pembacaan data. Dan jika membutuhkan detail lebih lanjut tentang kesalahan, ia dapat memeriksa properti `cause`-nya. Berikut kode yang mendefinisikan `ReadError` dan mendemonstrasikan penggunaannya dalam `readUser` dan `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 (Kesalahan sintaks: Token b yang tidak terduga di JSON pada posisi 1) alert("Original error: " + e.cause); */!* } else { throw e; } } ``` Pada kode di atas, `readUser` bekerja persis seperti yang dijelaskan -- menangkap kesalahan sintaks dan validasi dan melontarkan kesalahan `ReadError` (kesalahan yang tidak diketahui ditampilkan ulang seperti biasa). Jadi kode terluar memeriksa `instanceof ReadError` dan hanya itu. Tidak perlu mencantumkan semua kemungkinan jenis kesalahan. Pendekatan ini disebut "pembungkusan pengecualian", karena kita mengambil pengecualian "tingkat rendah" dan "membungkusnya" menjadi `ReadError` yang lebih abstrak. Ini banyak digunakan dalam pemrograman berorientasi objek. ## Ringkasan - Kita bisa mewarisi dari `Error` dan kelas kesalahan bawaan lainnya secara normal. Kita hanya perlu menjaga properti `name` dan jangan lupa memanggil `super`. - Kita bisa menggunakan `instanceof` untuk memeriksa kesalahan tertentu. Ini juga bekerja dengan warisan. Namun terkadang kita memiliki objek kesalahan yang berasal dari pustaka pihak ketiga dan tidak ada cara mudah untuk mendapatkan kelasnya. Kemudian properti `name` dapat digunakan untuk pemeriksaan semacam itu. - Pengecualian pembungkusan adalah teknik yang tersebar luas: fungsi menangani pengecualian tingkat rendah dan membuat kesalahan tingkat lebih tinggi daripada berbagai kesalahan tingkat rendah. Pengecualian tingkat rendah terkadang menjadi properti dari objek tersebut seperti `err.cause` dalam contoh di atas, tetapi itu tidak sepenuhnya diperlukan. ================================================ FILE: 1-js/10-error-handling/index.md ================================================ # Penanganan kesalahan ================================================ FILE: 1-js/11-async/01-callbacks/article.md ================================================ # Pengenalan: callback ```warn header="Disini kita menggunakan method dari browser" Untuk menunjukkan penggunaan callback, promise dan konsep abstract lainnya, kita akan menggunakan beberapa method dari browser; khususnya, memuat script dan melakukan manipulasi dokumen sederhana. Jika kamu belum terbiasa dengan method ini, dan penggunaanya didalam contoh membuat bingung, atau jika kamu hanya ingin mengerti lebih baik lagi,kamu mungkin mau membaca beberapa bab dari [bagian selanjutnya](/dokumen) tutorial ini. Meski, kita akan mencoba memperjelas situasi ini. Takkan ada yang sebijaksana browser komplex yang rumit. ``` Banyak action didalam JavaScript yang *asynchronous*. Dengan kata lain, kita inisiasi action tersebut sekarang, tetapi action tersebut selesai-nya nanti. Sebagai contoh, kita bisa atur action tersebut menggunakan `setTimeout`. Contoh-contoh lain dari action asynchronous di kehidupan nyata, misalnya memuat script dan module (kita akan bahas di bab selanjutnya). Coba lihat pada fungsi `loadScript(src)`, yang memuat sebuah script dengan pemberian `src`: ```js function loadScript(src) { // creates a ================================================ FILE: 1-js/11-async/02-promise-basics/03-animate-circle-promise/task.md ================================================ # Lingkaran animasi dengan promise Tulis ulang fungsi `showCircle` di dalam solusi tugas sehingga fungsi tersebut mengembalikan sebuah *promise* daripada menerima sebuah *callback*. Penggunaan baru: ```js showCircle(150, 150, 100).then(div => { div.classList.add('message-ball'); div.append("Hello, world!"); }); ``` Ambil solusi pada tugas sebagai dasar. ================================================ FILE: 1-js/11-async/02-promise-basics/article.md ================================================ # Promise Bayangkan kamu adalah seorang penyanyi top, dan penggemarmu bertanya siang dan malam untuk *single* terbarumu. Untuk mendapatkan kelegaan, kamu berjanji untuk mengirimkan *single* tersebut kepada mereka ketika diterbitkan. Kamu memberikan sebuah daftar kepada penggemarmu. Mereka dapat mengisi alamat surel mereka, sehingga saat lagu sudah tersedia, semua pihak yang berlangganan langsung menerimanya. Dan bahkan jika ada yang salah, katakanlah, ada kebakaran di dalam studio, sehingga kamu tidak dapat menerbitkan lagu, mereka masih akan diberitahu. Semua orang senang: kamu, karena orang-orang tidak memadati kamu lagi, dan penggemar, karena mereka tidak ketinggalan *single*nya. 1. "Kode produksi" itu melakukan sesuatu dan membutuhkan waktu. Sebagai contoh, sebuah kode yang memuat data melalui jaringan. Itu adalah seorang "penyanyi". 2. "Kode pengkonsumsi" yang menginginkan hasil dari "kode produksi" setelah siap. Banyak fungsi yang mungkin membutuhkan hasil itu. Ini adalah "penggemar". 3. *Promise* adalah objek Javascript khusus yang menghubungkan "kode produksi" dan "kode pengkonsumi" secara bersamaan. Dalam analogi kami: ini adalah "daftar berlangganan". "Kode produksi" membutuhkan waktu berapa pun untuk menghasilkan hasil yang dijanjikan, dan "*Promise*" membuat hasil tersebut tersedia untuk semua kode yang berlangganan ketika hasilnya sudah siap. Analogi ini tidak terlalu akurat, karena *promise* JavaScript lebih kompleks dari daftar berlangganan sederhana: daftar tersebut memiliki fitur dan batasan tambahan. Tetapi untuk awal tidak apa-apa. *Syntax constructor* untuk objek *promise* adalah: ```js let promise = new Promise(function(resolve, reject) { // eksekutor (kode produksi, "penyanyi") }); ``` Fungsi yang dilewatkan ke `new Promise` disebut sebagai *exekutor*. Ketika `new Promise` dibuat, exekutor tersebut berjalan secara otomatis. Exekutor itu berisi kode produksi, yang pada akhirnya harus memproduksi hasil. Dalam analogi di atas: exekutor adalah "penyanyi". Argumen `resolve` dan `reject` adalah *callback* yang disediakan oleh JavaScript itu sendiri. Kode kita hanya ada di dalam eksekutor. Ketika eksekutor mendapatkan hasilnya, baik itu cepat atau lambat - tidak masalah, eksekutor harus memanggil salah satu dari *callback* ini: - `resolve(value)` — jika pekerjaan selesai dengan sukses, dengan hasil `value`. - `reject(error)` — jika terjadi kesalahan, `error` adalah objek kesalahan. Jadi untuk meringkas: eksekutor berjalan secara otomatis, eksekutor harus melakukan pekerjaan dan kemudian memanggil salah satu dari `resolve` atau `reject`. Objek `promise` yang dikembalikan oleh *constructor* `new Promise` memiliki properti internal: - `state` — pada awalnya `"pending"`, kemudian berubah menjadi `"fulfilled"` saat `resolve` dipanggil atau `"rejected"` ketika `reject` dipanggil. - `result` — pada awalnya `undefined`, kemudian berubah menjadi `value` ketika `resolve(value)` dipanggil atau `error` ketika `reject(error)` dipanggil. Jadi eksekutor akhirnya memindahkan `promise` ke salah satu dari kondisi ini: ![](promise-resolve-reject.svg) Nanti kita akan melihat bagaimana "penggemar" dapat berlangganan kepada perubahan ini. Berikut ini contoh *constructor promise* dan fungsi eksekutor sederhana dengan "kode produksi" yang membutuhkan waktu (melalui `setTimeout`): ```js run let promise = new Promise(function(resolve, reject) { // fungsi tersebut dieksekusi secara otomatis ketika "promise" dibangun // setelah 1 detik menandakan bahwa pekerjaan selesai dengan hasil "done" setTimeout(() => *!*resolve("done")*/!*, 1000); }); ``` Kita dapat melihat dua hal dengan menjalankan kode di atas: 1. Exekutor dipanggil secara langsung dan otomatis (oleh `new Promise`). 2. Exekutor menerima dua argumen: `resolve` dan `reject` — fungsi ini sudah ditentukan sebelumnya oleh mesin JavaScript. Jadi kita tak perlu membuatnya. Kita hanya harus memanggil salah satu dari dua argumen tersebut ketika siap. Setelah satu detik "memproses" eksekutor memanggil `resolve("done")` untuk memproduksi hasilnya. Ini mengubah status objek `promise`: ![](promise-resolve-1.svg) Itu adalah contoh penyelesaian pekerjaan yang sukses, sebuah "*promise fulfilled*". Dan sekarang adalah contoh eksekutor menolak *promise* dengan sebuah *error*: ```js let promise = new Promise(function(resolve, reject) { // setelah 1 detik menandakan bahwa pekerjaan selesai dengan sebuah "error" setTimeout(() => *!*reject(new Error("Whoops!"))*/!*, 1000); }); ``` Panggilan untuk `reject(...)` memindahkan objek *promise* ke status `"rejected"`: ![](promise-reject-1.svg) Untuk meringkas, eksekutor harus melakukan pekerjaan (sesuatu yang biasanya membutuhkan waktu) dan kemudian memanggil `resolve` atau `reject` untuk mengubah state objek *promise* yang sesuai. *Promise* yang diputuskan atau ditolak disebut "diselesaikan", sebagai lawan dari *promise* "pending" awalnya. ````smart header="Hanya ada satu hasil atau sebuah 'error'" Eksekutor harus memanggil hanya satu `resolve` atau satu `reject`. Setiap perubahan status adalah final. Semua panggilan `resolve` dan `reject` lebih lanjut diabaikan: ```js let promise = new Promise(function(resolve, reject) { *!* resolve("done"); */!* reject(new Error("…")); // diabaikan setTimeout(() => resolve("…")); // diabaikan }); ``` Idenya adalah bahwa pekerjaan yang dilakukan oleh eksekutor mungkin hanya memiliki satu hasil atau *error*. Juga, `resolve`/`reject` hanya berharap satu argumen (atau tidak ada) dan akan mengabaikan argumen tambahan. ```` ```smart header="Reject dengan objek `Error`" Seandainya terjadi kesalahan, eksekutor harus memanggil `reject`. Itu bisa dilakukan dengan segala jenis argumen (seperti `resolve`). Tetapi direkomendasikan untuk menggunakan objek `Error` (atau objek yang mewarisi dari `Error`). Alasannya akan segera menjadi jelas. ``` ````smart header="Memanggil langsung `resolve`/`reject`" Dalam praktiknya, eksekutor biasanya melakukan sesuatu secara *asynchronous* dan memanggil `resolve`/`reject` setelah beberapa waktu, tetapi tidak harus. Kita juga bisa memanggil `resolve` atau `reject` secara langsung, seperti ini: ```js let promise = new Promise(function(resolve, reject) { // tidak mengambil waktu kita untuk melakukan pekerjaan itu resolve(123); // secara langsung memberikan hasil: 123 }); ``` Misalnya, ini mungkin terjadi ketika kita memulai suatu pekerjaan tetapi kemudian melihat segalanya sudah selesai dan di-*cache*. Tidak apa-apa. Kita segera menyelesaikan *promise*. ```` ```smart header="`State` dan `result` bersifat internal" Properti `state` dan `result` objek Promise bersifat internal. Kita tidak bisa mengakses properti tersebut secara langsung. Kita bisa menggunakan *method* `.then`/`.catch`/`.finally` untuk melakukannya. Penjelasan method-method tersebut ada di bawah ini. ``` ## Konsumen: then, catch, finally Objek *Promise* berfungsi sebagai tautan antara eksekutor ("kode produksi" atau "penyanyi") dan fungsi konsumsi ("penggemar"), yang akan menerima hasil atau *error*. Fungsi konsumsi bisa didaftarkan (berlangganan) menggunakan *method* `.then`, `.catch` and `.finally`. ### then Yang paling penting, yang mendasar adalah `.then`. *Syntax*nya adalah: ```js promise.then( function(result) { *!*/* menangani hasil yang sukses */*/!* }, function(error) { *!*/* menangani sebuah "error" */*/!* } ); ``` Argumen pertama dari `.then` adalah fungsi yang berjalan ketika *promise* terselesaikan, dan menerima hasil. Argumen kedua dari `.then` adalah fungsi yang berjalan ketika *promise* ditolak, dan menerima *error*. Sebagai contoh, disini reaksi ketika *promise* berhasil diselesaikan: ```js run let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done!"), 1000); }); // resolve menjalankan fungsi pertama di .then promise.then( *!* result => alert(result), // menampilkan "done!" setelah satu detik */!* error => alert(error) // tidak dijalankan ); ``` Fungsi pertama dijalankan. Dan dalam hal penolakan -- yang kedua: ```js run let promise = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("Whoops!")), 1000); }); // reject menjalankan fungsi kedua di .then promise.then( result => alert(result), // tidak dijalankan *!* error => alert(error) // menampilkan "Error: Whoops!" setelah satu detik */!* ); ``` Jika kita hanya tertarik pada penyelesaian yang berhasil, maka kita hanya dapat menyediakan satu argumen fungsi `.then`: ```js run let promise = new Promise(resolve => { setTimeout(() => resolve("done!"), 1000); }); *!* promise.then(alert); // menampilkan "done!" setelah satu detik */!* ``` ### catch Jika kita hanya tertarik pada *error*, maka kita dapat menggunakan `null` sebagai argumen pertama: `.then(null, errorHandlingFunction)`. Atau kita dapat menggunakan `.catch(errorHandlingFunction)`, yang mana keduanya sama persis: ```js run let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Whoops!")), 1000); }); *!* // .catch(f) sama seperti promise.then(null, f) promise.catch(alert); // menampilkan "Error: Whoops!" setelah satu detik */!* ``` Panggilan `.catch(f)` adalah analog lengkap dari `.then(null, f)`, itu hanya sebuah singkatan. ### finally Sama seperti ada klausa `finally` dalam `try {...} catch {...}`, ada `finally` dalam *promises*. Panggilan `.finally(f)` mirip dengan `.then(f, f)` dalam arti bahwa `f` selalu berjalan ketika *promise* diselesaikan: apakah itu *resolve* atau *reject*. `finally` adalah penanganan yang baik untuk melakukan pembersihan, mis. menghentikan indikator pemuatan kita, karena tidak diperlukan lagi, apa pun hasilnya. Seperti ini: ```js new Promise((resolve, reject) => { /* lakukan sesuatu yang membutuhkan waktu, dan kemudian panggil resolve/reject */ }) *!* // berjalan ketika "promise" diselesaikan, tidak peduli sukses atau tidak .finally(() => hentikan indikator pemuatan) */!* .then(result => munculkan hasil, err => munculkan "error") ``` Tapi ini bukan alias dari `then(f,f)`. Ada beberapa perbedaan penting: 1. *Handler* `finally` tidak memiliki argumen. Didalam `finally` kita tidak tahu apakah *promise* sukses atau tidak. Tidak apa-apa, karena tugas kita biasanya melakukan prosedur penyelesaian "umum". 2. *Handler* `finally` melewatkan hasil dan *error* ke *handler* selanjutnya. Misalnya, di sini hasilnya dilewatkan melalui `finally` ke `then`: ```js run new Promise((resolve, reject) => { setTimeout(() => resolve("result"), 2000) }) .finally(() => alert("Promise ready")) .then(result => alert(result)); // <-- .then menangani hasilnya ``` Dan di sini ada *error* di dalam *promise*, dilewatkan melalui `finally` ke `catch`: ```js run new Promise((resolve, reject) => { throw new Error("error"); }) .finally(() => alert("Promise ready")) .catch(err => alert(err)); // <-- .catch menangani objek galat ``` Itu sangat nyaman, karena `finally` tidak dimaksudkan untuk memproses hasil dari *promise*. Jadi itu melewatinya. Kita akan berbicara lebih banyak tentang *chaining promise* dan *passing-result* antara *handler* di bab selanjutnya. 3. Terakhir, namun tidak kalah pentingnya, *syntax* `.finally(f)` lebih nyaman daripada `.then(f, f)`: tidak perlu menduplikasi fungsi `f`. ````smart header="Dengan promise yang sudah ditentukan handler segera menjalankannya" Jika *promise* tertunda, *handler* `.then/catch/finally` akan menunggu *promise* tersebut. Jika tidak, jika *promise* sudah selesai, handler langsung menjalankan: ```js run // "promise" diselesaikan segera setelah dibuat let promise = new Promise(resolve => resolve("done!")); promise.then(alert); // done! (muncul sekarang) ``` Perhatikan bahwa ini membuat Promise lebih efektif daripada skenario "daftar berlangganan" di kehidupan nyata. Jika penyanyi sudah merilis lagu mereka dan kemudian seseorang mendaftar di daftar berlangganan, mereka mungkin tidak akan menerima lagu itu. Berlangganan dalam kehidupan nyata harus dilakukan sebelum acara tersebut. Promise lebih fleksibel. Kita bisa menambahkan handlers kapan saja: jika hasilnya sudah ada, mereka langsung mengeksekusi. ```` Selanjutnya, mari kita lihat contoh-contoh yang lebih praktis tentang bagaimana *promise* dapat membantu kita menulis kode *asynchronous*. ## Contoh: loadScript [#loadscript] Kita punya fungsi `loadScript` untuk memuat skrip dari bab sebelumnya. Inilah varian berbasis *callback*, hanya untuk mengingatkan kita tentang itu: ```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); } ``` Mari tulis ulang menggunakan *Promise*. Fungsi baru `loadScript` tidak akan memerlukan *callback*. Sebagai gantinya, fungsi tersebut akan membuat dan mengembalikkan sebuah objek *Promise* yang diselesaikan ketika pemuatan sudah selesai. Kode yang paling luar dapat menambah *handler* (fungsi berlangganan) dengan menggunakan `.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); }); } ``` Pemakaian: ```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...')); ``` Kita dapat segera melihat beberapa manfaat melalui pola berbasis *callback*: | Promise | Callback | |----------|-----------| | *Promise* memungkinkan kita melakukan hal-hal dalam urutan alami. Pertama, kita menjalankan `loadScript(script)`, dan `.then` kita menulis apa yang harus dilakukan dengan hasilnya. | Kita harus punya fungsi `callback` yang kita miliki saat memanggil `loadScript(script, callback)`. Dengan kata lain, kita harus tau apa yang harus dilakukan dengan hasil *sebelum* `loadScript` dipanggil. | | Kita dapat memanggil `.then` pada *Promise* sebanyak yang kita inginkan. Setiap kali, kita tambahkan "fan" baru, fungsi berlangganan baru, ke "daftar berlangganan". Lebih lanjut tentang ini di bab selanjutnya: [](info:promise-chaining). | Hanya ada satu *callback*. | Jadi *promise* memberikan kita aliran kode dan fleksibilitas yang lebih baik. Tetapi masih ada lagi. Kita akan melihatnya di bab-bab selanjutnya. ================================================ FILE: 1-js/11-async/02-promise-basics/head.html ================================================ ================================================ FILE: 1-js/11-async/03-promise-chaining/01-then-vs-catch/solution.md ================================================ Jawaban singkatnya adalah: **tidak, mereka tidak sama**: Perbedaannya adalah bahwa jika terjadi sebuah _error_ di dalam `f1`, kemudian ditangani oleh `.catch` disini: ```js run promise.then(f1).catch(f2); ``` ...Tetapi bukan disini: ```js run promise.then(f1, f2); ``` Itulah kenapa sebuah _error_ diturunkan ke _chain_, dan didalam bagian kode kedua disana tidak ada _chain_ dibawah `f1`. Dengan kata lain, `.then` meneruskan _result_/_error_ ke `.then/catch` selanjutnya. Jadi pada contoh pertama, ada sebuah `catch` di bawah, dan yang kedua -- disana tidak ada, jadi _error_ tidak ditangani. ================================================ FILE: 1-js/11-async/03-promise-chaining/01-then-vs-catch/task.md ================================================ # Promise: then versus catch Apakah potongan kode ini sama? Dengan kata lain, apakah mereka berperilaku sama dalam situasai apapun, untuk setiap _handler functions_? ```js promise.then(f1).catch(f2); ``` Versus: ```js promise.then(f1, f2); ``` ================================================ FILE: 1-js/11-async/03-promise-chaining/article.md ================================================ # Promises chaining Mari kembali ke masalah yang disebutkan di dalam bab : kita memiliki sebuah urutan tugas _asynchronous_ untuk dilakukan satu demi satu. Sebagai contoh, memuat _scripts_. Bagaimana kita bisa membuat kodenya dengan baik? Promises menyediakan beberapa resep untuk melakukannya. Di bab ini kita melibatkan _promise chaining_. Itu terlihat seperti ini: ```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; }); ``` Idenya adalah bahwa **result** diteruskan melalui rantai _handlers_ `.then`. Ini alurnya: 1. Promise pertama selesai dalam 1 detik `(*)`, 2. Kemudian _handler_ `.then` dipanggil `(**)`. 3. Nilai yang dikembalikan diteruskan ke handler `.then` selanjutnya `(***)` 4. ...dan seterusnya. Selama **result** diteruskan di sepanjang rantai _handlers_, kita bisa melihat urutan pemanggilan `alert`: `1` -> `2` -> `4`. ![](promise-then-chain.svg) Seluruhnya bekerja, karena pemanggilan ke `promise.then` mengembalikan sebuah _promise_, jadi kita bisa memanggil `.then` selanjutnya. Ketika sebuah _handler_ mengembalikan nilai, _handler_ tersebut menjadi hasil dari promise, jadi `.then` selanjutnya dipanggil dengan itu. **Kesalahan klasik pemula: secara teknis kita juga dapat menambahkan banyak `.then` ke satu promise. Ini bukan chaining.** Sebagai contoh: ```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; }); ``` Apa yang kita lakukan di sini hanya beberapa _handlers_ untuk satu _promise_. _Handlers_ tersebut tidak meneruskan result ke satu sama lain, melainkan memprosesnya masing-masing. Ini gambarnya (bandingkan dengan _chaining_ di atas): ![](promise-then-many.svg) Semua `.then` pada _promise_ yang sama mendapatkan _result_ yang sama -- _result_ dari _promise_. Jadi di dalam kode di atas semua `alert` menunjukkan yang sama: `1`. Dalam prakteknya kita jarang membutuhkan banyak _handlers_ untuk satu _promise_. Chaining lebih sering digunakan. ## Mengembalikan promises Sebuah _handler_, yang digunakan di dalam `.then(handler)` dapat membuat dan mengambalikan sebuah _promise_. Dalam hal ini _handlers_ selanjutnya menunggu sampai mengendap, dan kemudian mendapatkan hasilnya. Sebagai contoh: ```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 }); ``` Di sini `.then` pertama menunjukan `1` dan mengembalikan `new Promise(…)` pada baris `(*)`. Setelah satu detik selesai, dan hasil (argument `resolve`, di sini `result * 2`) diteruskan ke _handler_ `.then` kedua. Handler pada baris `(**)`, menunjukan `2` dan melakukan hal yang sama. Jadi keluarannya sama dengan contoh sebelumnya: 1 -> 2 -> 4, tetapi sekarang dengan menunda 1 detik antara pemanggilan `alert`. Mengembalikan _promises_ memperbolehkan kita untuk membangun rantai aksi _asynchronous_. ## Contoh: loadScript Mari menggunakan fitur ini dengan _promisified_ `loadScript`, didefinisikan di [bab sebelumnya](info:promise-basics#loadscript), untuk memuat _scripts_ satu demi satu, secara berurutan: ```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) { // gunakan functions yang dideklarasikan di dalam scripts // untuk menunjukkan bahwa functions memang dimuat one(); two(); three(); }); ``` Kode ini bisa lebih ringkas dengan _arrow functions_: ```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) => { // scripts dimuat, kita dapat menggunakan functions yang dideklarasikan di sini one(); two(); three(); }); ``` Di sini setiap pemanggilan `loadScript` mengembalikkan sebuah _promise_, dan `.then` selanjutnya berjalan ketika _promise_ selesai. Kemudian memulai pemuatan _script_ selanjutnya. Jadi _scripts_ dimuat satu setelah yang lain. Kita dapat menambahkan lagi aksi asynchronous ke rantainya. Harap catat bahwa kodenya masih "flat", kodenya tumbuh ke bawah bukan ke kanan. Tidak ada tanda-tanda "pyramid of doom". Secara teknis, kita dapat menambahkan `.then` secara langsung ke setiap `loadScript`, seperti ini: ```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) => { // this function has access to variables script1, script2 and script3 one(); two(); three(); }); }); }); ``` Kode ini melakukan hal yang sama: muat 3 _scripts_ berurutan. Tetapi "tumbuh ke kanan". Jadi kita punya masalah yang sama dengan _callbacks_. Orang yang baru memulai untuk menggunakan _promises_ kadang-kadang tidak tahu tentang _chaining_, jadi mereka menulisnya dengan cara ini. Umumnya, _chaining_ lebih disukai. Terkadang ok untuk menulis `.then` secara langsung, karena _function_ bersarang memiliki akses ke luar _scope_. Pada contoh di atas _callback_ paling bertingkat memiliki akses ke semua variabel `script1`, `script2`, `script3`. Tetapi itu pengecualian bukan aturan. ````smart header="Thenables" Tepatnya, sebuah handler mungkin tidak mengembalikkan sebuah promise, tetapi dipanggil objek "thenable" - sebuah objek sewenang-wenang yang memiliki method `.then`, dan diperlakukan sama seperti sebuah promise. Idenya adalah bahwa pustaka 3rd-party dapat menerapkan objek "promise-compatible" mereka sendiri. Mereka dapat memiliki serangkaian methods yang luas, tetapi juga kompatibel dengan promises asli, karena mereka menerapkan `.then`. Ini contoh dari objek thenable: ```js run class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // selesai dengan this.num*2 setelah 1 detik setTimeout(() => resolve(this.num * 2), 1000); // (**) } } new Promise(resolve => resolve(1)) .then(result => { *!* return new Thenable(result); // (*) */!* }) .then(alert); // menunjukkan 2 setelah 1000ms ``` JavaScript memeriksa objek yang dikembalikkan oleh handler `.then` di baris `(*)`: jika ada method callable yang bernama `then`, kemudian method tersebut memanggil method yang menyediakan functions `resolve`, `reject` asli sebagai arguments (mirip ke eksekutor) dan menunggu sampai satu dari mereka dipanggil. Pada contoh di atas `resolve(2)` dipanggil setelah 1 detik `(**)`. Kemudian result diteruskan ke bawah chain. Fitur ini memperbolehkan kita untuk untuk mengintegrasikan objek kustom dengan promise chains tanpa memiliki pewarisan dari `Promise`. ```` ## Contoh Terbesar: fetch Di dalam pemrograman frontend promises sering digunakan untuk permintaan jaringan. Jadi mari lihat contoh yang lebih luas dari itu. Kita akan menggunakan methos [fetch](info:fetch) untuk memuat informasi tentang pengguna dari server jarak jauh. Banyak sekali pilihan parameter yang dilibatkan di dalam [bab terpisah](info:fetch), tetapi sintaksis dasar cukup sederhana: ```js let promise = fetch(url); ``` Ini membuat permintaan jaringan ke `url` dan mengembalikkan sebuah _promise_. Promise selesai dengan objek `response` ketika server jarak jauh merespon dengan header, tetapi _sebelum response penuh diunduh_. Untuk membaca response penuh, kita harus memanggil sebuah method `response.text()`: method tersebut mengembalikkan sebuah promise yang selesai ketika teks penuh ull telah diunduh dari server jarak jauh, dengan teks tersebut sebagai hasilnya. Kode di bawah ini membuat permintaan ke `user.json` dan memuat teks dari server: ```js run fetch("/article/promise-chaining/user.json") // .then di bawah berjalan ketika server jarak jauh merespon .then(function (response) { // response.text() mengembalikkan sebuah promise baru yang selesai dengan response teks penuh // ketika dimuat return response.text(); }) .then(function (text) { // ...dan di sini isi dari file remote alert(text); // {"name": "iliakan", isAdmin: true} }); ``` Di sana juga ada method `response.json()` yang membaca data remote dan parsing sebagai JSON. Pada kasus kita lebih sesuai, jadi mari ganti dengan itu. .then(function(text) { // ...and here's the content of the remote file alert(text); // {"name": "iliakan", "isAdmin": true} }); ``` Kita juga akan menggunakan arrow functions untuk keringkasan: ```js run // sama seperti di atas, tetapi response.json() parsing konten remote sebagai JSON fetch("/article/promise-chaining/user.json") .then((response) => response.json()) .then((user) => alert(user.name)); // iliakan, mendapatkan user name ``` Sekarang mari lakukan sesuatu dengan memuat pengguna. Sebagai contoh, kita dapat membuat satu atau lebih permintaan ke GitHub, muat profil pengguna dan tunjukkan avatarnya: ```js run // Buat permintaan ke user.json fetch("/article/promise-chaining/user.json") // Muat sebagai json .then((response) => response.json()) // Buat permintaan ke GitHub .then((user) => fetch(`https://api.github.com/users/${user.name}`)) // Muat response sebagai json .then((response) => response.json()) // Tunjukkan gambar avatar (githubUser.avatar_url) untuk 3 detik (mungkin hidupkan itu) .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); // (*) }); ``` Kodenya bekerja, lihat komentar tentang detail. Meskipun, di sana ada potensi masalah di dalamnya, kesalahan umum dari mereka yang mulai menggunakan promise. Lihat pada baris `(*)`: bagaimana kita dapat melakukan sesuatu _setelah_ avatar telah muncul dan dihapus? sebagai contoh, kita ingin menunjukkan form untuk mengubah pengguna atau sesuatu yang lain. Sampai sekarang, tidak mungkin. Untuk membuat chain bisa diperpanjang, kita butuh untuk mengembalikkan sebuah promise yang selesai ketika avatar selesai muncul. Seperti ini: ```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); })) // terpicu setelah 3 detik .then(githubUser => alert(`Finished showing ${githubUser.name}`)); ``` Itu adalah, _handler_ `.then` pada baris `(*)` sekarang mengembalikkan `new Promise`, yang menjadi mengendap hanya setelah pemanggilan `resolve(githubUser)` dalam `setTimeout` `(**)`. `.then` selanjutnya di dalam _chain_ akan menunggu untuk itu. Seperti peraturan yang bagus, sebuah aksi _asynchronous_ harus selalu mengembalikkan sebuah promise. Itu membuat kemungkinan untuk rencana aksi setelahnya. Bahkan jika kita tidak berencana memperpanjang _chain_ sekarang, kita mungkin membutuhkannya nanti. Akhinya, kita dapat memecah kodenya ke dalam _function_ yang dapat digunakan kembali: ```js run function loadJson(url) { return fetch(url).then((response) => response.json()); } function loadGithubUser(name) { return fetch(`https://api.github.com/users/${name}`).then((response) => response.json() ); } 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); }); } // Gunakan mereka: loadJson("/article/promise-chaining/user.json") .then((user) => loadGithubUser(user.name)) .then(showAvatar) .then((githubUser) => alert(`Finished showing ${githubUser.name}`)); // ... ``` ## Ringkasan Jika ada _handler_ `.then` (atau `catch/finally`, tidak masalah) mengembalikkan sebuah _promise_, _chain_ sisanya akan menunggu sampai mengendap. Saat itu terjadi, hasilnya (atau error) diteruskan lebih jauh. Di sini gambar penuhnya: ![](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 ================================================ Jawabannya adalah: **tidak, tidak terpicu**: ```js run new Promise(function (resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert); ``` Seperti yang dikatakan di bab, ada sebuah "`try..catch` implisit" di sekitar kode function. jadi semua error synchronous ditangani. Tetapi di sini error tersebut tidak dihasilkan saat eksekutornya berjalan, tapi nanti. Jadi promise tidak dapat menanganinya. ================================================ FILE: 1-js/11-async/04-promise-error-handling/01-error-async/task.md ================================================ # Error di dalam setTimeout Apa yang anda pikirkan? Akankan `.catch` terpicu? Jelaskan jawaban anda. ```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 ================================================ # Penanganan error dengan promise Chain promise bagus dalam penanganan _error_. ketika sebuah promise me-reject, kontrolnya melompat ke handler rejection terdekat. Itu sangat nyaman dalam praktiknya. Sebagai contoh, kode di bawah sebuah URL ke `fetch` itu salah (tidak ada situs seperti itu) dan `.catch` menangani error tersebut: ```js run *!* fetch('https://no-such-server.blabla') // reject */!* .then(response => response.json()) .catch(err => alert(err)) // TypeError: failed to fetch (gagal mengambil resourse, error yang dihasilkan mungkin berbeda) ``` Seperti yang anda lihat, `.catch` tidak harus segera. `.catch` mungkin muncul setelah satu atau mungkin beberapa `.then`. Atau, mungkin, semuanya baik-baik saja dengan situs tersebut, tetapi response-nya bukan JSON yang valid. Cara termudah untuk catch semua _error_ adalah menambahkan `.catch` pada akhiran chain: ```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)); */!* ``` Biasanya, `.catch` semacam itu tidak memicu sama sekali. Tetapi jika salah satu promise di atas me-reject (sebuah masalah jaringan atau json yang tidak valid atau apapun itu), maka promise tersebut akan meng-catch-nya. ## try..catch implisit Kode dari sebuah eksekutor promise dan handler promise memiliki "`try..catch` yang tak terlihat" di sekitarnya. Jika terjadi pengecualian, maka pengecualian itu tertangkap dan diperlakukan sebagai rejection. Sebagai contoh, kode ini: ```js run new Promise((resolve, reject) => { *!* throw new Error("Whoops!"); */!* }).catch(alert); // Error: Whoops! ``` ...Bekerja persis sama seperti ini: ```js run new Promise((resolve, reject) => { *!* reject(new Error("Whoops!")); */!* }).catch(alert); // Error: Whoops! ``` "`try..catch` yang tak terlihat" di sekitar eksekutor secara otomatis menangkap _error_ dan mengubahnya menjadi promise yang direject. Ini terjadi bukan hanya di dalam function eksekutor saja, tetapi di handler-nya juga. Jika kita `throw` dalam handler `.then`, itu artinya sebuah promise yang direject, jadi kontrolnya melompat ke handler error terdekat. Ini contohnya: ```js run new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { *!* throw new Error("Whoops!"); // reject promise tersebut */!* }).catch(alert); // Error: Whoops! ``` Ini terjadi pada semua _error_, tidak hanya disebabkan oleh pernyataan `throw`. Sebagai contoh, sebuah _error_ pemrograman: ```js run new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { *!* blabla(); // tidak ada fungsi seperti ini */!* }).catch(alert); // ReferenceError: blabla is not defined (blabla tidak terdefinisi) ``` `.catch` terakhir tidak hanya meng-catch rejection secara ekplisit, tetapi juga sesekali _error_ dalam handler di atas. ## Melempar kembali Seperti yang sudah kita perhatikan, `.catch` di akhir chain mirip dengan `try..catch`. Kita bisa saja memiliki sebanyak mungkin handler `.then` seperti yang kita inginkan, dan kemudian menggunakan satu `.catch` di akhir untuk menangani _error_ di semuanya. Di dalam `try..catch` biasa kita dapat menganalisis _error_ nya dan mungkin melemparkannya kembali jika tidak bisa ditangani. Hal yang sama mungkin untuk promise. Jika kita `throw` di dalam `.catch`, kemudian kontrolnya mengarah ke handler _error_ terdekat selanjutnya. Dan jika kita menangani _error_ dan selesai dengan normal, kemudian berlanjut ke handler `.then` sukses terdekat berikutnya. Pada contoh di bawah ini `.catch` sukses menangani _error_: ```js run // eksekusi: 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")); ``` Disini blok `.catch` selesai secara normal. Jadi handler `.then` yang sukses selanjutnya dipanggil. Pada contoh di bawah ini kita lihat situasi lain dengan `.catch`. Handler `(*)` menangkap _error_ dan tidak bisa mengatasinya (misalnya hanya tahu bagaimana menangani `URIError`), jadi _error_-nya dilempar lagi: ```js run // eksekusi: catch -> catch -> then new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(function(error) { // (*) if (error instanceof URIError) { // tangani di sini } else { alert("Can't handle such error"); *!* throw error; // lemparkan ini atau error lain melompat ke catch selanjutnya */!* } }).then(function() { /* tidak berjalan di sini */ }).catch(error => { // (**) alert(`The unknown error has occurred: ${error}`); // tidak mengembalikkan apapun => eksekusi berjalan normal }); ``` Eksekusi tersebut melompat dari `.catch` `(*)` pertama ke yang selanjutnya `(**)` menuruni chain. ## Rejection yang tidak tertangani Apa yang terjadi ketika sebuah _error_ tidak ditangani? Sebagai contoh, kita lupa untuk menambahkan `.catch` ke akhir chain, seperti ini: ```js untrusted run refresh new Promise(function () { noSuchFunction(); // Error di sini (tidak ada function seperti itu) }).then(() => { // handler promise yang sukses, satu atau lebih }); // tanpa .catch di akhir! ``` Jika terjadi _error_, promise jadi direject, dan eksekusi harus melompat ke handler penolakan terdekat. Tapi tidak ada. Jadi _error_-nya "stuck". Tidak ada kode untuk menangani-nya. Dalam prakteknya, seperti _error_ biasa yang tidak tertangani dalam kode, itu berarti ada sesuatu yang tidak beres. Apa yang terjadi ketika sebuah _error_ biasa muncul dan tidak tertangkap oleh `try..catch`? script-nya mati dengan sebuah pesan di console. Sesuatu yang mirip terjadi dengan rejection promise yang tidak tertangani. Mesin JavaScript melacak rejection tersebut dan menghasilkan _error_ global dalam kasus itu. Anda dapat melihatnya di console jika anda menjalankan contoh di atas. Di dalam peramban kita dapat meng-catch kesalahan tersebut menggunakan event `unhandledrejection`: ```js run *!* window.addEventListener('unhandledrejection', function(event) { // object event tersebut memiliki dua properti spesial: alert(event.promise); // [object Promise] - promise yang menghasilkan error alert(event.reason); // Error: Whoops! - object error yang tidak tertangani }); */!* new Promise(function() { throw new Error("Whoops!"); }); // tidak ada catch untuk menangani error ``` Event tersebut adalah bagian dari [standar HTML](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections). Jika sebuah _error_ muncul, dan di sana tidak ada `.catch`, handler `unhandledrejection` terpicu, dan mendapatkan object `event` dengan informasi tentang _error_, jadi kita dapat melakukan sesuatu. Biasanya _error_ seperti itu tidak dapat dipulihkan, jadi jalan keluar terbaik kita adalah memberi tahu pengguna tentang masalah dan mungkin melaporkan insiden tersebut ke server. Di dalam lingkungan non-peramban seperti Node.js di sana ada cara lain untuk melacak _error_ yang tak tertangani. ## Ringkasan - `.catch` menangani _error_ di segala macam promise: baik itu panggilan `reject()`, atau sebuah _error_ yang dilemparkan di dalam handler. - Kita harus meletakkan `.catch` tepat di tempat dimana kita ingin menangani _error_ dan tahu bagaimana untuk menangani _error-error_ tersebut. Handler harus menganalisa _error_ (bantuan class _error_ khusus) dan melemparkan kembali yang tidak diketahui (mungkin itu adalah kesalahan pemrograman). - Ok untuk tidak menggunakan `.catch` sama sekali, jika tidak ada cara untuk memulihkan dari kesalahan. - Bagaimanapun kita harus memiliki handler event `unhandledrejection` (untuk peramban,dan analog untuk lingkungan lainnya), untuk melacak _error_ yang tak tertangani dan memberi tahu pengguna (dan mungkin server kita) tentang _error-error_ tersebut, jadi aplikasi kita tidak pernah "mati begitu saja". ================================================ 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 ================================================ # API Promise Ada 6 method static di dalam class `Promise`. Kita akan segera membahas kasus penggunaan method-method tersebut di sini. ## Promise.all Katakanlah kita ingin menjalankan banyak promise untuk dieksekusi secara paralel, dan menunggu sampai semua promise tersebut siap. Sebagai contoh, unduh beberapa URL secara paralel dan proses isinya ketika semuanya selesai. Itulah gunanya `Promise.all`. Sintaksis nya adalah: ```js let promise = Promise.all([...promises...]); ``` `Promise.all` mengambil sebuah array promise (secara teknis bisa menjadi iterable, tetapi biasanya sebuah array) dan mengembalikkan promise baru. Promise baru resolve ketika semua promise yang terdaftar diselesaikan dan array dari hasil promise menjadi hasilnya itu sendiri. Sebagai contoh, `Promise.all` di bawah selesai setelah 3 detik, dan kemudian hasilnya adalah sebuah array `[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 ketika promise sudah siap: setiap promise menyumbangkan sebuah member array ``` Harap dicatat bahwa urutan member array yang dihasilkan adalah sama dengan sumber promise. Meskipun promise pertama membutuhkan waktu yang lama untuk resolve, promise tersebut masih yang pertama di dalam hasil array. Sebuah trik umum adalah untuk memetakan sebuah array dari data pekerjaan kedalam array promise, dan kemudian membungkusnya kedalam `Promise.all`. Sebagai contoh, jika kita memiliki array URL, kita dapat mengambil array-array tersebut seperti ini: ```js run let urls = [ "https://api.github.com/users/iliakan", "https://api.github.com/users/remy", "https://api.github.com/users/jeresig", ]; // memetakan setiap url ke pengambilan promise let requests = urls.map((url) => fetch(url)); // Promise.all menunggu sampai semua pekerjaan telah diresolve Promise.all(requests).then((responses) => responses.forEach((response) => alert(`${response.url}: ${response.status}`)) ); ``` Contoh terbesar dengan mengambil informasi pengguna untuk sebuah array pengguna GitHub dengan nama mereka (kita dapat mengambil array dengan id mereka, logika nya sama): ```js run let names = ["iliakan", "remy", "jeresig"]; let requests = names.map((name) => fetch(`https://api.github.com/users/${name}`) ); Promise.all(requests) .then((responses) => { // semua response telah sukses diresolve for (let response of responses) { alert(`${response.url}: ${response.status}`); // menunjukkan 200 untuk setiap url } return responses; }) // memetakan array response kedalam array response.json() untuk membaca isinya .then((responses) => Promise.all(responses.map((r) => r.json()))) // semua jawaban JSON diuraikan: "users" adalah array dari jawaban tersebut .then((users) => users.forEach((user) => alert(user.name))); ``` **Jika ada promise yang direject, promise tersebut dikembalikkan oleh `Promise.all` secara langsung me-reject nya dengan error itu.** Sebagai contoh: ```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! ``` Disini promise kedua reject dalam dua detik. Itu langsung mengarah pada rejection `Promise.all`, jadi eksekusi `.catch`: error rejection menjadi hasil keseluruhan `Promise.all`. ```warn header="Jika terjadi sebuah error, promise lain diabaikan" Jika satu promise reject, `Promise.all` langsung reject, benar-benar melupakan yang lainnya yang ada di dalam daftar. Hasil dari promise-promise tersebut diabaikan. Sebagai contoh, jika disana ada banyak pemanggilan `fetch`, seperti contoh di atas, dan satu gagal, yang lainnya akan terus mengeksekusi, tetapi `Promise.all` tidak akan memperhatikan promise-promisenya lagi. Promise-promise tersebut mungkin selesai, tetapi hasilnya akan diabaikan. `Promise.all` tidak melakukan apapun untuk membatalkan promise-promise tersebut, karena tidak ada konsep "pembatalan" di dalam promise. Di [bab lainnya](info:fetch-abort) kita akan membahas `AbortController` yang bisa membantu, tetapi `AbortController` tersebut bukan bagian dari API Promise. ``` ````smart header="`Promise.all(iterable)`memungkinkan nilai \"regular\" non-promise di dalam `iterable`" Secara normal, `Promise.all(...)` menerima sebuah promise iterable (dalam banyak kasus sebuah array). Tetapi jika salah satu objek bukan promise, objek tersebut diteruskan ke array yang dihasilkan "sebagaimana adanya". Sebagai contoh, berikut hasilnya `[1, 2, 3]`: ```js run Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000); }), 2, 3, ]).then(alert); // 1, 2, 3 ``` Jadi kita dapat meneruskan nilai yang sudah siap ke `Promise.all` jika nyaman. ```` ## Promise.allSettled [recent browser="new"] `Promise.all` reject seluruhnya jika ada promise yang reject. Itu bagus untuk kasus "semua atau tidak sama sekali", ketika kita membutuhkan *semua* hasil untuk melanjutkan: ```js Promise.all([ fetch('/template.html'), fetch('/style.css'), fetch('/data.json') ]).then(render); // method render butuh hasil dari semua pengambilan ```` `Promise.allSettled` menunggu semua promise selesai. Array yang dihasilkan memiliki: - `{status:"fulfilled", value:result}` untuk response sukses, - `{status:"rejected", reason:error}` untuk error. Sebagai contoh, kita ingin mengambil informasi tentang banyak pengguna. Bahkan jika satu request gagal, kita masih tertarik pada yang request yang lain. Mari gunakan `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}`); } }); }); ``` `results` pada baris `(*)` di atas akan: ```js [ {status: 'fulfilled', value: ...response...}, {status: 'fulfilled', value: ...response...}, {status: 'rejected', reason: ...error object...} ] ``` Jadi, untuk setiap promise kita mendapatkan status-nya dan `value/error`. ### Polyfill Jika peramban tidak mendukung `Promise.allSettled`, mudah untuk melakukan 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); }; } ``` Dalam kode ini, `promises.map` mengambil nilai input, berubah menjadi promises (untuk berjaga-jaga jika non-promise yang diteruskan) dengan `p => Promise.resolve(p)`, dan kemudian menambahkan handler `.then` handler ke semuanya. Handler tersebut mengubah hasil `v` yang sukses menjadi `{state:'fulfilled', value:v}`, dan sebuah error `r` menjadi `{state:'rejected', reason:r}`. Itu persis dengan format `Promise.allSettled`. Kita bisa menggunakan `Promise.allSettled` untuk mendapatkan hasilnya atau _semua_ promise diberikan, bahkan jika beberapa dari promise itu reject. ## Promise.race Mirip dengan `Promise.all`, tetapi hanya menunggu promise pertama diselesaikan, dan mendapatkan hasilnya (atau error). Sintaksisnya adalah: ```js let promise = Promise.race(iterable); ``` Sebagai contoh, hasil di sini akan menjadi `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 ``` Promise pertama di sini adalah yang tercepat, jadi promise tersebut menjadi hasilnya. Setelah promise pertama yang selesai "memenangkan balapan", semua hasil/errors lebih lanjut akan diabaikan. ## 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 Method `Promise.resolve` dan `Promise.reject` jarang dibutuhkan dalam kode modern, karena sintaksis `async/await` (kita akan membahasnya [nanti](info:async-await)) membuat method-method tersebut usang. Kita membahas method-method tersebut di sini sebagai pelengkap, dan bagi mereka yang tidak bisa menggunakan `async/await` untuk beberapa alasan. - `Promise.resolve(value)` membuat promise yang diresolve dengan hasil `value`. Sama dengan: ```js let promise = new Promise((resolve) => resolve(value)); ``` Method ini digunakan untuk kompatibilitas, ketika function diharapkan untuk mengembalikkan promise. Sebagai contoh, function `loadCached` di bawah mengambil URL dan mengingat (cache) isinya. Untuk panggilan di masa mendatang dengan URL yang sama itu segera mendapatkan isi sebelumnya dari cache, tetapi menggunakan `Promise.resolve` untuk membuat promise tentang itu, jadi hasil yang dikembalikkan selalu sebuah promise: ```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; }); } ``` Kita dapat menulis `loadCached(url).then(…)`, karena function tersebut dijamin untuk mengembalikan promise. Kita selalu dapat menggunakan `.then` setelah `loadCached`. Itulah tujuan dari `Promise.resolve` pada baris `(*)`. ### Promise.reject - `Promise.reject(error)` membuat promise yang direjecet dengan `error`. Sama dengan: ```js let promise = new Promise((resolve, reject) => reject(error)); ``` Dalam praktiknya, method ini hampir tidak pernah digunakan. ## Ringkasan Ada 6 method static dari class `Promise`: 1. `Promise.all(promises)` -- menunggu semua promise selesai dan mengambalikan sebuah array sebagai hasilnya. Jika salah satu promises yang diberikan reject, maka menjadi error of `Promise.all`, dan semua hasil lainnya akan diabaikan. 2. `Promise.allSettled(promises)` (method yang baru ditambahkan) -- menunggu semua promise selesai dan mengembalikan hasilnya sebagai objek array dengan: - `state`: `"fulfilled"` or `"rejected"` - `value` (jika fulfilled) atau `reason` (jika rejected). 3. `Promise.race(promises)` -- menunggu promise pertama selesai, dan hasil/error menjadi hasilnya. 4. `Promise.any(promises)` (metode baru yang ditambahkan) - menunggu promise pertama terpenuhi, dan hasilnya menjadi hasil keseluruhan. Jika semua janji yang diberikan ditolak, [`AggregateError`] (mdn: js / AggregateError) menjadi eror`Promise.any`. 5. `Promise.resolve(value)` -- membuat promise yang resolved dengan nilai yang diberikan. 6. `Promise.reject(error)` -- membuat promise rejected dengan error yang diberikan. Dari semua ini, `Promise.all` mungkin yang paling umum dalam praktiknya. ``` ``` ================================================ 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 ================================================ # Promisifikasi Promisifikasi -- adalah kata yang panjang untuk transformasi sederhana. Ini adalah konversi dari function yang menerima sebuah callback menjadi function yang mengembalikkan sebuah promise. Transformasi seperti itu sering kali dibutuhkan dalam kehidupan nyata sebanyak function dan pustaka berbasis callback. Tetapi promise lebih nyaman. Jadi masuk akal untuk melakukan promisify ini. Untuk pemahaman yang lebih baik, mari kita lihat contoh. Misalnya, kita memiliki `loadScript (src, callback)` dari bab . ```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); } // penggunaan: // loadScript('path/script.js', (err, script) => {...}) ``` Fungsi memuat script dengan `src` yang diberikan, dan kemudian memanggil` callback (err) `jika terjadi kesalahan, atau` callback (null, script) `jika pemuatan berhasil. Itu adalah kesepakatan untuk menggunakan callback, kita telah melihat itu sebelumnya. Mari kita jadikan promise. Kita akan membuat fungsi baru `loadScriptPromise (src)`, yang melakukan hal yang sama (memuat skrip), tetapi mengembalikan sebuah promise daripada menggunakan callback. Dengan kata lain, kita hanya meneruskan `src` (tanpa` callback`) dan mendapatkan janji sebagai gantinya, yang menyelesaikan dengan `script` ketika pemuatan berhasil, dan menolak dengan kesalahan sebaliknya. Here it is: ```js let loadScriptPromise = function (src) { return new Promise((resolve, reject) => { loadScript(src, (err, script) => { if (err) reject(err); else resolve(script); }); }); }; // penggunaan: // loadScriptPromise('path/script.js').then(...) ``` Seperti yang bisa kita lihat, fungsi baru adalah pembungkus di sekitar fungsi `loadScript` asli. Ia menyebutnya menyediakan callback sendiri yang diterjemahkan menjadi janji `menyelesaikan / menolak`. Sekarang `loadScriptPromise` cocok dengan kode berbasis promise. Jika kita lebih menyukai promise daripada callback (dan segera kita akan melihat lebih banyak alasan untuk itu), maka kita akan menggunakannya. Dalam praktiknya kita mungkin perlu menjanjikan lebih dari satu fungsi, jadi masuk akal untuk menggunakan helper. Kita akan menyebutnya `promisify (f)`: ia menerima fungsi to-promisify `f` dan mengembalikan fungsi pembungkus. ```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); // tambahkan callback khusus kita ke akhir argumen f f.call(this, ...args); // panggil fungsi aslinya }); }; } // penggunaan: let loadScriptPromise = promisify(loadScript); loadScriptPromise(...).then(...); ``` Kode mungkin terlihat agak rumit, tetapi pada dasarnya sama dengan yang kita tulis di atas, sambil menjanjikan fungsi `loadScript`. Panggilan ke `promisify (f)` mengembalikan pembungkus di sekitar `f`` (*) `. Wrapper tersebut mengembalikan sebuah promise dan meneruskan panggilan ke `f` asli, melacak hasilnya di callback kustom` (**) `. Di sini, `promisify` mengasumsikan bahwa fungsi asli mengharapkan callback dengan tepat dua argumen` (err, result) `. Itulah yang paling sering kita temui. Maka callback khusus kita berada dalam format yang benar-benar tepat, dan `promisify` berfungsi dengan baik untuk kasus seperti itu. Tetapi bagaimana jika `f` asli mengharapkan callback dengan lebih banyak argumen` callback (err, res1, res2, ...) `? Kami dapat meningkatkan penolong kami. Mari buat versi lanjutan dari `promisify`. - Ketika dipanggil sebagai `promisify (f)` seharusnya berfungsi seperti versi di atas. - Ketika dipanggil sebagai `promisify (f, true)`, itu harus mengembalikan promise yang diselesaikan dengan array hasil callback. Itu persis untuk callback dengan banyak argumen. ```js // promisify(f, true) dapatkan array dari hasil function promisify(f, manyArgs = false) { return function (...args) { return new Promise((resolve, reject) => { function *!*callback(err, ...results*/!*) { // callback khusus kita untuk f if (err) { reject(err); } else { // resolve dengan semua hasil callback jika manyArgs ditentukan *!*resolve(manyArgs ? results : results[0]);*/!* } } args.push(callback); f.call(this, ...args); }); }; } // penggunaan: f = promisify(f, true); f(...).then(arrayOfResults => ..., err => ...); ``` Seperti yang Anda lihat, ini pada dasarnya sama seperti di atas, tetapi `menyelesaikan` dipanggil dengan hanya satu atau semua argumen bergantung pada apakah` manyArgs` benar. Untuk format callback yang lebih eksotis, seperti yang tidak memiliki `err` sama sekali:` callback (result) `, kita bisa memastikan fungsi tersebut secara manual tanpa menggunakan helper. Ada juga module dengan function promisifikasi yang sedikit lebih fleksibel, misalnya [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). Di Node.js, ada function `util.promisify` bawaan untuk itu. ```smart Promisifikasi adalah pendekatan yang bagus, khususnya ketika anda menggunakan `async/await` (lihat bab selanjutnya), tapi bukan pengganti callback secara total. Ingat, sebuah promise mungkin hanya memiliki satu hasil, tetapi callback mungkin secara teknis dipanggil berkali-kali. Jadi promisifikasi hanya dimaksudkan untuk function yang memanggil callback sekali. Panggilan selanjutnya akan diabaikan. ``` ================================================ FILE: 1-js/11-async/07-microtask-queue/article.md ================================================ # Microtasks Handler-handler promise `.then`/`.catch`/`.finally` selalu asynchronous. Bahkan ketika sebuah promise diresolve, kode pada baris _di bawah_ `.then`/`.catch`/`.finally` masih akan dieksekusi sebelum handler-handler ini. Berikut demo-nya: ```js run let promise = Promise.resolve(); promise.then(() => alert("promise done!")); alert("code finished"); // alert ini muncul lebih dahulu ``` Jika anda menjalankannya, anda melihat `code finished` lebih dahulu, dan kemudian `promise done!`. Itu aneh, karena promise pasti _done_ dari awal. Mengapa `.then` terpicu setelahnya? Apa yang sedang terjadi? ## Antrean Microtasks Task asynchronous membutuhkan manajemen yang tepat. Untuk itu, standar menentukan antrean internal `PromiseJobs`, lebih sering disebut sebagai "antrean microtask" (istilah v8). Seperti yang dikatakan di [spesifikasi](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues): - Antrean adalah yang pertama masuk-pertama keluar: tasks yang diantrekan pertama dijalankan terlebih dahulu. - Eksekusi dari task dimulai jika hanya tidak ada yang berjalan. Atau, untuk mengatakannya secara sederhana, ketika promise sudah siap, handler-handler `.then/catch/finally` ini dimasukkan kedalam antrean. Handler-handler tersebut belum dieksekusi. Mesin JavaScript mengambil tugas dari antrean dan menjalankannya. Itulah kenapa "code finished" pada contoh di atas muncul lebih dahulu. ![](promiseQueue.svg) Handler-handler promise selalu melalui antrean internal. Jika ada chain dengan banyak `.then/catch/finally`, maka masing-masing dieksekusi secara asynchronous. Artinya, itu pertama kali masuk ke antrean, dan dieksekusi ketika kode saat ini sudah selesai dan antrean handler-handler sebelumnya sudah selesai. **Bagaimana jika urutan penting untuk kita? Bagaimana kita membuat `code finished` berjalan setelah `promise done`?** Mudah, letakkan saja di dalam antrean dengan `.then`: ```js run Promise.resolve() .then(() => alert("promise done!")) .then(() => alert("code finished")); ``` Sekarang urutannya seperti yang diinginkan. ## Rejection yang tidak tertangani Ingat event `unhandledrejection` dari bab ? Sekarang kita bisa melihat bagaimana sebenarnya JavaScript menemukan bahwa ada rejection yang tidak tertangani. **"Rejection yang tidak tertangani" muncul ketika error promise tidak ditangani di akhir antrean microtask.** Biasanya, jika kita mengharapkan error, kita menambahkan `.catch` ke chain promise untuk menangani error tersebut: ```js run let promise = Promise.reject(new Error("Promise Failed!")); *!* promise.catch(err => alert('caught')); */!* // tidak berjalan: error ditangani window.addEventListener('unhandledrejection', event => alert(event.reason)); ``` ...Tetapi jika kita lupa menambah `.catch`, kemudian, setelah antrean microtask sudah kosong, mesin memicu event: ```js run let promise = Promise.reject(new Error("Promise Failed!")); // Promise Gagal! window.addEventListener("unhandledrejection", (event) => alert(event.reason)); ``` Bagaimana jika kita menangani error tersebut nanti? Seperti ini: ```js run let promise = Promise.reject(new Error("Promise Failed!")); *!* setTimeout(() => promise.catch(err => alert('caught')), 1000); */!* // Error: Promise Gagal! window.addEventListener('unhandledrejection', event => alert(event.reason)); ``` Sekarang, jika anda menjalankannya, kita akan melihat pesan `Promise Failed!` terlebih dahulu, dan kemudian `caught`. Jika kita tidak tahu tentang antrean microtasks, kita bisa bertanya-tanya: "Mengapa handler `unhandledrejection` berjalan? Kita menangkap error!". Tetapi sekarang kita mengerti bahwa `unhandledrejection` dihasilkan saat antrean microtask selesai: mesin memeriksa promise dan, jika ada promise yang berada di state "rejected", maka event akan dipicu. Pada contoh di atas, `.catch` ditambahkan oleh `setTimeout` juga pemicu, tetapi kemudian, setelah `unhandledrejection` telah terjadi, jadi itu tidak mengubah apapun. ## Ringkasan Penanganan promise selalu asynchronous, karena semua aksi promise melewati antrean internal "promise jobs", juga dipanggil "antrean microtask" (istilah v8). Jadi, handler-handler `.then/catch/finally` selalu dipanggil setelah kode saat ini selesai. Jika kita butuh untuk menjamin kalau potongan kode dieksekusi setelah `.then/catch/finally`, kita bisa menambahnya kedalam panggilan chain `.then`. Di sebagian besar mesin Javascript, termasuk peramban dan Node.js, konsep microtasks terkait erat dengan "event loop" dan "macrotasks". Karena ini tidak berhubungan langsung dengan promise, konsep-konsep tersebut akan dibahas di bagian lain pada tutorial, dalam bab . ================================================ FILE: 1-js/11-async/08-async-await/01-rewrite-async/solution.md ================================================ Catatannya ada di bawah kode: ```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("no-such-user.json").catch(alert); // Error: 404 (4) ``` Catatan: 1. Function `loadJson` menjadi `async`. 2. Semua `.then` di dalamnya di ganti dengan `await`. 3. Kita dapat `return response.json()` daripada menunggu untuk itu, seperti ini: ```js if (response.status == 200) { return response.json(); // (3) } ``` Lalu kode terluar harus `await` untuk promise tersebut resolve. Dalam kasus kita, itu tidak masalah. 4. Error yang dilempar dari `loadJson` ditangani oleh `.catch`. Kita tidak bisa menggunakan `await loadJson(…)` di sana, karena kita tidak berada di dalam function `async`. ================================================ FILE: 1-js/11-async/08-async-await/01-rewrite-async/task.md ================================================ # Menulis ulang menggunakan async/await Tulis ulang salah satu contoh di bab ini menggunakan `async/await` daripada `.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('no-such-user.json') .catch(alert); // Error: 404 ``` ================================================ FILE: 1-js/11-async/08-async-await/02-rewrite-async-2/solution.md ================================================ Tidak ada trik di sini. Hanya mengganti `.catch` dengan `try...catch` di dalam `demoGithubUser` dan menambahkan `async/await` ketika dibutuhkan: ```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); } } // Tanya nama pengguna sampai github mengembalikkan pengguna yang valid 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; // tidak ada error, keluar dari loop } catch (err) { if (err instanceof HttpError && err.response.status == 404) { // loop dilanjutkan setelah alert alert("No such user, please reenter."); } else { // error yang tidak diketahui, rethrow throw err; } } } alert(`Full name: ${user.name}.`); return user; } demoGithubUser(); ``` ================================================ FILE: 1-js/11-async/08-async-await/02-rewrite-async-2/task.md ================================================ # Menulis ulang "rethrow" dengan async/await Di bawah anda dapat menemukan contoh "rethrow" dari bab . Tulis ulang menggunakan `async/await` daripada `.then/catch`. Dan singkirkan rekursi yang mendukung masuk loop dalam `demoGithubUser`: dengan `async/await` itu menjadi mudah untuk dilakukan. ```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); } }); } // Tanya nama pengguna sampai github mengembalikkan user yang valid 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 ================================================ Itulah yang terjadi ketika mengetahui cara kerjanya di dalam sangat membantu. Hanya perlakukan pemanggilan `async` sebagai promise dan lampirkan `.then` ke dalamnya: ```js run async function wait() { await new Promise(resolve => setTimeout(resolve, 1000)); return 10; } function f() { // menunjukkan 10 setelah 1 detik *!* wait().then(result => alert(result)); */!* } f(); ``` ================================================ FILE: 1-js/11-async/08-async-await/03-async-from-regular/task.md ================================================ # Panggil async dari non-async Kita punya function "reguler". Bagaimana memanggil `async`dari function tersebut dan menggunakan hasilnya? ```js async function wait() { await new Promise((resolve) => setTimeout(resolve, 1000)); return 10; } function f() { // ...apa yang ditulis di sini? // kita harus memanggil async wait() dan tunggu sampai mendapatkan 10 // ingat, kita tidak bisa menggunakan "await" } ``` P.S. Task ini secara teknis sangat mudah, tetapi pertanyaan ini cukup umum bagi developer yang baru mengenal async/await. ================================================ FILE: 1-js/11-async/08-async-await/article.md ================================================ # Async/await Ada sintaksis spesial untuk bekerja dengan promise dengan cara yang lebih nyaman, dipanggil "async/await". Ini sangat mudah dipahami dan digunakan. ## Fungsi Async Mari mulai dengan keyword `async`. keyword ini dapat ditempatkan sebelum fungsi, seperti ini: ```js async function f() { return 1; } ``` Kata "async" sebelum fungsi berarti satu hal sederhana: fungsi tersebut selalu mengembalikkan promise. Value lain dibungkus didalam promise yang resolve secara otomatis. Sebagai contoh, fungsi ini mengembalikkan promise yang resolve dengan hasil `1`, mari kita uji: ```js run async function f() { return 1; } f().then(alert); // 1 ``` ...Kita secara eksplisit dapat mengembalikkan promise, itu akan sama dengan: ```js run async function f() { return Promise.resolve(1); } f().then(alert); // 1 ``` Jadi, `async` memastikan bahwa fungsi mengembalikkan promise, dan membungkus non-promise di dalamnya. Cukup mudah, bukan? Tapi tidak hanya itu. Ada keyword lain, `await`, yang hanya bekerja di dalam fungsi `async`, dan itu cukup keren. ## Await Sintaksis: ```js // bekerja hanya di dalam fungsi async let value = await promise; ``` Keyword `await` membuat JavaScript menunggu sampai promise tersebut selesai dan mengembalikkan hasilnya. Ini contoh dengan promise yang selesai dalam 1 detik: ```js run async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) }); *!* let result = await promise; // tunggu sampai promise selesai (*) */!* alert(result); // "done!" } f(); ``` Eksekusi fungsi tersebut "dipause" pada baris `(*)` dan dilanjutkan ketika promise selesai, dengan `result` menjadi hasilnya. Jadi kode di atas menunjukkan "done!" dalam satu detik. Mari kita tekankan: `await` benar-benar membuat JavaScript menunggu sampai promise selesai, lalu lanjutkan dengan hasilnya. Hal tersebut tidak membebani _resource_ CPU apapun, karena mesin dapat melakukan pekerjaan lain sementara itu: eksekusi script lain, menangani event dan lain-lain. Ini hanya sintaksis yang lebih elegan untuk mendapatkan hasil dari promise daripada `promise.then`, mudah untuk dibaca dan ditulis. ````warn header="Tidak dapat menggunakan `await`di dalam fungsi biasa" Jika kita coba untuk menggunakan`await` di dalam fungsi non-async, akan ada error sintaksis: ```js run function f() { let promise = Promise.resolve(1); *!* let result = await promise; // Error sintaksis */!* } ``` Kita akan mendapatkan error ini jika kita tidak meletakkan `async` sebelum fungsi. Seperti yang dikatakan, `await` hanya bekerja di dalam sebuah `fungsi async`. ````` Mari kita ambil contoh `showAvatar()` dari bab dan menulisnya ulang menggunakan `async/await`: 1. Kita harus mengganti call `.then` dengan `await`. 2. Juga kita harus membuat fungsi `async` agar mereka bekerja. ```js run async function showAvatar() { // baca JSON kita let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); // baca pengguna github let githubResponse = await fetch(`https://api.github.com/users/${user.name}`); let githubUser = await githubResponse.json(); // memunculkan avatar let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); // tunnggu 3 detik await new Promise((resolve, reject) => setTimeout(resolve, 3000)); img.remove(); return githubUser; } showAvatar(); ``` Cukup bersih dan mudah dibaca, bukan? Jauh lebih baik dari sebelumnya. ````smart header="`await` tidak bekerja pada top-level code" Orang yang baru mulai menggunakan `await` cenderung lupa faktanya bahwa kita tidak bisa menggunakan `await` pada top-level code. Sebagai contoh, ini tidak akan bekerja: ```js run // error sintaksis pada top-level code let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ``` Kita dapat membungkusnya kedalam fungsi async anonymous, seperti ini: ```js (async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })(); ``` P.S. Fitur baru: mulai dari mesin V8 versi 8.9+, tingkat atas menunggu bekerja di [modul](info: modul). ```` ````` ````smart header="`await`menerima \"thenables\"" Seperti`promise.then`, `await`memperbolehkan kita menggunakan objek thenable (mereka dengan method`then`callable). Idenya adalah objek 3rd-party mungkin bukan promise, tetapi promise-compatible: jika objek tersebut mendukung`.then`, itu cukup digunakan dengan `await`. Disini demo class `Thenable`, `await` di bawah menerima instances dari class: ```js run class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // resolve dengan this.num*2 setelah 1000ms setTimeout(() => resolve(this.num * 2), 1000); // (*) } } async function f() { // tunggu selama 1 detik, kemudian result-nya menjadi 2 let result = await new Thenable(1); alert(result); } f(); ``` Jika `await` mendapatkan objek non-promise dengan `.then`, objek itu memanggil method yang menyediakan fungsi asli `resolve`, `reject` sebagai argumen. Kemudian `await` menunggu sampai salah satu argumen tersebut dipanggil (pada contoh di atas hal tersebut terjadi pada baris `(*)`) dan kemudian melanjutkan dengan hasilnya. ````` ````smart header="Method class async " Untuk mendeklarasikan sebuah method class async, cukup tambahkan saja `async`: ```js run class Waiter { *!* async wait() { */!* return await Promise.resolve(1); } } new Waiter() .wait() .then(alert); // 1 (this is the same as (result => alert(result))) ``` Artinya sama saja: itu memastikan bahwa value yang dikembalikkan adalah promise dan memungkinkan `await`. ````` ## Penanganan error Jika sebuah promise resolve secara normal, kemudian `await promise` mengembalikkan result. Tetapi dalam kasus rejection, `await promise` melempar error, seolah olah ada pernyataan `throw` pada baris tersebut. Kode ini: ```js async function f() { *!* await Promise.reject(new Error("Whoops!")); */!* } ``` ...Sama dengan ini: ```js async function f() { *!* throw new Error("Whoops!"); */!* } ``` Di dalam situasi yang nyata, promise mungkin butuh waktu sebelum reject. Dalam hal ini akan terjadi penundaan sebelum `await` melemparkan sebuah error. Kita dapat catch error itu menggunakan `try..catch`, dengan cara yang sama seperti `throw` biasa: ```js run async function f() { try { let response = await fetch('http://no-such-url'); } catch(err) { *!* alert(err); // TypeError: failed to fetch (gagal mengambil resource) */!* } } f(); ``` Dalam kasus error, kontrolnya meloncat ke blok `catch`. kita juga dapat membungkus banyak baris: ```js run async function f() { try { let response = await fetch("/no-user-here"); let user = await response.json(); } catch (err) { // catch error baik di fetch dan response.json alert(err); } } f(); ``` Jika kita tidak punya `try..catch`, maka promise yang dihasilkan oleh pemanggilan fungsi async `f()` menjadi direject. Kita dapat menambahkan `.catch` untuk menanganinya: ```js run async function f() { let response = await fetch('http://no-such-url'); } // f() menjadi sebuah promise yang direject *!* f().catch(alert); // TypeError: failed to fetch (gagal mengambil resource) // (*) */!* ``` Jika kita lupa menambahkan `.catch` di sana, maka kita mendapatkan sebuah error promise yang tak tertangani (dapat dilihat di console). Kita dapat catch errors seperti itu menggunakan handler event global seperti yang dijelaskan di bab . ```smart header="`async/await`dan`promise.then/catch`" Ketika kita menggunakan `async/await`, kita jarang membutuhkan `.then`, karena `await`menangani waiting tersebut untuk kita. Dan kita dapat menggunakan sebuah `try..catch` biasa dibandingkan`.catch`. Itu biasanya (tidak selalu) lebih nyaman. Tetapi pada *top-level code*, saat kita berada di luar fungsi `async`, kita secara sintaks tidak dapat menggunakan `await`, jadi itu sebuah latihan normal untuk menambah `.then/catch` untuk menangani hasil akhir atau jika terjadi error. Seperti baris `(*)` contoh di atas. ````` ````smart header="`async/await` bekerja baik dengan `Promise.all`" Ketika kita perlu menunggu banyak promise, kita dapat membungkusnya di dalam `Promise.all` dan kemudian `await`: ```js // tunggu untuk result dalam array let results = await Promise.all([ fetch(url1), fetch(url2), ... ]); ````` Pada kasus error, itu menyebar seperti biasa: dari promise yang gagal ke `Promise.all`, dan kemudian menjadi sebuah pengecualian yang bisa kita catch menggunakan `try..catch` di sekitar pemanggilan. ``` ## Ringkasan Keyword `async` sebelum fungsi memiliki dua efek: 1. Membuatnya selalu mengembalikkan sebuah promise. 2. Memperbolehkan kita untuk menggunakan `await` di dalamnya. Keyword `await` sebelum promise membuat JavaScript menunggu sampai promise itu selesai, dan kemudian: 1. Jika ada error, pengecualian dihasilkan, sama seperti jika `throw error` dipanggil di tempat itu. 2. Sebaliknya, menghasilkan result. Bersama mereka menyediakan kerangka kerja yang bagus untuk menulis kode asynchronous yang mudah baik membaca dan menulis. Dengan `async/await` kita jarang menulis `promise.then/catch`, tetapi kita tetap tidak boleh lupa bahwa `async/await` berdasarkan promise, karena terkadang (misalnya di scope terluar) kita harus menggunakan method ini. Juga `Promise.all` adalah sesuatu yang bagus untuk menunggu banyak task secara bersamaan. ``` ================================================ FILE: 1-js/11-async/08-async-await/head.html ================================================ ================================================ FILE: 1-js/11-async/index.md ================================================ # Promises, 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 ``` Please note, the same can be done with a regular function, like this: ```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 ``` That also works. But then we lose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere. ================================================ FILE: 1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/task.md ================================================ # Pseudo-random generator There are many areas where we need random data. One of them is testing. We may need random data: text, numbers, etc. to test things out well. In JavaScript, we could use `Math.random()`. But if something goes wrong, we'd like to be able to repeat the test, using exactly the same data. For that, so called "seeded pseudo-random generators" are used. They take a "seed", the first value, and then generate the next ones using a formula so that the same seed yields the same sequence, and hence the whole flow is easily reproducible. We only need to remember the seed to repeat it. An example of such formula, that generates somewhat uniformly distributed values: ``` next = previous * 16807 % 2147483647 ``` If we use `1` as the seed, the values will be: 1. `16807` 2. `282475249` 3. `1622650073` 4. ...and so on... The task is to create a generator function `pseudoRandom(seed)` that takes `seed` and creates the generator with this formula. Usage example: ```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 ================================================ # Generators Regular functions return only one, single value (or nothing). Generators can return ("yield") multiple values, one after another, on-demand. They work great with [iterables](info:iterable), allowing to create data streams with ease. ## Generator functions To create a generator, we need a special syntax construct: `function*`, so-called "generator function". It looks like this: ```js function* generateSequence() { yield 1; yield 2; return 3; } ``` Generator functions behave differently from regular ones. When such function is called, it doesn't run its code. Instead it returns a special object, called "generator object", to manage the execution. Here, take a look: ```js run function* generateSequence() { yield 1; yield 2; return 3; } // "generator function" creates "generator object" let generator = generateSequence(); *!* alert(generator); // [object Generator] */!* ``` The function code execution hasn't started yet: ![](generateSequence-1.svg) The main method of a generator is `next()`. When called, it runs the execution until the nearest `yield ` statement (`value` can be omitted, then it's `undefined`). Then the function execution pauses, and the yielded `value` is returned to the outer code. The result of `next()` is always an object with two properties: - `value`: the yielded value. - `done`: `true` if the function code has finished, otherwise `false`. For instance, here we create the generator and get its first yielded value: ```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} ``` As of now, we got the first value only, and the function execution is on the second line: ![](generateSequence-2.svg) Let's call `generator.next()` again. It resumes the code execution and returns the next `yield`: ```js let two = generator.next(); alert(JSON.stringify(two)); // {value: 2, done: false} ``` ![](generateSequence-3.svg) And, if we call it a third time, the execution reaches the `return` statement that finishes the function: ```js let three = generator.next(); alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*} ``` ![](generateSequence-4.svg) Now the generator is done. We should see it from `done:true` and process `value:3` as the final result. New calls to `generator.next()` don't make sense any more. If we do them, they return the same object: `{done: true}`. ```smart header="`function* f(…)` or `function *f(…)`?" Both syntaxes are correct. But usually the first syntax is preferred, as the star `*` denotes that it's a generator function, it describes the kind, not the name, so it should stick with the `function` keyword. ``` ## Generators are iterable As you probably already guessed looking at the `next()` method, generators are [iterable](info:iterable). We can loop over their values using `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 } ``` Looks a lot nicer than calling `.next().value`, right? ...But please note: the example above shows `1`, then `2`, and that's all. It doesn't show `3`! It's because `for..of` iteration ignores the last `value`, when `done: true`. So, if we want all results to be shown by `for..of`, we must return them with `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 } ``` As generators are iterable, we can call all related functionality, e.g. the spread syntax `...`: ```js run function* generateSequence() { yield 1; yield 2; yield 3; } let sequence = [0, ...generateSequence()]; alert(sequence); // 0, 1, 2, 3 ``` In the code above, `...generateSequence()` turns the iterable generator object into an array of items (read more about the spread syntax in the chapter [](info:rest-parameters-spread#spread-syntax)) ## Using generators for iterables Some time ago, in the chapter [](info:iterable) we created an iterable `range` object that returns values `from..to`. Here, let's remember the code: ```js run let range = { from: 1, to: 5, // for..of range calls this method once in the very beginning [Symbol.iterator]() { // ...it returns the iterator object: // onward, for..of works only with that object, asking it for next values return { current: this.from, last: this.to, // next() is called on each iteration by the for..of loop next() { // it should return the value as an object {done:.., value :...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; // iteration over range returns numbers from range.from to range.to alert([...range]); // 1,2,3,4,5 ``` We can use a generator function for iteration by providing it as `Symbol.iterator`. Here's the same `range`, but much more compact: ```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; } } }; alert( [...range] ); // 1,2,3,4,5 ``` That works, because `range[Symbol.iterator]()` now returns a generator, and generator methods are exactly what `for..of` expects: - it has a `.next()` method - that returns values in the form `{value: ..., done: true/false}` That's not a coincidence, of course. Generators were added to JavaScript language with iterators in mind, to implement them easily. The variant with a generator is much more concise than the original iterable code of `range`, and keeps the same functionality. ```smart header="Generators may generate values forever" In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers. That surely would require a `break` (or `return`) in `for..of` over such generator. Otherwise, the loop would repeat forever and hang. ``` ## Generator composition Generator composition is a special feature of generators that allows to transparently "embed" generators in each other. For instance, we have a function that generates a sequence of numbers: ```js function* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; } ``` Now we'd like to reuse it to generate a more complex sequence: - first, digits `0..9` (with character codes 48..57), - followed by uppercase alphabet letters `A..Z` (character codes 65..90) - followed by lowercase alphabet letters `a..z` (character codes 97..122) We can use this sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first. In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end. For generators, there's a special `yield*` syntax to "embed" (compose) one generator into another. The composed generator: ```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 ``` The `yield*` directive *delegates* the execution to another generator. This term means that `yield* gen` iterates over the generator `gen` and transparently forwards its yields outside. As if the values were yielded by the outer generator. The result is the same as if we inlined the code from nested generators: ```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 ``` A generator composition is a natural way to insert a flow of one generator into another. It doesn't use extra memory to store intermediate results. ## "yield" is a two-way street Until this moment, generators were similar to iterable objects, with a special syntax to generate values. But in fact they are much more powerful and flexible. That's because `yield` is a two-way street: it not only returns the result to the outside, but also can pass the value inside the generator. To do so, we should call `generator.next(arg)`, with an argument. That argument becomes the result of `yield`. Let's see an example: ```js run function* gen() { *!* // Pass a question to the outer code and wait for an answer let result = yield "2 + 2 = ?"; // (*) */!* alert(result); } let generator = gen(); let question = generator.next().value; // <-- yield returns the value generator.next(4); // --> pass the result into the generator ``` ![](genYield2.svg) 1. The first call `generator.next()` should be always made without an argument (the argument is ignored if passed). It starts the execution and returns the result of the first `yield "2+2=?"`. At this point the generator pauses the execution, while staying on the line `(*)`. 2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code. 3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`. Please note, the outer code does not have to immediately call `next(4)`. It may take time. That's not a problem: the generator will wait. For instance: ```js // resume the generator after some time setTimeout(() => generator.next(4), 1000); ``` As we can see, unlike regular functions, a generator and the calling code can exchange results by passing values in `next/yield`. To make things more obvious, here's another example, with more calls: ```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 ``` The execution picture: ![](genYield2-2.svg) 1. The first `.next()` starts the execution... It reaches the first `yield`. 2. The result is returned to the outer code. 3. The second `.next(4)` passes `4` back to the generator as the result of the first `yield`, and resumes the execution. 4. ...It reaches the second `yield`, that becomes the result of the generator call. 5. The third `next(9)` passes `9` into the generator as the result of the second `yield` and resumes the execution that reaches the end of the function, so `done: true`. It's like a "ping-pong" game. Each `next(value)` (excluding the first one) passes a value into the generator, that becomes the result of the current `yield`, and then gets back the result of the next `yield`. ## generator.throw As we observed in the examples above, the outer code may pass a value into the generator, as the result of `yield`. ...But it can also initiate (throw) an error there. That's natural, as an error is a kind of result. To pass an error into a `yield`, we should call `generator.throw(err)`. In that case, the `err` is thrown in the line with that `yield`. For instance, here the yield of `"2 + 2 = ?"` leads to an error: ```js run function* gen() { try { let result = yield "2 + 2 = ?"; // (1) alert("The execution does not reach here, because the exception is thrown above"); } catch(e) { alert(e); // shows the error } } let generator = gen(); let question = generator.next().value; *!* generator.throw(new Error("The answer is not found in my database")); // (2) */!* ``` The error, thrown into the generator at line `(2)` leads to an exception in line `(1)` with `yield`. In the example above, `try..catch` catches it and shows it. If we don't catch it, then just like any exception, it "falls out" the generator into the calling code. The current line of the calling code is the line with `generator.throw`, labelled as `(2)`. So we can catch it here, like this: ```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("The answer is not found in my database")); } catch(e) { alert(e); // shows the error } */!* ``` If we don't catch the error there, then, as usual, it falls through to the outer calling code (if any) and, if uncaught, kills the script. ## Summary - Generators are created by generator functions `function* f(…) {…}`. - Inside generators (only) there exists a `yield` operator. - The outer code and the generator may exchange results via `next/yield` calls. In modern JavaScript, generators are rarely used. But sometimes they come in handy, because the ability of a function to exchange data with the calling code during the execution is quite unique. And, surely, they are great for making iterable objects. Also, in the next chapter we'll learn async generators, which are used to read streams of asynchronously generated data (e.g paginated fetches over a network) in `for await ... of` loops. In web-programming we often work with streamed data, so that's another very important use case. ================================================ FILE: 1-js/12-generators-iterators/2-async-iterators-generators/article.md ================================================ # Iterasi dan generator asinkron Iterasi asinkron memungkinkan kita melakukan iterasi atas data yang datang secara asinkron, sesuai permintaan. Seperti, misalnya, ketika kita mengunduh sesuatu yang sepotong demi sepotong melalui jaringan. Dan generator asinkron membuatnya lebih nyaman. Mari kita lihat contoh sederhana terlebih dahulu, untuk memahami sintaksnya, lalu meninjau kasus penggunaan kehidupan nyata. ## Ingat _iterable_ Mari kita ingat topik tentang _iterable_. Idenya adalah kita memiliki objek, seperti `range` di sini: ```js let range = { from: 1, to: 5, }; ``` ...Dan kita ingin menggunakan perulangan `for..of` di atasnya, seperti `for(value of range)`, untuk mendapatkan nilai dari `1` hingga `5`. Dengan kata lain, kita ingin menambahkan kemampuan iterasi ke objek. Itu bisa diimplementasikan menggunakan metode khusus dengan nama `Symbol.iterator`: - Metode ini dipanggil oleh konstruksi `for..of` ketika perulangan dimulai, dan harus mengembalikan objek dengan metode `next`. - Untuk setiap iterasi, metode `next()` dipanggil untuk nilai berikutnya. - `next()` harus mengembalikan nilai dalam bentuk `{done: true/false, value:}`, di mana `done:true` berarti akhir dari perulangan. Berikut implementasi untuk `range` _iterable_: ```js run let range = { from: 1, to: 5, *!* [Symbol.iterator]() { // dipanggil sekali, di awal for..of */!* return { current: this.from, last: this.to, *!* next() { // memanggil setiap iterasi, untuk mendapatkan nilai berikutnya */!* if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; for(let value of range) { alert(value); // 1 lalu 2, lalu 3, lalu 4, lalu 5 } ``` Jika ada yang tidak jelas, silakan kunjungi bab [](info:iterable), ini memberikan semua detail tentang iterable biasa. ## _Iterable_ asinkron Iterasi asinkron diperlukan jika nilai muncul secara asinkron: setelah `setTimeout` atau jenis penundaan lainnya. Kasus yang paling umum adalah objek perlu membuat permintaan jaringan untuk memberikan nilai berikutnya, kita akan melihat contoh kehidupan nyata nanti. Untuk membuat sebuah objek dapat diulang secara asinkron: 1. Gunakan `Symbol.asyncIterator` sebagai ganti `Symbol.iterator`. 2. Metode `next()` harus mengembalikan sebuah _promise_ (untuk dipenuhi dengan nilai berikutnya). - Kata kunci `async` menanganinya, kita cukup membuat `async next() `. 3. Untuk melakukan iterasi pada objek seperti itu, kita harus menggunakan perulangan `for await (let item of iterable)`. - Perhatikan kata `await`. Sebagai contoh awal, mari kita buat objek `range` yang dapat diulang, serupa seperti sebelumnya, tetapi sekarang akan mengembalikan nilai secara asinkron, satu per detik. Yang perlu kita lakukan adalah melakukan beberapa penggantian pada kode di atas: ```js run let range = { from: 1, to: 5, *!* [Symbol.asyncIterator]() { // (1) */!* return { current: this.from, last: this.to, *!* async next() { // (2) */!* *!* // catatan: kita bisa menggunakan "await" di dalam async next: 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 } */!* })() ``` Seperti yang bisa kita lihat, strukturnya mirip dengan iterator biasa: 1. Untuk membuat sebuah objek asinkron _iterable_, itu harus memiliki metode `Symbol.asyncIterator` `(1)`. 2. Metode ini harus mengembalikan objek dengan metode `next()` mengembalikan _promise_ `(2)`. 3. Metode `next()` tidak harus menjadi `async`, ini mungkin metode biasa yang mengembalikan sebuah _promise_, tetapi `async` memungkinkan kita untuk menggunakan `await`, jadi itu mudah. Disini kita hanya menunda sebentar `(3)`. 4. Untuk iterasi, kita menggunakan `for await(let value of range)` `(4)`, yaitu menambahkan "await" setelah "for". Ini memanggil `range[Symbol.asyncIterator]()` sekali, dan kemudian `next()` untuk nilai. Berikut tabel kecil dengan perbedaannya: | | Iterator | Iterator Asinkron | | --------------------------------------- | ----------------- | ---------------------- | | Metode objek untuk menyediakan iterator | `Symbol.iterator` | `Symbol.asyncIterator` | | Nilai kembali `next()` adalah | nilai apapun | `Promise` | | Untuk mengulang, gunakan | `for..of` | `for await..of` | ````warn header="Sintaks _spread_ `...` tidak bekerja secara asinkron" Fitur yang membutuhkan iterator sinkron dan reguler, tidak berfungsi dengan yang asinkron. Misalnya, sintaks _spread_ tidak akan berfungsi: ```js alert([...range]); // Error, no Symbol.iterator (tidak ada Symbol.iterator) ``` Itu wajar, karena mengharapkan untuk menemukan `Symbol.iterator`, bukan `Symbol.asyncIterator`. Ini juga kasus untuk `for..of`: sintaks tanpa `await` membutuhkan` Symbol.iterator`. ````` ## Ingat generator Sekarang mari kita ingat generator, karena memungkinkan untuk membuat kode iterasi jauh lebih pendek. Seringkali, ketika kita ingin membuat _iterable_, kita akan menggunakan generator. Untuk kesederhanaan semata, menghilangkan beberapa hal penting, mereka adalah "fungsi yang menghasilkan nilai". Mereka dijelaskan secara rinci di bab [] (info:generators). Generator diberi label dengan `function*` (catat permulaannya) dan gunakan `yield` untuk menghasilkan nilai, kemudian kita dapat menggunakan `for..of` untuk mengulanginya. Contoh ini menghasilkan urutan nilai dari `start` hingga `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, lalu 2, lalu 3, lalu 4, lalu 5 } ``` Seperti yang telah kita ketahui, untuk membuat sebuah objek menjadi _iterable_, kita harus menambahkan `Symbol.iterator` padanya. ```js let range = { from: 1, to: 5, *!* [Symbol.iterator]() { return } */!* } ``` Praktik umum untuk `Symbol.iterator` adalah mengembalikan generator, ini membuat kode lebih pendek, seperti yang kamu lihat: ```js run let range = { from: 1, to: 5, *[Symbol.iterator]() { // singkatan untuk [Symbol.iterator]: function*() for(let value = this.from; value <= this.to; value++) { yield value; } } }; for(let value of range) { alert(value); // 1, lalu 2, lalu 3, lalu 4, lalu 5 } ``` Silakan lihat bab [](info:generators) jika kamu ingin lebih jelasnya. Di generator biasa kita tidak bisa menggunakan `await`. Semua nilai harus datang secara sinkron, seperti yang diharuskan oleh konstruksi `for..of`. Bagaimana jika kita ingin menghasilkan nilai secara asinkron? Dari permintaan jaringan, misalnya. Mari beralih ke generator asinkron untuk memungkinkannya. ## Generator asinkron (akhirnya) Untuk sebagian besar aplikasi praktis, ketika kita ingin membuat objek yang menghasilkan urutan nilai secara asinkron, kita dapat menggunakan generator asinkron. Sintaksnya sederhana: tambahkan `function*` dengan `async`. Itu membuat generator asinkron. Dan kemudian gunakan `for await (...)` untuk mengulanginya, seperti ini: ```js run *!*async*/!* function* generateSequence(start, end) { for (let i = start; i <= end; i++) { *!* // Wow, can use 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, lalu 2, lalu 3, lalu 4, lalu 5 (dengan penundaan antaranya) } })(); ``` Karena generatornya asinkron, kita bisa menggunakan `await` di dalamnya, mengandalkan _promise_, melakukan permintaan jaringan, dan sebagainya. ````smart header="Perbedaan didalamnya" Secara teknis, jika kamu adalah pembaca tingkat lanjut yang mengingat detail tentang generator, ada perbedaan internal. Untuk generator asinkron, metode `generator.next()` adalah asinkron, yang mengembalikan _promise_. Dalam generator biasa kita akan menggunakan `result = generator.next()` untuk mendapatkan nilai. Dalam generator asinkron, kita harus menambahkan `await`, seperti ini: ```js result = await generator.next(); // result = {value: ..., done: true/false} ``` Itulah mengapa generator asinkron bekerja dengan `for await...of`. ````` ### Rentang _iterable_ asinkron Generator biasa dapat digunakan sebagai `Symbol.iterator` untuk mempersingkat kode iterasi. Serupa dengan itu, generator asinkron dapat digunakan sebagai `Symbol.asyncIterator` untuk mengimplementasikan iterasi asinkron. Misalnya, kita dapat membuat objek `range` menghasilkan nilai secara asinkron, sekali per detik, dengan mengganti `Symbol.iterator` sinkron dengan `Symbol.asyncIterator` asinkron: ```js run let range = { from: 1, to: 5, // baris ini sama dengan [Symbol.asyncIterator]: async function*() { *!* async *[Symbol.asyncIterator]() { */!* for(let value = this.from; value <= this.to; value++) { // buat jeda di antara value, tunggu sesuatu await new Promise(resolve => setTimeout(resolve, 1000)); yield value; } } }; (async () => { for *!*await*/!* (let value of range) { alert(value); // 1, lalu 2, lalu 3, lalu 4, lalu 5 } })(); ``` Sekarang _value_ datang dengan jeda 1 detik di antara mereka. ```smart Secara teknis, kita bisa menambahkan `Symbol.iterator` dan `Symbol.asyncIterator` ke objek, jadi keduanya secara sinkron (`for..of`) dan asinkron (`for await..of`) _iterable_. Namun dalam praktiknya, itu akan menjadi hal yang aneh untuk dilakukan. ``` ## Contoh kehidupan nyata: _paginated_ data Sejauh ini kita telah melihat contoh-contoh dasar, untuk mendapatkan pemahaman. Sekarang mari kita tinjau kasus penggunaan kehidupan nyata. Ada banyak layanan daring yang memberikan _paginated_ data. Misalnya, saat kita membutuhkan daftar pengguna, permintaan mengembalikan jumlah yang telah ditentukan sebelumnya (misalnya 100 pengguna) - "satu halaman", dan memberikan URL ke halaman berikutnya. Pola ini sangat umum. Ini bukan tentang pengguna, tetapi tentang apa saja. Misalnya, GitHub memungkinkan kita untuk mengambil _commits_ dengan cara yang sama, _paginated_: - Kita harus membuat permintaan untuk `fetch` dalam formulir `https://api.github.com/repos//commits`. - Ini merespons dengan JSON 30 _commits_, dan juga menyediakan tautan ke halaman berikutnya di tajuk `Link`. - Lalu kita bisa menggunakan tautan itu untuk permintaan berikutnya, untuk mendapatkan lebih banyak _commits_, dan seterusnya. Untuk kode kita, kita ingin memiliki cara yang lebih sederhana untuk mendapatkan _commits_. Mari buat fungsi `fetchCommits(repo)` yang mendapatkan _commits_ untuk kita, membuat permintaan kapan pun diperlukan. Dan biarkan peduli tentang semua hal penomoran halaman. Bagi kita ini akan menjadi iterasi asinkron sederhana `for await..of`. Jadi penggunaannya akan seperti ini: ```js for await (let commit of fetchCommits('username/repository')) { // proses commit } ``` Berikut fungsi tersebut, diimplementasikan sebagai generator asinkron: ```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 membutuhkan header user-agent }); const body = await response.json(); // (2) respon adalah JSON (senarai commits) // (3) URL halaman berikutnya ada di header, ekstrak itu let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/); nextPage = nextPage?.[1]; url = nextPage; for (let commit of body) { // (4) menghasilkan commit satu per satu, sampai halaman berakhir yield commit; } } } ``` Penjelasan lebih lanjut tentang cara kerjanya: 1. Kita menggunakan metode peramban [fetch](info:fetch) untuk mengunduh _commits_. - URL awalnya adalah `https://api.github.com/repos//commits`, dan halaman berikutnya akan berada di `Link` header tanggapan. - Metode `fetch` memungkinkan kita untuk memberikan otorisasi dan tajuk lainnya jika diperlukan -- di sini GitHub memerlukan `User-Agent`. 2. _commits_ dikembalikan dalam format JSON. 3. Kita harus mendapatkan URL halaman berikutnya dari tajuk `Link` dari respon. Ini memiliki format khusus, jadi kita menggunakan ekspresi reguler untuk itu. - URL halaman berikutnya mungkin terlihat seperti ini `https://api.github.com/repositories/93253246/commits?page=2`. Ini dihasilkan oleh GitHub itu sendiri. 4. Kemudian kita menghasilkan _commits_ yang diterima satu per satu, dan ketika mereka selesai, iterasi `while(url)` berikutnya akan terpicu, membuat satu permintaan lagi. Contoh penggunaan (menunjukkan penulis _commit_ di konsol): ```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) { // mari berhenti di 100 commits break; } } })(); // Note: If you are running this in an external sandbox, you'll need to paste here the function fetchCommits described above ``` Itulah yang kita inginkan. Mekanisme internal permintaan _paginated_ tidak terlihat dari luar. Bagi kita, ini hanyalah generator asinkron yang mengembalikan _commits_. ## Ringkasan Iterator dan generator reguler berfungsi dengan baik dengan data yang tidak membutuhkan waktu untuk dibuat. Saat kita mengharapkan data datang secara asinkron, dengan penundaan, pasangan asinkronnya dapat digunakan, dan `for await..of` daripada `for..of`. Perbedaan sintaks antara asinkron dan iterator biasa: | | _Iterable_ | Asinkron _Iterable_ | | --------------------------------- | ----------------------------- | ------------------------------------------------------------- | | Metode untuk menyediakan iterator | `Symbol.iterator` | `Symbol.asyncIterator` | | Nilai kembali `next()` adalah | `{value:…, done: true/false}` | `Promise` yang memutuskan untuk `{value:…, done: true/false}` | Perbedaan sintaks antara generator asinkron dan biasa: | | Generator | Generator asinkron | | ----------------------------- | ----------------------------- | ------------------------------------------------------------- | | Deklarasi | `function*` | `async function*` | | Nilai kembali `next()` adalah | `{value:…, done: true/false}` | `Promise` yang memutuskan untuk `{value:…, done: true/false}` | Dalam pengembangan web, kita sering menemui aliran data, ketika mengalir potongan demi potongan. Misalnya, mengunduh atau mengunggah file besar. Kita dapat menggunakan generator asinkron untuk memproses data tersebut. Perlu juga dicatat bahwa di beberapa lingkungan, seperti di peramban, ada juga Antarmuka Pemrograman Aplikasi (APA) lain yang disebut _Streams_, yang menyediakan antarmuka khusus untuk bekerja dengan aliran semacam itu, untuk mengubah data dan meneruskannya dari satu aliran ke aliran lain (misalnya mengunduh dari satu tempat dan segera kirim ke tempat lain). ================================================ FILE: 1-js/12-generators-iterators/2-async-iterators-generators/head.html ================================================ ================================================ FILE: 1-js/12-generators-iterators/index.md ================================================ # Generator, iterasi lanjutan ================================================ FILE: 1-js/13-modules/01-modules-intro/article.md ================================================ # Modul, Pengenalan Saat aplikasi kita berkembang menjadi lebih besar, kita ingin membaginya menjadi banyak file, yang disebut modul. Sebuah modul bisa berisi kelas atau fungsi *library* untuk tujuan spesifik. Untuk waktu yang lama, Javascript ada tanpa sintaks modul tingkat-bahasa. Hal ini tidak menjadi masalah, karena awalnya kode skrip lebih kecil dan simpel, jadi modul tidak diperlukan. Tetapi akhirnya kode skrip menjadi lebih kompleks, jadi komunitas membuat berbagai cara untuk mengatur kode menjadi modul, *library* khusus untuk memuat modul sesuai permintaan. Disebut beberapa dengan nama (Untuk alasan sejarah): - [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- satu dari sistem modul yang paling kuno, awalnya diimplementasikan oleh [require.js](http://requirejs.org/). - [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- Sistem modul yang dibuat untuk *server* Node.js. - [UMD](https://github.com/umdjs/umd) -- ada satu lagi sistem modul, disarankan untuk menjadi modul universal, karena cocok dengan AMD dan CommonJS. Sekarang semua ini perlahan menjadi bagian dari sejarah, tetapi kita masih bisa menemukannya di kode skrip lama. Sistem modul tingkat-bahasa muncul di tahun 2015, berevolusi secara bertahap sejak saat itu, dan sekarang di dukung oleh semua browser utama dan Node.js. Jadi sekarang kita akan mulai mempelajari modul Javascript modern. ## Apa itu modul? Modul hanya sebuah file. Satu kode skrip adalah satu modul. Sangat simpel bukan. Modul bisa memuat satu sama lain dan menggunakan pengarah khusus `export` dan `import` untuk fungsi pertukaran, memanggil fungsi dari satu modul ke modul lainnya. - `export` kata kunci variabel label dan fungsi yang bisa diakses diluar modul saat ini. - `import` memperbolehkan impor fungsi dari modul lain. Misalnya, jika kita memiliki file `sayHi.js` ekspor fungsi: ```js // 📁 sayHi.js export function sayHi(user) { alert(`Hello, ${user}!`); } ``` ...Lalu file lain bisa di impor dan menggunakannya: ```js // 📁 main.js import {sayHi} from './sayHi.js'; alert(sayHi); // function... sayHi('John'); // Hello, John! ``` `import` akan diarahkan untuk memuat modul dari *path* `sayHi.js` yang relatif dengan file saat ini, dan menetapkan fungsi yang diekspor `sayHi` pada variabel yang sesuai. Mari kita jalankan contoh ini di browser. Karena modul didukung oleh kata kunci dan fitur khusus, kita harus memberi tahu browser bahwa kode skrip harus diperlakukan sebagai modul, dengan menggunakan attribut ` ``` ### Batasan level-modul Setiap modul memiliki batasan level-tinggi sendiri. Dengan kata lain, variabel level-tinggi dan fungsi dari modul tidak bisa dilihat di kode skrip lainnya. Contoh dibawah ini, dua kode skrip di impor, dan `hello.js` mencoba untuk menggunakan variabel `user` yang dideklarasikan di `user.js`, maka akan gagal: [codetabs src="scopes" height="140" current="index.html"] Modul diharapkan untuk `export` yang ingin bisa diakses dari luar `import` yang dibutuhkan. Jadi kita harus impor `user.js` pada `hello.js` untuk mendapatkan fungsi yang dibutuhkan dari `user.js` daripada mengandalkan variabel global. Ini variasi yang benar: [codetabs src="scopes-working" height="140" current="hello.js"] Pada browser, batasan level-tinggi independen juga ada untuk setiap ` ``` Jika kita benar-benar perlu untuk membuat variabel global tingkat *window*, kita akan menetapkan secara eksplisit pada `window` dan mengaksesnya sebagai `window.user`. Tetapi itu adalah pengecualian yang membutukan alasan bagus. ### Kode modul dievaluasi saat pertama kali saat di impor Jika modul yang sama di impor di banyak tempat yang lain, kode yang akan dijalankan hanya yang pertama saja, lalu ekspor akan di berikan pada semua modul impor. Hal ini memiliki konsekuensi yang penting. Mari kita lihat dengan contoh: Pertama, jika kode modul dijalankan akan memberikan efek samping, seperti menampilkan pesan, maka mengimpornya berkali-kali hanya akan memicu pesan satu kali saja: ```js // 📁 alert.js alert("Module is evaluated!"); ``` ```js // Import the same module from different files(impor modul yang sama dari file yang berbeda) // 📁 1.js import `./alert.js`; // Module is evaluated!(modul dievaluasi) // 📁 2.js import `./alert.js`; // (shows nothing)(menampilkan kosong) ``` Pada praktek, modul level-tinggi banyak di gunakan untuk inisialisasi, membuat struktur data internal, dan jika kita ingin membuat sesuatu bisa digunakan kembali -- ekspor saja. Sekarang, contoh yang lebih lanjut. Katakanlah, modul mengekspor sebuah objek: ```js // 📁 admin.js export let admin = { name: "John" }; ``` Jika modul ini diimpor dari banyak file, modul hanya akan dievalusi pertama kali saja, lalu objek `admin` dibuat, dan akan diteruskan kepada semua file impor lebih lanjut lainnya. Semua file impor akan mendapatkan persis satu hanya objek `admin`: ```js // 📁 1.js import {admin} from './admin.js'; admin.name = "Pete"; // 📁 2.js import {admin} from './admin.js'; alert(admin.name); // Pete *!* // Both 1.js and 2.js imported the same object(1.js dan 2.js mengimpor objek yang sama) // Changes made in 1.js are visible in 2.js(perubahan di buat di 1.js terlihat di 2.js) */!* ``` Jadi, mari kita ulangi -- modul hanya dijalankan satu kali, ekspor akan dihasilkan, dan kemudian dibagikan diantara impor, jadi jika sesuatu merubah objek `admin`, modul lainnya bisa melihat hal tersebut. Perilaku seperti itu mengizinkan kita untuk bisa *mengkonfigurasi* modul di impor pertama. Kita bisa mengatur properti satu kali, dan file impor lebih lanjut sudah siap. Misalnya, modul `admin.js` mungkin menyediakan fungsi tertentu, tetapi ingin mendapatkan kredensial pada objek `admin` dari luar: ```js // 📁 admin.js export let config = { }; export function sayHi() { alert(`Ready to serve, ${config.user}!`); } ``` Pada `init.js`, kode skrip pertama dari aplikasi, kita mengatur `admin.name`. Kemudian semua orang akan melihatnya, termasuk pemanggilan yang dibuat di dalam `admin.js` itu sendiri: ```js // 📁 init.js import {config} from './admin.js'; config.user = "Pete"; ``` ...Sekarang modul `admin.js` telah dikonfigurasi. Importir lebih lanjut dapat memanggilnya, dan itu menunjukkan dengan benar pengguna saat ini: ```js // 📁 another.js import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` ### import.meta Objek `import.meta` berisi informasi tentang modul saat ini. Kontennya tergantung dengan lingkungannya. Pada browser, berisi *url* dari kode skrip, atau *url* halaman web saat ini di dalam HTML: ```html run height=0 ``` ### Dalam modul, "this" tidak didefinisikan Ini semacam fitur yang kecil , tetapi untuk lebih lengkap kita harus menyebutkannya. Dalam modul, level-tinggi `this` tidak didefinisikan. Dibedakan dengan kode skrip tidak modul, dimana `this` adalah objek global: ```html run height=0 ``` ## Fitur spesifik browser Ada juga beberapa perbedaan spesifik browser skrip dengan `type="module"` dibandingkan dengan yang biasa. Kamu mungkin ingin melewati bagian ini jika kamu baru membacanya pertama kali, atau jika kamu tidak menggunakan Javascript pada browser. ### Kode skrip modul di tangguhkan Kode skrip modul selalu ditangguhkan, sama dengan efek attribut `defer` (dideskripsikan di bab [](info:script-async-defer)), untuk kode skrip diluar dan di dalam baris. Dalam kata lain: - mengunggah kode skrip eksternal ` Bandingkan dengan kode skrip dibawah: ``` Perlu dicatat: Kode skrip kedua sebenarnya berjalan sebelum yang pertama! Jadi kita akan melihat `undefined` dahulu, baru kemudian `object`. Itu karena modul ditangguhkan, jadi kita akan menunggu dokumen untuk diproses. Kode skrip biasa berjalan secara langsung, jadi kita akan melihat keluarannya dahulu. Saat menggunakan modul, kita harus sadar bahwa halaman HTML menampilkan apa yang di muat, dan modul Javascript berjalan setelah itu, jadi pengguna mungkin akan melihat halaman sebelum aplikasi Javascript siap. Beberapa fungsi mungkin belum bisa berjalan. Kita harus menambahkan "indikator memuat", atau jika tidak pastikan pengunjung tidak bingung dengan itu. ### *Async* bekerja pada kode skrip satu baris Untuk kode skrip bukan modul, attribut `async` hanya bekerja pada kode skrip eksternal. Kode skrip async berjalan langsung saat sudah siap, independen dari kode skrip lain atau dokumen HTML. Untuk skrip modul, akan berjalan pada kode skrip satu baris. Contoh, kode skrip satu baris dibawah ini memiliki `async`, jadi tidak perlu menunggu sesuatu. Ini melakukan import (fetch `./analytics.js`) dan berjalan saat sudah siap, meski saat dokumen HTML belum siap, atau jika ada kode skrip lain yang masih tertunda. Itu adalah fungsi yang bagus yang tidak bergantung pada sesuatu, seperti perhitungan, iklan, level-dokumen *event listener*. ```html ``` ### Kode skrip eksternal Kode eksternal yang memiliki `type="module"` memiliki perbedaan pada dua aspek: 1. Kode skrip eksternal dengan 'src' yang sama hanya berjalan satu kali: ```html ``` 2. Kode skrip eksternal hanya diambil dari *origin* lain (contoh situs lain) membutuhkan *header* [CORS](mdn:Web/HTTP/CORS), akan dideskripsikan pada bab . Dengan kata lain, jika kode skrip modul diambil dari *origin* lain, maka *server remote* harus ... hader `Access-Control-Allow-Origin` mengizinkan untuk mengambil data. ```html ``` Ini memastikan kemanan yang baik secara default. ### Modul "kosong" tidak diizinkan Pada Browser, `import` harus mendapatkan relatif atau *URL* mutlak. Modul tanpa *path* disebut dengan modul "kosong". Modul seperti ini tidak diizinkan pada `import`. Misalnya, `import` ini tidak valid: ```js import {sayHi} from 'sayHi'; // Error, "bare" module // the module must have a *path*, e.g. './sayHi.js' or wherever the module is ``` Pada lingkungan tertentu, seperti Node.js atau alat pembungkus mengizinkan modul kosong, tanpa ada *path*, karena mereka memiliki cara tersendiri untuk menemukan modul dan pengait untuk menyesuaikannya. Tetapi browser masih belum mendukung modul kosong. ### Kompabilitas, "nomodule" Browser lama tidak mengerti `type="module"`. Kode skrip dari tipe yang tidak diketahui akan diabaikan. Untuk itu, ada kemungkinan untuk menyediakan *fallback* menggunakan attribut `nomodule`: ```html run ``` ## Alat Pembangun Di dunia nyata, modul browser jarang digunakan dalam bentuk "mentah". Biasanya, kita membungkusnya bersama dengan alat khusus seperti [Webpack](https://webpack.js.org/) dan di *deploy* di *server* produksi. Salah satu keuntungan menggunakan pembungkus -- mereka memberikan lebih banyak kontrol bagaimana modul diselesaikan, mengizinkan modul kosong dan yang lainnya, seperti CSS/modul HTML. Alat pembangun melakukan langkah seperti ini: 1. Modul "main", salah satu yang dimaksud untuk ditambahkan ` ``` Meskipun demikian, modul asli juga dapat digunakan. Jadi kita tidak akan menggunakan webpack disini: kamu bisa mengkonfigurasinya nanti. ## Ringkasan Secara ringkas, konsep intinya adalah: 1. Modul adalah file. Untuk membuat `import/export` bekerja, browser membutuhkan ` ================================================ 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); // no such variable (each module has independent variables) ================================================ 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 ================================================ # Export dan Import Perintah export dan import memiliki beberapa varian sintaks. Pada artikel sebelumnya kita melihat cara penggunaan yang sederhana, sekarang mari telusuri lebih banyak contoh. ## Export sebelum deklarasi Kita dapat memberi label deklarasi apapus untuk diekspor dengan menempatkan `export` sebelumnya, baik itu variabel, fungsi atau kelas. Misalnya, dibawah ini semua export valid: ```js // export sebuah array *!*export*/!* let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // export sebuah konstanta *!*export*/!* const MODULES_BECAME_STANDARD_YEAR = 2015; // export a class *!*export*/!* class User { constructor(name) { this.name = name; } } ``` ````smart header="Tidak ada titik koma setelah perintah export kelas/fungsi" Mohon diperhatikan bahwa `export` sebelum sebuah kelas atau fungsi tidak menjadikannya sebagai [ekspresi fungsi](info:function-expressions). Ini masih sebuah deklarasi fungsi meskipun sudah diekspor. Kebanyakan panduan gaya penulisan Javascript tidak merekomendasikan titik koma setelah deklarasi fungsi dan kelas. Itu mengapa tidak perlu menambahkan titik koma di akhir `export class` dan `export function`: ```js export function sayHi(user) { alert(`Hello, ${user}!`); } *!* // tidak ada ; diakhir */!* ``` ```` ## Export selain dari deklarasi Kita juga dapat meletakkan perintah `export` secara terpisah. Di bawah ini kita mendeklarasikan terlebih dahulu, lalu kemudian melakukan ekspor: ```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); } function sayBye(user) { alert(`Bye, ${user}!`); } *!* export {sayHi, sayBye}; // daftar variabel yang diekspor */!* ``` ...Atau, secara teknis kita dapat meletakkan `export` diatas fungsi juga. ## Import \* Biasanya, kita membuat daftar apa yang akan kita impor di dalam kurung kurawal `import {...}`, seperti ini: ```js // 📁 main.js *!* import {sayHi, sayBye} from './say.js'; */!* sayHi('John'); // Halo, John! sayBye('John'); // Selamat tinggal, John! ``` Tetapi jika ada banyak yang harus diimpor, kita dapat melakukan impor semuanya sebagai sebuah objek menggunakan `import * as `. Sebagai contoh: ```js // 📁 main.js *!* import * as say from './say.js'; */!* say.sayHi('John'); say.sayBye('John'); ``` Kesan pertama terkait "impor semuanya" adalah terdengar seperti sesuatu yang keren dan singkat ketika ditulis. Mengapa kita harus secara eksplisit membuat daftar apa yang perlu kita impor? Jadi, ini adalah beberapa alasannya. 1. Perkakas penggabung yang modern ([webpack](http://webpack.github.io) dan lainnya) menggabungkan semua modul sekaligus dan mengoptimalkannya untuk mempercepat proses pemuatan dan menghapus modul yang tidak digunakan. Katakanlah kita menambahkan sebuah pustaka pihak ketiga `say.js` ke dalam proyek dengan banyak fungsi: ```js // 📁 say.js export function sayHi() { ... } export function sayBye() { ... } export function becomeSilent() { ... } ``` Sekarang jika kita hanya menggunakan salah satu dari fungsi `say.js` di proyek kita: ```js // 📁 main.js import { sayHi } from './say.js'; ``` ...Maka kemudian ketika proses pengoptimalan berjalan akan melihatnya dan menghapus fungsi lainnya (yang tidak digunakan) dari kode yang digabungkan, ini membuat kode hasil penggabungan lebih kecil. Itulah yang disebut _"tree-shaking"_ 2. Mendaftarkan secara eksplisit apa yang akan diimpor dengan nama yang lebih pendek: `sayHi()` sebagai ganti dari `say.sayHi()`. 3. Daftar import eksplisit memberikan gambaran yang lebih baik tentang struktur kode: apa yang digunakan dan dimana. Itu membuat dukungan kode dan proses refactoring lebih mudah. ## Import "as" Kita juga dapat menggunakan `as` untuk mengimpor dengan nama yang berbeda. Sebagai contoh, cobalah impor `sayHi` ke dalam variabel lokal `hi` agar lebih singkat, dan impor `sayBye` sebagai `bye`: ```js // 📁 main.js *!* import {sayHi as hi, sayBye as bye} from './say.js'; */!* hi('John'); // Halo, John! bye('John'); // Selamat Tinggal, John! ``` ## Export "as" Sintaks serupa berlaku juga untuk `export` Mari ekspor fungsi sebagai `hi` dan `bye`: ```js // 📁 say.js ... export {sayHi as hi, sayBye as bye}; ``` Sekarang `hi` dan `bye` adalah nama resmi yang diekspor dan kemudian dapat digunakan dalam impor: ```js // 📁 main.js import * as say from './say.js'; say.*!*hi*/!*('John'); // Halo, John! say.*!*bye*/!*('John'); // Selamat Tinggal, John! ``` ## Export default Dalam praktiknya, terdapat dua jenis modul. 1. Modul yang berisi pustaka, paket fungsi, seperti `say.js` diatas. 2. Modul yang mendeklarasikan sebuah entitas, misalnya modul `user.js` hanya mengekspor `class User`. Kebanyakan pendekatan kedua yang lebih disukai, jadi untuk setiap `benda` berada dalam modulnya sendiri. Tentu itu membutuhkan banyak berkas, karena semuanya menginginkan modulnya sendiri. Tetapi itu tidak menjadi masalah sama sekali. Sebenarnya, navigasi kode menjadi lebih mudah jika berkas diberi nama dengan baik dan terstruktur didalam direktori. Modul menyediakan perintah khusus `export default` ("ekspor bawaan") untuk membuat cara "satu hal per modul" terlihat lebih baik. Tambahkan `export default` sebelum entitas yang akan diekspor: ```js // 📁 user.js export *!*default*/!* class User { // tambahkan saja "default" constructor(name) { this.name = name; } } ``` Mungkin hanya ada satu `export default` dalam setiap berkas: ...Dan kemudian impor tanpa menggunakan kurung kurawal: ```js // 📁 main.js import *!*User*/!* from './user.js'; // Bukan {User}, Hanya User new User('John'); ``` Impor tanpa kurung kurawal terlihat bagus. Kesalahan umum ketika memulai menggunakan modul adalah ketika sepenuhnya melupakan kurung kurawal. Jadi, ingat `import` memerlukan kurung kurawal untuk ekspor bernama dan tidak memerlukannya untuk ekspor bawaan. | Ekspor bernama | Ekspor bawaan | | ------------------------- | --------------------------------- | | `export class User {...}` | `export default class User {...}` | | `import {User} from ...` | `import User from ...` | Secara teknis, kita dapat memiliki keduanya (ekspor bawaan dan ekspor bernama) dalam satu modul yang sama, tetapi pada praktiknya, orang tidak mencampurnya. Sebuah modul memiliki antara ekspor bernama atau ekspor bawaan. Karena mungkin hanya ada paling banyak satu ekspor bawaan tiap berkas, entitas yang diekspor mungkin tidak memiliki nama. Misalnya, dibawah ini adalah ekspor bawaan yang benar-benar valid: ```js export default class { // tidak ada nama kelas constructor() { ... } } ``` ```js export default function (user) { // tidak ada nama fungsi alert(`Hello, ${user}!`); } ``` ```js // ekspor nilai tunggal tanpa membuat variabel export default ['Jan', 'Feb', 'Mar', 'Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; ``` Tidak memberikan nama tidak masalah, karena hanya ada satu `export default` setiap berkas, jadi `import` tanpa tanda kurung kurawal tahu apa yang perlu diimpor. Tanpa `default`, ekspor seperti itu akan memberikan sebuh _error_: ```js export class { // Error! (selain 'default export' memerlukan sebuah nama) constructor() {} } ``` ### Penamaan "default" Pada situasi tertentu kata kunci `default` digunakan untuk mereferensikan ekspor bawaan. Misalnya, untuk mengekspor fungsi yang terpisah dari tempat deklarasinya: ```js function sayHi(user) { alert(`Hello, ${user}!`); } // sama seperti jika menambahkan "export default" sebelum fungsi export { sayHi as default }; ``` Atau pada situasi yang lainnya, katakanlah sebuah modul `user.js` mengekspor satu hal utama "bawaan", dan beberapa yang dinamai (jarang terjadi, tetapi bisa saja terjadi): ```js // 📁 user.js export default class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); } ``` Berikut cara mengimpor ekspor bawaan bersama dengan yang ekspor bernama: ```js // 📁 main.js import {*!*default as User*/!*, sayHi} from './user.js'; new User('John'); ``` Dan terakhir, jika mengimpor semua `*` sebagai sebuah objek, maka properti `default` sama persis dengan ekspor bawaan: ```js // 📁 main.js import * as user from './user.js'; let User = user.default; // ekspor bawaan new User('John'); ``` ### Sebuah kata yang menentang ekspor bawaan Ekspor bernama eksplisit. Mereka secara persis menyebutkan apa yang mereka impor, jadi kita bisa mendapatkan informasi itu dari mereka; itu adalah sesuatu yang bagus. Ekspor bernama memaksa kita untuk menggunakan nama yang tepat untuk melakukan impor: ```js import { User } from './user.js'; // impor {MyUser} tidak akan berfungsi, penamaannya haruss {User} ``` ...Sedangkan untuk ekspor bawaan, kita selalu dapat memilih nama ketika mengimpor: ```js import User from './user.js'; // berfungsi import MyUser from './user.js'; // berfungsi juga // Dapat melakukan impor apa saja... dan itu tetap berfungsi ``` Kemungkinan anggota tim menggunakan penamaan yang berbeda untuk mengimpor hal yang sama, dan itu tidak baik. Biasanya, untuk menghindari hal tersebut dan menjaga konsistensi kode terdapat aturan bahwa variabel yang di impor harus sesuai dengan nama berkas, misalnya: ```js import User from './user.js'; import LoginForm from './loginForm.js'; import func from '/path/to/func.js'; ... ``` Namun, beberapa tim menganggapnya sebagai kelemahan serius dari ekspor default. Jadi, mereka lebih selalu suka menggunakan ekspor bernama. Meskipun hanya satu hal yang diekspor, itu masih diekspor dengan nama, tanpa `default`. Itu juga membuat ekspor ulang (lihat di bawah) sedikit lebih mudah. ## Ekspor Ulang "Ekspor ulang" sintaks `export ... from ...` memperbolehkann untuk mengimpor sesuatu dan segera mengekspornya kembali (memungkinkan dengan nama yang berbeda) seperti ini: ```js export { sayHi } from './say.js'; // ekspor ulang sayHi export { default as User } from './user.js'; // ekspor ulang bawaan ``` Kenapa hal tersebut diperlukan? mari lihat praktik penggunaannya. Bayangkan kita menulis sebuah "paket": sebuah direktori dengan banyak modul, dengan beberapa fungsi yang diekpor ke luar (perkakas seperti NPM memungkinkan kita untuk menerbitkan dan mendistribusikan paket seperti itu), dan kebanyakan modul hanyalah "pembantu" untuk penggunaan internal di paket modul lainnya. Struktur berkas bisa seperti ini: ``` auth/ index.js user.js helpers.js tests/ login.js providers/ github.js facebook.js ... ``` Kita ingin menunjukkan fungsionalitas paket melalui satu titik masuk, "Berkas utama" `auth/index.js` dapat digunakan seperti ini. ```js import { login, logout } from 'auth/index.js'; ``` Idenya adalah bahwa orang luar (pengembang) yang menggunakan paket kita tidak boleh ikut campur dengan struktur internalnya, serta mencari berkas didalam direktori paket kita. Kita hanya mengekspor apa yang penting di dalam `auth/index.js` dan menyembunyikan sisaya dari pengintaian. Karena fungsionalitas yang diekspor sebenarnya tersebar diantara paket, kita dapat mengimpornya ke dalam `auth/index.js` dan kemudian kembali mengekspornya: ```js // 📁 auth/index.js // impor login/logout dan kemudian segera mengekspornya kembali import {login, logout} from './helpers.js'; export {login, logout}; // impor default sebagai User kemudian mengekspornya import User from './user.js'; export {User}; ... ``` Sekarang pengguna dari paket kita dapat `import { login }` dari `"auth/index.js"`. Sintaks `export ... from ...` hanyalah notasi pendek untuk proses impor-ekspor. ```js // 📁 auth/index.js // impor login/logout dan kemudian segera mengekspornya kembali export {login, logout} from './helpers.js'; // impor default sebagai User kemudian mengekspornya export {default as User} from './user.js'; ... ``` ### Ekspor ulang ekspor bawaan Ekspor bawaan memerlukan penanganan terpisah ketika melakukan ekspor ulang. Misalnya kita memiliki `user.js` dan kita ingin melakukan ekspor ulang kelas `User` tersebut: ```js // 📁 user.js export default class User { // ... } ``` 1. `export User form './user.js'` tidak dapat digunakan. Apa yang salah?... Ini adalah kesalahan sintaks! Untuk melakukan ekspor ulang ekspor bawaan, kita harus menuliskan `export { default as User }` seperti contoh diatas. 2. `export * from './user.js'` mengekspor ulang hanya ekspor bernama, tetapi mengabaikan ekspor bawaan. Jika kita ingin melakukan ekspor ulang keduanya (ekspor bernama dan ekspor bawaan), maka diperlukan dua pernyataan seperti berikut: ```js export * from './user.js'; // ekspor ulang ekspor bernama export { default } from './user.js'; // ekspor ulang ekspor bawaan ``` Keanehan dari proses mengekspor ulang ekspor bawaan adalah salah satu alasan mengapa beberapa pengembang tidak menyukainya. ## Ringkasan Berikut merupakan semua jenis `export` yang kita bahas di artikel ini dan sebelumnya. Kamu dapat mengeceknya secara mandiri dengan membacanya dan mengingat apa maksudnya: Ekspor: - Sebelum deklarasi dari sebuah kelas/fungsi/..: - `export [default] class/function/variable ...` - Ekspor mandiri: - `export { x [as y], ... } from "module"` - Ekspor ulang: - `export {x [as y], ...} from "module"` - `export * from "module"` (tidak mengekspor ulang bawaan). - `export {default [as y]} from "module"` (ekspor ulang bawaan). Impor: - Ekspor bernama dari modul: - `import {x [as y], ...} from "module"` - Ekspor bawaan: - `import x from "module"` - `import {default as x} from "module"` - Semuanya: - `import * as obj from "module"` - Impor modul (ini menjalankan kode), tetapi tidak disimpan kedalam variabel: - `import "module"` Kita dapat meletakkan pernyataan `import/export` di bagian atas atau bawah dari skrip, itu tidak masalah. Jadi secara teknis kode ini tidak dipermasalahkan: ```js sayHi(); // ... import { sayHi } from './say.js'; // impor dibagian bawah berkas ``` Dalam praktiknya, impor biasanya dilakukan pada awal sebuah berkas, tetapi itu hanya untuk memberikan kenyamanan lebih. **Harap diperhatikan bahwa pernyataan import/export tidak dapat digunakan jika didalam `{...}`.** Sebuah impor bersyarat seperti ini tidak akan dapat berfungsi: ```js if (something) { import { sayHi } from './say.js'; // Error: import must be at top level (impor harus diluar pernyataan if dann { ... } sehingga kode tersebut tidak akan bekerja) } ``` ...Tetapi bagaimana jika kita benar-benar perlu menhimpor sesuatu dengan syarat tertentu? atau pada waktu yang tepat? Seperti memuat modul berdasarkan permintaan, pada saat itu benar-benar dibutuhkan? Oleh karena itu, kita akan mempelajari impor dinamis di artikel selanjutnya. ================================================ FILE: 1-js/13-modules/03-modules-dynamic-imports/article.md ================================================ # Impor dinamis Pernyataan ekspor dan impor yang kita bahas di bab sebelumnya disebut "statis". Sintaksnya sangat sederhana dan bersifat _strict_. Pertama, kita tidak bisa membuat parameter `import` secara dinamis. Jalur modul harus berupa _string_, tidak boleh berupa _function_ panggilan. Berikut contoh yang tidak akan berhasil: ```js import ... from *!*getModuleName()*/!*; // Error, hanya "string" yang diperbolehkan ``` Kedua, kita tidak bisa meng-impor secara kondisional atau pada saat _run-time_: ```js if(...) { import ...; // Error, tidak diperbolehkan! } { import ...; // Error, kita tidak bisa meng-impor di dalam block-scope } ``` Itu karena `import`/`export` bertujuan untuk menyediakan tulang punggung untuk struktur kode. Itu hal yang baik, karena struktur kode dapat dianalisa, modul dapat dikumpulkan dan digabungkan menjadi satu _file_ dengan alat khusus, ekspor yang tidak digunakan dapat dihapus ("tree-shaken"). Itu memungkinkan hanya karena struktur dari impor/ekspor sederhana dan tetap. Tetapi bagaimana kita bisa meng-impor modul secara dinamis, sesuai permintaan? ## Ekspresi `import()` Ekspresi `import(modul)` memuat modul dan mengembalikan sebuah _promise_ yang diselesaikan menjadi objek modul yang berisi semua ekspornya. Itu dapat dipanggil dari mana saja dalam kode. Kita bisa menggunakannya secara dinamis di sembarang tempat kode, misalnya: ```js let modulePath = prompt("Modul mana yang ingin dimuat?"); import(modulePath) .then(obj => ) .catch(err => ) ``` Atau, kita bisa menggunakan `let module = await import(modulePath)` jika di dalam _async function_. Misalnya, jika kita memiliki modul berikut `say.js`: ```js // 📁 say.js export function hi() { alert(`Halo`); } export function bye() { alert(`Selamat tinggal`); } ``` ...Kemudian impor dinamisnya bisa seperti ini: ```js let { hi, bye } = await import("./say.js"); hi(); bye(); ``` Atau, kalau `say.js` mempunyai ekspor _default_: ```js // 📁 say.js export default function () { alert("Modul dimuat (ekspor default)!"); } ``` ...Kemudian, untuk mengaksesnya, kita bisa menggunakan properti `default` dari objek modul: ```js let obj = await import("./say.js"); let say = obj.default; // jika dalam satu baris: let {default: say} = await import('./say.js'); say(); ``` Berikut contoh lengkapnya: [codetabs src="say" current="index.html"] ```smart Impor dinamis berfungsi dalam skrip biasa, mereka tidak memerlukan `script type="module"`. ``` ```smart Meskipun `import()` terlihat seperti pemanggilan sebuah function, akan tetapi itu adalah sintaks khusus yang kebetulan menggunakan tanda kurung (mirip dengan `super()`). Jadi, kita tidak bisa menyalin `import` ke dalam variabel atau menggunakan `call/apply` dengannya. `import` bukan sebuah function. ``` ================================================ 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(`Halo`); } export function bye() { alert(`Selamat tinggal`); } export default function () { alert("Modul dimuat (ekspor 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 ================================================ # Error on reading non-existent property Usually, an attempt to read a non-existent property returns `undefined`. Create a proxy that throws an error for an attempt to read of a non-existent property instead. That can help to detect programming mistakes early. Write a function `wrap(target)` that takes an object `target` and return a proxy that adds this functionality aspect. That's how it should work: ```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: Property doesn't exist: "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) { // even if we access it like arr[1] // prop is a string, so need to convert it to number 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 ================================================ # Accessing array[-1] In some programming languages, we can access array elements using negative indexes, counted from the end. Like this: ```js let array = [1, 2, 3]; array[-1]; // 3, the last element array[-2]; // 2, one step from the end array[-3]; // 1, two steps from the end ``` In other words, `array[-N]` is the same as `array[array.length - N]`. Create a proxy to implement that behavior. That's how it should work: ```js let array = [1, 2, 3]; array = new Proxy(array, { /* your code */ }); alert( array[-1] ); // 3 alert( array[-2] ); // 2 // Other array functionality should be kept "as is" ``` ================================================ FILE: 1-js/99-js-misc/01-proxy/03-observable/solution.md ================================================ The solution consists of two parts: 1. Whenever `.observe(handler)` is called, we need to remember the handler somewhere, to be able to call it later. We can store handlers right in the object, using our symbol as the property key. 2. We need a proxy with `set` trap to call handlers in case of any change. ```js run let handlers = Symbol('handlers'); function makeObservable(target) { // 1. Initialize handlers store target[handlers] = []; // Store the handler function in array for future calls target.observe = function(handler) { this[handlers].push(handler); }; // 2. Create a proxy to handle changes return new Proxy(target, { set(target, property, value, receiver) { let success = Reflect.set(...arguments); // forward the operation to object if (success) { // if there were no error while setting the property // call all handlers 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 Create a function `makeObservable(target)` that "makes the object observable" by returning a proxy. Here's how it should work: ```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 ``` In other words, an object returned by `makeObservable` is just like the original one, but also has the method `observe(handler)` that sets `handler` function to be called on any property change. Whenever a property changes, `handler(key, value)` is called with the name and value of the property. P.S. In this task, please only take care about writing to a property. Other operations can be implemented in a similar way. ================================================ FILE: 1-js/99-js-misc/01-proxy/article.md ================================================ # Proxy and Reflect A `Proxy` object wraps another object and intercepts operations, like reading/writing properties and others, optionally handling them on its own, or transparently allowing the object to handle them. Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this article. ## Proxy The syntax: ```js let proxy = new Proxy(target, handler) ``` - `target` -- is an object to wrap, can be anything, including functions. - `handler` -- proxy configuration: an object with "traps", methods that intercept operations. - e.g. `get` trap for reading a property of `target`, `set` trap for writing a property into `target`, and so on. For operations on `proxy`, if there's a corresponding trap in `handler`, then it runs, and the proxy has a chance to handle it, otherwise the operation is performed on `target`. As a starting example, let's create a proxy without any traps: ```js run let target = {}; let proxy = new Proxy(target, {}); // empty handler proxy.test = 5; // writing to proxy (1) alert(target.test); // 5, the property appeared in target! alert(proxy.test); // 5, we can read it from proxy too (2) for(let key in proxy) alert(key); // test, iteration works (3) ``` As there are no traps, all operations on `proxy` are forwarded to `target`. 1. A writing operation `proxy.test=` sets the value on `target`. 2. A reading operation `proxy.test` returns the value from `target`. 3. Iteration over `proxy` returns values from `target`. As we can see, without any traps, `proxy` is a transparent wrapper around `target`. ![](proxy.svg) `Proxy` is a special "exotic object". It doesn't have own properties. With an empty `handler` it transparently forwards operations to `target`. To activate more capabilities, let's add traps. What can we intercept with them? For most operations on objects, there's a so-called "internal method" in the JavaScript specification that describes how it works at the lowest level. For instance `[[Get]]`, the internal method to read a property, `[[Set]]`, the internal method to write a property, and so on. These methods are only used in the specification, we can't call them directly by name. Proxy traps intercept invocations of these methods. They are listed in the [Proxy specification](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots) and in the table below. For every internal method, there's a trap in this table: the name of the method that we can add to the `handler` parameter of `new Proxy` to intercept the operation: | Internal Method | Handler Method | Triggers when... | |-----------------|----------------|-------------| | `[[Get]]` | `get` | reading a property | | `[[Set]]` | `set` | writing to a property | | `[[HasProperty]]` | `has` | `in` operator | | `[[Delete]]` | `deleteProperty` | `delete` operator | | `[[Call]]` | `apply` | function call | | `[[Construct]]` | `construct` | `new` operator | | `[[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 enforces some invariants -- conditions that must be fulfilled by internal methods and traps. Most of them are for return values: - `[[Set]]` must return `true` if the value was written successfully, otherwise `false`. - `[[Delete]]` must return `true` if the value was deleted successfully, otherwise `false`. - ...and so on, we'll see more in examples below. There are some other invariants, like: - `[[GetPrototypeOf]]`, applied to the proxy object must return the same value as `[[GetPrototypeOf]]` applied to the proxy object's target object. In other words, reading prototype of a proxy must always return the prototype of the target object. Traps can intercept these operations, but they must follow these rules. Invariants ensure correct and consistent behavior of language features. The full invariants list is in [the specification](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). You probably won't violate them if you're not doing something weird. ``` Let's see how that works in practical examples. ## Default value with "get" trap The most common traps are for reading/writing properties. To intercept reading, the `handler` should have a method `get(target, property, receiver)`. It triggers when a property is read, with following arguments: - `target` -- is the target object, the one passed as the first argument to `new Proxy`, - `property` -- property name, - `receiver` -- if the target property is a getter, then `receiver` is the object that's going to be used as `this` in its call. Usually that's the `proxy` object itself (or an object that inherits from it, if we inherit from proxy). Right now we don't need this argument, so it will be explained in more detail later. Let's use `get` to implement default values for an object. We'll make a numeric array that returns `0` for nonexistent values. Usually when one tries to get a non-existing array item, they get `undefined`, but we'll wrap a regular array into the proxy that traps reading and returns `0` if there's no such property: ```js run let numbers = [0, 1, 2]; numbers = new Proxy(numbers, { get(target, prop) { if (prop in target) { return target[prop]; } else { return 0; // default value } } }); *!* alert( numbers[1] ); // 1 alert( numbers[123] ); // 0 (no such item) */!* ``` As we can see, it's quite easy to do with a `get` trap. We can use `Proxy` to implement any logic for "default" values. Imagine we have a dictionary, with phrases and their translations: ```js run let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome'] ); // undefined ``` Right now, if there's no phrase, reading from `dictionary` returns `undefined`. But in practice, leaving a phrase untranslated is usually better than `undefined`. So let's make it return an untranslated phrase in that case instead of `undefined`. To achieve that, we'll wrap `dictionary` in a proxy that intercepts reading operations: ```js run let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; dictionary = new Proxy(dictionary, { *!* get(target, phrase) { // intercept reading a property from dictionary */!* if (phrase in target) { // if we have it in the dictionary return target[phrase]; // return the translation } else { // otherwise, return the non-translated phrase return phrase; } } }); // Look up arbitrary phrases in the dictionary! // At worst, they're not translated. alert( dictionary['Hello'] ); // Hola *!* alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (no translation) */!* ``` ````smart Please note how the proxy overwrites the variable: ```js dictionary = new Proxy(dictionary, ...); ``` The proxy should totally replace the target object everywhere. No one should ever reference the target object after it got proxied. Otherwise it's easy to mess up. ```` ## Validation with "set" trap Let's say we want an array exclusively for numbers. If a value of another type is added, there should be an error. The `set` trap triggers when a property is written. `set(target, property, value, receiver)`: - `target` -- is the target object, the one passed as the first argument to `new Proxy`, - `property` -- property name, - `value` -- property value, - `receiver` -- similar to `get` trap, matters only for setter properties. The `set` trap should return `true` if setting is successful, and `false` otherwise (triggers `TypeError`). Let's use it to validate new values: ```js run let numbers = []; numbers = new Proxy(numbers, { // (*) *!* set(target, prop, val) { // to intercept property writing */!* if (typeof val == 'number') { target[prop] = val; return true; } else { return false; } } }); numbers.push(1); // added successfully numbers.push(2); // added successfully alert("Length is: " + numbers.length); // 2 *!* numbers.push("test"); // TypeError ('set' on proxy returned false) */!* alert("This line is never reached (error in the line above)"); ``` Please note: the built-in functionality of arrays is still working! Values are added by `push`. The `length` property auto-increases when values are added. Our proxy doesn't break anything. We don't have to override value-adding array methods like `push` and `unshift`, and so on, to add checks in there, because internally they use the `[[Set]]` operation that's intercepted by the proxy. So the code is clean and concise. ```warn header="Don't forget to return `true`" As said above, there are invariants to be held. For `set`, it must return `true` for a successful write. If we forget to do it or return any falsy value, the operation triggers `TypeError`. ``` ## Iteration with "ownKeys" and "getOwnPropertyDescriptor" `Object.keys`, `for..in` loop and most other methods that iterate over object properties use `[[OwnPropertyKeys]]` internal method (intercepted by `ownKeys` trap) to get a list of properties. Such methods differ in details: - `Object.getOwnPropertyNames(obj)` returns non-symbol keys. - `Object.getOwnPropertySymbols(obj)` returns symbol keys. - `Object.keys/values()` returns non-symbol keys/values with `enumerable` flag (property flags were explained in the article ). - `for..in` loops over non-symbol keys with `enumerable` flag, and also prototype keys. ...But all of them start with that list. In the example below we use `ownKeys` trap to make `for..in` loop over `user`, and also `Object.keys` and `Object.values`, to skip properties starting with an underscore `_`: ```js run let user = { name: "John", age: 30, _password: "***" }; user = new Proxy(user, { *!* ownKeys(target) { */!* return Object.keys(target).filter(key => !key.startsWith('_')); } }); // "ownKeys" filters out _password for(let key in user) alert(key); // name, then: age // same effect on these methods: alert( Object.keys(user) ); // name,age alert( Object.values(user) ); // John,30 ``` So far, it works. Although, if we return a key that doesn't exist in the object, `Object.keys` won't list it: ```js run let user = { }; user = new Proxy(user, { *!* ownKeys(target) { */!* return ['a', 'b', 'c']; } }); alert( Object.keys(user) ); // ``` Why? The reason is simple: `Object.keys` returns only properties with the `enumerable` flag. To check for it, it calls the internal method `[[GetOwnProperty]]` for every property to get [its descriptor](info:property-descriptors). And here, as there's no property, its descriptor is empty, no `enumerable` flag, so it's skipped. For `Object.keys` to return a property, we need it to either exist in the object, with the `enumerable` flag, or we can intercept calls to `[[GetOwnProperty]]` (the trap `getOwnPropertyDescriptor` does it), and return a descriptor with `enumerable: true`. Here's an example of that: ```js run let user = { }; user = new Proxy(user, { ownKeys(target) { // called once to get a list of properties return ['a', 'b', 'c']; }, getOwnPropertyDescriptor(target, prop) { // called for every property return { enumerable: true, configurable: true /* ...other flags, probable "value:..." */ }; } }); alert( Object.keys(user) ); // a, b, c ``` Let's note once again: we only need to intercept `[[GetOwnProperty]]` if the property is absent in the object. ## Protected properties with "deleteProperty" and other traps There's a widespread convention that properties and methods prefixed by an underscore `_` are internal. They shouldn't be accessed from outside the object. Technically that's possible though: ```js run let user = { name: "John", _password: "secret" }; alert(user._password); // secret ``` Let's use proxies to prevent any access to properties starting with `_`. We'll need the traps: - `get` to throw an error when reading such property, - `set` to throw an error when writing, - `deleteProperty` to throw an error when deleting, - `ownKeys` to exclude properties starting with `_` from `for..in` and methods like `Object.keys`. Here's the 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) { // to intercept property writing */!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { target[prop] = val; return true; } }, *!* deleteProperty(target, prop) { // to intercept property deletion */!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { delete target[prop]; return true; } }, *!* ownKeys(target) { // to intercept property list */!* return Object.keys(target).filter(key => !key.startsWith('_')); } }); // "get" doesn't allow to read _password try { alert(user._password); // Error: Access denied } catch(e) { alert(e.message); } // "set" doesn't allow to write _password try { user._password = "test"; // Error: Access denied } catch(e) { alert(e.message); } // "deleteProperty" doesn't allow to delete _password try { delete user._password; // Error: Access denied } catch(e) { alert(e.message); } // "ownKeys" filters out _password for(let key in user) alert(key); // name ``` Please note the important detail in the `get` trap, in the line `(*)`: ```js get(target, prop) { // ... let value = target[prop]; *!* return (typeof value === 'function') ? value.bind(target) : value; // (*) */!* } ``` Why do we need a function to call `value.bind(target)`? The reason is that object methods, such as `user.checkPassword()`, must be able to access `_password`: ```js user = { // ... checkPassword(value) { // object method must be able to read _password return value === this._password; } } ``` A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. So we bind the context of object methods to the original object, `target`, in the line `(*)`. Then their future calls will use `target` as `this`, without any traps. That solution usually works, but isn't ideal, as a method may pass the unproxied object somewhere else, and then we'll get messed up: where's the original object, and where's the proxied one? Besides, an object may be proxied multiple times (multiple proxies may add different "tweaks" to the object), and if we pass an unwrapped object to a method, there may be unexpected consequences. So, such a proxy shouldn't be used everywhere. ```smart header="Private properties of a class" Modern JavaScript engines natively support private properties in classes, prefixed with `#`. They are described in the article . No proxies required. Such properties have their own issues though. In particular, they are not inherited. ``` ## "In range" with "has" trap Let's see more examples. We have a range object: ```js let range = { start: 1, end: 10 }; ``` We'd like to use the `in` operator to check that a number is in `range`. The `has` trap intercepts `in` calls. `has(target, property)` - `target` -- is the target object, passed as the first argument to `new Proxy`, - `property` -- property name Here's the demo: ```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 */!* ``` Nice syntactic sugar, isn't it? And very simple to implement. ## Wrapping functions: "apply" [#proxy-apply] We can wrap a proxy around a function as well. The `apply(target, thisArg, args)` trap handles calling a proxy as function: - `target` is the target object (function is an object in JavaScript), - `thisArg` is the value of `this`. - `args` is a list of arguments. For example, let's recall `delay(f, ms)` decorator, that we did in the article . In that article we did it without proxies. A call to `delay(f, ms)` returned a function that forwards all calls to `f` after `ms` milliseconds. Here's the previous, function-based implementation: ```js run function delay(f, ms) { // return a wrapper that passes the call to f after the timeout return function() { // (*) setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } // after this wrapping, calls to sayHi will be delayed for 3 seconds sayHi = delay(sayHi, 3000); sayHi("John"); // Hello, John! (after 3 seconds) ``` As we've seen already, that mostly works. The wrapper function `(*)` performs the call after the timeout. But a wrapper function does not forward property read/write operations or anything else. After the wrapping, the access is lost to properties of the original functions, such as `name`, `length` and others: ```js run function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } *!* alert(sayHi.length); // 1 (function length is the arguments count in its declaration) */!* sayHi = delay(sayHi, 3000); *!* alert(sayHi.length); // 0 (in the wrapper declaration, there are zero arguments) */!* ``` `Proxy` is much more powerful, as it forwards everything to the target object. Let's use `Proxy` instead of a wrapping function: ```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 (*) proxy forwards "get length" operation to the target */!* sayHi("John"); // Hello, John! (after 3 seconds) ``` The result is the same, but now not only calls, but all operations on the proxy are forwarded to the original function. So `sayHi.length` is returned correctly after the wrapping in the line `(*)`. We've got a "richer" wrapper. Other traps exist: the full list is in the beginning of this article. Their usage pattern is similar to the above. ## Reflect `Reflect` is a built-in object that simplifies creation of `Proxy`. It was said previously that internal methods, such as `[[Get]]`, `[[Set]]` and others are specification-only, they can't be called directly. The `Reflect` object makes that somewhat possible. Its methods are minimal wrappers around the internal methods. Here are examples of operations and `Reflect` calls that do the same: | Operation | `Reflect` call | Internal method | |-----------------|----------------|-------------| | `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]]` | | ... | ... | ... | For example: ```js run let user = {}; Reflect.set(user, 'name', 'John'); alert(user.name); // John ``` In particular, `Reflect` allows us to call operators (`new`, `delete`...) as functions (`Reflect.construct`, `Reflect.deleteProperty`, ...). That's an interesting capability, but here another thing is important. **For every internal method, trappable by `Proxy`, there's a corresponding method in `Reflect`, with the same name and arguments as the `Proxy` trap.** So we can use `Reflect` to forward an operation to the original object. In this example, both traps `get` and `set` transparently (as if they didn't exist) forward reading/writing operations to the object, showing a 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; // shows "GET name" user.name = "Pete"; // shows "SET name=Pete" ``` Here: - `Reflect.get` reads an object property. - `Reflect.set` writes an object property and returns `true` if successful, `false` otherwise. That is, everything's simple: if a trap wants to forward the call to the object, it's enough to call `Reflect.` with the same arguments. In most cases we can do the same without `Reflect`, for instance, reading a property `Reflect.get(target, prop, receiver)` can be replaced by `target[prop]`. There are important nuances though. ### Proxying a getter Let's see an example that demonstrates why `Reflect.get` is better. And we'll also see why `get/set` have the third argument `receiver`, that we didn't use before. We have an object `user` with `_name` property and a getter for it. Here's a proxy around it: ```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 ``` The `get` trap is "transparent" here, it returns the original property, and doesn't do anything else. That's enough for our example. Everything seems to be all right. But let's make the example a little bit more complex. After inheriting another object `admin` from `user`, we can observe the incorrect behavior: ```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" }; // Expected: Admin alert(admin.name); // outputs: Guest (?!?) */!* ``` Reading `admin.name` should return `"Admin"`, not `"Guest"`! What's the matter? Maybe we did something wrong with the inheritance? But if we remove the proxy, then everything will work as expected. The problem is actually in the proxy, in the line `(*)`. 1. When we read `admin.name`, as `admin` object doesn't have such own property, the search goes to its prototype. 2. The prototype is `userProxy`. 3. When reading `name` property from the proxy, its `get` trap triggers and returns it from the original object as `target[prop]` in the line `(*)`. A call to `target[prop]`, when `prop` is a getter, runs its code in the context `this=target`. So the result is `this._name` from the original object `target`, that is: from `user`. To fix such situations, we need `receiver`, the third argument of `get` trap. It keeps the correct `this` to be passed to a getter. In our case that's `admin`. How to pass the context for a getter? For a regular function we could use `call/apply`, but that's a getter, it's not "called", just accessed. `Reflect.get` can do that. Everything will work right if we use it. Here's the corrected variant: ```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 */!* ``` Now `receiver` that keeps a reference to the correct `this` (that is `admin`), is passed to the getter using `Reflect.get` in the line `(*)`. We can rewrite the trap even shorter: ```js get(target, prop, receiver) { return Reflect.get(*!*...arguments*/!*); } ``` `Reflect` calls are named exactly the same way as traps and accept the same arguments. They were specifically designed this way. So, `return Reflect...` provides a safe no-brainer to forward the operation and make sure we don't forget anything related to that. ## Proxy limitations Proxies provide a unique way to alter or tweak the behavior of the existing objects at the lowest level. Still, it's not perfect. There are limitations. ### Built-in objects: Internal slots Many built-in objects, for example `Map`, `Set`, `Date`, `Promise` and others make use of so-called "internal slots". These are like properties, but reserved for internal, specification-only purposes. For instance, `Map` stores items in the internal slot `[[MapData]]`. Built-in methods access them directly, not via `[[Get]]/[[Set]]` internal methods. So `Proxy` can't intercept that. Why care? They're internal anyway! Well, here's the issue. After a built-in object like that gets proxied, the proxy doesn't have these internal slots, so built-in methods will fail. For example: ```js run let map = new Map(); let proxy = new Proxy(map, {}); *!* proxy.set('test', 1); // Error */!* ``` Internally, a `Map` stores all data in its `[[MapData]]` internal slot. The proxy doesn't have such a slot. The [built-in method `Map.prototype.set`](https://tc39.es/ecma262/#sec-map.prototype.set) method tries to access the internal property `this.[[MapData]]`, but because `this=proxy`, can't find it in `proxy` and just fails. Fortunately, there's a way to fix it: ```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 (works!) ``` Now it works fine, because `get` trap binds function properties, such as `map.set`, to the target object (`map`) itself. Unlike the previous example, the value of `this` inside `proxy.set(...)` will be not `proxy`, but the original `map`. So when the internal implementation of `set` tries to access `this.[[MapData]]` internal slot, it succeeds. ```smart header="`Array` has no internal slots" A notable exception: built-in `Array` doesn't use internal slots. That's for historical reasons, as it appeared so long ago. So there's no such problem when proxying an array. ``` ### Private fields A similar thing happens with private class fields. For example, `getName()` method accesses the private `#name` property and breaks after proxying: ```js run class User { #name = "Guest"; getName() { return this.#name; } } let user = new User(); user = new Proxy(user, {}); *!* alert(user.getName()); // Error */!* ``` The reason is that private fields are implemented using internal slots. JavaScript does not use `[[Get]]/[[Set]]` when accessing them. In the call `getName()` the value of `this` is the proxied `user`, and it doesn't have the slot with private fields. Once again, the solution with binding the method makes it work: ```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 ``` That said, the solution has drawbacks, as explained previously: it exposes the original object to the method, potentially allowing it to be passed further and breaking other proxied functionality. ### Proxy != target The proxy and the original object are different objects. That's natural, right? So if we use the original object as a key, and then proxy it, then the proxy can't be found: ```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 */!* ``` As we can see, after proxying we can't find `user` in the set `allUsers`, because the proxy is a different object. ```warn header="Proxies can't intercept a strict equality test `===`" Proxies can intercept many operators, such as `new` (with `construct`), `in` (with `has`), `delete` (with `deleteProperty`) and so on. But there's no way to intercept a strict equality test for objects. An object is strictly equal to itself only, and no other value. So all operations and built-in classes that compare objects for equality will differentiate between the object and the proxy. No transparent replacement here. ``` ## Revocable proxies A *revocable* proxy is a proxy that can be disabled. Let's say we have a resource, and would like to close access to it any moment. What we can do is to wrap it into a revocable proxy, without any traps. Such a proxy will forward operations to object, and we can disable it at any moment. The syntax is: ```js let {proxy, revoke} = Proxy.revocable(target, handler) ``` The call returns an object with the `proxy` and `revoke` function to disable it. Here's an example: ```js run let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); // pass the proxy somewhere instead of object... alert(proxy.data); // Valuable data // later in our code revoke(); // the proxy isn't working any more (revoked) alert(proxy.data); // Error ``` A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around while leaving `revoke` in the current scope. We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`. Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy: ```js run *!* let revokes = new WeakMap(); */!* let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); // ..somewhere else in our code.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Error (revoked) ``` We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more. ## References - Specification: [Proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). - MDN: [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). ## Summary `Proxy` is a wrapper around an object, that forwards operations on it to the object, optionally trapping some of them. It can wrap any kind of object, including classes and functions. The syntax is: ```js let proxy = new Proxy(target, { /* traps */ }); ``` ...Then we should use `proxy` everywhere instead of `target`. A proxy doesn't have its own properties or methods. It traps an operation if the trap is provided, otherwise forwards it to `target` object. We can trap: - Reading (`get`), writing (`set`), deleting (`deleteProperty`) a property (even a non-existing one). - Calling a function (`apply` trap). - The `new` operator (`construct` trap). - Many other operations (the full list is at the beginning of the article and in the [docs](mdn:/JavaScript/Reference/Global_Objects/Proxy)). That allows us to create "virtual" properties and methods, implement default values, observable objects, function decorators and so much more. We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality. The [Reflect](mdn:/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects. Proxies have some limitations: - Built-in objects have "internal slots", access to those can't be proxied. See the workaround above. - The same holds true for private class fields, as they are internally implemented using slots. So proxied method calls must have the target object as `this` to access them. - Object equality tests `===` can't be intercepted. - Performance: benchmarks depend on an engine, but generally accessing a property using a simplest proxy takes a few times longer. In practice that only matters for some "bottleneck" objects though. ================================================ FILE: 1-js/99-js-misc/02-eval/1-eval-calculator/solution.md ================================================ Mari gunakan `eval` untuk menghitung rumus matematika: ```js demo run let expr = prompt("Type an arithmetic expression?", '2*3+2'); alert( eval(expr) ); ``` Pengguna dapat memasukkan teks atau kode apa pun. Untuk membuat semuanya aman dan membatasinya hanya untuk operasi aritmatika, kita dapat memeriksa `expr` menggunakan [_regular expression_](info:regular-expressions), sehingga hanya dapat berisi angka dan operator. ================================================ FILE: 1-js/99-js-misc/02-eval/1-eval-calculator/task.md ================================================ importance: 4 --- # Kalkulator-_eval_ Buatlah kalkulator yang meminta operasi aritmatika dan mengembalikan hasilnya. Tidak perlu memeriksa kebenaran operasi dalam tugas ini. Cukup evaluasi dan kembalikan hasilnya. [demo] ================================================ FILE: 1-js/99-js-misc/02-eval/article.md ================================================ # Eval: menjalankan kode dari _string_ Fungsi bawaan `eval` memungkinkan kita untuk menjalankan kode dari sebuah _string_. Sintaksnya adalah: ```js let result = eval(code); ``` Sebagai contoh: ```js run let code = 'alert("Halo")'; eval(code); // Halo ``` Sebuah kode yang berupa _string_ bisa panjang, berupa deklarasi fungsi, variabel dan lain-lain. Hasil dari `eval` adalah hasil dari peryataan terakhir. Sebagai contoh: ```js run let value = eval('1+1'); alert(value); // 2 ``` ```js run let value = eval('let i = 0; ++i'); alert(value); // 1 ``` Kode yang dievaluasi akan dieksekusi di lingkungan leksikal saat ini, sehingga dapat melihat variabel luar: ```js run no-beautify let a = 1; function f() { let a = 2; *!* eval('alert(a)'); // 2 */!* } f(); ``` Itu juga dapat mengubah variabel luar: ```js untrusted refresh run let x = 5; eval("x = 10"); alert(x); // 10, nilai diubah ``` Dalam mode ketat, `eval` memiliki lingkungan leksikal sendiri. Jadi, fungsi dan variabel yang dideklarasikan di dalam eval, tidak akan terlihat di luar: ```js untrusted refresh run // pengingat: dalam contoh yang dijalankan 'use strict' diaktifkan secara bawaan eval("let x = 5; function f() {}"); alert(typeof x); // undefined (tidak ada variabel) // fungsi f juga tidak terlihat ``` Tanpa `use strict`, `eval` tidak memiliki lingkungan leksikal sendiri, jadi kita akan melihat `x` dan `f` di luar. ## Menggunakan "eval" Dalam pemrograman modern `eval` jarang digunakan. Sering dikatakan bahwa "_eval is evil_" atau "`eval` itu jahat". Alasannya sederhana: dulu JavaScript adalah bahasa yang jauh lebih lemah, banyak hal yang hanya bisa dilakukan dengan `eval`. Tapi waktu itu telah berlalu satu dekade yang lalu. Sekarang, hampir tidak ada alasan untuk menggunakan `eval`. Jika seseorang menggunakannya, ada kemungkinan mereka dapat menggantinya dengan konstruksi bahasa modern atau [JavaScript Module](info:modules). Harap dicatat bahwa kemampuannya untuk mengakses variabel luar memiliki efek samping. _Code minifiers_ (alat yang digunakan sebelum JS masuk ke produksi, untuk mengkompresnya) mengubah nama variabel lokal menjadi lebih pendek (seperti `a`, `b` dll) untuk membuat kode menjadi lebih kecil. Biasanya itu aman, tetapi tidak jika `eval` digunakan, karena variabel lokal dapat diakses dari kode yang dievaluasi dari _string_. Jadi _minifiers_ tidak melakukan itu untuk mengganti nama semua variabel yang terlihat dari `eval`. Itu berdampak negatif pada rasio kompresi kode. Menggunakan variabel lokal luar di dalam `eval` juga dianggap sebagai praktik pemrograman yang buruk, karena membuat kode lebih sulit dipertahankan. Ada dua cara untuk terhindar dan aman dari masalah seperti itu. **Jika kode yang dievaluasi tidak menggunakan variabel luar, panggil `eval` sebagai `window.eval(...)`:** Dengan cara ini, kode akan dijalankan dalam lingkup global: ```js untrusted refresh run let x = 1; { let x = 5; window.eval('alert(x)'); // 1 (variabel global) } ``` **Jika kode yang dievaluasi membutuhkan variabel lokal, ubah `eval` menjadi `new Function` dan teruskan sebagai argumen:** ```js run let f = new Function('a', 'alert(a)'); f(5); // 5 ``` Konstruksi `new Function` dijelaskan dalam bab . Itu membuat fungsi baru dari sebuah _string_ dan juga dalam lingkup global. Jadi tidak bisa melihat variabel lokal. Tetapi jauh lebih jelas jika meneruskannya sebagai argumen secara eksplisit, seperti pada contoh di atas. ## Ringkasan Pemanggilan `eval(code)` menjalankan kode dari sebuah _string_ dan mengembalikan hasil dari pernyataan terakhir. - Jarang digunakan dalam JavaScript modern, karena biasanya tidak diperlukan. - Dapat mengakses variabel lokal luar. Itu dianggap praktik yang buruk. - Sebaga gantinya, untuk `eval` sebuah kode dalam lingkup global, gunakan `window.eval(code)`. - Atau, jika kode Anda memerlukan beberapa data dari cakupan luar, gunakan `new Function` dan teruskan sebagai argumen. ================================================ FILE: 1-js/99-js-misc/03-currying-partials/article.md ================================================ libs: - lodash --- # _Currying_ [_Currying_](https://en.wikipedia.org/wiki/Currying) adalah teknik lanjutan dalam mengerjakan sebuah fungsi. _Currying_ tidak hanya digunakan di JavaScript tetapi dalam bahasa lain juga. _Currying_ adalah transformasi fungsi yang mengubah fungsi yang dipanggil sebagai `f(a, b, c)` menjadi `f(a)(b)(c)`. _Currying_ tidak memanggil suatu fungsi melainkan hanya mengubahnya. Mari kita lihat contoh terlebih dahulu untuk memahami apa yang akan kita bicarakan lalu kemudian mempraktikkannya. Kita akan membuat fungsi pembantu `curry(f)` yang melakukan _currying_ untuk dua argumen `f`. Dengan kata lain, `curry(f)` untuk dua argumen `f(a, b)` diubah menjadi fungsi yang dijalankan sebagai `f(a)(b)`: ```js run *!* function curry(f) { // curry(f) melakukan currying return function(a) { return function(b) { return f(a, b); }; }; } */!* // penggunan function sum(a, b) { return a + b; } let curriedSum = curry(sum); alert( curriedSum(1)(2) ); // 3 ``` Seperti yang Anda lihat, implementasinya cukup mudah: hanya membutuhkan dua pembungkus. - Hasil dari `curry(func)` adalah pembungkus `function(a)`. - Ketika dipanggil `curriedSum(1)`, argumen disimpan di lingkungan leksikal, dan pembungkus baru dikembalikan `function(b)`. - Kemudian pembungkus ini dipanggil dengan `2` sebagai argumen, dan ini meneruskan panggilan ke fungsi `sum` yang asli. Implementasi currying yang lebih lanjut, seperti [_.curry](https://lodash.com/docs#curry) dari _library_ lodash, mengembalikan pembungkus yang memungkinkan fungsi dipanggil secara normal maupun parsial: ```js run function sum(a, b) { return a + b; } let curriedSum = _.curry(sum); // menggunakan _.curry dari library lodash alert( curriedSum(1, 2) ); // 3, tetap bisa dijalankan secara normal alert( curriedSum(1)(2) ); // 3, secara parsial ``` ## _Currying_? Untuk apa? Untuk memahami manfaatnya kita membutuhkan contoh implementasi di dunia nyata. Sebaga contoh, kita memiliki fungsi pencatatan `log(date, importance, message)` yang memformat dan mengeluarkan informasi. Dalam proyek yang sebenarnya, fungsi seperti itu memiliki banyak fitur yang berguna seperti mengirim log melalui jaringan, disini kita akan menggunakan `alert`: ```js function log(date, importance, message) { alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`); } ``` Mari lakukan _currying_! ```js log = _.curry(log); ``` Setelah itu `log` berjalan normal: ```js log(new Date(), "DEBUG", "some debug"); // log(a, b, c) ``` ...Tetapi juga bekerja dalam bentuk _currying_: ```js log(new Date())("DEBUG")("some debug"); // log(a)(b)(c) ``` Sekarang kita dapat dengan mudah membuat fungsi untuk log saat ini: ```js // logNow akan menjadi bagian dari log dengan argumen pertama tetap let logNow = log(new Date()); // gunakan logNow("INFO", "pesan"); // [HH:mm] INFO pesan ``` Sekarang `logNow` adalah `log` dengan argumen pertama yang sudah ditentukan, dengan kata lain "fungsi yang diterapkan sebagian" atau singkatnya "parsial". Kita bisa melanjutkannya dan membuat fungsi untuk log _debug_ saat ini: ```js let debugNow = logNow("DEBUG"); debugNow("pesan"); // [HH:mm] DEBUG pesan ``` Jadi: 1. Kita tidak kehilangan apapun setelah melakukan _currying_: `log` tetap bisa dipanggil secara normal. 2. Kita dapat dengan mudah membuat fungsi parsial seperti "log untuk hari ini". ## Implementasi _currying_ lanjutan Jika Anda ingin mengetahui lebih detail, berikut implementasi _currying_ "lanjutan" untuk fungsi multi-argumen yang dapat kita gunakan di atas. Itu cukup pendek: ```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)); } } }; } ``` Contoh penggunaan: ```js function sum(a, b, c) { return a + b + c; } let curriedSum = curry(sum); alert( curriedSum(1, 2, 3) ); // 6, tetap bisa dijalankan secara normal alert( curriedSum(1)(2,3) ); // 6, currying argumen pertama alert( curriedSum(1)(2)(3) ); // 6, currying secara penuh ``` Fungsi `curry` yang baru mungkin terlihat rumit, tetapi sebenarnya cukup mudah dipahami. Hasil dari pemanggilan `curry(func)` adalah pembungkus `curried` yang terlihat seperti ini: ```js // func adalah fungsi untuk di transformasi 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)); } } }; ``` Ketika kita menjalankannya, ada dua cabang eksekusi `if`: 1. Jika lewat `args` count adalah sama atau lebih dari fungsi asli memiliki definisi (` func.length`), maka cukup teruskan panggilan menggunakan `func.apply`. 2. Jika tidak, dapatkan sebagian: kita belum memanggil `func`. Sebagai gantinya, pembungkus lain dikembalikan, yang akan menerapkan kembali `curried` yang memberikan argumen sebelumnya bersama dengan yang baru. Kemudian, jika kita menyebutnya, sekali lagi, kita akan mendapatkan parsial baru (jika tidak cukup argumen) atau, akhirnya, hasilnya. ```smart header="Fixed-length functions only" Currying membutuhkan fungsi untuk memiliki sejumlah argumen tetap. Fungsi yang menggunakan sisa parameter, seperti `f(...args)`, tidak bisa di currying dengan cara ini. ``` ```smart header="A little more than currying" Menurut definisi, currying harus mengubah `sum(a, b, c)` menjadi `sum(a)(b)(c)`. Namun sebagian besar implementasi currying di JavaScript bersifat lanjutan, seperti yang dijelaskan: implementasi tersebut juga membuat fungsi dapat dipanggil dalam bentuk multi-argumen. ``` ## Ringkasan *Currying* adalah transformasi yang membuat `f(a,b,c)` dapat dipanggil sebagai `f(a)(b)(c)`. Implementasi JavaScript biasanya membuat fungsi dapat dipanggil secara normal dan mengembalikan dalam bentuk parsial jika jumlah argumen tidak cukup. _Currying_ memungkinkan kita untuk mendapatkan sebuah bagian. Seperti yang kita lihat di contoh logging, setelah _currying_ tiga argumen dari fungsi universal `log(date, importance, message)` akan memberikan kita fungsi parsial ketika dipanggil dengan satu argumen (seperti `log(date)`) atau dua argumen (seperti `log(date, importance)`). ================================================ FILE: 1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md ================================================ **Error**! Coba ini: ```js run let user = { name: "John", go: function() { alert(this.name) } } (user.go)() // error! ``` Pesan error pada kebanyakan peramban tidak memberitahukan kita cukup petunjuk tentang hal apa yang salah. **Error muncul karena tidak adanya sebuah titik koma setelah `user = {...}`.** JavaScript tidak secara otomatis menyisipkan sebuah tanda titik koma setelah tanda kurung kurawa `(user.go)()`, jadi JavaScript membaca kode seperti ini: ```js no-beautify let user = { go:... }(user.go)() ``` Lalu kita juga bisa melihat bahwa ekspresi gabungan semacam itu adalah sebuah panggilan objek `{ go: ... }` secara sintaks yang juga sebagai sebuah fungsi dengan argumen `(user.go)`. Dan hal tersebut juga terjadi pada baris yang sama dengan `let user`, jadi objek `user` belum didefinisikan, oleh karena itu terjadi error. Jika kita menyisipkan tanda titik koma, semuanya akan baik-baik saja: ```js run let user = { name: "John", go: function() { alert(this.name) } }*!*;*/!* (user.go)() // John ``` Tolong ingat bahwa tanda kurung kurawa yang merangkap `(user.go)` tidak melakukan apapun di sini. Biasanya Biasanya tanda kurung kurawa mengatur urutan operasi, tapi di sini tanda titik-lah (`.`) yang berjalan terlebih dulu, jadi tidak ada pengaruh apapun. Hanya tanda titik koma yang berpengaruh. ================================================ FILE: 1-js/99-js-misc/04-reference-type/2-check-syntax/task.md ================================================ importance: 2 --- # Cek sintaks Apa hasil dari kode berikut ini? ```js no-beautify let user = { name: "John", go: function() { alert(this.name) } } (user.go)() ``` P.S. Ada jebakannya :) ================================================ FILE: 1-js/99-js-misc/04-reference-type/3-why-this/solution.md ================================================ Ini dia penjelasannya. 1. Itu adalah sebuah panggilan metode objek biasa. 2. Sama halnya, tanda kurung kurawa tidak merubah urutan operasi di sini, lagi pula tanda titik lah yang pertama di urutan operasi. 3. Di sini kita memiliki sebuah panggilan yang lebih kompleks lagi yakni `(expression).method()`. Pagnggilan tersebut bekerja sebagaimana jika panggilan itu dipisah menjadi dua baris kode: ```js no-beautify f = obj.go; // mengkalkulasi ekspresi f(); // memanggil apa yang kita punya ``` Di sini `f()` dieksekusi sebagai sebuah fungsi, tanpa `this`. 4. Hal serupa pada panggilan `(3)`, di sebelah kiri tanda titik `.` kita memiliki sebuah ekspresi. Untuk menjelaskan perilaku panggilan `(3)` dan `(4)` kita perlu memanggil ulang, yang mana properti pengakses (tanda titik atau tanda kurung siku) mengembalikan sebuah nilai dari tipe referensi (*Reference Type*). Operasi apapun kecuali sebuah panggilan metode (seperti penugasan `=` atau `||`) membuat membuat ekspresi tersebut menjadi sebuah nilai biasa, yang tidak membawa informasi yang memungkinkan untuk menentukan `this`. ================================================ FILE: 1-js/99-js-misc/04-reference-type/3-why-this/task.md ================================================ importance: 3 --- # Jelaskan nilai dari "this" Dalam kode di bawah ini kita bermaksud untuk memanggil metode `obj.go()` sebanyak 4 kali sekaligus. Tapi panggilan `(1)` dan `(2)` bekerja berbeda dibanding dengan `(3)` dan `(4)`. Mengapa demikian? ```js run no-beautify let obj, method; obj = { go: function() { alert(this); } }; obj.go(); // (1) [objek Object] (obj.go)(); // (2) [objek Object] (method = obj.go)(); // (3) undefined (obj.go || obj.stop)(); // (4) undefined ``` ================================================ FILE: 1-js/99-js-misc/04-reference-type/article.md ================================================ # Reference Type ```warn header="In-depth language feature" This article covers an advanced topic, to understand certain edge-cases better. It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood. ``` A dynamically evaluated method call can lose `this`. For instance: ```js run let user = { name: "John", hi() { alert(this.name); }, bye() { alert("Bye"); } }; user.hi(); // works // now let's call user.hi or user.bye depending on the name *!* (user.name == "John" ? user.hi : user.bye)(); // Error! */!* ``` On the last line there is a conditional operator that chooses either `user.hi` or `user.bye`. In this case the result is `user.hi`. Then the method is immediately called with parentheses `()`. But it doesn't work correctly! As you can see, the call results in an error, because the value of `"this"` inside the call becomes `undefined`. This works (object dot method): ```js user.hi(); ``` This doesn't (evaluated method): ```js (user.name == "John" ? user.hi : user.bye)(); // Error! ``` Why? If we want to understand why it happens, let's get under the hood of how `obj.method()` call works. ## Reference type explained Looking closely, we may notice two operations in `obj.method()` statement: 1. First, the dot `'.'` retrieves the property `obj.method`. 2. Then parentheses `()` execute it. So, how does the information about `this` get passed from the first part to the second one? If we put these operations on separate lines, then `this` will be lost for sure: ```js run let user = { name: "John", hi() { alert(this.name); } } *!* // split getting and calling the method in two lines let hi = user.hi; hi(); // Error, because this is undefined */!* ``` Here `hi = user.hi` puts the function into the variable, and then on the last line it is completely standalone, and so there's no `this`. **To make `user.hi()` calls work, JavaScript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).** The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language. The value of Reference Type is a three-value combination `(base, name, strict)`, where: - `base` is the object. - `name` is the property name. - `strict` is true if `use strict` is in effect. The result of a property access `user.hi` is not a function, but a value of Reference Type. For `user.hi` in strict mode it is: ```js // Reference Type value (user, "hi", true) ``` When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case). Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`. Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`. So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). ## Summary Reference Type is an internal type of the language. Reading a property, such as with dot `.` in `obj.method()` returns not exactly the property value, but a special "reference type" value that stores both the property value and the object it was taken from. That's for the subsequent method call `()` to get the object and set `this` to it. For all other operations, the reference type automatically becomes the property value (a function in our case). The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression. ================================================ FILE: 1-js/99-js-misc/05-bigint/article.md ================================================ # BigInt [recent caniuse="bigint"] `BigInt` is a special numeric type that provides support for integers of arbitrary length. A bigint is created by appending `n` to the end of an integer literal or by calling the function `BigInt` that creates bigints from strings, numbers etc. ```js const bigint = 1234567890123456789012345678901234567890n; const sameBigint = BigInt("1234567890123456789012345678901234567890"); const bigintFromNumber = BigInt(10); // same as 10n ``` ## Math operators `BigInt` can mostly be used like a regular number, for example: ```js run alert(1n + 2n); // 3 alert(5n / 2n); // 2 ``` Please note: the division `5/2` returns the result rounded towards zero, without the decimal part. All operations on bigints return bigints. We can't mix bigints and regular numbers: ```js run alert(1n + 2); // Error: Cannot mix BigInt and other types ``` We should explicitly convert them if needed: using either `BigInt()` or `Number()`, like this: ```js run let bigint = 1n; let number = 2; // number to bigint alert(bigint + BigInt(number)); // 3 // bigint to number alert(Number(bigint) + number); // 3 ``` The conversion operations are always silent, never give errors, but if the bigint is too huge and won't fit the number type, then extra bits will be cut off, so we should be careful doing such conversion. ````smart header="The unary plus is not supported on bigints" The unary plus operator `+value` is a well-known way to convert `value` to a number. In order to avoid confusion, it's not supported on bigints: ```js run let bigint = 1n; alert( +bigint ); // error ``` So we should use `Number()` to convert a bigint to a number. ```` ## Comparisons Comparisons, such as `<`, `>` work with bigints and numbers just fine: ```js run alert( 2n > 1n ); // true alert( 2n > 1 ); // true ``` Please note though, as numbers and bigints belong to different types, they can be equal `==`, but not strictly equal `===`: ```js run alert( 1 == 1n ); // true alert( 1 === 1n ); // false ``` ## Boolean operations When inside `if` or other boolean operations, bigints behave like numbers. For instance, in `if`, bigint `0n` is falsy, other values are truthy: ```js run if (0n) { // never executes } ``` Boolean operators, such as `||`, `&&` and others also work with bigints similar to numbers: ```js run alert( 1n || 2 ); // 1 (1n is considered truthy) alert( 0n || 2 ); // 2 (0n is considered falsy) ``` ## Polyfills Polyfilling bigints is tricky. The reason is that many JavaScript operators, such as `+`, `-` and so on behave differently with bigints compared to regular numbers. For example, division of bigints always returns a bigint (rounded if necessary). To emulate such behavior, a polyfill would need to analyze the code and replace all such operators with its functions. But doing so is cumbersome and would cost a lot of performance. So, there's no well-known good polyfill. Although, the other way around is proposed by the developers of [JSBI](https://github.com/GoogleChromeLabs/jsbi) library. This library implements big numbers using its own methods. We can use them instead of native bigints: | Operation | native `BigInt` | JSBI | |-----------|-----------------|------| | Creation from Number | `a = BigInt(789)` | `a = JSBI.BigInt(789)` | | Addition | `c = a + b` | `c = JSBI.add(a, b)` | | Subtraction | `c = a - b` | `c = JSBI.subtract(a, b)` | | ... | ... | ... | ...And then use the polyfill (Babel plugin) to convert JSBI calls to native bigints for those browsers that support them. In other words, this approach suggests that we write code in JSBI instead of native bigints. But JSBI works with numbers as with bigints internally, emulates them closely following the specification, so the code will be "bigint-ready". We can use such JSBI code "as is" for engines that don't support bigints and for those that do support - the polyfill will convert the calls to native bigints. ## References - [MDN docs on BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt). - [Specification](https://tc39.es/ecma262/#sec-bigint-objects). ================================================ FILE: 1-js/99-js-misc/index.md ================================================ # Miscellaneous ================================================ FILE: 1-js/index.md ================================================ # Bahasa JavaScript Di sini kita belajar JavaScript, mulai dari nol dan lanjut terus hingga konsep yang mutakhir macam OOP. Konsentrasi kita di sini tertuju ke bahasanya itu sendiri, dengan catatan lingkungan tertentu se-minimum mungkin. ================================================ FILE: 2-ui/1-document/01-browser-environment/article.md ================================================ # Browser environment, specs Bahasa JavaScript awal mulanya dibuat untuk web browser. Sejak saat itu terus berevolusi dan menjadi sebuah bahasa dengan banyak pengguna dan platform. Sebuah platform bisa menjadi browser, atau sebuah web-server ataupun *host* yang lain, sampai sebuah mesin kopi yang "cerdas", jika itu bisa menjalankan JavaScript. Masing-masing darinya menyediakan fungsionalitas platform yang spesifik. Spesifik JavaScript menyebut itu sebagai sebuah *host environment*. Sebuah host environment menyediakan objek-objek tersendiri dan fungsi-fungsi tambahan ke pusat bahasa. Web browser memberikan sebuah sarana untuk mengontrol halaman-halaman web. Berikut adalah sebuah pandangan luas tentang apa yang kita punya ketika JavaScript berjalan di sebuah web browser: ![](windowObjects.svg) Ada sebuah "root" objek yang dinamakan `window`. Mempunyai 2 peranan: 1. Pertama, ini adalah sebuah global objek untuk kode JavaScript, dideskripsikan di bab . 2. Kedua, ini mempresentasikan dari "browser window" dan menyediakan metode-metode untuk mengontrolnya. Misalnya, disini kita akan menggunakannya sebagai sebuah global objek: ```js berjalan function sayHi() { alert("Hello"); } // fungsi-fungsi global adalah metode objek global: window.sayHi(); ``` Dan sekarang kita menggunakannya sebagai jendela browser, untuk melihat tinggi jendela: ```js berjalan alert(window.innerHeight); // tinggi jendela bagian dalam ``` Berikut lebih banyak metode dan properti window-specific, kita akan membahasnya nanti. ## DOM (Document Object Model) Document Object Model, atau disingkat DOM, mempresentasikan semua konten halaman sebagai objek yang bisa dimodifikasi. Objek `document` adalah "pintu masuk" utama dari halaman. Kita bisa mengubahnya atau membuat apapun untuk dipakai halaman tersebut. Sebagai contoh: ```js run // ubah warna latar belakang menjadi merah document.body.style.background = "red"; // ubah kembali setelah 1 detik setTimeout(() => document.body.style.background = "", 1000); ``` Disini kita menggunakan `document.body.style`, tapi ada lebih banyak, banyak lagi. Properti dan metode-metode dideskripsikan di spesifikasi: [DOM Living Standard](https://dom.spec.whatwg.org). ```smart header="DOM is not only for browsers" Spesifikasi DOM menjelaskan struktur dari sebuah dokumen dan penyedia objek untuk memanipulasinya. Ada instrumen dari non-browser yang menggunakan DOM juga. Sebagai contoh, skrip server-side yang mengunduh halaman-halaman HTML dan memprosesnya juga menggunakan DOM. Mereka mungkin hanya mendukung sebagian dari spesifikasi tersebut. ``` ```smart header="CSSOM for styling" Ada juga spesifikasi terpisah, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) untuk aturan dan stylesheets CSS, yang menjelaskan bagaimana mereka di representasilan sebagai objek, dan bagaimana mereka membaca dan menulisnya. CSSOM digunakan bersamaan dengan DOM ketika kita memodifikasi style aturan untuk dokumen. Di prakteknya sekalipun, CSSOM jarang dibutuhkan, karena kita jarang untuk memodifikasi aturan CSS dari JavaScript (biasanya kita hanya menambah/menghapus class dari CSS, tidak memodifikasi aturan dari CSS-nya), tapi itu juga memungkinkan. ``` ## BOM (Browser Object Model) Browser Object Model (BOM) mempresentasikan tambahan dari objek-objek yang disediakan oleh browser (browser environment) untuk dikerjakan oleh apapun kecuali oleh dokumen. Sebagai contoh: - Objek [navigator](mdn:api/Window/navigator) menyediakan informasi di latar belakang tentang browser dan operasi sistem. Disitu banyak sekali properti, tapi ada 2 yang sudah banyak diketahui yaitu: `navigator.userAgent` -- tentang browser sekarang, dan `navigator.platform` -- tentang platform (yang bisa membantu untuk membedakan antara Windows/Linux/Mac dan sebagainya). - Objek [location](mdn:api/Window/location) memungkinkan kita untuk membaca URL sekarang yang akan mengarahkan browser ke satu yang baru. Berikut adalah bagaimana kita bisa menggunakan objek `location`: ```js run alert(location.href); // tampilkan URL sekarang if (confirm("Pergi ke Wikipedia?")) { location.href = "https://wikipedia.org"; // mengarahkan browser ke URL yang lain } ``` Fungsi `alert/confirm/prompt` juga bagian dari BOM: mereka tidak berhubungan secara langsung dengan dokumen, tapi mempresentasikan metode komunikasi dengan pengguna pada browser asli. ```smart header="Specifications" BOM adalah bagian dari [spesifikasi HTML](https://html.spec.whatwg.org) pada umumnya. Ya, yang kamu dengar benar. Spesifikasi HTML pada bukanlah satu-satunya tentang "bahasa HTML" (tags, attributes), tapi juga mencakup banyak objek, metode-metode dan spesifikasi-browser ekstensi DOM. Itu adalah "HTML dalam istilah yang luas". Juga, beberapa bagian-bagian punya tambahan spesifikasi yang terdaftar di . ``` ## Ringkasan Berbicara tentang standard, kita mempunyai: Spesifikasi DOM : Mendeskripsikan struktur dokumen, manipulasi dan events, lihat . Spesifikasi CSSOM : Mendeskripsikan stylesheets dan aturan gaya, manipulasi dengannya dan perbandingannya dengan dokumen, lihat . Spesifikasi HTML : Menjelaskan bahasa HTML (contoh. tags) dan juga BOM (browser object model) -- macam-macam fungsi browser: `setTimeout`, `alert`, `location` dan banyak lagi, lihat . Dengan itu bisa mengambil spesifikasi DOM dan meluaskannya dengan banyak properti dan metode. Selain itu, beberapa class telah dideskripsikan terpisah di . Mohon catat tautan ini, karena ada begitu banyak hal untuk dipelajari dan mustahil untuk mencakup dan mengingat semuanya. Ketika anda ingin untuk membaca tentang sebua properti atau sebuah metode, Mozilla manual di juga sebuah bahan yang bagus, tapi kesesuaian spesifikasi mungkin lebih baik: ada yang lebih kompleks dan bacaan yang panjang, tapi akan membuat dasar pengetahuan anda bersuara dan lengkap. Untuk menemukan sesuatu, akan semakin nyaman memakai pencarian internet "WHATWG [term]" atau "MDN [term]", contoh , . Sekarang kita akan turun untuk mempelajari DOM, karena dokumen memiliki peranan pusat dalam UI. ================================================ FILE: 2-ui/1-document/02-dom-nodes/article.md ================================================ libs: - d3 - domtree --- # DOM tree Tulang punggung dari dokumen HTML adalah tag. Berdasarkan Document Object Model (DOM), setiap tag HTML merupakan sebuah objek. Tag berlapis adalah "anak" dari tag yang melampirkan. Teks di dalam sebuah tag merupakan sebuah objek juga. Semua objek ini dapat diakses menggunakan JavaScript, dan kita bisa menggunakannya untuk memodifikasi halaman. Misalnya, `document.body` merupakan objek yang merepresentasikan tag ``. Menjalankan kode ini akan membuat `` menjadi merah selama 3 detik. ```js run document.body.style.background = 'red'; // buat background menjadi merah setTimeout(() => document.body.style.background = '', 3000); // kembalikan seperti semula ``` Disini kita menggunakan `style.background` untuk mengubah warna background `document.body`, tetapi ada banyak properti lain, seperti: - `innerHTML` -- Konten-konten HTML dari node. - `offsetWidth` -- lebar node (dalam piksel) - ...dan seterusnya. Kita akan segera mempelajari lebih banyak cara untuk memanipulasi DOM, tetapi pertama-tama kita perlu mengetahui tentang strukturnya. ## Contoh dari DOM Mari kita mulai dengan dokumen sederhana berikut: ```html run no-beautify About elk The truth about elk. ``` DOM menggambarkan HTML seperti struktur pohon pada tag. Begini tampilannya:

```online Pada gambar di atas, Anda dapat mengklik node elemen dan anaknya akan membuka/menutup. ``` Setiap node pohon merupakan sebuah objek. Tag-tag merupakan *node elemen* (atau hanya elemen) dan membentuk struktur pohon: `` merupakan root, kemudian `` dan `` adalah anak-anaknya, dll. Teks di dalam elemen-elemen membentuk *node teks*, dilabeli sebagai `#text`. Sebuah node teks hanya berisi string. Ia mungkin tidak memiliki anak dan selalu menjadi daun pohon. Misalnya, tag `` memiliki teks `"About elk"` Harap perhatikan karakter khusus dalam node teks: - baris baru: `↵` (di dalam JavaScript seperti `\n`) - spasi: `␣` Spasi dan baris baru adalah karakter yang benar-benar valid, seperti huruf-huruf dan angka-angka. Mereka membentuk node teks dan menjadi bagian dari DOM. Jadi, misalnya, pada contoh di atas, tag `<head>` berisi beberapa spasi sebelum `<title>`, dan teks tersebut menjadi node `#teks` (ini berisi baris baru dan beberapa spasi). Hanya ada dua pengecualian top-level: 1. Spasi dan baris baru sebelum `<head>` diabaikan karena alasan historis. 2. Jika kita meletakkan sesuatu setelah `</body>`, maka secara otomatis dipindahkan ke dalam `body`, di bagian bawah, karena spesifikasi HTML mengharuskan semua konten harus berada di dalam `<body>`. Jadi tidak boleh ada spasi setelah `</body>`. Dalam kasus lain semuanya mudah -- Jika ada spasi-spasi (seperti karakter lainnya) di dalam dokumen, maka mereka menjadi node teks di DOM tersebut, dan jika kita menghapusnya, maka akan hilang. Berikut tidak ada node teks khusus spasi: ```html no-beautify <!DOCTYPE HTML> <html><head><title>About elkThe truth about elk. ```
```smart header="Spasi di awal/akhir string dan node teks khusus spasi biasanya disembunyikan di alat" Alat browser (akan segera dibahas) yang bekerja dengan DOM biasanya tidak menampilkan spasi di awal/akhir teks dan node teks kosong (jeda baris) di antara tag. Alat pengembang menghemat ruang layar dengan cara ini. Pada gambar DOM lebih lanjut, kita terkadang mengabaikannya saat mereka tidak relevan. Spasi seperti itu biasanya tidak mempengaruhi bagaimana dokumen ditampilkan. ``` ## Autocorrection Jika browser menemukan HTML yang salah format, browser akan memperbaikinya secara otomatis saat membuat DOM. Misalnya, tag yang paling atas selalu ``. Bahkan jika itu tidak ada di dalam dokumen, ia akan ada ada sendiri di dalam DOM, karena browser tersebut akan membuatnya. Hal yang sama berlaku untuk ``. Contoh, jika file HTML kata tunggal `"Hello"`, browser akan membungkusnya ke dalam `` dan ``, dan menambahkan `` yang diperlukan, dan DOM akan menjadi seperti ini:
Selagi sedang menghasilkan DOM, browser secara otomatis memproses kesalahan-kesalahan di dalam dokumen, tag penutup, dan sebagainya. dokumen dengan tag yang tidak ditutup: ```html no-beautify

Hello

  • Mom
  • and
  • Dad ``` ...Akan menjadi DOM normal saat browser membaca tag dan memulihkan bagian yang hilang:
    ````warn header="Tabel selalu memiliki ``" “Kasus khusus” yang menarik adalah tabel. Berdasarkan spesifikasi DOM, mereka harus memiliki tag , tetapi teks HTML dapat menghilangkannya. Kemudian browser membuat di DOM secara otomatis. Untuk HTML: ```html no-beautify
    1
    ``` Struktur DOM akan seperti ini:
    Anda lihat? `` muncul entah dari mana. Kita harus mengingat ini saat bekerja dengan tabel untuk menghindari kejutan. ## Jenis-jenis node lain Ada beberapa jenis-jenis node lain selain node elemen dan teks. Contoh, komentar: ```html The truth about elk.
    1. An elk is a smart
    2. *!* */!*
    3. ...and cunning animal!
    ```
    Kita bisa lihat disini sebuah jenis node pohon baru -- *comment node*, yang dilabeli sebagai `#comment`, diantar dua node teks. Kita mungkin berpikir -- kenapa komentar ditambahkan ke DOM? komentar tersebut tidak mempengaruhi representasi visual dengan cara apa pun. Tapi ada sebuah aturan -- jika sesuatu ada di dalam HTML, maka ia juga harus di dalam pohon DOM. **Semuanya di dalam HTML, bahkan komentar, akan menjadi bagian dari DOM.** Bahkan direktif `` di awal HTML juga merupakan node DOM. Letaknya di pohon DOM tepat sebelum ``. Kita tidak akan menyentuh node itu, Kita bahkan tidak menggambarnya pada diagram karena alasan itu, tetapi node itu ada. Objek `document` yang mewakili seluruh dokumen, secara formal, juga merupakan node DOM. Ada [12 jenis-jenis node](https://dom.spec.whatwg.org/#node). Dalam praktiknya kita biasanya bekerja dengan 4 di antaranya: 1. `document` -- "titik masuk" ke DOM. 2. node elemen -- tag-tag HTML, blok bangunan pohon. 3. node teks -- berisi teks. 4. komentar -- terkadang kita meletakkan informasi disini, ia tidak akan ditampilkan, tetapi JS bisa membacanya dari DOM. ## Melihatnya untuk kita sendiri Untuk melihat struktur DOM secara real-time, coba [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Cukup ketikkan dokumen, dan itu akan muncul sebagai DOM dalam sekejap. Cara lain untuk menjelajahi DOM gunakan alat pengembang browser. Sebenarnya itulah yang kita gunakan saat mengembangkan. Untuk melakukannya, buka halaman web [elk.html](elk.html), aktifkan alat pengembang browser dan beralih ke tab Elemen. Seharusnya tampil seperti ini: ![](elk.svg) Anda bisa melihat DOM tersebut, klik pada elemen-elemen, melihat detailnya, dan sebagainya. Harap perhatikan bahwa struktur DOM di alat pengembang disederhanakan. Node teks ditampilkan hanya sebagai teks. Dan tidak ada samak sekali node teks "kosong" (hanya spasi). Tidak apa-apa, karena sebagian besar waktu kita berkepentingan pada node elemen. Mengklik tombol di pojok kiri atas memungkinkan kita memilih node dari halaman web menggunakan mouse (atau perangkat penunjuk lain) dan "memeriksanya" (gulir ke sana di tab Elemen). Ini berfungsi dengan baik ketika kita memiliki halaman HTML besar (dan DOM besar yang sesuai) dan ingin melihat tempat elemen tertentu di dalamnya. Cara lain untuk melakukannya adalah dengan mengklik kanan pada halaman web dan memilih "Inspect" di menu konteks. ![](inspect.svg) Di bagian kanan alat ada subtabs berikut: - **Styles** -- kita bisa melihat CSS diterapkan ke elemen saat ini aturan demi aturan, termasuk aturan bawaan (abu-abu). Hampir semuanya dapat diedit di tempat, termasuk dimensi/margin/padding kotak di bawah ini. - **Computed** -- untuk melihat CSS diterapkan ke elemen berdasarkan properti: untuk setiap properti kita dapat melihat aturan yang memberikannya (termasuk pewarisan CSS dan semacamnya). - **Event Listeners** -- untuk melihat event listener yang dilampirkan ke elemen DOM (kita akan membahasnya di bagian selanjutnya dari tutorial). - ...dan seterusnya. Cara terbaik untuk mempelajarinya adalah dengan mengklik. Sebagian besar nilai dapat diedit di tempat. ## Interaksi dengan konsol Saat kami mengerjakan DOM, kita juga mungkin ingin menerapkan JavaScript padanya. Seperti: mendapatkan node dan jalankan beberapa kode untuk memodifikasinya, Berikut beberapa tip untuk berpindah antara tab Elemen dan konsol. Sebagai permulaan: 1. Pilih `
  • ` pertama di dalam tab Elements. 2. Tekan `key:Esc` -- itu akan membuka konsol tepat di bawah tab Elements. Sekarang elemen yang dipilih terakhir tersedia sebagai `$0`, yang dipilih sebelumnya adalah `$1` dll. Kita bisa menjalankan perintah pada mereka. Misalnya , `$0.style.background = 'red'` membuat item list yang dipilih bewarna merah, seperti ini: ![](domconsole0.svg) Begitulah cara mendapatkan node dari Elements di Console. Ada juga jalan kembali. Jika ada variabel yang mereferensikan node DOM, maka kita dapat menggunakan perintah `inspect (node)` di Console untuk melihatnya di panel Elements. Atau kita bisa mengeluarkan simpul DOM di konsol dan menjelajahi "di tempat", seperti `document.body` di bawah ini: ![](domconsole1.svg) Itu tentu saja untuk tujuan debugging. Dari bab selanjutnya kita akan mengakses dan memodifikasi DOM menggunakan JavaScript. Alat pengembang browser sangat membantu dalam pengembangan: kita dapat menjelajahi DOM, mencoba berbagai hal dan melihat apa yang salah. ## Summary Dokumen HTML/XML direpresentasikan di dalam browser sebagai pohon DOM. - Tag menjadi node elemen dan membentuk struktur. - Teks menjadi node teks. - ...dll, semuanya di dalam HTML mempunyai tempatnya di dalam DOM, bahkan komentar. Kita dapat menggunakan alat pengembang untuk memeriksa DOM dan memodifikasinya secara manual. Di sini kami membahas dasar-dasar, tindakan yang paling sering digunakan dan penting untuk memulai. Ada dokumentasi lengkap tentang Alat Pengembang Chrome di . Cara terbaik untuk mempelajari alat ini adalah dengan mengklik di sana-sini, membaca menu: sebagian besar opsi sudah jelas. Nanti, jika Anda mengenal mereka secara umum, bacalah dokumennya dan pelajari sisanya. Node DOM memiliki properti dan method yang memungkinkan kita untuk melakukan perjalanan di antara mereka, memodifikasinya, memindahkan halaman, dan banyak lagi. Kami akan membahasnya di bab berikutnya. ================================================ 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 ================================================ There are many ways, for instance: The `
    ` DOM node: ```js document.body.firstElementChild // or document.body.children[0] // or (the first node is space, so we take 2nd) document.body.childNodes[1] ``` The `