diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..490051876d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/.gitignore b/.gitignore index 6f90fd1907..1a71fb7c82 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ sftp-config.json Thumbs.db +/svgs \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 693d29b18d..fe3fadb799 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -26,7 +26,7 @@ - [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- Chrome과 Opera에서 쓰입니다. - [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- Firefox에서 쓰입니다. -- IE는 버전에 따라 'Trident'나 'Chakra'라 불리는 엔진을 사용합니다. 'ChakraCore'는 Microsoft Edge에 사용되며, 'SquirrelFish'는 Safari에 사용됩니다. +- IE의 "Chakra", Safari의 "JavaScriptCore", "Nitro", "SquirrelFish" 같은 다른 코드네임들도 있습니다. 위의 코드네임은 개발 관련 글에서 종종 언급되기 때문에 기억해 두는 것이 좋습니다. 본 튜토리얼에서도 해당 코드네임을 사용할 예정입니다. "X라는 기능은 V8에서만 지원합니다."라는 식으로 말이죠. 이런 문장을 만나면 Chrome과 Opera에서만 이 기능을 지원한다고 이해하시면 됩니다. @@ -110,6 +110,9 @@ - [TypeScript](http://www.typescriptlang.org/)는 개발을 단순화 하고 복잡한 시스템을 지원하려는 목적으로 '자료형의 명시화(strict data typing)'에 집중해 만든 언어입니다. Microsoft가 개발하였습니다. - [Flow](http://flow.org/) 역시 자료형을 강제하는데, TypeScript와는 다른 방식을 사용합니다. Facebook이 개발하였습니다. - [Dart](https://www.dartlang.org/)는 모바일 앱과 같이 브라우저가 아닌 환경에서 동작하는 고유의 엔진을 가진 독자적 언어입니다. Google이 개발하였습니다. +- [Brython](https://brython.info/)은 자바스크립트 없이 순수 Python만으로 애플리케이션을 작성할 수 있게 해주는 Python → JavaScript 트랜스파일러입니다. +- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html)은 브라우저와 Node를 대상으로 사용할 수 있는 현대적이고 간결하며 안전한 프로그래밍 언어입니다. + 이 외에도 자바스크립트로 트랜스파일 할 수 있는 언어는 다양합니다. 개발 언어로 이런 언어 중 하나를 택한다고 하더라도 자신이 무엇을 하고 있는지 이해하려면 결국엔 자바스크립트를 알아야 합니다. diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md index 16e0affb2e..c0507a6337 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -34,4 +34,4 @@ ECMA-262명세서는 새로운 버전이 매년 나옵니다. 공식 버전이 실제 개발을 하다 보면 위에 언급 드린 자료가 아주 유용할 겁니다. 메서드나 함수 관련 정보, 브라우저 지원 여부 등은 의사결정을 내릴 때 꼭 필요한 정보이기 때문입니다. -말씀드린 사이트나 이 페이지를 기억해 놓았다가 특정 기능에 대한 상세한 정보가 필요할 때 방문해 보시길 바랍니다. \ No newline at end of file +말씀드린 사이트나 이 페이지를 기억해 놓았다가 특정 기능에 대한 상세한 정보가 필요할 때 방문해 보시길 바랍니다. diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index e8ee348f0d..2a48668c02 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -31,8 +31,6 @@ Windows 사용자라면 'Visual Studio'라는 IDE를 들어보셨을 겁니다. 추천하는 에디터는 다음과 같습니다. -- [Atom](https://atom.io/) (크로스 플랫폼, 무료) -- [Visual Studio Code](https://code.visualstudio.com/) (크로스 플랫폼, 무료) - [Sublime Text](http://www.sublimetext.com) (크로스 플랫폼, 셰어웨어) - [Notepad++](https://notepad-plus-plus.org/) (Windows, 무료) - [Vim](http://www.vim.org/)이나 [Emacs](https://www.gnu.org/software/emacs/)도 에디터로 사용법만 잘 숙지하면 충분히 에디터 역할을 잘합니다. @@ -44,3 +42,9 @@ Windows 사용자라면 'Visual Studio'라는 IDE를 들어보셨을 겁니다. 이 외에도 제가 모르는 훌륭한 에디터가 있을 수 있으니 여러분이 가장 좋아하는 것 하나를 택하시면 됩니다. 여타 툴과 마찬가지로 에디터를 선택하는 것은 프로젝트의 종류, 개발 습관, 개인 성향에 따라 다르므로 이에 관한 논쟁은 지양하도록 합시다. + +저자의 개인적인 의견은 다음과 같습니다. + +- 프론트엔드 개발을 주로 한다면 [Visual Studio Code](https://code.visualstudio.com/)를 사용하는 편이 좋습니다. +- 반대로 다른 언어·플랫폼 개발이 주가 되고 프론트엔드는 일부만 다룬다면 Xcode(Mac), Microsoft Visual Studio(Windows), 또는 JetBrains 계열 IDE(WebStorm, PHPStorm, RubyMine 등)를 고려해볼 수 있습니다. 사용하는 언어에 따라 적절한 제품을 선택하면 됩니다. + diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index 178866cb38..aaaf27ea00 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -22,7 +22,7 @@ 아래와 같이 말이죠. -![chrome](chrome.png) +![chrome](chrome.webp) 화면 구성은 사용하고 있는 Chrome 버전에 따라 다릅니다. 버전이 바뀔 때 마다 구성이 조금씩 바뀌긴 하지만 큰 틀은 바뀌지 않습니다. diff --git a/1-js/01-getting-started/4-devtools/chrome.png b/1-js/01-getting-started/4-devtools/chrome.png deleted file mode 100644 index 4cb3ea2f46..0000000000 Binary files a/1-js/01-getting-started/4-devtools/chrome.png and /dev/null differ diff --git a/1-js/01-getting-started/4-devtools/chrome.webp b/1-js/01-getting-started/4-devtools/chrome.webp new file mode 100644 index 0000000000..bdf067079e Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome.webp differ diff --git a/1-js/01-getting-started/4-devtools/chrome@2.webp b/1-js/01-getting-started/4-devtools/chrome@2.webp new file mode 100644 index 0000000000..2aeca5898a Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome@2.webp differ diff --git a/1-js/01-getting-started/4-devtools/chrome@2x.png b/1-js/01-getting-started/4-devtools/chrome@2x.png deleted file mode 100644 index b87404a8f4..0000000000 Binary files a/1-js/01-getting-started/4-devtools/chrome@2x.png and /dev/null differ diff --git a/1-js/01-getting-started/4-devtools/safari.png b/1-js/01-getting-started/4-devtools/safari.png index 64c7a3f6ca..4538827eb0 100644 Binary files a/1-js/01-getting-started/4-devtools/safari.png and b/1-js/01-getting-started/4-devtools/safari.png differ diff --git a/1-js/01-getting-started/4-devtools/safari@2x.png b/1-js/01-getting-started/4-devtools/safari@2x.png index 27def4d09b..1561b2bd97 100644 Binary files a/1-js/01-getting-started/4-devtools/safari@2x.png and b/1-js/01-getting-started/4-devtools/safari@2x.png differ diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md index a242ecef2d..eb5bafc0a6 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -9,7 +9,11 @@ ## 'script' 태그 +<<<<<<< HEAD ` ``` +<<<<<<< HEAD 여기서 `/path/to/script.js`는 사이트의 루트에서부터 파일이 위치한 절대 경로를 나타냅니다. 현재 페이지에서의 상대 경로를 사용하는 것도 가능합니다. 같은 폴더 내에 있는 파일인 `"script.js"`를 `src="script.js"`로 참조하는 것처럼 말이죠. +======= +Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"`, just like `src="./script.js"`, would mean a file `"script.js"` in the current folder. +>>>>>>> upstream/master 물론 아래와 같이 URL 전체를 속성으로 사용할 수도 있습니다. diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 4ea4fd9e21..e5e124bce3 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -46,7 +46,11 @@ alert(3 + + 2); ``` +<<<<<<< HEAD 세미콜론 자동 삽입이 일어나지 않았기 때문에 `6`이 출력됩니다. 어떤 줄이 `"+"` 로 끝나면, 그 줄은 '불완전한 표현식'이므로 세미콜론이 필요하지 않다는 걸 직감하실 겁니다. 위 코드도 이런 의도로 동작합니다. +======= +The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so a semicolon there would be incorrect. And in this case, that works as intended. +>>>>>>> upstream/master **반면, 세미콜론이 정말로 필요하지만 자바스크립트가 이를 추정하지 '못하는' 상황도 존재합니다.** @@ -56,19 +60,31 @@ alert(3 + 자바스크립트가 세미콜론을 자동으로 삽입해주지 못하는 구체적인 상황은 다음과 같습니다. ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` +<<<<<<< HEAD 지금은 대괄호 `[]`와 `forEach`는 아직 배우지 않았으므로, 깊이 생각하지 말고 코드를 살펴봅시다. 이들에 대해선 추후에 학습할 예정입니다. 코드를 실행하면 결과가 `1`과 `2`가 출력된다는 사실만 기억하세요. 이제 위에서 작성한 코드 앞쪽에 *세미콜론을 붙이지 않은 채* `alert`를 추가해 봅시다. ```js run no-beautify alert("에러가 발생합니다.") +======= +No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of running the code: it shows `Hello`, then `1`, then `2`. -[1, 2].forEach(alert) +Now let's remove the semicolon after the `alert`: + +```js run no-beautify +alert("Hello") +>>>>>>> upstream/master + +[1, 2].forEach(alert); ``` +<<<<<<< HEAD 예제를 실행해 보면 새롭게 추가한 `alert`만 제대로 출력되고 에러가 발생하는 걸 확인할 수 있습니다. `alert` 끝에 세미콜론을 추가해 다시 실행하면 코드는 잘 작동합니다. @@ -90,6 +106,23 @@ alert("에러가 발생합니다.")[1, 2].forEach(alert) ``` 하지만 원래 이 코드는 단일 문이 아닌 두 개의 서로 다른 문이었습니다. 문이 잘못 합쳐지면서 에러가 발생한 것이죠. 이 예제 외에도 이런 상황이 발생할 여지는 언제나 있습니다. +======= +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. + +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. + +That's because JavaScript does not assume a semicolon before square brackets `[...]`. So, the code in the last example is treated as a single statement. + +Here's how the engine sees it: + +```js run no-beautify +alert("Hello")[1, 2].forEach(alert); +``` + +Looks weird, right? Such merging in this case is just wrong. We need to put a semicolon after `alert` for the code to work correctly. + +This can happen in other situations also. +>>>>>>> upstream/master ```` 줄 바꿈으로 문을 나눴더라도, 문 사이엔 세미콜론을 넣는 것이 좋습니다. 자바스크립트 커뮤니티에서도 이를 규칙으로 정해 권장하고 있습니다. 다시 한번 정리하도록 하겠습니다. 세미콜론은 *생략할 수 있습니다.* 하지만 세미콜론을 사용하는 것이 더 안전하므로 이를 기억하고 따르도록 합시다. 입문자라면 이를 더 잘 지키도록 합시다. diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md index 03d3cf3bd0..b2b493fb18 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md @@ -6,7 +6,11 @@ let ourPlanetName = "Earth"; ``` +<<<<<<< HEAD `planet`이라는 간단한 이름을 지어줘도 되지만, 이렇게 하면 어떤 행성(planet)을 언급하는 것인지 모호합니다. 변수명이 아주 길어지지 않는 이상, 설명을 좀 더 곁들어주는 것이 좋습니다. +======= +Note, we could use a shorter name `planet`, but it might not be obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong. +>>>>>>> upstream/master ## 현재 방문자를 나타내는 변수 이름 짓기 diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index a532d0673f..335a788bc8 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -12,13 +12,24 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` +<<<<<<< HEAD 위 코드의 상수 `birthday`는 태어난 날짜 정보를 담고 있습니다. `age`라는 상수는 나이에 관한 값을 담고 있는데 `birthday`를 조작하여 그 값을 도출합니다(생일을 이용하여 나이를 도출하는 코드는 간결성을 위해 여기선 언급하지 않겠습니다. 이 문제에서 해당 코드가 중요한 역할을 하지 않기도 합니다). +======= +Here we have a constant `birthday` for the date, and also the `age` constant. + +The `age` is calculated from `birthday` using `someCode()`, which means a function call that we didn't explain yet (we will soon!), but the details don't matter here, the point is that `age` is calculated somehow based on the `birthday`. +>>>>>>> upstream/master 이런 상황에서 `birthday`를 대문자 상수로 바꾸는 것이 적절할까요? `age` 역시 대문자 상수로 바꾸는 것이 괜찮은 선택일까요? ```js +<<<<<<< HEAD const BIRTHDAY = '18.04.1982'; // 대문자 상수로 바꿔도 괜찮을까요? const AGE = someCode(BIRTHDAY); // 대문자 상수로 바꿔도 괜찮을까요? -``` +======= +const BIRTHDAY = '18.04.1982'; // make birthday uppercase? +const AGE = someCode(BIRTHDAY); // make age uppercase? +>>>>>>> upstream/master +``` diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index b17049faa5..e8d20622a0 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -24,7 +24,7 @@ let message; let message; *!* -message = 'Hello'; // 문자열을 저장합니다. +message = 'Hello'; // 문자열 'Hello'를 message라는 변수에 저장합니다. */!* ``` @@ -63,7 +63,8 @@ let age = 25; let message = 'Hello'; ``` -어떤 사람들은 이런 방식으로도 변수를 정의합니다. +어떤 사람들은 다음과 같이 여러 줄에 걸쳐 변수를 정의하기도 합니다. + ```js no-beautify let user = 'John', age = 25, @@ -96,13 +97,14 @@ let user = 'John' '상자' 안에 데이터를 저장하는데, 이 상자에는 특별한 이름표가 붙어 있다고 상상해 봅시다. 이렇게 하면 '변수'를 좀 더 쉽게 이해할 수 있습니다. -예를 들어, 변수 `message`는 `message`라는 이름표가 붙어있는 상자에 `"Hello!"`라는 값을 저장한 것이라고 생각할 수 있습니다. +예를 들어, 변수 `message`는 `'message'`라는 이름표가 붙어있는 상자에 `'Hello!'`라는 값을 저장한 것이라고 생각할 수 있습니다. ![](variable.svg) 상자 속엔 어떤 값이든지 넣을 수 있습니다. -원하는 만큼 값을 변경할 수도 있습니다. +다음과 같이 원하는 만큼 값을 변경할 수도 있습니다. + ```js run let message; @@ -149,11 +151,11 @@ let message = "That"; // SyntaxError: 'message' has already been declared ```` ```smart header="함수형 언어" -[함수형(functional)](https://en.wikipedia.org/wiki/Functional_programming) 프로그래밍 언어는 변숫값 변경을 금지합니다. [스칼라(Scala)](http://www.scala-lang.org/)와 [얼랭(Erlang)](http://www.erlang.org/)은 대표적인 함수형 언어입니다. +흥미롭게도, [하스켈(Haskell)](https://en.wikipedia.org/wiki/Haskell)과 같이 변숫값 변경을 금지하는, 이른바 [순수 함수형(pure functional)](https://en.wikipedia.org/wiki/Purely_functional_programming) 프로그래밍 언어도 있습니다. 이들 언어에서는 '상자 속에' 값이 일단 저장되면, 그 값을 영원히 유지합니다. 다른 값을 저장하고 싶다면 새로운 상자를 만들어야(새 변수를 선언해야)만 합니다. 이전 변수를 재사용할 수 없습니다. -처음 봤을 땐 좀 이상해 보일 수 있지만, 함수형 언어는 중대한 개발에 상당히 적합한 언어입니다. 이런 제약이 장점으로 작용하는 병렬 계산(parallel computation)과 같은 영역도 있죠. 당장은 사용할 계획이 없더라도 이런 언어를 공부하는 것은 시야를 넓히는 데 도움이 되므로, 학습을 권유 드립니다. +처음 봤을 땐 좀 이상해 보일 수 있지만, 함수형 언어는 중대한 개발에 상당히 적합한 언어입니다. 이런 제약이 장점으로 작용하는 병렬 계산(parallel computation)과 같은 영역도 있죠. ``` ## 변수 명명 규칙 @@ -192,11 +194,11 @@ let my-name; // 하이픈 '-'은 변수명에 올 수 없습니다. ``` ```smart header="대·소문자 구별" -`apple`와 `AppLE`은 서로 다른 변수입니다. +`apple`와 `APPLE`은 서로 다른 변수입니다. ``` -````smart header="비 라틴계 언어도 변수명에 사용할 수 있지만 권장하진 않습니다." -키릴 문자, 심지어 상형문자도 변수명에 사용할 수 있습니다. 모든 언어를 변수명에 사용할 수 있죠. +````smart header="비 라틴계 문자도 변수명에 사용할 수 있지만 권장하진 않습니다." +키릴 문자나 한자를 비롯한 어떤 언어의 문자든 변수명에 사용할 수 있습니다. 다음과 같이 말이죠. ```js let имя = '...'; @@ -263,7 +265,7 @@ myBirthday = '01.01.2001'; // error, can't reassign the constant! ### 대문자 상수 -기억하기 힘든 값을 변수에 할당해 별칭으로 사용하는 것은 널리 사용되는 관습입니다. +실행 전부터 값이 정해져 있더라도 의미를 떠올리기 어려운 값에는, 상수를 별칭으로 사용하는 관례가 널리 쓰입니다. 이런 상수는 대문자와 밑줄로 구성된 이름으로 명명합니다. @@ -290,7 +292,8 @@ alert(color); // #FF7F00 '상수'는 변수의 값이 절대 변하지 않음을 의미합니다. 그중에는 (빨간색을 나타내는 16진수 값처럼) 코드가 실행되기 전에 이미 그 값을 알고 있는 상수도 있고, 런타임 과정에서 *계산되지만* 최초 할당 이후 값이 변하지 않는 상수도 있습니다. -예시: +예를 들면 다음과 같습니다. + ```js const pageLoadTime = /* 웹페이지를 로드하는데 걸린 시간 */; ``` diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 46134566a7..0aedac73dc 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -46,13 +46,15 @@ n = 12.345; alert( "숫자가 아님" / 2 ); // NaN, 문자열을 숫자로 나누면 오류가 발생합니다. ``` - `NaN`은 여간해선 바뀌지 않습니다. `NaN`에 어떤 추가 연산을 해도 결국 `NaN`이 반환됩니다. + `NaN`은 여간해선 바뀌지 않습니다. `NaN`에 어떤 추가 수학 연산을 해도 결국 `NaN`이 반환됩니다. ```js run - alert( "숫자가 아님" / 2 + 5 ); // NaN + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "숫자가 아님" / 2 - 1 ); // NaN ``` - 연산 과정 어디에선가 `NaN`이 반환되었다면, 이는 모든 결과에 영향을 미칩니다. + 연산 과정 어디에선가 `NaN`이 반환되었다면, 이는 모든 결과에 영향을 미칩니다. (단 하나의 예외는 `NaN ** 0`이 `1`이 된다는 점입니다.) ```smart header="수학 연산은 안전합니다." 자바스크립트에서 행해지는 수학 연산은 '안전'하다고 볼 수 있습니다. 0으로 나눈다거나 숫자가 아닌 문자열을 숫자로 취급하는 등의 이례적인 연산이 자바스크립트에선 가능합니다. @@ -64,9 +66,20 @@ n = 12.345; 숫자를 다루는 방법에 대해선 챕터에서 자세히 알아보도록 하겠습니다. -## BigInt +## BigInt [#bigint-type] -내부 표현 방식 때문에 자바스크립트에선 (253-1)(`9007199254740991`) 보다 큰 값 혹은 -(253-1) 보다 작은 정수는 '숫자형'을 사용해 나타낼 수 없습니다. +내부 표현 방식 때문에 자바스크립트에선 (253-1)(`9007199254740991`) 보다 큰 정수 혹은 -(253-1) 보다 작은 정수는 '숫자형'을 사용해 나타낼 수 없습니다. + +정확히 말하면, '숫자형'은 더 큰 정수도 저장할 수 있습니다. 최대 1.7976931348623157 * 10308까지 저장할 수 있죠. 하지만 안전한 정수의 범위인 ±(253-1)를 벗어나면 정밀도 오류가 발생합니다. 모든 자릿수를 고정된 64비트 저장 공간에 담을 수 없기 때문입니다. 따라서 '근삿값'이 저장될 수 있습니다. + +예를 들어, 안전한 정수의 범위를 살짝 벗어나는 다음 두 숫자는 같은 값으로 취급합니다. + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +다시 말해, (253-1)보다 큰 모든 홀수 정수는 '숫자형'에 아예 저장할 수 없습니다. 사실 대부분의 상황에서 이런 제약사항은 문제가 되지 않습니다. 그렇지만 암호 관련 작업같이 아주 큰 숫자가 필요한 상황이거나 아주 높은 정밀도로 작업을 해야 할 때는 이런 큰 숫자가 필요합니다. @@ -81,10 +94,6 @@ const bigInt = 1234567890123456789012345678901234567890n; `BigInt`형 숫자는 자주 쓰이지 않기 때문에 여기서 자세히 다루지 않고 별도의 챕터, 에서 설명드리겠습니다. 아주 큰 숫자를 사용해야 하는 경우라면 해당 챕터를 참고해 주시기 바랍니다. -```smart header="호환성 이슈" -이 글이 작성된 시점엔 Firefox, Chrome, Edge, Safari에서만 `BigInt`를 지원합니다. IE에선 지원하지 않습니다. -``` - ## 문자형 자바스크립트에선 문자열(string)을 따옴표로 묶습니다. @@ -127,7 +136,7 @@ alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (큰따옴표는 ```smart header="*글자형*은 없습니다." 일부 언어는 글자 하나를 저장할 때 쓰이는 자료형, '글자(character)'형을 따로 지원합니다. C 언어와 Java의 `char`가 대표적인 예입니다. -자바스크립트는 글자형을 지원하지 않습니다. `문자형`만 있을 뿐입니다. 여기엔 글자가 하나 혹은 여러 개 들어갈 수 있습니다. +자바스크립트는 글자형을 지원하지 않습니다. `문자형`만 있을 뿐입니다. 여기엔 글자가 0개일 수도 있고(빈 문자열), 하나 혹은 여러 개일 수도 있습니다. ``` ## 불린형 @@ -210,13 +219,6 @@ alert(age); // "undefined" `typeof` 연산자는 인수의 자료형을 반환합니다. 자료형에 따라 처리 방식을 다르게 하고 싶거나 변수의 자료형을 빠르게 알아내고자 할 때 유용합니다. -`typeof` 연산자는 두 가지 형태의 문법을 지원합니다. - -1. 연산자: `typeof x` -2. 함수: `typeof(x)` - -괄호가 있든 없든 결과가 동일합니다. - `typeof x`를 호출하면 인수의 자료형을 나타내는 문자열을 반환합니다. ```js @@ -251,23 +253,35 @@ typeof alert // "function" (3) 2. `typeof null`의 결과는 `"object"`입니다. `null`은 별도의 고유한 자료형을 가지는 특수 값으로 객체가 아니지만, 하위 호환성을 유지하기 위해 이런 오류를 수정하지 않고 남겨둔 상황입니다. 언어 자체의 오류이므로 `null`이 객체가 아님에 유의하시기 바랍니다. 3. `typeof`는 피연산자가 함수면 `"function"`을 반환합니다. 그러므로 `typeof alert`는 `"function"`을 출력해줍니다. 그런데 '함수'형은 따로 없습니다. 함수는 객체형에 속합니다. 이런 동작 방식이 형식적으론 잘못되긴 했지만, 아주 오래전에 만들어진 규칙이었기 때문에 하위 호환성 유지를 위해 남겨진 상태입니다. 한편, 실무에선 이런 특징이 매우 유용하게 사용되기도 합니다. +```smart header="`typeof(x)` 문법" +코드에서 `typeof(x)`라는 또 다른 문법을 보실 수도 있습니다. 이는 `typeof x`와 동일합니다. + +정확히 말하자면, `typeof`는 함수가 아니라 연산자입니다. 여기서 괄호는 `typeof`의 일부가 아닙니다. 수학에서 식을 묶을 때 쓰는 괄호와 같은 종류의 괄호입니다. + +보통 이런 괄호 안에는 `(2 + 2)`처럼 수식이 들어가지만, 여기서는 `(x)`처럼 인수 하나만 들어갑니다. 문법상 `typeof x` 대신 `typeof(x)`처럼 `typeof` 연산자 바로 뒤에 괄호를 붙여 쓸 수 있으며, 이를 선호하는 사람들도 있습니다. + +`typeof x` 문법이 훨씬 더 일반적이지만, `typeof(x)`를 선호하는 사람들도 있습니다. +``` + ## 요약 자바스크립트에는 여덟 가지 기본 자료형이 있습니다. -- `숫자형` -- 정수, 부동 소수점 숫자 등의 숫자를 나타낼 때 사용합니다. 정수의 한계는 ±253 입니다. -- `bigint` -- 길이 제약 없이 정수를 나타낼 수 있습니다. -- `문자형` -- 빈 문자열이나 글자들로 이뤄진 문자열을 나타낼 때 사용합니다. 단일 문자를 나타내는 별도의 자료형은 없습니다. -- `불린형` -- `true`, `false`를 나타낼 때 사용합니다. -- `null` -- `null` 값만을 위한 독립 자료형입니다. `null`은 알 수 없는 값을 나타냅니다. -- `undefined` -- `undefined` 값만을 위한 독립 자료형입니다. `undefined`는 할당되지 않은 값을 나타냅니다. -- `객체형` -- 복잡한 데이터 구조를 표현할 때 사용합니다. -- `심볼형` -- 객체의 고유 식별자를 만들 때 사용합니다. +- 원시 자료형 일곱 가지는 다음과 같습니다. + - `숫자형` -- 정수, 부동 소수점 숫자 등의 숫자를 나타낼 때 사용합니다. 정수의 한계는 ±(253-1) 입니다. + - `bigint` -- 길이 제약 없이 정수를 나타낼 수 있습니다. + - `문자형` -- 빈 문자열이나 글자들로 이뤄진 문자열을 나타낼 때 사용합니다. 단일 문자를 나타내는 별도의 자료형은 없습니다. + - `불린형` -- `true`, `false`를 나타낼 때 사용합니다. + - `null` -- `null` 값만을 위한 독립 자료형입니다. `null`은 알 수 없는 값을 나타냅니다. + - `undefined` -- `undefined` 값만을 위한 독립 자료형입니다. `undefined`는 할당되지 않은 값을 나타냅니다. + - `심볼형` -- 객체의 고유 식별자를 만들 때 사용합니다. +- 그리고 원시 자료형이 아닌 자료형이 하나 있습니다. + - `객체형` -- 복잡한 데이터 구조를 표현할 때 사용합니다. `typeof` 연산자는 피연산자의 자료형을 알려줍니다. -- `typeof x` 또는 `typeof(x)` 형태로 사용합니다. +- 보통 `typeof x` 형태로 사용하지만, `typeof(x)` 형태도 가능합니다. - 피연산자의 자료형을 문자열 형태로 반환합니다. -- `null`의 typeof 연산은 `"object"`인데, 이는 언어상 오류입니다. null은 객체가 아닙니다. +- `null`의 typeof 연산은 `"object"`인데, 이는 언어상 오류입니다. null은 객체가 아닙니다. 이어지는 챕터에선 원시 자료형에 대해 학습해 볼 예정입니다. 원시형에 어느 정도 익숙해지면 객체형에 대해 알아보도록 하겠습니다. diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md index a4aecec113..3b52eeecb4 100644 --- a/1-js/02-first-steps/07-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -6,8 +6,8 @@ 이 외에, 전달받은 값을 의도를 갖고 원하는 타입으로 변환(명시적 변환)해 주는 경우도 형 변환이라고 할 수 있습니다. -```smart header="객체의 형변환은 나중에 다룹니다." -이 챕터에선 객체는 다루지 않겠습니다. 여기선 원시형의 형변환에 대해서만 다룰 예정입니다. +```smart header="객체의 형 변환은 나중에 다룹니다." +이 챕터에선 객체는 다루지 않겠습니다. 여기선 원시형의 형 변환에 대해서만 다룰 예정입니다. 객체의 형 변환이 어떻게 이뤄지는지 에 대해선 객체에 대한 학습이 끝난 후 알아보겠습니다. ``` @@ -70,7 +70,7 @@ alert(age); // NaN, 형 변환이 실패합니다. |`undefined`|`NaN`| |`null`|`0`| |true and false | `1` 과 `0` | -| `string` | 문자열의 처음과 끝 공백이 제거됩니다. 공백 제거 후 남아있는 문자열이 없다면 `0`, 그렇지 않다면 문자열에서 숫자를 읽습니다. 변환에 실패하면 `NaN`이 됩니다.| +| `string` | 문자열의 처음과 끝 공백(스페이스, 탭 `\t`, 줄 바꿈 `\n` 등)이 제거됩니다. 공백 제거 후 남아있는 문자열이 없다면 `0`, 그렇지 않다면 문자열에서 숫자를 읽습니다. 변환에 실패하면 `NaN`이 됩니다.| 예시: @@ -130,7 +130,7 @@ alert( Boolean(" ") ); // 공백이 있는 문자열도 비어있지 않은 문 |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | -| `string` | 전달받은 문자열을 "그대로" 읽되, 처음과 끝의 공백을 무시합니다. 문자열이 비어있다면 `0`이 되고, 오류 발생 시 `NaN`이 됩니다. | +| `string` | 전달받은 문자열을 '그대로' 읽되, 처음과 끝의 공백(스페이스, 탭 `\t`, 줄 바꿈 `\n` 등)을 무시합니다. 문자열이 비어있다면 `0`이 되고, 오류 발생 시 `NaN`이 됩니다. | **`불린형으로 변환`** 은 논리 연산 시 발생합니다. `Boolean(value)`으로도 변환할 수 있습니다. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index bde9d58328..da909ab018 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -9,7 +9,6 @@ true + false = 1 "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN -7 / 0 = Infinity " -9 " + 5 = " -9 5" // (3) " -9 " - 5 = -14 // (4) null + 1 = 1 // (5) @@ -17,10 +16,10 @@ undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` -1. 피 연산자 중 하나가 문자열인 `"" + 1`에서 `1`은 문자형으로 변환됩니다. 따라서 공백과 문자열 1을 더한, `"" + 1 = "1"`과 같은 효과를 발휘하죠. 그다음 연산 `"1" + 0`에도 같은 규칙이 적용됩니다. +1. 피연산자 중 하나가 문자열인 `"" + 1`에서 `1`은 문자형으로 변환됩니다. 따라서 공백과 문자열 1을 더한, `"" + 1 = "1"`과 같은 효과를 발휘하죠. 그다음 연산 `"1" + 0`에도 같은 규칙이 적용됩니다. 2. 뺄셈 연산자 `-`는 기타 수학 연산자처럼 숫자형만을 인수로 받습니다. 빈 문자열 `""`는 숫자 `0`으로 변환되기 때문에 결과는 `-1`이 됩니다. -3. 피 연산자 중 하나가 문자열이므로 숫자 5가 문자열로 변환됩니다. +3. 피연산자 중 하나가 문자열이므로 숫자 5가 문자열로 변환됩니다. 4. 뺄셈 연산자는 인수를 숫자형으로 변화시키므로 `" -9 "`는 숫자 `-9`로 변합니다. 앞, 뒤 공백은 제거되죠. 5. 숫자형으로 변환 시 `null`은 `0`이 됩니다. -6. `undefined`는 숫자형으로 변환시 `NaN`이 됩니다. -7. 문자열이 숫자형으로 변할 땐 문자열 앞뒤의 공백이 삭제됩니다. 뺄셈 연산자 앞의 피연산자는 공백을 만드는 문자 `\t`와 `\n`, 그 사이의 "일반적인" 공백으로 구성됩니다. 따라서 `" \t \n"`는 숫자형으로 변환 시 길이가 `0`인 문자열로 취급되어 숫자 `0`이 됩니다. \ No newline at end of file +6. 숫자형으로 변환 시 `undefined`는 `NaN`이 됩니다. +7. 문자열이 숫자형으로 변할 땐 문자열 앞뒤의 공백이 삭제됩니다. 뺄셈 연산자 앞의 피연산자는 공백을 만드는 문자 `\t`와 `\n`, 그 사이의 '일반적인' 공백으로 구성됩니다. 따라서 `" \t \n"`는 숫자형으로 변환 시 길이가 `0`인 문자열로 취급되어 숫자 `0`이 됩니다. \ No newline at end of file diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md index cff6c67e6a..2c45b127f9 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -16,7 +16,6 @@ true + false "$" + 4 + 5 "4" - 2 "4px" - 2 -7 / 0 " -9 " + 5 " -9 " - 5 null + 1 diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index 29193f4995..5a43db6fc9 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -52,21 +52,26 @@ ```js run alert( 5 % 2 ); // 5를 2로 나눈 후의 나머지인 1을 출력 alert( 8 % 3 ); // 8을 3으로 나눈 후의 나머지인 2를 출력 +alert( 8 % 4 ); // 8을 4로 나눈 후의 나머지인 0을 출력 ``` ## 거듭제곱 연산자 ** 거듭제곱 연산자(exponentiation operator)를 사용한 `a ** b`를 평가하면 `a`를 `b`번 곱한 값이 반환됩니다. +학교 교육 과정에서는 이를 ab로 표기합니다. + 예시: ```js run -alert( 2 ** 2 ); // 4 (2 * 2) -alert( 2 ** 3 ); // 8 (2 * 2 * 2) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 ``` -거듭제곱 연산자는 정수가 아닌 숫자에 대해서도 동작합니다. `1/2`을 사용하면 제곱근을 구할 수 있죠. +수학에서와 마찬가지로, 거듭제곱 연산자는 정수가 아닌 숫자에 대해서도 동작합니다. + +예를 들어, 제곱근은 ½ 제곱으로 나타낼 수 있습니다. ```js run alert( 4 ** (1/2) ); // 2 (1/2 거듭제곱은 제곱근) @@ -104,7 +109,12 @@ alert( 2 + '1' ); // "21" alert(2 + 2 + '1' ); // '221'이 아니라 '41'이 출력됩니다. ``` -연산은 왼쪽에서 오른쪽으로 순차적으로 진행되기 때문에 이런 결과가 나왔습니다. 두 개의 숫자 뒤에 문자열이 오는 경우, 숫자가 먼저 더해지고, 그 후 더해진 숫자와 문자열과의 병합이 일어납니다. +여기서는 연산자가 하나씩 순서대로 동작합니다. 첫 번째 `+`는 두 숫자를 더하므로 `4`를 반환하고, 그다음 `+`는 여기에 문자열 `'1'`을 더하므로 `4 + '1' = '41'`과 같은 결과가 됩니다. + +```js run +alert('1' + 2 + 2); // '14'가 아니라 '122'가 출력됩니다. +``` +첫 번째 피연산자가 문자열이므로, 컴파일러는 나머지 두 피연산자도 문자열로 취급합니다. `2`가 `'1'`에 병합되므로, `'1' + 2 = "12"`가 되고 `"12" + 2 = "122"`가 됩니다. 이처럼 이항 덧셈 연산자 `+`는 문자열 연결과 변환이라는 특별한 기능을 제공합니다. 다른 산술 연산자가 오직 숫자형의 피연산자만 다루고, 피연산자가 숫자형이 아닌 경우에 그 형을 숫자형으로 바꾸는 것과는 대조적입니다. @@ -185,22 +195,22 @@ alert( +apples + +oranges ); // 5 | 순위 | 연산자 이름 | 기호 | | --- | ------ | --- | | ... | ... | ... | -| 17 | 단항 덧셈 | `+` | -| 17 | 단항 부정 | `-` | -| 16 | 지수 | `**` | -| 15 | 곱셈 | `*` | -| 15 | 나눗셈 | `/` | -| 13 | 덧셈 | `+` | -| 13 | 뺄셈 | `-` | +| 14 | 단항 덧셈 | `+` | +| 14 | 단항 부정 | `-` | +| 13 | 거듭제곱 | `**` | +| 12 | 곱셈 | `*` | +| 12 | 나눗셈 | `/` | +| 11 | 덧셈 | `+` | +| 11 | 뺄셈 | `-` | | ... | ... | ... | -| 3 | 할당 | `=` | +| 2 | 할당 | `=` | | ... | ... | ... | -'단항 덧셈 연산자'는 우선순위 `17`로, '(이항) 덧셈 연산자'의 우선순위 `13`보다 높습니다. 표현식 `"+apples + +oranges"`에서 단항 덧셈 연산자가 덧셈보다 먼저 수행되는 이유가 바로 이 때문입니다. +'단항 덧셈 연산자'는 우선순위 `14`로, '(이항) 덧셈 연산자'의 우선순위 `11`보다 높습니다. 표현식 `"+apples + +oranges"`에서 단항 덧셈 연산자가 덧셈보다 먼저 수행되는 이유가 바로 이 때문입니다. ## 할당 연산자 -무언가를 할당할 때 쓰이는 `=`도 연산자입니다. 이 연산자는 할당(assignment) 연산자라고 불리는데, 우선순위는 `3`으로 아주 낮습니다. +무언가를 할당할 때 쓰이는 `=`도 연산자입니다. 이 연산자는 할당(assignment) 연산자라고 불리는데, 우선순위는 `2`로 아주 낮습니다. `x = 2 * 2 + 1`과 같은 표현식에서 계산이 먼저 이뤄지고, 그 결과가 `x`에 할당되는 이유가 바로 이 때문입니다. @@ -214,7 +224,7 @@ alert( x ); // 5 `=`는 연산자이기 때문에 흥미로운 함축성을 내포하고 있습니다. -자바스크립트에서 대부분의 연산자들은 값을 반환합니다. `+`와 `-`뿐만 아니라 `=` 역시 값을 반환하죠. +자바스크립트의 모든 연산자는 값을 반환합니다. `+`와 `-`뿐만 아니라 `=` 역시 값을 반환하죠. `x = value`을 호출하면 `value`가 `x`에 쓰여지고, 이에 더하여 *`value`가 반환됩니다*. @@ -294,9 +304,9 @@ alert( n ); // 14 ```js run let n = 2; -n *= 3 + 5; +n *= 3 + 5; // right part evaluated first, same as n *= 8 -alert( n ); // 16 (*=의 우측이 먼저 평가되므로, 위 식은 n *= 8과 동일합니다.) +alert( n ); // 16 ``` ## 증가·감소 연산자 @@ -428,7 +438,7 @@ counter++; - 오른쪽 시프트(RIGHT SHIFT) ( `>>` ) - 부호 없는 오른쪽 시프트(ZERO-FILL RIGHT SHIFT) ( `>>>` ) -비트 연산자는 저수준(2진 표현)에서 숫자를 다뤄야 할 때 쓰이므로 흔하게 쓰이진 않습니다. 웹 개발 시엔 이런 일이 자주 일어나지 않기 때문에 비트 연산자를 만날 일은 거의 없죠. 그렇긴 해도 암호를 다뤄야 할 땐 비트 연산자가 유용하기 때문에 때가 되면 MDN의 [비트 연산자](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) 문서를 보시는 걸 추천합니다. +비트 연산자는 저수준(2진 표현)에서 숫자를 다뤄야 할 때 쓰이므로 흔하게 쓰이진 않습니다. 웹 개발 시엔 이런 일이 자주 일어나지 않기 때문에 비트 연산자를 만날 일은 거의 없죠. 그렇긴 해도 암호를 다뤄야 할 땐 비트 연산자가 유용하기 때문에 때가 되면 MDN의 [비트 연산자](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) 문서를 보시는 걸 추천합니다. ## 쉼표 연산자 diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md index 6066afae22..6ffb614612 100644 --- a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md @@ -14,8 +14,8 @@ null === +"\n0\n" → false 1. 명백히 true입니다. 2. 문자열의 비교는 사전순서가 기준이므로 false입니다. `"a"`는 `"p"`보다 작습니다. -3. 두 피연산자는 문자열이므로, 사전순으로 비교가 이뤄집니다. 왼쪽 피연산자의 첫 번째 글자 `"2"`는 오른쪽 피연산자의 첫 번째 글자 `"1"`보다 큽니다. -4. `null`과 `undefined`는 같습니다. +3. 두 피연산자는 문자열이므로, 사전 순으로 비교가 이뤄집니다. 왼쪽 피연산자의 첫 번째 글자 `"2"`는 오른쪽 피연산자의 첫 번째 글자 `"1"`보다 큽니다. +4. `null`과 `undefined`는 서로에 대해서만 같습니다. 5. 일치 연산자는 형도 체크합니다. 형이 다르면 false가 반환됩니다. -6. (4)와 유사한 문제입니다. `null`은 오직 `undefined`와 같습니다. -7. 형이 다르므로 false가 반환됩니다. \ No newline at end of file +6. `(4)`와 유사한 문제입니다. `null`은 오직 `undefined`와 같습니다. +7. 서로 다른 형을 일치 연산자로 비교한 경우입니다. diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md index 0619bae6c0..3d74c71286 100644 --- a/1-js/02-first-steps/09-comparison/article.md +++ b/1-js/02-first-steps/09-comparison/article.md @@ -6,8 +6,8 @@ - 보다 큼·작음: a > b, a < b - 보다 크거나·작거나 같음: a >= b, a <= b -- 같음(동등): `a == b`. 등호 `=`가 두 개 연달아 오는 것에 유의하세요. `a ​​= b`와 같이 등호가 하나일 때는 할당을 의미합니다. -- 같지 않음(부등): 같지 않음을 나타내는 수학 기호 는 자바스크립트에선 a != b로 나타냅니다. 할당연산자 `=` 앞에 느낌표 `!`를 붙여서 표시합니다. +- 같음(동등): `a == b`. 등호가 두 개인 `==`는 두 값이 같은지 검사하는 동등 연산자이며, `a = b`처럼 등호가 하나인 경우는 할당을 의미한다는 점에 유의하세요. +- 같지 않음(부등): 같지 않음을 나타내는 수학 기호 는 자바스크립트에선 a != b로 나타냅니다. 이번 글에선 비교 시 일어나는 기이한 현상을 포함하여 다양한 자료형을 대상으로 자바스크립트가 어떻게 비교를 하는지에 대해 다룰 예정입니다. @@ -57,7 +57,9 @@ alert( 'Bee' > 'Be' ); // true 4. 글자 간 비교가 끝날 때까지 이 과정을 반복합니다. 5. 비교가 종료되었고 문자열의 길이도 같다면 두 문자열은 동일하다고 결론 냅니다. 비교가 종료되었지만 두 문자열의 길이가 다르면 길이가 긴 문자열이 더 크다고 결론 냅니다. -예시의 `'Z' > 'A'`는 위 알고리즘의 첫 번째 단계에서 비교 결과가 도출됩니다. 반면, 문자열 `'Glow'`와 `'Glee'`는 복수의 문자로 이루어진 문자열이기 때문에, 아래와 같은 순서로 문자열 비교가 이뤄집니다. +위 첫 번째 예시에서 `'Z' > 'A'` 비교는 첫 번째 단계에서 결과가 도출됩니다. + +두 번째 비교인 `'Glow'`와 `'Glee'`는 문자열이 글자 단위로 비교되므로 더 많은 단계가 필요합니다. 1. `G`는 `G`와 같습니다. 2. `l`은 `l`과 같습니다. @@ -211,4 +213,4 @@ alert( undefined == 0 ); // false (3) - 문자열은 문자 단위로 비교되는데, 이때 비교 기준은 '사전' 순입니다. - 서로 다른 타입의 값을 비교할 땐 숫자형으로 형 변환이 이뤄지고 난 후 비교가 진행됩니다(일치 연산자는 제외). - `null`과 `undefined`는 동등 비교(`==`) 시 서로 같지만 다른 값과는 같지 않습니다. -- `null`이나 `undefined`가 될 확률이 있는 변수가 `>` 또는 `<`의 피연산자로 올 때는 주의를 기울이시기 바랍니다. `null`, `undefined` 여부를 확인하는 코드를 따로 추가하는 습관을 들이길 권유합니다. +- `null`이나 `undefined`가 될 확률이 있는 변수가 `>` 또는 `<`의 피연산자로 올 때는 주의를 기울이시기 바랍니다. `null`, `undefined` 여부를 확인하는 코드를 따로 추가하는 습관을 들이길 권유합니다. \ No newline at end of file diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index 2f0a250112..ed517093ee 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -6,7 +6,11 @@ importance: 2 `if..else` 구조를 이용해 "자바스크립트의 '공식' 이름은 무엇일까요?"라는 질문을 하는 코드를 작성해 보세요. +<<<<<<< HEAD 사용자가 'ECMAScript'를 입력했다면 '정답입니다!', 아니라면 '모르셨나요? 정답은 ECMAScript입니다!'라는 메시지를 보여주세요. +======= +If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!" +>>>>>>> upstream/master ![](ifelse_task2.svg) diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 9d12c2ce83..e26c79a9d2 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -68,7 +68,11 @@ if (cond) { ## 'else'절 +<<<<<<< HEAD `if`문엔 `else` 절을 붙일 수 있습니다. `else` 뒤에 이어지는 코드 블록은 조건이 거짓일 때 실행됩니다. +======= +The `if` statement may contain an optional `else` block. It executes when the condition is falsy. +>>>>>>> upstream/master 예시: ```js run @@ -180,10 +184,17 @@ alert( message ); 물음표 연산자를 이런 방식으로 쓰는 걸 처음 본 분이라면 이 코드가 어떻게 동작하는지 파악하기 힘들 수 있습니다. 그러나 주의를 집중하고 보면, 단순히 여러 조건을 나열한 코드임에 불과하다는 것을 알 수 있습니다. +<<<<<<< HEAD 1. 첫 번째 물음표에선 조건문 `age < 3`을 검사합니다. 2. 그 결과가 참이면 `'아기야 안녕?'`를 반환합니다. 그렇지 않다면 첫 번째 콜론 `":"`에 이어지는 조건문 `age < 18`을 검사합니다. 3. 그 결과가 참이면 `'안녕!'`를 반환합니다. 그렇지 않다면 다음 콜론 `":"`에 이어지는 조건문 `age < 100`을 검사합니다. 4. 그 결과가 참이면 `'환영합니다!'`를 반환합니다. 그렇지 않다면 마지막 콜론 `":"` 이후의 표현식인 `'나이가 아주 많으시거나, 나이가 아닌 값을 입력 하셨군요!'`를 반환합니다. +======= +1. The first question mark checks whether `age < 3`. +2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon ":", checking `age < 18`. +3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon ":", checking `age < 100`. +4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon ":", returning `'What an unusual age!'`. +>>>>>>> upstream/master `if..else`를 사용하면 위 예시를 아래와 같이 변형할 수 있습니다. diff --git a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md index f3265b77bb..994e62d7bf 100644 --- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md +++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md @@ -1,6 +1,6 @@ 피연산자 중 첫 번째 falsy인 `null`이 출력됩니다. ```js run -alert( 1 && null && 2 ); +alert(1 && null && 2); ``` diff --git a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md index e11019df3c..47bdbf221f 100644 --- a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md +++ b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md @@ -4,6 +4,10 @@ importance: 3 # 사이 범위 확인하기 +<<<<<<< HEAD `age`(나이)가 `14`세 이상 `90`세 이하에 속하는지를 확인하는 `if`문을 작성하세요. +======= +Write an `if` condition to check that `age` is between `14` and `90` inclusively. +>>>>>>> upstream/master "이상과 이하"는 `age`(나이) 범위에 `14`나 `90`이 포함된다는 의미입니다. diff --git a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md index 8add5934a5..0d9904c48c 100644 --- a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md +++ b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md @@ -4,6 +4,10 @@ importance: 3 # 바깥 범위 확인하기 +<<<<<<< HEAD `age`(나이)가 `14`세 이상 `90`세 이하에 속하지 않는지를 확인하는 `if`문을 작성하세요. +======= +Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively. +>>>>>>> upstream/master 답안은 NOT `!` 연산자를 사용한 답안과 사용하지 않은 답안 2가지를 제출해 주세요. \ No newline at end of file diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md index 66e7916d0d..b283650045 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md @@ -3,20 +3,32 @@ ```js run demo let userName = prompt("사용자 이름을 입력해주세요.", ''); -if (userName == 'Admin') { +if (userName === 'Admin') { let pass = prompt('비밀번호:', ''); +<<<<<<< HEAD if (pass == 'TheMaster') { alert( '환영합니다!' ); } else if (pass == '' || pass == null) { alert( '취소되었습니다.' ); +======= + if (pass === 'TheMaster') { + alert( 'Welcome!' ); + } else if (pass === '' || pass === null) { + alert( 'Canceled' ); +>>>>>>> upstream/master } else { alert( '인증에 실패하였습니다.' ); } +<<<<<<< HEAD } else if (userName == '' || userName == null) { alert( '취소되었습니다.' ); +======= +} else if (userName === '' || userName === null) { + alert( 'Canceled' ); +>>>>>>> upstream/master } else { alert( "인증되지 않은 사용자입니다." ); } diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md index a71878d535..dddc2d2209 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -1,6 +1,10 @@ # 논리 연산자 +<<<<<<< HEAD 자바스크립트엔 세 종류의 논리 연산자 `||`(OR), `&&`(AND), `!`(NOT)이 있습니다. +======= +There are four logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT), `??` (Nullish Coalescing). Here we cover the first three, the `??` operator is in the next article. +>>>>>>> upstream/master 연산자에 '논리'라는 수식어가 붙긴 하지만 논리 연산자는 피연산자로 불린형뿐만 아니라 모든 타입의 값을 받을 수 있습니다. 연산 결과 역시 모든 타입이 될 수 있습니다. @@ -64,7 +68,11 @@ if (hour < 10 || hour > 18 || isWeekend) { } ``` +<<<<<<< HEAD ## 첫 번째 truthy를 찾는 OR 연산자 '||' +======= +## OR "||" finds the first truthy value [#or-finds-the-first-truthy-value] +>>>>>>> upstream/master 지금까진 피연산자가 불린형인 경우만을 다뤘습니다. 전통적인 방식이죠. 이제 자바스크립트에서만 제공하는 논리연산자 OR의 '추가'기능에 대해 알아보겠습니다. @@ -84,7 +92,11 @@ result = value1 || value2 || value3; 여기서 핵심은 반환 값이 형 변환을 하지 않은 원래 값이라는 것입니다. +<<<<<<< HEAD 정리해 보자면 이렇습니다. OR `"||"` 연산자를 여러 개 체이닝(chaining) 하면 첫 번째 truthy를 반환합니다. 피연산자에 truthy가 하나도 없다면 마지막 피연산자를 반환합니다. +======= +In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found. +>>>>>>> upstream/master 예시: @@ -101,9 +113,15 @@ alert( undefined || null || 0 ); // 0 (모두 falsy이므로, 마지막 값을 1. **변수 또는 표현식으로 구성된 목록에서 첫 번째 truthy 얻기** +<<<<<<< HEAD `firstName`, `lastName`, `nickName`이란 변수가 있는데 이 값들은 모두 옵션 값이라고 해봅시다. OR `||`을 사용하면 실제 값이 들어있는 변수를 찾고, 그 값을 보여줄 수 있습니다. 변수 모두에 값이 없는 경우엔 `익명`를 보여줍시다. +======= + For instance, we have `firstName`, `lastName` and `nickName` variables, all optional (i.e. can be undefined or have falsy values). + + Let's use OR `||` to choose the one that has the data and show it (or `"Anonymous"` if nothing set): +>>>>>>> upstream/master ```js run let firstName = ""; @@ -115,7 +133,11 @@ alert( undefined || null || 0 ); // 0 (모두 falsy이므로, 마지막 값을 */!* ``` +<<<<<<< HEAD 모든 변수가 falsy이면 `"익명"`이 출력되었을 겁니다. +======= + If all variables were falsy, `"Anonymous"` would show up. +>>>>>>> upstream/master 2. **단락 평가** @@ -123,7 +145,11 @@ alert( undefined || null || 0 ); // 0 (모두 falsy이므로, 마지막 값을 위에서 설명해 드린 바와 같이 OR`||`은 왼쪽부터 시작해서 오른쪽으로 평가를 진행하는데, truthy를 만나면 나머지 값들은 건드리지 않은 채 평가를 멈춥니다. 이런 프로세스를 '단락 평가'라고 합니다. +<<<<<<< HEAD 단락 평가의 동작 방식은 두 번째 피연산자가 변수 할당과 같은 부수적인 효과(side effect)를 가지는 표현식 일 때 명확히 볼 수 있습니다. +======= + The importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call. +>>>>>>> upstream/master 아래 예시를 실행하면 두 번째 메시지만 출력됩니다. @@ -223,8 +249,13 @@ AND 연산자 `&&`의 우선순위는 OR 연산자 `||`보다 높습니다. 따라서 `a && b || c && d`는 `(a && b) || (c && d)`와 동일하게 동작합니다. ```` +<<<<<<< HEAD ````warn header="`if`를 ||나 &&로 대체하지 마세요." 어떤 개발자들은 AND 연산자 `&&`를 `if`문을 '짧게' 줄이는 용도로 사용하곤 합니다. +======= +````warn header="Don't replace `if` with `||` or `&&`" +Sometimes, people use the AND `&&` operator as a "shorter way to write `if`". +>>>>>>> upstream/master 예시: @@ -244,7 +275,11 @@ let x = 1; if (x > 0) alert( '0보다 큽니다!' ); ``` +<<<<<<< HEAD `&&`를 사용한 코드가 더 짧긴 하지만 `if`문을 사용한 예시가 코드에서 무엇을 구현하고자 하는지 더 명백히 드러내고, 가독성도 좋습니다. 그러니 if 조건문이 필요하면 `if`를 사용하고 AND 연산자는 연산자 목적에 맞게 사용합시다. +======= +Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want `if` and use `&&` if we want AND. +>>>>>>> upstream/master ```` diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md index 04ca8f04bd..0cc588cde9 100644 --- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -2,6 +2,7 @@ [recent browser="new"] +<<<<<<< HEAD nullish 병합 연산자(nullish coalescing operator) `??`를 사용하면 짧은 문법으로 여러 피연산자 중 그 값이 '확정되어있는' 변수를 찾을 수 있습니다. `a ?? b`의 평가 결과는 다음과 같습니다. @@ -9,11 +10,27 @@ nullish 병합 연산자(nullish coalescing operator) `??`를 사용하면 짧 - 그 외의 경우는 `b` nullish 병합 연산자 `??`없이 `x = a ?? b`와 동일한 동작을 하는 코드를 작성하면 다음과 같습니다. +======= +The nullish coalescing operator is written as two question marks `??`. + +As it treats `null` and `undefined` similarly, we'll use a special term here, in this article. For brevity, we'll say that a value is "defined" when it's neither `null` nor `undefined`. + +The result of `a ?? b` is: +- if `a` is defined, then `a`, +- if `a` isn't defined, then `b`. + +In other words, `??` returns the first argument if it's not `null/undefined`. Otherwise, the second one. + +The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two. + +We can rewrite `result = a ?? b` using the operators that we already know, like this: +>>>>>>> upstream/master ```js -x = (a !== null && a !== undefined) ? a : b; +result = (a !== null && a !== undefined) ? a : b; ``` +<<<<<<< HEAD 비교 연산자와 논리 연산자만으로 nullish 병합 연산자와 같은 기능을 하는 코드를 작성하니 코드 길이가 길어지네요. 또 다른 예시를 살펴봅시다. `firstName`, `lastName`, `nickName`이란 변수에 사용자 이름이나 별명을 저장하는데, 사용자가 아무런 정보도 입력하지 않는 케이스도 허용한다고 해보겠습니다. @@ -21,13 +38,46 @@ x = (a !== null && a !== undefined) ? a : b; 화면엔 세 변수 중 실제 값이 있는 변수의 값을 출력하는데, 세 변수 모두 값이 없다면 '익명의 사용자'가 출력되도록 해보죠. 이럴 때 nullish 병합 연산자 `??`를 사용하면 값이 정해진 변수를 간편하게 찾아낼 수 있습니다. +======= +Now it should be absolutely clear what `??` does. Let's see where it helps. + +The common use case for `??` is to provide a default value. + +For example, here we show `user` if its value isn't `null/undefined`, otherwise `Anonymous`: + +```js run +let user; + +alert(user ?? "Anonymous"); // Anonymous (user is undefined) +``` + +Here's the example with `user` assigned to a name: + +```js run +let user = "John"; + +alert(user ?? "Anonymous"); // John (user is not null/undefined) +``` + +We can also use a sequence of `??` to select the first value from a list that isn't `null/undefined`. + +Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be not defined, if the user decided not to fill in the corresponding values. + +We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are `null/undefined`. + +Let's use the `??` operator for that: +>>>>>>> upstream/master ```js run let firstName = null; let lastName = null; let nickName = "바이올렛"; +<<<<<<< HEAD // null이나 undefined가 아닌 첫 번째 피연산자 +======= +// shows the first defined value: +>>>>>>> upstream/master *!* alert(firstName ?? lastName ?? nickName ?? "익명의 사용자"); // 바이올렛 */!* @@ -35,6 +85,7 @@ alert(firstName ?? lastName ?? nickName ?? "익명의 사용자"); // 바이올 ## '??'와 '||'의 차이 +<<<<<<< HEAD nullish 병합 연산자는 OR 연산자 `||`와 상당히 유사해 보입니다. 실제로 위 예시에서 `??`를 `||`로 바꿔도 그 결과는 동일하기까지 하죠. 관련 내용은 [이전 챕터](info:logical-operators#or-finds-the-first-truthy-value)에서 살펴본 바 있습니다. 그런데 두 연산자 사이에는 중요한 차이점이 있습니다. @@ -42,9 +93,38 @@ nullish 병합 연산자는 OR 연산자 `||`와 상당히 유사해 보입니 - `??`는 첫 번째 *정의된(defined)* 값을 반환합니다. `null`과 `undefined`, 숫자 `0`을 구분 지어 다뤄야 할 때 이 차이점은 매우 중요한 역할을 합니다. +======= +The OR `||` operator can be used in the same way as `??`, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value). + +For example, in the code above we could replace `??` with `||` and still get the same result: + +```js run +let firstName = null; +let lastName = null; +let nickName = "Supercoder"; + +// shows the first truthy value: +*!* +alert(firstName || lastName || nickName || "Anonymous"); // Supercoder +*/!* +``` + +Historically, the OR `||` operator was there first. It's been there since the beginning of JavaScript, so developers were using it for such purposes for a long time. + +On the other hand, the nullish coalescing operator `??` was added to JavaScript only recently, and the reason for that was that people weren't quite happy with `||`. + +The important difference between them is that: +- `||` returns the first *truthy* value. +- `??` returns the first *defined* value. + +In other words, `||` doesn't distinguish between `false`, `0`, an empty string `""` and `null/undefined`. They are all the same -- falsy values. If any of these is the first argument of `||`, then we'll get the second argument as the result. + +In practice though, we may want to use default value only when the variable is `null/undefined`. That is, when the value is really unknown/not set. +>>>>>>> upstream/master 예시를 살펴봅시다. +<<<<<<< HEAD ```js height = height ?? 100; ``` @@ -53,6 +133,8 @@ height = height ?? 100; 이제 `??`와 `||`을 비교해봅시다. +======= +>>>>>>> upstream/master ```js run let height = 0; @@ -60,19 +142,36 @@ alert(height || 100); // 100 alert(height ?? 100); // 0 ``` +<<<<<<< HEAD `height || 100`은 `height`에 `0`을 할당했지만 `0`을 falsy 한 값으로 취급했기 때문에 `null`이나 `undefined`를 할당한 것과 동일하게 처리합니다. 따라서 `height || 100`의 평가 결과는 `100`입니다. 반면 `height ?? 100`의 평가 결과는 `height`가 정확하게 `null`이나 `undefined`일 경우에만 `100`이 됩니다. 예시에선 `height`에 `0`이라는 값을 할당했기 때문에 얼럿창엔 `0`이 출력됩니다. 이런 특징 때문에 높이처럼 `0`이 할당될 수 있는 변수를 사용해 기능을 개발할 땐 `||`보다 `??`가 적합합니다. +======= +- The `height || 100` checks `height` for being a falsy value, and it's `0`, falsy indeed. + - so the result of `||` is the second argument, `100`. +- The `height ?? 100` checks `height` for being `null/undefined`, and it's not, + - so the result is `height` "as is", that is `0`. + +In practice, the zero height is often a valid value, that shouldn't be replaced with the default. So `??` does just the right thing. +>>>>>>> upstream/master ## 연산자 우선순위 +<<<<<<< HEAD [`??`의 연산자 우선순위](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table)는 `5`로 꽤 낮습니다. 따라서 `??`는 `=`와 `?` 보다는 먼저, 대부분의 연산자보다는 나중에 평가됩니다. 그렇기 때문에 복잡한 표현식 안에서 `??`를 사용해 값을 하나 선택할 땐 괄호를 추가하는 게 좋습니다. +======= +The precedence of the `??` operator is the same as `||`. They both equal `3` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). + +That means that, just like `||`, the nullish coalescing operator `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`. + +So we may need to add parentheses in expressions like this: +>>>>>>> upstream/master ```js run let height = null; @@ -84,6 +183,7 @@ let area = (height ?? 100) * (width ?? 50); alert(area); // 5000 ``` +<<<<<<< HEAD 그렇지 않으면 `*`가 `??`보다 우선순위가 높기 때문에 `*`가 먼저 실행됩니다. 결국엔 아래 예시처럼 동작하겠죠. @@ -96,6 +196,21 @@ let area = height ?? (100 * width) ?? 50; `??`엔 자바스크립트 언어에서 규정한 또 다른 제약사항이 있습니다. **안정성 관련 이슈 때문에 `??`는 `&&`나 `||`와 함께 사용하지 못합니다.** +======= +Otherwise, if we omit parentheses, then as `*` has the higher precedence than `??`, it would execute first, leading to incorrect results. + +```js +// without parentheses +let area = height ?? 100 * width ?? 50; + +// ...works this way (not what we want): +let area = height ?? (100 * width) ?? 50; +``` + +### Using ?? with && or || + +Due to safety reasons, JavaScript forbids using `??` together with `&&` and `||` operators, unless the precedence is explicitly specified with parentheses. +>>>>>>> upstream/master 아래 예시를 실행하면 문법 에러가 발생합니다. @@ -103,7 +218,11 @@ let area = height ?? (100 * width) ?? 50; let x = 1 && 2 ?? 3; // SyntaxError: Unexpected token '??' ``` +<<<<<<< HEAD 이 제약에 대해선 아직 논쟁이 많긴 하지만 사람들이 `||`를 `??`로 바꾸기 시작하면서 만드는 실수를 방지하고자 명세서에 제약이 추가된 상황입니다. +======= +The limitation is surely debatable, it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch from `||` to `??`. +>>>>>>> upstream/master 제약을 피하려면 괄호를 사용해주세요. @@ -117,7 +236,11 @@ alert(x); // 2 ## 요약 +<<<<<<< HEAD - nullish 병합 연산자 `??`를 사용하면 피연산자 중 '값이 할당된' 변수를 빠르게 찾을 수 있습니다. +======= +- The nullish coalescing operator `??` provides a short way to choose the first "defined" value from a list. +>>>>>>> upstream/master `??`는 변수에 기본값을 할당하는 용도로 사용할 수 있습니다. @@ -126,5 +249,10 @@ alert(x); // 2 height = height ?? 100; ``` +<<<<<<< HEAD - `??`의 연산자 우선순위는 대다수의 연산자보다 낮고 `?`와 `=` 보다는 높습니다. - 괄호 없이 `??`를 `||`나 `&&`와 함께 사용하는 것은 금지되어있습니다. +======= +- The operator `??` has a very low precedence, only a bit higher than `?` and `=`, so consider adding parentheses when using it in an expression. +- It's forbidden to use it with `||` or `&&` without explicit parentheses. +>>>>>>> upstream/master diff --git a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md index dff25b0bc7..1c66afe898 100644 --- a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md @@ -9,7 +9,12 @@ do { `do..while`반복문을 사용해 아래 두 조건이 모두 truthy인 경우 프롬프트 창이 뜨게 하면 됩니다. +<<<<<<< HEAD 1. `num <= 100`인지 확인하기. `100`보다 작거나 같은 값을 입력한 경우 프롬프트 창이 떠야 합니다. 2. `num`이 `null`이나 빈 문자열인지 확인하기. `num`이 `null`이나 빈 문자열이면 `&& num`이 거짓이 되므로 `while` 반복문이 종료됩니다. +======= +1. The check for `num <= 100` -- that is, the entered value is still not greater than `100`. +2. The check `&& num` is false when `num` is `null` or an empty string. Then the `while` loop stops too. +>>>>>>> upstream/master 참고: `num`이 `null`인 경우 `num <= 100`은 `true`가 되므로 두 번째 조건이 없으면 취소 버튼을 눌러도 반복문이 계속해서 실행됩니다. 따라서 위 두 조건을 모두 확인해야 합니다. diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md index fee9f4adcc..3779a7c569 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -6,7 +6,24 @@ *반복문(loop)* 을 사용하면 동일한 코드를 여러 번 반복할 수 있습니다. +<<<<<<< HEAD ## 'while' 반복문 +======= +```smart header="The for..of and for..in loops" +A small announcement for advanced readers. + +This article covers only basic loops: `while`, `do..while` and `for(..;..;..)`. + +If you came to this article searching for other types of loops, here are the pointers: + +- See [for..in](info:object#forin) to loop over object properties. +- See [for..of](info:array#loops) and [iterables](info:iterable) for looping over arrays and iterable objects. + +Otherwise, please read on. +``` + +## The "while" loop +>>>>>>> upstream/master `while` 반복문의 문법은 다음과 같습니다. @@ -106,10 +123,17 @@ for (let i = 0; i < 3; i++) { // 0, 1, 2가 출력됩니다. | 구성 요소 | | | |-------|----------|----------------------------------------------------------------------------| +<<<<<<< HEAD | begin | `i = 0` | 반복문에 진입할 때 단 한 번 실행됩니다. | | condition | `i < 3`| 반복마다 해당 조건이 확인됩니다. false이면 반복문을 멈춥니다. | | body | `alert(i)`| condition이 truthy일 동안 계속해서 실행됩니다. | | step| `i++` | 각 반복의 body가 실행된 이후에 실행됩니다. | +======= +| begin | `let i = 0` | Executes once upon entering the loop. | +| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. | +| body | `alert(i)`| Runs again and again while the condition is truthy. | +| step| `i++` | Executes after the body on each iteration. | +>>>>>>> upstream/master 일반적인 반복문 알고리즘은 다음과 같습니다. @@ -162,11 +186,14 @@ for (i = 0; i < 3; i++) { // 기존에 정의된 변수 사용 alert(i); // 3, 반복문 밖에서 선언한 변수이므로 사용할 수 있음 ``` - ```` +<<<<<<< HEAD ### 구성 요소 생략하기 +======= +### Skipping parts +>>>>>>> upstream/master `for`문의 구성 요소를 생략하는 것도 가능합니다. @@ -268,7 +295,11 @@ for (let i = 0; i < 10; i++) { 기술적인 관점에서 봤을 때, 이 예시는 위쪽에 있는 예시와 동일합니다. `continue`를 사용하는 대신 코드를 `if` 블록으로 감싼 점만 다릅니다. +<<<<<<< HEAD 그런데 이렇게 코드를 작성하면 부작용으로 중첩 레벨(중괄호 안의 `alert` 호출)이 하나 더 늘어납니다. `if` 안의 코드가 길어진다면 전체 가독성이 떨어질 수 있습니다. +======= +But as a side effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability. +>>>>>>> upstream/master ```` ````warn header="'?' 오른쪽엔 `break`나 `continue`가 올 수 없습니다." @@ -286,7 +317,6 @@ if (i > 5) { 물음표를 사용해서 위 조건문을 아래와 같이 바꾸려는 시도를 할 수 있을겁니다. - ```js no-beautify (i > 5) ? alert(i) : *!*continue*/!*; // 여기에 continue를 사용하면 안 됩니다. ``` @@ -318,9 +348,16 @@ alert('완료!'); 사용자가 `Cancel` 버튼을 눌렀을 때 반복문을 중단시킬 방법이 필요합니다. +<<<<<<< HEAD `input` 아래에 평범한 `break` 지시자를 사용하면 안쪽에 있는 반복문만 빠져나올 수 있습니다. 이것만으론 충분하지 않습니다(중첩 반복문을 포함한 반복문 두 개 모두를 빠져나와야 하기 때문이죠 - 옮긴이). 이럴 때 레이블을 사용할 수 있습니다. *레이블(label)* 은 반복문 앞에 콜론과 함께 쓰이는 식별자입니다. +======= +The ordinary `break` after `input` would only break the inner loop. That's not sufficient -- labels, come to the rescue! + +A *label* is an identifier with a colon before a loop: + +>>>>>>> upstream/master ```js labelName: for (...) { ... @@ -342,7 +379,12 @@ labelName: for (...) { // 입력받은 값을 가지고 무언가를 함 } } +<<<<<<< HEAD alert('완료!'); +======= + +alert('Done!'); +>>>>>>> upstream/master ``` 위 예시에서 `break outer`는 `outer`라는 레이블이 붙은 반복문을 찾고, 해당 반복문을 빠져나오게 해줍니다. @@ -361,14 +403,37 @@ for (let i = 0; i < 3; i++) { ... } ````warn header="레이블은 마음대로 '점프'할 수 있게 해주지 않습니다." 레이블을 사용한다고 해서 원하는 곳으로 마음대로 점프할 수 있는 것은 아닙니다. +<<<<<<< HEAD 아래 예시처럼 레이블을 사용하는 것은 불가능합니다. ```js break label; // 아래 for 문으로 점프할 수 없습니다. +======= +For example, it is impossible to do this: + +```js +break label; // jump to the label below (doesn't work) +>>>>>>> upstream/master label: for (...) ``` +<<<<<<< HEAD `break`와 `continue`는 반복문 안에서만 사용할 수 있고, 레이블은 반드시 `break`이나 `continue` 지시자 위에 있어야 합니다. +======= +A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.: + +```js +label: { + // ... + break label; // works + // ... +} +``` + +...Although, 99.9% of the time `break` is used inside loops, as we've seen in the examples above. + +A `continue` is only possible from inside a loop. +>>>>>>> upstream/master ```` ## 요약 diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md index 251cbba446..1fd1f3804d 100644 --- a/1-js/02-first-steps/14-switch/article.md +++ b/1-js/02-first-steps/14-switch/article.md @@ -47,7 +47,11 @@ switch (a) { break; */!* case 5: +<<<<<<< HEAD alert( '비교하려는 값보다 큽니다.' ); +======= + alert( 'Too big' ); +>>>>>>> upstream/master break; default: alert( "어떤 값인지 파악이 되지 않습니다." ); @@ -139,7 +143,11 @@ switch (a) { `case 3`과 `case 5`는 동일한 메시지를 보여줍니다. +<<<<<<< HEAD `switch/case`문에서 `break`문이 없는 경우엔 조건에 상관없이 다음 `case`문이 실행되는 부작용이 발생합니다. 위 예시에서 `case 3`이 참인 경우엔 `(*)`로 표시한 줄 아래의 코드가 실행되는데, 그 아래 줄엔 `case 5`가 있고 `break`문도 없기 때문에 12번째 줄의 `break`문을 만날 때까지 코드는 계속 실행됩니다. +======= +The ability to "group" cases is a side effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`. +>>>>>>> upstream/master ## 자료형의 중요성 diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md index 85b5d8ab37..6e53e0b272 100644 --- a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md @@ -1 +1,7 @@ -동일하게 동작합니다. \ No newline at end of file +<<<<<<< HEAD +동일하게 동작합니다. +======= +No difference! + +In both cases, `return confirm('Did parents allow you?')` executes exactly when the `if` condition is falsy. +>>>>>>> upstream/master diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md index e0a188c9ba..d3030e11a6 100644 --- a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md @@ -14,4 +14,8 @@ function checkAge(age) { } ``` -`age > 18`을 감싸고 있는 괄호는 가독성을 높이기 위해 넣었습니다. 괄호가 없어도 동일하게 동작합니다. \ No newline at end of file +<<<<<<< HEAD +`age > 18`을 감싸고 있는 괄호는 가독성을 높이기 위해 넣었습니다. 괄호가 없어도 동일하게 동작합니다. +======= +Note that the parentheses around `age > 18` are not required here. They exist for better readability. +>>>>>>> upstream/master diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index 327bed8f91..c21dab18f1 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -20,11 +20,19 @@ function showMessage() { } ``` +<<<<<<< HEAD `function` 키워드, *함수 이름*, 괄호로 둘러싼 매개변수를 차례로 써주면 함수를 선언할 수 있습니다. 위 함수에는 매개변수가 없는데, 만약 매개변수가 여러 개 있다면 각 매개변수를 콤마로 구분해 줍니다. 이어서 함수를 구성하는 코드의 모임인 '함수 본문(body)'을 중괄호로 감싸 붙여줍시다. ```js function name(parameter1, parameter2, ... parameterN) { // 함수 본문 +======= +The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above, we'll see examples later) and finally the code of the function, also named "the function body", between curly braces. + +```js +function name(parameter1, parameter2, ... parameterN) { + // body +>>>>>>> upstream/master } ``` @@ -137,12 +145,20 @@ alert( userName ); // 함수는 외부 변수에 접근하지 않습니다. 따 ## 매개변수 +<<<<<<< HEAD 매개변수(parameter)를 이용하면 임의의 데이터를 함수 안에 전달할 수 있습니다. 매개변수는 *인자(parameter)* 라고 불리기도 합니다. +======= +We can pass arbitrary data to functions using parameters. +>>>>>>> upstream/master 아래 예시에서 함수 showMessage는 매개변수 `from` 과 `text`를 가집니다. ```js run +<<<<<<< HEAD function showMessage(*!*from, text*/!*) { // 인자: from, text +======= +function showMessage(*!*from, text*/!*) { // parameters: from, text +>>>>>>> upstream/master alert(from + ': ' + text); } @@ -152,8 +168,11 @@ function showMessage(*!*from, text*/!*) { // 인자: from, text `(*)`, `(**)`로 표시한 줄에서 함수를 호출하면, 함수에 전달된 인자는 지역변수 `from`과 `text`에 복사됩니다. 그 후 함수는 지역변수에 복사된 값을 사용합니다. +<<<<<<< HEAD 예시 하나를 더 살펴봅시다. 전역 변수 `from`이 있고, 이 변수를 함수에 전달하였습니다. 함수가 `from`을 변경하지만, 변경 사항은 외부 변수 `from`에 반영되지 않았습니다. 함수는 언제나 복사된 값을 사용하기 때문입니다. +======= +>>>>>>> upstream/master ```js run function showMessage(from, text) { @@ -172,9 +191,27 @@ showMessage(from, "Hello"); // *Ann*: Hello alert( from ); // Ann ``` +<<<<<<< HEAD 함수의 매개변수에 전달된 값을 *인수(argument)*라고 부르기도 합니다. 더 정확한 이해를 돕기 위해 용어를 다시 한번 정리해볼까요? +======= +When a value is passed as a function parameter, it's also called an *argument*. + +In other words, to put these terms straight: + +- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term). +- An argument is the value that is passed to the function when it is called (it's a call time term). + +We declare functions listing their parameters, then call them passing arguments. + +In the example above, one might say: "the function `showMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`". + + +## Default values + +If a function is called, but an argument is not provided, then the corresponding value becomes `undefined`. +>>>>>>> upstream/master - 매개변수는 함수 선언 방식 괄호 사이에 있는 변수입니다(선언 시 쓰이는 용어). - 인수는 함수를 호출할 때 매개변수에 전달되는 값입니다(호출 시 쓰이는 용어). @@ -193,9 +230,15 @@ alert( from ); // Ann showMessage("Ann"); ``` +<<<<<<< HEAD 이렇게 코드를 작성해도 에러가 발생하지 않습니다. 두 번째 매개변수에 값을 전달하지 않았기 때문에 `text`엔 `undefined`가 할당될 뿐입니다. 따라서 에러 없이 `"Ann: undefined"`가 출력됩니다. 매개변수에 값을 전달하지 않아도 그 값이 `undefined`가 되지 않게 하려면 함수를 선언할 때 `=`를 사용해 '기본값(default value)'을 설정해주면 됩니다. +======= +That's not an error. Such a call would output `"*Ann*: undefined"`. As the value for `text` isn't passed, it becomes `undefined`. + +We can specify the so-called "default" (to use if omitted) value for a parameter in the function declaration, using `=`: +>>>>>>> upstream/master ```js run function showMessage(from, *!*text = "no text given"*/!*) { @@ -205,7 +248,17 @@ function showMessage(from, *!*text = "no text given"*/!*) { showMessage("Ann"); // Ann: no text given ``` +<<<<<<< HEAD 이젠 `text`가 값을 전달받지 못해도 `undefined` 대신 기본값 `"no text given"`이 할당됩니다. +======= +Now if the `text` parameter is not passed, it will get the value `"no text given"`. + +The default value also jumps in if the parameter exists, but strictly equals `undefined`, like this: + +```js +showMessage("Ann", undefined); // Ann: no text given +``` +>>>>>>> upstream/master 매개변수에 값을 전달해도 그 값이 `undefined`와 엄격히 일치한다면 기본값이 할당됩니다. @@ -230,6 +283,7 @@ function showMessage(from, text = anotherFunction()) { 반면 `text`에 값이 없는 경우 `showMessage()`를 호출할 때마다 `anotherFunction()`이 호출됩니다. +<<<<<<< HEAD ``` ````smart header="구식 자바스크립트에서 매개변수 기본값 설정하는 방법" @@ -270,14 +324,63 @@ function showMessage(from, text) { 가끔은 함수를 선언할 때가 아닌 함수 선언 후에 매개변수 기본값을 설정하는 것이 적절한 경우도 있습니다. 이런 경우엔 함수를 호출할 때 매개변수를 `undefined`와 비교하여 매개변수가 전달되었는지를 확인합니다. +======= +In the example above, `anotherFunction()` isn't called at all, if the `text` parameter is provided. + +On the other hand, it's independently called every time when `text` is missing. +``` + +````smart header="Default parameters in old JavaScript code" +Several years ago, JavaScript didn't support the syntax for default parameters. So people used other ways to specify them. + +Nowadays, we can come across them in old scripts. + +For example, an explicit check for `undefined`: + +```js +function showMessage(from, text) { +*!* + if (text === undefined) { + text = 'no text given'; + } +*/!* + + alert( from + ": " + text ); +} +``` + +...Or using the `||` operator: + +```js +function showMessage(from, text) { + // If the value of text is falsy, assign the default value + // this assumes that text == "" is the same as no text at all + text = text || 'no text given'; + ... +} +``` +```` + + +### Alternative default parameters + +Sometimes it makes sense to assign default values for parameters at a later stage after the function declaration. + +We can check if the parameter is passed during the function execution, by comparing it with `undefined`: +>>>>>>> upstream/master ```js run function showMessage(text) { // ... *!* +<<<<<<< HEAD if (text === undefined) { // 매개변수가 생략되었다면 text = '빈 문자열'; +======= + if (text === undefined) { // if the parameter is missing + text = 'empty message'; +>>>>>>> upstream/master } */!* @@ -290,18 +393,31 @@ showMessage(); // 빈 문자열 이렇게 `if`문을 쓰는 것 대신 논리 연산자 `||`를 사용할 수도 있습니다. ```js +<<<<<<< HEAD // 매개변수가 생략되었거나 빈 문자열("")이 넘어오면 변수에 '빈 문자열'이 할당됩니다. function showMessage(text) { text = text || '빈 문자열'; +======= +function showMessage(text) { + // if text is undefined or otherwise falsy, set it to 'empty' + text = text || 'empty'; +>>>>>>> upstream/master ... } ``` +<<<<<<< HEAD 이 외에도 모던 자바스크립트 엔진이 지원하는 [nullish 병합 연산자(nullish coalescing operator)](info:nullish-coalescing-operator) `??`를 사용하면 `0`처럼 falsy로 평가되는 값들을 일반 값처럼 처리할 수 있어서 좋습니다. ```js run // 매개변수 'count'가 `undefined` 또는 `null`이면 'unknown'을 출력해주는 함수 +======= +Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when most falsy values, such as `0`, should be considered "normal": + +```js run +>>>>>>> upstream/master function showCount(count) { + // if count is undefined or null, show "unknown" alert(count ?? "unknown"); } @@ -463,9 +579,15 @@ checkPermission(..) // 승인 여부를 확인하고 true나 false를 반환함 ```smart header="아주 짧은 이름" 정말 *빈번히* 쓰이는 함수 중에 이름이 아주 짧은 함수가 있습니다. +<<<<<<< HEAD [jQuery](http://jquery.com) 프레임워크에서 쓰이는 함수 `$`와 [Lodash](http://lodash.com/) 라이브러리의 핵심 함수 `_` 말이죠. 이 함수들은 지금까지 소개한 함수 이름짓기에 관련된 규칙을 지키지 않고 있습니다. 예외에 속하죠. 함수 이름은 간결하고 함수가 어떤 일을 하는지 설명할 수 있게 지어야 합니다. +======= +For example, the [jQuery](https://jquery.com/) framework defines a function with `$`. The [Lodash](https://lodash.com/) library has its core function named `_`. + +These are exceptions. Generally function names should be concise and descriptive. +>>>>>>> upstream/master ``` ## 함수 == 주석 @@ -531,7 +653,11 @@ function 함수이름(복수의, 매개변수는, 콤마로, 구분합니다) { 깔끔하고 이해하기 쉬운 코드를 작성하려면 함수 내부에서 외부 변수를 사용하는 방법 대신 지역 변수와 매개변수를 활용하는 게 좋습니다. +<<<<<<< HEAD 개발자는 매개변수를 받아서 그 변수를 가지고 반환 값을 만들어 내는 함수를 더 쉽게 이해할 수 있습니다. 매개변수 없이 함수 내부에서 외부 변수를 수정해 반환 값을 만들어 내는 함수는 쉽게 이해하기 힘듭니다. +======= +It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side effect. +>>>>>>> upstream/master 함수 이름을 지을 땐 아래와 같은 규칙을 따르는 것이 좋습니다. diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md index 1ea5b81e3a..52655a7df8 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -12,7 +12,13 @@ function sayHi() { 함수 선언 방식 외에 *함수 표현식(Function Expression)* 을 사용해서 함수를 만들 수 있습니다. +<<<<<<< HEAD 함수 표현식으로 함수를 생성해보겠습니다. +======= +It allows us to create a new function in the middle of any expression. + +For example: +>>>>>>> upstream/master ```js let sayHi = function() { @@ -20,9 +26,25 @@ let sayHi = function() { }; ``` +<<<<<<< HEAD 함수를 생성하고 변수에 값을 할당하는 것처럼 함수가 변수에 할당되었습니다. 함수가 어떤 방식으로 만들어졌는지에 관계없이 함수는 값이고, 따라서 변수에 할당할 수 있습니다. 위 예시에선 함수가 변수 `sayHi`에 저장된 값이 되었습니다. 위 예시를 간단한 말로 풀면 다음과 같습니다: "함수를 만들고 그 함수를 변수 `sayHi`에 할당하기" +======= +Here we can see a variable `sayHi` getting a value, the new function, created as `function() { alert("Hello"); }`. + +As the function creation happens in the context of the assignment expression (to the right side of `=`), this is a *Function Expression*. + +Please note, there's no name after the `function` keyword. Omitting a name is allowed for Function Expressions. + +Here we immediately assign it to the variable, so the meaning of these code samples is the same: "create a function and put it into the variable `sayHi`". + +In more advanced situations, that we'll come across later, a function may be created and immediately called or scheduled for a later execution, not stored anywhere, thus remaining anonymous. + +## Function is a value + +Let's reiterate: no matter how the function is created, a function is a value. Both examples above store a function in the `sayHi` variable. +>>>>>>> upstream/master 함수는 값이기 때문에 `alert`를 이용하여 함수 코드를 출력할 수도 있습니다. @@ -63,22 +85,31 @@ sayHi(); // Hello // 본래 함수도 정상적으로 실행됩니다. 2. `(2)` 에선 `sayHi`를 새로운 변수 `func`에 복사합니다. 이때 `sayHi` 다음에 괄호가 없다는 점에 유의하시기 바랍니다. 괄호가 있었다면 `func = sayHi()` 가 되어 `sayHi` *함수* 그 자체가 아니라, *함수 호출 결과(함수의 반환 값)* 가 `func`에 저장되었을 겁니다. 3. 이젠 `sayHi()` 와 `func()`로 함수를 호출할 수 있게 되었습니다. +<<<<<<< HEAD 함수 `sayHi`는 아래와 같이 함수 표현식을 사용해 정의할 수 있습니다. +======= +We could also have used a Function Expression to declare `sayHi`, in the first line: +>>>>>>> upstream/master ```js -let sayHi = function() { +let sayHi = function() { // (1) create alert( "Hello" ); }; -let func = sayHi; +let func = sayHi; //(2) // ... ``` 동작 결과는 동일합니다. +<<<<<<< HEAD ````smart header="끝에 세미 콜론은 왜 있나요?" 함수 표현식의 끝에 왜 세미 콜론 `;`이 붙는지 의문이 들 수 있습니다. 함수 선언문에는 세미 콜론이 없는데 말이죠. +======= +````smart header="Why is there a semicolon at the end?" +You might wonder, why do Function Expressions have a semicolon `;` at the end, but Function Declarations do not: +>>>>>>> upstream/master ```js function sayHi() { @@ -90,9 +121,15 @@ let sayHi = function() { }*!*;*/!* ``` +<<<<<<< HEAD 이유는 간단합니다. - `if { ... }`, `for { }`, `function f { }` 같이 중괄호로 만든 코드 블록 끝엔 `;`이 없어도 됩니다. - 함수 표현식은 `let sayHi = ...;`과 같은 구문 안에서 값의 역할을 합니다. 코드 블록이 아니고 값처럼 취급되어 변수에 할당되죠. 모든 구문의 끝엔 세미 콜론 `;`을 붙이는 게 좋습니다. 함수 표현식에 쓰인 세미 콜론은 함수 표현식 때문에 붙여진 게 아니라, 구문의 끝이기 때문에 붙여졌습니다. +======= +The answer is simple: a Function Expression is created here as `function(…) {…}` inside the assignment statement: `let sayHi = …;`. The semicolon `;` is recommended at the end of the statement, it's not a part of the function syntax. + +The semicolon would be there for a simpler assignment, such as `let sayHi = 5;`, and it's also there for a function assignment. +>>>>>>> upstream/master ```` ## 콜백 함수 @@ -132,13 +169,21 @@ function showCancel() { ask("동의하십니까?", showOk, showCancel); ``` +<<<<<<< HEAD 이렇게 함수를 작성하는 방법은 실무에서 아주 유용하게 쓰입니다. 면대면으로 질문하는 것보다 위처럼 컨펌창을 띄워 질문을 던지고 답변을 받으면 간단하게 설문조사를 진행할 수 있습니다. 실제 상용 서비스에선 컨펌 창을 좀 더 멋지게 꾸미는 등의 작업이 동반되긴 하지만, 일단 여기선 그게 중요한 포인트는 아닙니다. +======= +In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such functions usually draw a nice-looking question window. But that's another story. +>>>>>>> upstream/master **함수 `ask`의 인수, `showOk`와 `showCancel`은 *콜백 함수* 또는 *콜백*이라고 불립니다.** 함수를 함수의 인수로 전달하고, 필요하다면 인수로 전달한 그 함수를 "나중에 호출(called back)"하는 것이 콜백 함수의 개념입니다. 위 예시에선 사용자가 "yes"라고 대답한 경우 `showOk`가 콜백이 되고, "no"라고 대답한 경우 `showCancel`가 콜백이 됩니다. +<<<<<<< HEAD 아래와 같이 함수 표현식을 사용하면 코드 길이가 짧아집니다. +======= +We can use Function Expressions to write an equivalent, shorter function: +>>>>>>> upstream/master ```js run no-beautify function ask(question, yes, no) { @@ -174,7 +219,11 @@ ask( 첫 번째는 문법입니다. 코드를 통해 어떤 차이가 있는지 살펴봅시다. +<<<<<<< HEAD - *함수 선언문:* 함수는 주요 코드 흐름 중간에 독자적인 구문 형태로 존재합니다. +======= +- *Function Declaration:* a function, declared as a separate statement, in the main code flow: +>>>>>>> upstream/master ```js // 함수 선언문 @@ -182,8 +231,13 @@ ask( return a + b; } ``` +<<<<<<< HEAD - *함수 표현식:* 함수는 표현식이나 구문 구성(syntax construct) 내부에 생성됩니다. 아래 예시에선 함수가 할당 연산자 `=`를 이용해 만든 "할당 표현식" 우측에 생성되었습니다. +======= +- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created on the right side of the "assignment expression" `=`: + +>>>>>>> upstream/master ```js // 함수 표현식 let sum = function(a, b) { @@ -279,9 +333,15 @@ if (age < 18) { welcome(); // \ (실행) */!* // | +<<<<<<< HEAD function welcome() { // | alert("안녕!"); // | 함수 선언문은 함수가 선언된 블록 내 } // | 어디에서든 유효합니다 +======= + function welcome() { // | + alert("Hello!"); // | Function Declaration is available + } // | everywhere in the block where it's declared +>>>>>>> upstream/master // | *!* welcome(); // / (실행) @@ -289,8 +349,13 @@ if (age < 18) { } else { +<<<<<<< HEAD function welcome() { alert("안녕하세요!"); +======= + function welcome() { + alert("Greetings!"); +>>>>>>> upstream/master } } @@ -347,8 +412,13 @@ welcome(); // 제대로 동작합니다. ``` +<<<<<<< HEAD ```smart header="함수 선언문과 함수 표현식 중 무엇을 선택해야 하나요?" 제 경험에 따르면 함수 선언문을 이용해 함수를 선언하는 걸 먼저 고려하는 게 좋습니다. 함수 선언문으로 함수를 정의하면, 함수가 선언되기 전에 호출할 수 있어서 코드 구성을 좀 더 자유롭게 할 수 있습니다. +======= +```smart header="When to choose Function Declaration versus Function Expression?" +As a rule of thumb, when we need to declare a function, the first thing to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared. +>>>>>>> upstream/master 함수 선언문을 사용하면 가독성도 좋아집니다. 코드에서 `let f = function(…) {…}`보다 `function f(…) {…}` 을 찾는 게 더 쉽죠. 함수 선언 방식이 더 "눈길을 사로잡습니다". diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index b944933855..d2690fa736 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,7 +1,7 @@ ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index 2f98f308a9..479ba89c8e 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -5,7 +5,7 @@ ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/17-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md index bcaa1f38aa..e733c2785f 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/article.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md @@ -5,15 +5,19 @@ 바로 화살표 함수(arrow function)를 사용하는 것입니다. 화살표 함수라는 이름은 문법의 생김새를 차용해 지어졌습니다. ```js -let func = (arg1, arg2, ...argN) => expression +let func = (arg1, arg2, ..., argN) => expression; ``` +<<<<<<< HEAD 이렇게 코드를 작성하면 인자 `arg1..argN`를 받는 함수 `func`이 만들어집니다. 함수 `func`는 화살표(`=>`) 우측의 `표현식(expression)`을 평가하고, 평가 결과를 반환합니다. +======= +This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result. +>>>>>>> upstream/master 아래 함수의 축약 버전이라고 할 수 있죠. ```js -let func = function(arg1, arg2, ...argN) { +let func = function(arg1, arg2, ..., argN) { return expression; }; ``` @@ -33,7 +37,11 @@ let sum = function(a, b) { alert( sum(1, 2) ); // 3 ``` +<<<<<<< HEAD 보시는 바와 같이 `(a, b) => a + b`는 인수 `a`와 `b`를 받는 함수입니다. `(a, b) => a + b`는 실행되는 순간 표현식 `a + b`를 평가하고 그 결과를 반환합니다. +======= +As you can see, `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result. +>>>>>>> upstream/master - 인수가 하나밖에 없다면 인수를 감싸는 괄호를 생략할 수 있습니다. 괄호를 생략하면 코드 길이를 더 줄일 수 있습니다. @@ -48,7 +56,11 @@ alert( sum(1, 2) ); // 3 alert( double(3) ); // 6 ``` +<<<<<<< HEAD - 인수가 하나도 없을 땐 괄호를 비워놓으면 됩니다. 다만, 이 때 괄호는 생략할 수 없습니다. +======= +- If there are no arguments, parentheses are empty, but they must be present: +>>>>>>> upstream/master ```js run let sayHi = () => alert("안녕하세요!"); @@ -64,8 +76,13 @@ alert( sum(1, 2) ); // 3 let age = prompt("나이를 알려주세요.", 18); let welcome = (age < 18) ? +<<<<<<< HEAD () => alert('안녕') : () => alert("안녕하세요!"); +======= + () => alert('Hello!') : + () => alert("Greetings!"); +>>>>>>> upstream/master welcome(); ``` @@ -76,9 +93,15 @@ welcome(); ## 본문이 여러 줄인 화살표 함수 +<<<<<<< HEAD 위에서 소개해 드린 화살표 함수들은 `=>` 왼쪽에 있는 인수를 이용해 `=>` 오른쪽에 있는 표현식을 평가하는 함수들이었습니다. 그런데 평가해야 할 표현식이나 구문이 여러 개인 함수가 있을 수도 있습니다. 이 경우 역시 화살표 함수 문법을 사용해 함수를 만들 수 있습니다. 다만, 이때는 중괄호 안에 평가해야 할 코드를 넣어주어야 합니다. 그리고 `return` 지시자를 사용해 명시적으로 결괏값을 반환해 주어야 합니다. +======= +The arrow functions that we've seen so far were very simple. They took arguments from the left of `=>`, evaluated and returned the right-side expression with them. + +Sometimes we need a more complex function, with multiple expressions and statements. In that case, we can enclose them in curly braces. The major difference is that curly braces require a `return` within them to return a value (just like a regular function does). +>>>>>>> upstream/master 아래와 같이 말이죠. @@ -86,7 +109,11 @@ welcome(); let sum = (a, b) => { // 중괄호는 본문 여러 줄로 구성되어 있음을 알려줍니다. let result = a + b; *!* +<<<<<<< HEAD return result; // 중괄호를 사용했다면, return 지시자로 결괏값을 반환해주어야 합니다. +======= + return result; // if we use curly braces, then we need an explicit "return" +>>>>>>> upstream/master */!* }; @@ -105,7 +132,14 @@ alert( sum(1, 2) ); // 3 ## 요약 +<<<<<<< HEAD 화살표 함수는 본문이 한 줄인 함수를 작성할 때 유용합니다. 본문이 한 줄이 아니라면 다른 방법으로 화살표 함수를 작성해야 합니다. 1. 중괄호 없이 작성: `(...args) => expression` -- 화살표 오른쪽에 표현식을 둡니다. 함수는 이 표현식을 평가하고, 평가 결과를 반환합니다. 2. 중괄호와 함께 작성: `(...args) => { body }` -- 본문이 여러 줄로 구성되었다면 중괄호를 사용해야 합니다. 다만, 이 경우는 반드시 `return` 지시자를 사용해 반환 값을 명기해 주어야 합니다. +======= +Arrow functions are handy for simple actions, especially for one-liners. They come in two flavors: + +1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result. Parentheses can be omitted, if there's only a single argument, e.g. `n => n*2`. +2. With curly braces: `(...args) => { body }` -- brackets allow us to write multiple statements inside the function, but we need an explicit `return` to return something. +>>>>>>> upstream/master diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md index 915fdf1513..65baa080d6 100644 --- a/1-js/02-first-steps/18-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -55,7 +55,11 @@ for(;;) { `'use strict'`는 스크립트 최상단이나 함수 본문 최상단에 있어야 합니다. +<<<<<<< HEAD `'use strict'`가 없어도 코드는 정상적으로 동작합니다. 다만, 모던한 방식이 아닌 옛날 방식으로 동작하죠. '하위 호환성'을 지키면서 말이죠. 될 수 있으면 모던한 방식을 사용하는 걸 추천해 드립니다. +======= +Without `"use strict"`, everything still works, but some features behave in the old-fashioned, "compatible" way. We'd generally prefer the modern behavior. +>>>>>>> upstream/master 참고로, 추후에 배우게 될 클래스와 같은 몇몇 모던 기능은 엄격 모드를 자동으로 활성화합니다. @@ -103,6 +107,7 @@ typeof function(){} == "function" // 함수는 특별하게 취급됩니다. 호스트 환경이 브라우저인 경우, 다음과 같은 UI 함수를 이용해 사용자와 상호작용할 수 있습니다. +<<<<<<< HEAD [`prompt(question, [default])`](mdn:api/Window/prompt) : 프롬프트 창에 매개변수로 받은 `question`을 넣어 사용자에게 보여줍니다. '확인' 버튼을 눌렀을 땐 사용자가 입력한 값을 반환해주고, '취소' 버튼을 눌렀을 땐 `null`을 반환합니다. @@ -111,6 +116,16 @@ typeof function(){} == "function" // 함수는 특별하게 취급됩니다. [`alert(message)`](mdn:api/Window/alert) : `message`가 담긴 얼럿 창을 보여줍니다. +======= +[`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) +: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel". + +[`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) +: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`. + +[`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) +: Output a `message`. +>>>>>>> upstream/master 세 함수는 모두 *모달* 창을 띄워주는데, 모달 창이 닫히기 전까지 코드 실행이 중지됩니다. 사용자는 모달 창 외에 페이지에 있는 그 무엇과도 상호작용할 수 없습니다. @@ -143,8 +158,13 @@ alert( "차 주문 여부: " + isTeaWanted ); // true 할당 연산자 : `a = b` 형태의 할당 연산자와 `a *= 2` 형태의 복합 할당 연산자가 있습니다. +<<<<<<< HEAD 비트 연산자 : 비트 연산자는 인수를 32비트 정수로 변환하여 이진 연산을 수행합니다. 자세한 내용은 [docs](mdn:/JavaScript/Reference/Operators/Bitwise_Operators)에서 볼 수 있습니다. +======= +Bitwise +: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed. +>>>>>>> upstream/master 조건부 연산자 : 조건부 연산자는 자바스크립트 연산자 중 유일하게 매개변수가 3개인 연산자입니다. `cond ? resultA : resultB`와 같은 형태로 사용하고, `cond`가 truthy면 `resultA`를, 아니면 `resultB`를 반환합니다. @@ -256,7 +276,11 @@ switch (age) { 3. 화살표 함수: ```js +<<<<<<< HEAD // 화살표(=>) 우측엔 표현식이 있음 +======= + // expression on the right side +>>>>>>> upstream/master let sum = (a, b) => a + b; // 중괄호{ ... }를 사용하면 본문에 여러 줄의 코드를 작성할 수 있음. return문이 꼭 있어야 함. @@ -273,9 +297,15 @@ switch (age) { ``` +<<<<<<< HEAD - 함수는 지역 변수를 가질 수 있습니다. 지역 변수는 함수의 본문에 선언된 변수로, 함수 내부에서만 접근할 수 있습니다. - 매개변수에 기본값을 설정할 수 있습니다. 문법은 다음과 같습니다. `function sum(a = 1, b = 2) {...}` - 함수는 항상 무언가를 반환합니다. `return`문이 없는 경우는 `undefined`를 반환합니다. +======= +- Functions may have local variables: those declared inside its body or its parameter list. Such variables are only visible inside the function. +- Parameters can have default values: `function sum(a = 1, b = 2) {...}`. +- Functions always return something. If there's no `return` statement, then the result is `undefined`. +>>>>>>> upstream/master 자세한 내용은 에서 살펴보시기 바랍니다. diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md index b8852c546b..77d3cb49bb 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,4 +1,4 @@ -# Chrome으로 디버깅하기 +# 브라우저에서 디버깅하기 좀 더 복잡한 코드를 작성하기 전에, 디버깅이란 것에 대해 이야기해봅시다. @@ -63,7 +63,7 @@ Sources 패널 우측의 디버깅 영역을 보면 중단점 목록을 확인 - 이 외에도 다양한 기능이 있습니다. ```smart header="조건부 중단점" -줄 번호에 커서를 옮긴 후 마우스 오른쪽 버튼을 클릭하면 *조건부 중단점(conditional breakpoint)* 을 설정할 수 있습니다. `Add conditional breakpoint`를 클릭했을 때 뜨는 작은 창에 표현식을 입력하면, 표현식이 참인 경우에만 실행을 중지시킬 수 있습니다. +줄 번호에 커서를 옮긴 후 마우스 오른쪽 버튼을 클릭하면 *조건부 중단점(conditional breakpoint)* 을 설정할 수 있습니다. `Add conditional breakpoint`를 클릭했을 때 뜨는 작은 창에 표현식을 입력하면, 표현식이 참으로 평가되는 경우에만 실행을 중지시킬 수 있습니다. 조건부 중단점을 설정하면 변수에 특정 값이 할당될 때나 함수의 매개 변수에 특정 값이 들어올 때만 실행을 중단시킬 수 있어 디버깅 시 유용하게 활용할 수 있습니다. ``` @@ -84,7 +84,7 @@ function hello(name) { } ``` -debugger 명령어를 사용하면 브라우저를 켜 개발자 도구를 열고 소스 코드 영역을 띄워 중단점을 설정하는 수고를 하지 않아도 됩니다. 에디터를 떠나지 않고도 중단점을 설정할 수 있기 때문에 편리하죠. +이 명령어는 개발자 도구가 열려있을 때만 동작하며, 그렇지 않으면 브라우저가 이를 무시합니다. ## 멈추면 보이는 것들 diff --git a/1-js/03-code-quality/02-coding-style/article.md b/1-js/03-code-quality/02-coding-style/article.md index 04ad87bae4..46d5d1c87f 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -301,11 +301,11 @@ Linter라는 도구를 사용하면 내가 작성한 코드가 스타일 가이 유명 linter: -- [JSLint](http://www.jslint.com/) -- 역사가 오래된 linter -- [JSHint](http://www.jshint.com/) -- JSLint보다 세팅이 좀 더 유연한 linter -- [ESLint](http://eslint.org/) -- 가장 최근에 나온 linter +- [JSLint](https://www.jslint.com/) -- 역사가 오래된 linter +- [JSHint](https://jshint.com/) -- JSLint보다 세팅이 좀 더 유연한 linter +- [ESLint](https://eslint.org/) -- 가장 최근에 나온 linter -위 linter 모두 훌륭한 기능을 제공합니다. 글쓴이는 [ESLint](http://eslint.org/)를 사용하고 있습니다. +위 linter 모두 훌륭한 기능을 제공합니다. 글쓴이는 [ESLint](https://eslint.org/)를 사용하고 있습니다. 대부분의 linter는 플러그인 형태로 유명 에디터와 통합해 사용할 수 있습니다. 원하는 스타일을 설정하는 것 역시 가능합니다. @@ -328,14 +328,14 @@ ESLint를 사용한다고 가정했을 때 아래 절차를 따르면 에디터 }, "rules": { "no-console": 0, - "indent": ["warning", 2] + "indent": 2 } } ``` 위 예시에서 지시자 `"extends"`는 "eslint:recommended"를 기반으로 이를 확장해 스타일 가이드를 설정하겠다는 걸 의미합니다. 이렇게 세팅한 이후에 자신만의 스타일을 설정하면 됩니다. -스타일 규칙을 모아놓은 세트를 웹에서 다운로드해 이를 기반으로 스타일 가이드를 설정하는 것도 가능합니다. 설치 방법에 대한 자세한 내용은 에서 확인해 보시기 바랍니다. +스타일 규칙을 모아놓은 세트를 웹에서 다운로드해 이를 기반으로 스타일 가이드를 설정하는 것도 가능합니다. 설치 방법에 대한 자세한 내용은 에서 확인해 보시기 바랍니다. 몇몇 IDE에서는 자체 lint 도구가 있어 편리하긴 하지만 ESLint처럼 쉽게 설정을 변경하는 게 불가능하다는 단점이 있습니다. diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 88643e5991..5dd95310b7 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -143,7 +143,7 @@ function pow(x, n) { [WebStorm](https://www.jetbrains.com/webstorm/) 등의 다양한 에디터는 이런 주석을 이용해 자동 완성 기능, 자동 에러 검출 기능 등을 제공합니다. -[JSDoc 3](https://github.com/jsdoc3/jsdoc)이나 기타 유사한 툴을 사용하면 주석으로 HTML 문서를 만들 수 있습니다. 자세한 정보는 에서 확인하시기 바랍니다. +[JSDoc 3](https://github.com/jsdoc/jsdoc)이나 기타 유사한 툴을 사용하면 주석으로 HTML 문서를 만들 수 있습니다. 자세한 정보는 에서 확인하시기 바랍니다. 왜 이런 방법으로 문제를 해결했는지를 설명하는 주석 : 무엇이 적혀있는지는 중요합니다. 그런데 무슨 일이 일어나고 있는지 파악하려면 무엇이 *적혀있지 않은 지*가 더 중요할 수 있습니다. '왜 이 문제를 이런 방법으로 해결했나?'라는 질문에 코드는 답을 해 줄 수 없기 때문입니다. diff --git a/1-js/03-code-quality/04-ninja-code/article.md b/1-js/03-code-quality/04-ninja-code/article.md index 074e031430..44afd05d5c 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -1,7 +1,7 @@ # 닌자 코드 -```quote author="공자" +```quote author="공자(논어)" 생각 없이 배우기만 하면 얻는 것이 없고,
생각만 하고 배우지 않으면 오류나 독단에 빠질 위험이 있다. ``` @@ -106,10 +106,10 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ## 동의어 사용하기 -```quote author="공자" -모든 일 중 가장 어려운 일은
-어두운 방에서 검은 고양이를 찾는 일이다.

-특히 그 방에 고양이가 없을 때에. +```quote author="노자(도덕경)" +말할 수 있는 도(道)는 영원한 도가 아니며,
+이름 붙일 수 있는 이름은
+영원한 이름이 아니다. ``` *유사한* 뜻을 가진 단어 여러 개를 *같은*걸 명명하는 데 사용해서 당신의 풍부한 어휘력을 은연중에 드러내 봅시다. diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index a267f103ee..cd3c11fcb2 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -69,7 +69,7 @@ describe("pow", function() { 1. 명세서 초안을 작성합니다. 초안엔 기본적인 테스트도 들어갑니다. 2. 명세서 초안을 보고 코드를 작성합니다. -3. 코드가 작동하는지 확인하기 위해 [Mocha](http://mochajs.org/)라 불리는 테스트 프레임워크를 사용해 명세서를 실행합니다.(Mocha에 대해선 아래에서 다룰 예정입니다.) 이때, 코드가 잘못 작성되었다면 에러가 출력됩니다. 개발자는 테스트를 모두 통과해 에러가 더는 출력되지 않을 때까지 코드를 수정합니다. +3. 코드가 작동하는지 확인하기 위해 [Mocha](https://mochajs.org/)라 불리는 테스트 프레임워크를 사용해 명세서를 실행합니다.(Mocha에 대해선 아래에서 다룰 예정입니다.) 이때, 코드가 잘못 작성되었다면 에러가 출력됩니다. 개발자는 테스트를 모두 통과해 에러가 더는 출력되지 않을 때까지 코드를 수정합니다. 4. 모든 테스트를 통과하는 코드 초안이 완성되었습니다. 5. 명세서에 지금까진 고려하지 않았던 유스케이스 몇 가지를 추가합니다. 테스트가 실패하기 시작할 겁니다. 6. 세 번째 단계로 돌아가 테스트를 모두 통과할 때까지 코드를 수정합니다. @@ -85,9 +85,9 @@ describe("pow", function() { 본 튜토리얼에선 총 3개의 라이브러리를 사용해 테스트를 진행해보겠습니다. 각 라이브러리에 대한 설명은 아래와 같습니다. -- [Mocha](http://mochajs.org/) -- 핵심 테스트 프레임워크로, `describe`, `it`과 같은 테스팅 함수와 테스트 실행 관련 주요 함수를 제공합니다. -- [Chai](http://chaijs.com) -- 다양한 assertion을 제공해 주는 라이브러리입니다. 우리 예시에선 `assert.equal` 정도만 사용해 볼 예정입니다. -- [Sinon](http://sinonjs.org/) -- 함수의 정보를 캐내는 데 사용되는 라이브러리로, 내장 함수 등을 모방합니다. 본 챕터에선 사용하지 않고, 다른 챕터에서 실제로 사용해 볼 예정입니다. +- [Mocha](https://mochajs.org/) -- 핵심 테스트 프레임워크로, `describe`, `it`과 같은 테스팅 함수와 테스트 실행 관련 주요 함수를 제공합니다. +- [Chai](https://www.chaijs.com/) -- 다양한 assertion을 제공해 주는 라이브러리입니다. 우리 예시에선 `assert.equal` 정도만 사용해 볼 예정입니다. +- [Sinon](https://sinonjs.org/) -- 함수의 정보를 캐내는 데 사용되는 라이브러리로, 내장 함수 등을 모방합니다. 본 챕터에선 사용하지 않고, 다른 챕터에서 실제로 사용해 볼 예정입니다. 세 라이브러리 모두, 브라우저나 서버 사이드 환경을 가리지 않고 사용 가능합니다. 여기선 브라우저 환경을 가정하고 사용해 보겠습니다. @@ -338,14 +338,14 @@ describe("pow", function() { ```smart header="다양한 assertion" 위에서 사용한 `assert.isNaN`은 `NaN`인지 아닌지를 확인해줍니다. -[Chai](http://chaijs.com)는 이 외에도 다양한 assertion을 지원합니다. +[Chai](https://www.chaijs.com/)는 이 외에도 다양한 assertion을 지원합니다. - `assert.equal(value1, value2)` -- `value1`과 `value2`의 동등성을 확인합니다(`value1 == value2`). - `assert.strictEqual(value1, value2)` -- `value1`과 `value2`의 일치성을 확인합니다(`value1 === value2`). - `assert.notEqual`, `assert.notStrictEqual` -- 비 동등성, 비 일치성을 확인합니다. - `assert.isTrue(value)` -- `value`가 `true`인지 확인합니다(`value === true`). - `assert.isFalse(value)` -- `value`가 `false`인지 확인합니다(`value === false`). -- 이 외의 다양한 assertion은 [docs](http://chaijs.com/api/assert/)에서 확인할 수 있습니다. +- 이 외의 다양한 assertion은 [docs](https://www.chaijs.com/api/assert/)에서 확인할 수 있습니다. ``` 새롭게 추가한 테스트를 통과할 수 있도록 `pow`에 코드를 몇 줄 추가해보겠습니다. diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 21e768863b..538d317241 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,54 +1,89 @@ -# 폴리필 +# 폴리필과 트랜스파일러 -자바스크립트는 끊임없이 진화하는 언어입니다. 새로운 제안(proposal)이 정기적으로 등록, 분석되고, 가치가 있다고 판단되는 제안은 에 추가됩니다. 그리고 궁극적으로 [명세서(specification)](http://www.ecma-international.org/publications/standards/Ecma-262.htm)에 등록됩니다. +자바스크립트는 끊임없이 진화하는 언어입니다. 새로운 제안(proposal)이 정기적으로 등록, 분석되고, 가치가 있다고 판단되는 제안은 에 추가됩니다. 그리고 궁극적으로 [명세서(specification)](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/)에 등록됩니다. 자바스크립트 엔진을 만드는 각 조직은 나름대로 우선순위를 매겨 명세서 내 어떤 기능을 먼저 구현할지 결정합니다. 명세서에 등록된 기능보다 초안(draft)에 있는 제안을 먼저 구현하기로 결정하는 경우도 있습니다. 구현 난도가 높아서 이런 결정을 내리는 경우도 있지만, 구미를 당기지 않아 이런 결정을 내리기도 합니다. 엔진이 표준 전체를 지원하지 않고 일부만 지원하는 건 흔한 일이죠. -엔진별로 어떤 기능을 지원하고 있는지는 에서 확인할 수 있습니다. 표가 상당히 큰데, 각 기능에 대해선 차근차근 배울 예정이니 너무 겁먹지 않으셔도 됩니다. +엔진별로 어떤 기능을 지원하고 있는지는 에서 확인할 수 있습니다. 표가 상당히 큰데, 각 기능에 대해선 차근차근 배울 예정이니 너무 겁먹지 않으셔도 됩니다. -## 바벨 +개발자라면 누구나 최신 기능을 쓰고 싶기 마련입니다. 멋진 기능은 많을수록 좋으니까요! -명세서에 등록된 지 얼마 안 된 기능을 사용해 코드를 작성하다 보면 특정 엔진에서 우리가 작성한 코드를 지원하지 않는다는 걸 알게 되는 경우가 있습니다. 명세서 내 모든 기능을 모든 엔진이 구현하고 있지 않기 때문이죠. +그런데 최신 기능을 알아듣지 못하는 구식 엔진에서도 모던 코드가 잘 동작하게 하려면 어떻게 해야 할까요? -이럴 때 바벨을 사용할 수 있습니다. +이때 두 가지 도구를 활용할 수 있습니다. -[바벨(Babel)](https://babeljs.io)은 [트랜스파일러(transpiler)](https://en.wikipedia.org/wiki/Source-to-source_compiler)로, 모던 자바스크립트 코드를 구 표준을 준수하는 코드로 바꿔줍니다. +1. 트랜스파일러(Transpiler) +2. 폴리필(Polyfill) -바벨의 주요 역할은 다음과 같습니다. +이번 챕터에선 두 도구가 어떻게 동작하는지, 웹 개발에서 어떤 역할을 하는지 큰 그림을 잡아보겠습니다. -1. 트랜스파일러 -- 바벨은 코드를 재작성해주는 트랜스파일러 프로그램입니다. 바벨은 개발자의 컴퓨터에서 돌아가는데, 이를 실행하면 기존 코드가 구 표준을 준수하는 코드로 변경됩니다. 변경된 코드는 웹사이트 형태로 사용자에게 전달됩니다. [웹팩(webpack)](http://webpack.github.io/)과 같은 모던 프로젝트 빌드 시스템은 코드가 수정될 때마다 자동으로 트랜스파일러를 동작시켜줍니다. 이런 과정이 없으면 개발이 끝난 코드를 한데 통합하는 데 어려움이 있을 수 있습니다. +## 트랜스파일러 -2. 폴리필 +[트랜스파일러(transpiler)](https://en.wikipedia.org/wiki/Source-to-source_compiler)는 소스 코드를 다른 소스 코드로 바꿔 주는 프로그램입니다. 모던 코드를 분석("읽고 해석")한 다음 옛 문법으로 다시 작성해 주죠. 변환된 코드는 구식 엔진에서도 잘 동작합니다. - 명세서엔 새로운 문법이나 기존에 없던 내장 함수에 대한 정의가 추가되곤 합니다. - 새로운 문법을 사용해 코드를 작성하면 트랜스파일러는 이를 구 표준을 준수하는 코드로 변경해줍니다. 반면, 새롭게 표준에 추가된 함수는 명세서 내 정의를 읽고 이에 맞게 직접 함수를 구현해야 사용할 수 있습니다. 자바스크립트는 매우 동적인 언어라서 원하기만 하면 어떤 함수라도 스크립트에 추가할 수 있습니다. 물론 기존 함수를 수정하는 것도 가능합니다. 개발자는 스크립트에 새로운 함수를 추가하거나 수정해서 스크립트가 최신 표준을 준수할 수 있게 작업할 수 있습니다. +예를 들어 2020년 이전 자바스크립트엔 "nullish 병합 연산자(nullish coalescing operator)" `??`가 없었습니다. 그래서 사용자가 구식 브라우저를 쓰고 있다면 `height = height ?? 100` 같은 코드를 처리하지 못할 수 있죠. - 이렇게 변경된 표준을 준수할 수 있게 기존 함수의 동작 방식을 수정하거나, 새롭게 구현한 함수의 스크립트를 "폴리필(polyfill)"이라 부릅니다. 폴리필(poly`fill`)은 말 그대로 구현이 누락된 새로운 기능을 메꿔주는(`fill in`) 역할을 합니다. +트랜스파일러는 코드를 분석해 `height ?? 100`을 `(height !== undefined && height !== null) ? height : 100`으로 변환해 줍니다. - 주목할 만한 폴리필 두 가지는 아래와 같습니다. - - [core js](https://github.com/zloirock/core-js) -- 다양한 폴리필을 제공합니다. 특정 기능의 폴리필만 사용하는 것도 가능합니다. - - [polyfill.io](http://polyfill.io) -- 기능이나 사용자의 브라우저에 따라 폴리필 스크립트를 제공해주는 서비스입니다. +```js +// 트랜스파일러 실행 전 +height = height ?? 100; -모던 자바스크립트를 이용해 스크립트를 작성하려면 트랜스파일러와 폴리필은 필수입니다. +// 트랜스파일러 실행 후 +height = (height !== undefined && height !== null) ? height : 100; +``` -## 튜토리얼에서 예시 실행하기 +이제 변환된 코드는 구버전 자바스크립트 엔진에서도 잘 돌아갑니다. +보통은 개발자가 자기 컴퓨터에서 트랜스파일러를 돌린 다음, 변환된 코드를 서버에 배포하는 식으로 사용합니다. -````online -튜토리얼 내 예시 대부분은 아래와 같이 클릭 한 번으로 바로 실행할 수 있습니다. +잘 알려진 트랜스파일러로는 [바벨(Babel)](https://babeljs.io)이 있습니다. -```js run -alert('우측 상단 모서리에 있는 "재생" 버튼을 눌러 스크립트를 실행해 보세요.'); -``` +[웹팩(webpack)](https://webpack.js.org/) 같은 모던 빌드 시스템은 코드가 바뀔 때마다 트랜스파일러를 자동으로 돌려 주기 때문에, 개발 과정에 자연스럽게 녹여 쓸 수 있습니다. + +## 폴리필 + +새로 추가되는 언어 기능엔 문법이나 연산자뿐 아니라 내장 함수도 포함됩니다. + +예를 들어 `Math.trunc(n)`은 숫자의 소수점 이하를 "잘라내는" 함수입니다. `Math.trunc(1.23)`을 호출하면 `1`이 반환되죠. + +그런데 (아주 오래된) 일부 자바스크립트 엔진엔 `Math.trunc`가 없어서 위 코드를 그대로 쓰면 에러가 납니다. + +이건 문법이 바뀐 게 아니라 함수가 추가된 경우라서 트랜스파일을 거칠 필요가 없습니다. 빠져 있는 함수를 직접 선언해 주기만 하면 되죠. + +이렇게 함수를 새로 추가하거나 갱신해 주는 스크립트를 "폴리필(polyfill)"이라고 부릅니다. 빠진 자리를 "메워 주는(`fill in`)" 셈이죠. -모던 자바스크립트를 사용해 작성한 예시는 해당 기능을 지원하는 브라우저에서만 작동합니다. -```` +`Math.trunc`를 위한 폴리필은 아래처럼 함수를 직접 구현해 주는 스크립트입니다. -```offline -오프라인에서 PDF 포맷으로 튜토리얼을 읽고 계신다면 예시를 실행할 수 없습니다. EPUB에선 일부 예시가 실행 가능합니다. +```js +if (!Math.trunc) { // 함수가 없는 경우 + // 직접 구현 + Math.trunc = function(number) { + // Math.ceil과 Math.floor는 옛 자바스크립트 엔진에도 있습니다. + // 둘에 대해선 튜토리얼 뒷부분에서 다룰 예정입니다. + return number < 0 ? Math.ceil(number) : Math.floor(number); + }; +} ``` -Google Chrome은 모든 브라우저 중 대개 가장 먼저 최신 기능을 지원합니다. 트랜스파일러 없이 최신 기능을 사용할 수 있기 때문에 Chrome은 데모용으로 사용하기 좋습니다. 그런데 다른 브라우저들도 많이 뒤처지는 편은 아니니 안심하고 사용하셔도 될 것 같습니다. +자바스크립트는 동적인 언어입니다. 스크립트로 어떤 함수든 추가하거나 수정할 수 있고, 심지어 내장 함수도 예외가 아니죠. + +눈여겨볼 만한 폴리필 라이브러리로 [core-js](https://github.com/zloirock/core-js)가 있는데요, 다양한 기능을 폭넓게 지원하면서도 필요한 기능만 골라 쓸 수 있습니다. + +## 요약 + +자바스크립트 엔진이 아직 잘 지원하지 않는 기능이라도, 모던 기능, 심지어 "최첨단(bleeding-edge)" 기능까지 적극적으로 공부해 보시길 권합니다. + +단, 모던 문법이나 연산자를 쓸 땐 트랜스파일러를, 일부 엔진에 없을 수 있는 함수를 추가할 땐 폴리필을 꼭 함께 사용하세요. 이 둘이 갖춰져야 코드가 제대로 동작합니다. + +자바스크립트가 좀 익숙해진 뒤엔 [웹팩(webpack)](https://webpack.js.org/) 기반에 [babel-loader](https://github.com/babel/babel-loader) 플러그인을 얹어 빌드 시스템을 꾸려볼 수도 있죠. + +각 기능의 지원 현황을 확인할 땐 아래 자료가 유용합니다. +- - 순수 자바스크립트 기능 +- - 브라우저 관련 기능 + +참고로 Google Chrome은 보통 최신 언어 기능을 가장 빨리 지원합니다. 튜토리얼 데모가 잘 안 돌아간다면 Chrome으로 한 번 시도해 보세요. 대부분의 데모는 모던 브라우저 어디서나 잘 작동합니다. + diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 63412c01fa..9ea60fc048 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -5,7 +5,11 @@ 그런데 객체형은 원시형과 달리 다양한 데이터를 담을 수 있습니다. 키로 구분된 데이터 집합이나 복잡한 개체(entity)를 저장할 수 있죠. 객체는 자바스크립트 거의 모든 면에 녹아있는 개념이므로 자바스크립트를 잘 다루려면 객체를 잘 이해하고 있어야 합니다. +<<<<<<< HEAD 객체는 중괄호 `{…}`를 이용해 만들 수 있습니다. 중괄호 안에는 '키(key): 값(value)' 쌍으로 구성된 *프로퍼티(property)* 를 여러 개 넣을 수 있는데, `키`엔 문자형, `값`엔 모든 자료형이 허용됩니다. 프로퍼티 키는 '프로퍼티 이름' 이라고도 부릅니다. +======= +An object can be created with curly braces `{…}` with an optional list of *properties*. A property is a "key: value" pair, where `key` is a string (also called a "property name"), and `value` can be anything. +>>>>>>> upstream/master 서랍장을 상상하면 객체를 이해하기 쉽습니다. 서랍장 안 파일은 프로퍼티, 파일 각각에 붙어있는 이름표는 객체의 키라고 생각하시면 됩니다. 복잡한 서랍장 안에서 이름표를 보고 원하는 파일을 쉽게 찾을 수 있듯이, 객체에선 키를 이용해 프로퍼티를 쉽게 찾을 수 있습니다. 추가나 삭제도 마찬가지입니다. @@ -20,7 +24,11 @@ let user = {}; // '객체 리터럴' 문법 ![](object-user-empty.svg) +<<<<<<< HEAD 중괄호 `{...}`를 이용해 객체를 선언하는 것을 *객체 리터럴(object literal)* 이라고 부릅니다. 객체를 선언할 땐 주로 이 방법을 사용합니다. +======= +Usually, the curly braces `{...}` are used. That declaration is called an *object literal*. +>>>>>>> upstream/master ## 리터럴과 프로퍼티 @@ -44,7 +52,11 @@ let user = { // 객체 ![user object](object-user.svg) +<<<<<<< HEAD 서랍장에 파일을 추가하고 뺄 수 있듯이 개발자는 프로퍼티를 추가, 삭제할 수 있습니다. +======= +We can add, remove and read files from it at any time. +>>>>>>> upstream/master 점 표기법(dot notation)을 이용하면 프로퍼티 값을 읽는 것도 가능합니다. @@ -62,7 +74,11 @@ user.isAdmin = true; ![user object 2](object-user-isadmin.svg) +<<<<<<< HEAD `delete` 연산자를 사용하면 프로퍼티를 삭제할 수 있습니다. +======= +To remove a property, we can use the `delete` operator: +>>>>>>> upstream/master ```js delete user.age; @@ -92,6 +108,7 @@ let user = { ``` 이런 쉼표를 'trailing(길게 늘어지는)' 혹은 'hanging(매달리는)' 쉼표라고 부릅니다. 이렇게 끝에 쉼표를 붙이면 모든 프로퍼티가 유사한 형태를 보이기 때문에 프로퍼티를 추가, 삭제, 이동하는 게 쉬워집니다. +<<<<<<< HEAD ````smart header="상수 객체는 수정될 수 있습니다." 주의하세요. `const`로 선언된 객체는 수정될 수 있습니다. @@ -117,6 +134,9 @@ alert(user.name); // Pete ```` ## 대괄호 표기법 +======= +## Square brackets +>>>>>>> upstream/master 여러 단어를 조합해 프로퍼티 키를 만든 경우엔, 점 표기법을 사용해 프로퍼티 값을 읽을 수 없습니다. @@ -225,13 +245,21 @@ let bag = { }; ``` +<<<<<<< HEAD 대괄호 표기법은 프로퍼티 이름과 값의 제약을 없애주기 때문에 점 표기법보다 훨씬 강력합니다. 그런데 작성하기 번거롭다는 단점이 있습니다. +======= +Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write. +>>>>>>> upstream/master 이런 이유로 프로퍼티 이름이 확정된 상황이고, 단순한 이름이라면 처음엔 점 표기법을 사용하다가 뭔가 복잡한 상황이 발생했을 때 대괄호 표기법으로 바꾸는 경우가 많습니다. ## 단축 프로퍼티 +<<<<<<< HEAD 실무에선 프로퍼티 값을 기존 변수에서 받아와 사용하는 경우가 종종 있습니다. +======= +In real code, we often use existing variables as values for property names. +>>>>>>> upstream/master 예시: @@ -276,7 +304,11 @@ let user = { ## 프로퍼티 이름의 제약사항 +<<<<<<< HEAD 아시다시피 변수 이름(키)엔 'for', 'let', 'return' 같은 예약어를 사용하면 안됩니다. +======= +As we already know, a variable cannot have a name equal to one of the language-reserved words like "for", "let", "return" etc. +>>>>>>> upstream/master 그런데 객체 프로퍼티엔 이런 제약이 없습니다. @@ -349,7 +381,11 @@ alert( "blabla" in user ); // user.blabla는 존재하지 않기 때문에 false `in` 왼쪽엔 반드시 *프로퍼티 이름*이 와야 합니다. 프로퍼티 이름은 보통 따옴표로 감싼 문자열입니다. +<<<<<<< HEAD 따옴표를 생략하면 아래 예시와 같이 엉뚱한 변수가 조사 대상이 됩니다. +======= +If we omit quotes, that means a variable should contain the actual name to be tested. For instance: +>>>>>>> upstream/master ```js run let user = { age: 30 }; @@ -379,7 +415,11 @@ alert( "test" in obj ); // `in`을 사용하면 프로퍼티 유무를 제대로 `undefined`는 변수는 정의되어 있으나 값이 할당되지 않은 경우에 쓰기 때문에 프로퍼티 값이 `undefined`인 경우는 흔치 않습니다. 값을 '알 수 없거나(unknown)' 값이 '비어 있다는(empty)' 것을 나타낼 때는 주로 `null`을 사용합니다. 위 예시에서 `in` 연산자는 자리에 어울리지 않는 초대손님처럼 보이네요. +<<<<<<< HEAD ## 'for..in' 반복문 +======= +## The "for..in" loop [#forin] +>>>>>>> upstream/master `for..in` 반복문을 사용하면 객체의 모든 키를 순회할 수 있습니다. `for..in`은 앞서 학습했던 `for(;;)` 반복문과는 완전히 다릅니다. @@ -436,7 +476,11 @@ for (let code in codes) { */!* ``` +<<<<<<< HEAD 현재 개발 중인 애플리케이션의 주 사용자가 독일인이라고 가정해 봅시다. 나라 번호를 선택하는 화면에서 `49`가 맨 앞에 오도록 하는 게 좋을 겁니다. +======= +The object may be used to suggest a list of options to the user. If we're making a site mainly for a German audience then we probably want `49` to be the first. +>>>>>>> upstream/master 그런데 코드를 실행해 보면 예상과는 전혀 다른 결과가 출력됩니다. @@ -448,6 +492,7 @@ for (let code in codes) { ````smart header="정수 프로퍼티? 그게 뭔가요?" '정수 프로퍼티'라는 용어는 변형 없이 정수에서 왔다 갔다 할 수 있는 문자열을 의미합니다. +<<<<<<< HEAD 문자열 "49"는 정수로 변환하거나 변환한 정수를 다시 문자열로 바꿔도 변형이 없기 때문에 정수 프로퍼티입니다. 하지만 '+49'와 '1.2'는 정수 프로퍼티가 아닙니다. ```js run @@ -455,6 +500,16 @@ for (let code in codes) { alert( String(Math.trunc(Number("49"))) ); // '49'가 출력됩니다. 기존에 입력한 값과 같으므로 정수 프로퍼티입니다. alert( String(Math.trunc(Number("+49"))) ); // '49'가 출력됩니다. 기존에 입력한 값(+49)과 다르므로 정수 프로퍼티가 아닙니다. alert( String(Math.trunc(Number("1.2"))) ); // '1'이 출력됩니다. 기존에 입력한 값(1.2)과 다르므로 정수 프로퍼티가 아닙니다. +======= +So, `"49"` is an integer property name, because when it's transformed to an integer number and back, it's still the same. But `"+49"` and `"1.2"` are not: + +```js run +// Number(...) explicitly converts to a number +// Math.trunc is a built-in function that removes the decimal part +alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property +alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property +alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property +>>>>>>> upstream/master ``` ```` @@ -503,9 +558,15 @@ for (let code in codes) { - 프로퍼티 키는 문자열이나 심볼이어야 합니다. 보통은 문자열입니다. - 값은 어떤 자료형도 가능합니다. +<<<<<<< HEAD 아래와 같은 방법을 사용하면 프로퍼티에 접근할 수 있습니다. - 점 표기법: `obj.property` - 대괄호 표기법 `obj["property"]`. 대괄호 표기법을 사용하면 `obj[varWithKey]`같이 변수에서 키를 가져올 수 있습니다. +======= +To access a property, we can use: +- The dot notation: `obj.property`. +- Square brackets notation `obj["property"]`. Square brackets allow taking the key from a variable, like `obj[varWithKey]`. +>>>>>>> upstream/master 객체엔 다음과 같은 추가 연산자를 사용할 수 있습니다. - 프로퍼티를 삭제하고 싶을 때: `delete obj.prop` diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md index eec0a0b413..f614c8e472 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # 참조에 의한 객체 복사 객체와 원시 타입의 근본적인 차이 중 하나는 객체는 '참조에 의해(by reference)' 저장되고 복사된다는 것입니다. @@ -5,12 +6,24 @@ 원시값(문자열, 숫자, 불린 값)은 '값 그대로' 저장·할당되고 복사되는 반면에 말이죠. 예시: +======= +# Object references and copying + +One of the fundamental differences of objects versus primitives is that objects are stored and copied "by reference", whereas primitive values: strings, numbers, booleans, etc -- are always copied "as a whole value". + +That's easy to understand if we look a bit under the hood of what happens when we copy a value. + +Let's start with a primitive, such as a string. + +Here we put a copy of `message` into `phrase`: +>>>>>>> upstream/master ```js let message = "Hello!"; let phrase = message; ``` +<<<<<<< HEAD 예시를 실행하면 두 개의 독립된 변수에 각각 문자열 `"Hello!"`가 저장됩니다. ![](variable-copy-value.svg) @@ -20,6 +33,19 @@ let phrase = message; **변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어있는 '메모리 주소'인 객체에 대한 '참조 값'이 저장됩니다.** 그림을 통해 변수 user에 객체를 할당할 때 무슨 일이 일어나는지 알아봅시다. +======= +As a result we have two independent variables, each one storing the string `"Hello!"`. + +![](variable-copy-value.svg) + +Quite an obvious result, right? + +Objects are not like that. + +**A variable assigned to an object stores not the object itself, but its "address in memory" -- in other words "a reference" to it.** + +Let's look at an example of such a variable: +>>>>>>> upstream/master ```js let user = { @@ -27,11 +53,25 @@ let user = { }; ``` +And here's how it's actually stored in memory: + ![](variable-contains-reference.svg) +<<<<<<< HEAD 객체는 메모리 내 어딘가에 저장되고, 변수 `user`엔 객체를 '참조'할 수 있는 값이 저장됩니다. 따라서 **객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않습니다.** +======= +The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it. + +We may think of an object variable, such as `user`, like a sheet of paper with the address of the object on it. + +When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object. + +Now here's why it's important. + +**When an object variable is copied, the reference is copied, but the object itself is not duplicated.** +>>>>>>> upstream/master 예시: @@ -41,11 +81,21 @@ let user = { name: "John" }; let admin = user; // 참조값을 복사함 ``` +<<<<<<< HEAD 변수는 두 개이지만 각 변수엔 동일 객체에 대한 참조 값이 저장되죠. ![](variable-copy-reference.svg) 따라서 객체에 접근하거나 객체를 조작할 땐 여러 변수를 사용할 수 있습니다. +======= +Now we have two variables, each storing a reference to the same object: + +![](variable-copy-reference.svg) + +As you can see, there's still one object, but now with two variables that reference it. + +We can use either variable to access the object and modify its contents: +>>>>>>> upstream/master ```js run let user = { name: 'John' }; @@ -59,15 +109,25 @@ admin.name = 'Pete'; // 'admin' 참조 값에 의해 변경됨 alert(*!*user.name*/!*); // 'Pete'가 출력됨. 'user' 참조 값을 이용해 변경사항을 확인함 ``` +<<<<<<< HEAD 객체를 서랍장에 비유하면 변수는 서랍장을 열 수 있는 열쇠라고 할 수 있습니다. 서랍장은 하나, 서랍장을 열 수 있는 열쇠는 두 개인데, 그중 하나(`admin`)를 사용해 서랍장을 열어 정돈한 후, 또 다른 열쇠로 서랍장을 열면 정돈된 내용을 볼 수 있습니다. +======= +It's as if we had a cabinet with two keys and used one of them (`admin`) to get into it and make changes. Then, if we later use another key (`user`), we are still opening the same cabinet and can access the changed contents. +>>>>>>> upstream/master ### 참조에 의한 비교 +<<<<<<< HEAD 객체 비교 시 동등 연산자 `==`와 일치 연산자 `===`는 동일하게 동작합니다. **비교 시 피연산자인 두 객체가 동일한 객체인 경우에 참을 반환하죠.** 두 변수가 같은 객체를 참조하는 예시를 살펴봅시다. 일치·동등 비교 모두에서 참이 반환됩니다. +======= +Two objects are equal only if they are the same object. + +For instance, here `a` and `b` reference the same object, thus they are equal: +>>>>>>> upstream/master ```js run let a = {}; @@ -77,7 +137,11 @@ alert( a == b ); // true, 두 변수는 같은 객체를 참조합니다. alert( a === b ); // true ``` +<<<<<<< HEAD 다른 예시를 살펴봅시다. 두 객체 모두 비어있다는 점에서 같아 보이지만, 독립된 객체이기 때문에 일치·동등 비교하면 거짓이 반환됩니다. +======= +And here two independent objects are not equal, even though they look alike (both are empty): +>>>>>>> upstream/master ```js run let a = {}; @@ -86,17 +150,53 @@ let b = {}; // 독립된 두 객체 alert( a == b ); // false ``` +<<<<<<< HEAD `obj1 > obj2` 같은 대소 비교나 `obj == 5` 같은 원시값과의 비교에선 객체가 원시형으로 변환됩니다. 객체가 어떻게 원시형으로 변하는지에 대해선 곧 학습할 예정인데, 이러한 비교(객체끼리의 대소 비교나 원시값과 객체를 비교하는 것)가 필요한 경우는 매우 드물긴 합니다. 대개 코딩 실수 때문에 이런 비교가 발생합니다. ## 객체 복사, 병합과 Object.assign +======= +For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake. + +````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 +``` + +It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. + +In other words, the `const user` gives an error only if we try to set `user=...` as a whole. + +That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . +```` + +## Cloning and merging, Object.assign [#cloning-and-merging-object-assign] +>>>>>>> upstream/master 객체가 할당된 변수를 복사하면 동일한 객체에 대한 참조 값이 하나 더 만들어진다는 걸 배웠습니다. +<<<<<<< HEAD 그런데 객체를 복제하고 싶다면 어떻게 해야 할까요? 기존에 있던 객체와 똑같으면서 독립적인 객체를 만들고 싶다면 말이죠. 방법은 있는데 자바스크립트는 객체 복제 내장 메서드를 지원하지 않기 때문에 조금 어렵습니다. 사실 객체를 복제해야 할 일은 거의 없습니다. 참조에 의한 복사로 해결 가능한 일이 대다수이죠. 정말 복제가 필요한 상황이라면 새로운 객체를 만든 다음 기존 객체의 프로퍼티들을 순회해 원시 수준까지 프로퍼티를 복사하면 됩니다. +======= +But what if we need to duplicate an object? + +We can create a new object and replicate the structure of the existing one, by iterating over its properties and copying them on the primitive level. +>>>>>>> upstream/master 아래와 같이 말이죠. @@ -121,14 +221,19 @@ clone.name = "Pete"; // clone의 데이터를 변경합니다. alert( user.name ); // 기존 객체에는 여전히 John이 있습니다. ``` +<<<<<<< HEAD [Object.assign](mdn:js/Object/assign)를 사용하는 방법도 있습니다. +======= +We can also use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). +>>>>>>> upstream/master 문법과 동작 방식은 다음과 같습니다. ```js -Object.assign(dest, [src1, src2, src3...]) +Object.assign(dest, ...sources) ``` +<<<<<<< HEAD - 첫 번째 인수 `dest`는 목표로 하는 객체입니다. - 이어지는 인수 `src1, ..., srcN`는 복사하고자 하는 객체입니다. `...`은 필요에 따라 얼마든지 많은 객체를 인수로 사용할 수 있다는 것을 나타냅니다. - 객체 `src1, ..., srcN`의 프로퍼티를 `dest`에 복사합니다. `dest`를 제외한 인수(객체)의 프로퍼티 전부가 첫 번째 인수(객체)로 복사됩니다. @@ -136,6 +241,16 @@ Object.assign(dest, [src1, src2, src3...]) `assign` 메서드를 사용해 여러 객체를 하나로 병합하는 예시를 살펴봅시다. ```js +======= +- The first argument `dest` is a target object. +- Further arguments is a list of source objects. + +It copies the properties of all source objects into the target `dest`, and then returns it as the result. + +For example, we have `user` object, let's add a couple of permissions to it: + +```js run +>>>>>>> upstream/master let user = { name: "John" }; let permissions1 = { canView: true }; @@ -147,6 +262,9 @@ Object.assign(user, permissions1, permissions2); */!* // now user = { name: "John", canView: true, canEdit: true } +alert(user.name); // John +alert(user.canView); // true +alert(user.canEdit); // true ``` 목표 객체(`user`)에 동일한 이름을 가진 프로퍼티가 있는 경우엔 기존 값이 덮어씌워 집니다. @@ -159,9 +277,13 @@ Object.assign(user, { name: "Pete" }); alert(user.name); // user = { name: "Pete" } ``` +<<<<<<< HEAD `Object.assign`을 사용하면 반복문 없이도 간단하게 객체를 복사할 수 있습니다. +======= +We also can use `Object.assign` to perform a simple object cloning: +>>>>>>> upstream/master -```js +```js run let user = { name: "John", age: 30 @@ -170,13 +292,26 @@ let user = { *!* let clone = Object.assign({}, user); */!* + +alert(clone.name); // John +alert(clone.age); // 30 ``` +<<<<<<< HEAD 예시를 실행하면 `user`에 있는 모든 프로퍼티가 빈 배열에 복사되고 변수에 할당됩니다. +======= +Here it copies all properties of `user` into the empty object and returns it. + +There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial. +>>>>>>> upstream/master ## 중첩 객체 복사 +<<<<<<< HEAD 지금까진 `user`의 모든 프로퍼티가 원시값인 경우만 가정했습니다. 그런데 프로퍼티는 다른 객체에 대한 참조 값일 수도 있습니다. 이 경우는 어떻게 해야 할까요? +======= +Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. +>>>>>>> upstream/master 아래와 같이 말이죠. ```js run @@ -191,9 +326,13 @@ let user = { alert( user.sizes.height ); // 182 ``` +<<<<<<< HEAD `clone.sizes = user.sizes`로 프로퍼티를 복사하는 것만으론 객체를 복제할 수 없습니다. `user.sizes`는 객체이기 때문에 참조 값이 복사되기 때문입니다. `clone.sizes = user.sizes`로 프로퍼티를 복사하면 `clone`과 `user`는 같은 sizes를 공유하게 됩니다. 아래와 같이 말이죠. +======= +Now it's not enough to copy `clone.sizes = user.sizes`, because `user.sizes` is an object, and will be copied by reference, so `clone` and `user` will share the same sizes: +>>>>>>> upstream/master ```js run let user = { @@ -208,6 +347,7 @@ let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // true, 같은 객체입니다. +<<<<<<< HEAD // user와 clone는 sizes를 공유합니다. user.sizes.width++; // 한 객체에서 프로퍼티를 변경합니다. alert(clone.sizes.width); // 51, 다른 객체에서 변경 사항을 확인할 수 있습니다. @@ -218,11 +358,87 @@ alert(clone.sizes.width); // 51, 다른 객체에서 변경 사항을 확인할 깊은 복사 시 사용되는 표준 알고리즘인 [Structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#safe-passing-of-structured-data)을 사용하면 위 사례를 비롯한 다양한 상황에서 객체를 복제할 수 있습니다. 자바스크립트 라이브러리 [lodash](https://lodash.com)의 메서드인 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep)을 사용하면 이 알고리즘을 직접 구현하지 않고도 깊은 복사를 처리할 수 있으므로 참고하시기 바랍니다. +======= +// user and clone share sizes +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 60, get the result from the other one +``` + +To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning. + + +### structuredClone + +The call `structuredClone(object)` clones the `object` with all nested properties. + +Here's how we can use it in our example: + +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; + +*!* +let clone = structuredClone(user); +*/!* + +alert( user.sizes === clone.sizes ); // false, different objects + +// user and clone are totally unrelated now +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 50, not related +``` + +The `structuredClone` method can clone most data types, such as objects, arrays, primitive values. + +It also supports circular references, when an object property references the object itself (directly or via a chain or references). + +For instance: + +```js run +let user = {}; +// let's create a circular reference: +// user.me references the user itself +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // true +``` + +As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well. + +Although, there are cases when `structuredClone` fails. + +For instance, when an object has a function property: + +```js run +// error +structuredClone({ + f: function() {} +}); +``` + +Function properties aren't supported. + +To handle such complex cases we may need to use a combination of cloning methods, write custom code 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). +>>>>>>> upstream/master ## 요약 +<<<<<<< HEAD 객체는 참조에 의해 할당되고 복사됩니다. 변수엔 '객체' 자체가 아닌 메모리상의 주소인 '참조'가 저장됩니다. 따라서 객체가 할당된 변수를 복사하거나 함수의 인자로 넘길 땐 객체가 아닌 객체의 참조가 복사됩니다. +======= +Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object itself. +>>>>>>> upstream/master 그리고 복사된 참조를 이용한 모든 작업(프로퍼티 추가·삭제 등)은 동일한 객체를 대상으로 이뤄집니다. +<<<<<<< HEAD 객체의 '진짜 복사본'을 만들려면 '얕은 복사(shallow copy)'를 가능하게 해주는 `Object.assign`이나 '깊은 복사'를 가능하게 해주는 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep)를 사용하면 됩니다. 이때 얕은 복사본은 중첩 객체를 처리하지 못한다는 점을 기억해 두시기 바랍니다. +======= +To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function `structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +>>>>>>> upstream/master diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 8ef2a634f8..997971baef 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -14,16 +14,27 @@ 예시: +<<<<<<< HEAD - 현재 함수의 지역 변수와 매개변수 - 중첩 함수의 체인에 있는 함수에서 사용되는 변수와 매개변수 - 전역 변수 - 기타 등등 +======= + - The currently executing function, its local variables and parameters. + - Other functions on the current chain of nested calls, their local variables and parameters. + - Global variables. + - (there are some other, internal ones as well) +>>>>>>> upstream/master 이런 값은 *루트(root)* 라고 부릅니다. 2. 루트가 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값은 도달 가능한 값이 됩니다. +<<<<<<< HEAD 전역 변수에 객체가 저장되어있다고 가정해 봅시다. 이 객체의 프로퍼티가 또 다른 객체를 참조하고 있다면, 프로퍼티가 참조하는 객체는 도달 가능한 값이 됩니다. 이 객체가 참조하는 다른 모든 것들도 도달 가능하다고 여겨집니다. 자세한 예시는 아래에서 살펴보겠습니다. +======= + For instance, if there's an object in a global variable, and that object has a property referencing another object, *that* object is considered reachable. And those that it references are also reachable. Detailed examples to follow. +>>>>>>> upstream/master 자바스크립트 엔진 내에선 [가비지 컬렉터(garbage collector)](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science))가 끊임없이 동작합니다. 가비지 컬렉터는 모든 객체를 모니터링하고, 도달할 수 없는 객체는 삭제합니다. @@ -74,7 +85,11 @@ let admin = user; user = null; ``` +<<<<<<< HEAD 전역 변수 `admin`을 통하면 여전히 객체 John에 접근할 수 있기 때문에 John은 메모리에서 삭제되지 않습니다. 이 상태에서 `admin`을 다른 값(null 등)으로 덮어쓰면 John은 메모리에서 삭제될 수 있습니다. +======= +...Then the object is still reachable via `admin` global variable, so it must stay in memory. If we overwrite `admin` too, then it can be removed. +>>>>>>> upstream/master ## 연결된 객체 @@ -169,11 +184,19 @@ John과 Ann은 여전히 서로를 참조하고 있고, 두 객체 모두 외부 ![](garbage-collection-2.svg) +<<<<<<< HEAD 이후 루트가 참조하고 있는 것들을 mark 합니다. ![](garbage-collection-3.svg) 도달 가능한 모든 객체를 방문할 때까지, mark 한 객체가 참조하는 객체를 계속해서 mark 합니다. +======= +Then we follow their references and mark referenced objects: + +![](garbage-collection-3.svg) + +...And continue to follow further references, while possible: +>>>>>>> upstream/master ![](garbage-collection-4.svg) @@ -183,13 +206,23 @@ John과 Ann은 여전히 서로를 참조하고 있고, 두 객체 모두 외부 루트에서 페인트를 들이붓는다고 상상하면 이 과정을 이해하기 쉽습니다. 루트를 시작으로 참조를 따라가면서 도달가능한 객체 모두에 페인트가 칠해진다고 생각하면 됩니다. 이때 페인트가 묻지 않은 객체는 메모리에서 삭제됩니다. +<<<<<<< HEAD 지금까지 가비지 컬렉션이 어떻게 동작하는지에 대한 개념을 알아보았습니다. 자바스크립트 엔진은 실행에 영향을 미치지 않으면서 가비지 컬렉션을 더 빠르게 하는 다양한 최적화 기법을 적용합니다. +======= +That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not introduce any delays into the code execution. +>>>>>>> upstream/master 최적화 기법: +<<<<<<< HEAD - **generational collection(세대별 수집)** -- 객체를 '새로운 객체'와 '오래된 객체'로 나눕니다. 객체 상당수는 생성 이후 제 역할을 빠르게 수행해 금방 쓸모가 없어지는데, 이런 객체를 '새로운 객체'로 구분합니다. 가비지 컬렉터는 이런 객체를 공격적으로 메모리에서 제거합니다. 일정 시간 이상 동안 살아남은 객체는 '오래된 객체'로 분류하고, 가비지 컬렉터가 덜 감시합니다. - **incremental collection(점진적 수집)** -- 방문해야 할 객체가 많다면 모든 객체를 한 번에 방문하고 mark 하는데 상당한 시간이 소모됩니다. 가비지 컬렉션에 많은 리소스가 사용되어 실행 속도도 눈에 띄게 느려지겠죠. 자바스크립트 엔진은 이런 현상을 개선하기 위해 가비지 컬렉션을 여러 부분으로 분리한 다음, 각 부분을 별도로 수행합니다. 작업을 분리하고, 변경 사항을 추적하는 데 추가 작업이 필요하긴 하지만, 긴 지연을 짧은 지연 여러 개로 분산시킬 수 있다는 장점이 있습니다. - **idle-time collection(유휴 시간 수집)** -- 가비지 컬렉터는 실행에 주는 영향을 최소화하기 위해 CPU가 유휴 상태일 때에만 가비지 컬렉션을 실행합니다. +======= +- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". In typical code, many objects have a short life span: they appear, do their job and die fast, so it makes sense to track new objects and clear the memory from them if that's the case. Those that survive for long enough, become "old" and are examined less often. +- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine splits the whole set of existing objects into multiple parts. And then clear these parts one after another. There are many small garbage collections instead of a total one. That requires some extra bookkeeping between them to track changes, but we get many tiny delays instead of a big one. +- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution. +>>>>>>> upstream/master 이 외에도 다양한 최적화 기법과 가비지 컬렉션 알고리즘이 있습니다. 다양한 기법과 알고리즘을 소개해 드리고 싶지만, 엔진마다 세부 사항이나 기법이 다르기 때문에 여기서 멈추도록 하겠습니다. 엔진이 발전하면 기법도 달라지기 때문에 학습해야 할 이유가 진짜 없다면 '심화' 학습은 그리 가치 있지 않다고 생각합니다. 순수한 호기심 때문이라면 물론 괜찮습니다. 이런 분들을 위해 아래에 링크를 몇 개를 소개해놓았습니다. @@ -197,16 +230,30 @@ John과 Ann은 여전히 서로를 참조하고 있고, 두 객체 모두 외부 지금까지 알아본 내용을 요약해 봅시다. +<<<<<<< HEAD - 가비지 컬렉션은 엔진이 자동으로 수행하므로 개발자는 이를 억지로 실행하거나 막을 수 없습니다. - 객체는 도달 가능한 상태일 때 메모리에 남습니다. - 참조된다고 해서 도달 가능한 것은 아닙니다. 서로 연결된 객체들도 도달 불가능할 수 있습니다. +======= +- Garbage collection is performed automatically. We cannot force or prevent it. +- Objects are retained in memory while they are reachable. +- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole, as we've seen in the example above. +>>>>>>> upstream/master 모던 자바스크립트 엔진은 좀 더 발전된 가비지 컬렉션 알고리즘을 사용합니다. 어떤 알고리즘을 사용하는지 궁금하다면 'The Garbage Collection Handbook: The Art of Automatic Memory Management'(저자 - R. Jones et al)를 참고하시기 바랍니다. +<<<<<<< HEAD 저수준(low-level) 프로그래밍에 익숙하다면, [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection)을 읽어보세요. V8 가비지 컬렉터에 대한 자세한 내용을 확인해 볼 수 있습니다. [V8 공식 블로그](https://v8.dev/)에도 메모리 관리 방법 변화에 대한 내용이 올라옵니다. 가비지 컬렉션을 심도 있게 학습하려면 V8 내부구조를 공부하거나 V8 엔지니어로 일했던 [Vyacheslav Egorov](http://mrale.ph)의 블로그를 읽는 것도 좋습니다. 여러 엔진 중 'V8' 엔진을 언급하는 이유는 인터넷에서 관련 글을 쉽게 찾을 수 있기 때문입니다. V8과 타 엔진들은 동작 방법이 비슷한데, 가비지 컬렉션 동작 방식에는 많은 차이가 있습니다. 저수준 최적화가 필요한 상황이라면, 엔진에 대한 조예가 깊어야 합니다. 먼저 자바스크립트에 익숙해진 후에 엔진에 대해 학습하는 것을 추천해 드립니다. +======= +If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). + +The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. + +In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. +>>>>>>> upstream/master diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md index 4de3fce647..52f0cf60ec 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md @@ -7,7 +7,7 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); @@ -45,7 +45,7 @@ function makeUser() { } */!* }; -}; +} let user = makeUser(); diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md index 59797972ef..22cea4d1fd 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md @@ -14,7 +14,7 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js index 1f71eda4c6..4decb76dc1 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js @@ -15,6 +15,11 @@ describe("calculator", function() { 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); diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/task.md b/1-js/04-object-basics/04-object-methods/7-calculator/task.md index 1c2bd2953f..a588bba126 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/task.md +++ b/1-js/04-object-basics/04-object-methods/7-calculator/task.md @@ -6,9 +6,15 @@ importance: 5 `calculator`라는 객체를 만들고 세 메서드를 구현해 봅시다. +<<<<<<< HEAD - `read()`에선 프롬프트 창을 띄우고 더할 값 두 개를 입력받습니다. 입력받은 값은 객체의 프로퍼티에 저장합니다. - `sum()`은 저장된 두 값의 합을 반환합니다. - `mul()`은 저장된 두 값의 곱을 반환합니다. +======= +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. +- `sum()` returns the sum of saved values. +- `mul()` multiplies saved values and returns the result. +>>>>>>> upstream/master ```js let calculator = { @@ -21,4 +27,3 @@ alert( calculator.mul() ); ``` [demo] - diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js index e98fe6410c..a35c009cce 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js @@ -11,5 +11,6 @@ let ladder = { }, showStep: function() { alert(this.step); + return this; } }; \ No newline at end of file diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js index a2b17fcc4f..b4f2459b7b 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js @@ -32,6 +32,14 @@ describe('Ladder', function() { it('down().up().up().up() ', function() { assert.equal(ladder.down().up().up().up().step, 2); }); + + it('showStep() should return this', function() { + assert.equal(ladder.showStep(), ladder); + }); + + it('up().up().down().showStep().down().showStep()', function () { + assert.equal(ladder.up().up().down().showStep().down().showStep().step, 0) + }); after(function() { ladder.step = 0; diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md index 41c0b61ccb..ab045cdb61 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md @@ -21,9 +21,9 @@ let ladder = { return this; */!* } -} +}; -ladder.up().up().down().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` 체이닝이 길어질 땐 메서드 호출을 별도의 줄에 작성하면 가독성이 좋아집니다. @@ -33,7 +33,7 @@ ladder .up() .up() .down() - .up() + .showStep() // 1 .down() - .showStep(); // 1 + .showStep(); // 0 ``` diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md index 1ba79f94c8..da207d22d2 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -4,7 +4,11 @@ importance: 2 # 체이닝 +<<<<<<< HEAD 올라가기(`up`)와 내려가기(`down`) 메서드를 제공하는 객체 `ladder`가 있습니다. +======= +There's a `ladder` object that allows you to go up and down: +>>>>>>> upstream/master ```js let ladder = { @@ -21,19 +25,33 @@ let ladder = { }; ``` +<<<<<<< HEAD 메서드를 연이어 호출하고자 한다면 아래와 같이 코드를 작성할 수 있습니다. +======= +Now, if we need to make several calls in sequence, we can do it like this: +>>>>>>> upstream/master ```js ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 +ladder.down(); +ladder.showStep(); // 0 ``` +<<<<<<< HEAD `up`, `down`, `showStep`을 수정해 아래처럼 메서드 호출 체이닝이 가능하도록 해봅시다. +======= +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: +>>>>>>> upstream/master ```js -ladder.up().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` +<<<<<<< HEAD 참고로 이런 방식은 자바스크립트 라이브러리에서 널리 사용됩니다. +======= +Such an approach is widely used across JavaScript libraries. +>>>>>>> upstream/master diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index fb41d92371..44d2eeff9c 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -32,11 +32,19 @@ user.sayHi = function() { user.sayHi(); // 안녕하세요! ``` +<<<<<<< HEAD 함수 표현식으로 함수를 만들고, 객체 프로퍼티 `user.sayHi`에 함수를 할당해 주었습니다. 이제 객체에 할당된 함수를 호출하면 user가 인사를 해줍니다. 이렇게 객체 프로퍼티에 할당된 함수를 *메서드(method)* 라고 부릅니다. +======= +Here we've just used a Function Expression to create a function and assign it to the property `user.sayHi` of the object. + +Then we can call it as `user.sayHi()`. The user can now speak! + +A function that is a property of an object is called its *method*. +>>>>>>> upstream/master 위 예시에선 `user`에 할당된 `sayHi`가 메서드이죠. @@ -50,8 +58,13 @@ let user = { *!* // 함수 선언 function sayHi() { +<<<<<<< HEAD alert("안녕하세요!"); }; +======= + alert("Hello!"); +} +>>>>>>> upstream/master // 선언된 함수를 메서드로 등록 user.sayHi = sayHi; @@ -81,7 +94,11 @@ user = { // 단축 구문을 사용하니 더 깔끔해 보이네요. user = { *!* +<<<<<<< HEAD sayHi() { // "sayHi: function()"과 동일합니다. +======= + sayHi() { // same as "sayHi: function(){...}" +>>>>>>> upstream/master */!* alert("Hello"); } @@ -90,7 +107,11 @@ user = { 위처럼 `function`을 생략해도 메서드를 정의할 수 있습니다. +<<<<<<< HEAD 일반적인 방법과 단축 구문을 사용한 방법이 완전히 동일하진 않습니다. 객체 상속과 관련된 미묘한 차이가 존재하는데 지금으로선 이 차이가 중요하지 않기 때문에 넘어가도록 하겠습니다. +======= +To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases, the shorter syntax is preferred. +>>>>>>> upstream/master ## 메서드와 this @@ -160,14 +181,24 @@ let user = { let admin = user; user = null; // user를 null로 덮어씁니다. +<<<<<<< HEAD admin.sayHi(); // sayHi()가 엉뚱한 객체를 참고하면서 에러가 발생했습니다. +======= +*!* +admin.sayHi(); // TypeError: Cannot read property 'name' of null +*/!* +>>>>>>> upstream/master ``` `alert` 함수가 `user.name` 대신 `this.name`을 인수로 받았다면 에러가 발생하지 않았을 겁니다. ## 자유로운 this +<<<<<<< HEAD 자바스크립트의 `this`는 다른 프로그래밍 언어의 `this`와 동작 방식이 다릅니다. 자바스크립트에선 모든 함수에 `this`를 사용할 수 있습니다. +======= +In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function, even if it's not a method of an object. +>>>>>>> upstream/master 아래와 같이 코드를 작성해도 문법 에러가 발생하지 않습니다. diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index cb8daef42c..fc4b2c0715 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -4,14 +4,18 @@ importance: 2 # 함수 두 개로 동일한 객체 만들기 +<<<<<<< HEAD `new A()==new B()`가 성립 가능한 함수 `A`와 `B`를 만드는 게 가능할까요? +======= +Is it possible to create functions `A` and `B` so that `new A() == new B()`? +>>>>>>> upstream/master ```js no-beautify function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js index 036053927c..bba80e5c2e 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js @@ -10,6 +10,11 @@ describe("calculator", function() { 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); diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index 0529b5f49a..5bdaa79abd 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -6,9 +6,15 @@ importance: 5 아래와 같은 세 개의 메서드를 가진 생성자 함수, `Calculator`를 만들어보세요. +<<<<<<< HEAD - `read()` -- `prompt` 함수를 이용해 사용자로부터 값 두 개를 받고, 이를 객체 프로퍼티에 저장합니다. - `sum()` -- 프로퍼티에 저장된 값 두 개를 더한 후 반환합니다. - `mul()` -- 프로퍼티에 저장된 값 두 개를 곱한 후 반환합니다. +======= +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. +- `sum()` returns the sum of these properties. +- `mul()` returns the multiplication product of these properties. +>>>>>>> upstream/master 예시: diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index 1ac38b10bc..d6a36085ed 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -1,6 +1,10 @@ # new 연산자와 생성자 함수 +<<<<<<< HEAD 객체 리터럴 `{...}` 을 사용하면 객체를 쉽게 만들 수 있습니다. 그런데 개발을 하다 보면 유사한 객체를 여러 개 만들어야 할 때가 생기곤 합니다. 복수의 사용자, 메뉴 내 다양한 아이템을 객체로 표현하려고 하는 경우가 그렇죠. +======= +The regular `{...}` syntax allows us to create one object. But often we need to create many similar objects, like multiple users or menu items and so on. +>>>>>>> upstream/master `'new'` 연산자와 생성자 함수를 사용하면 유사한 객체 여러 개를 쉽게 만들 수 있습니다. @@ -64,13 +68,21 @@ let user = { 생성자의 의의는 바로 여기에 있습니다. 재사용할 수 있는 객체 생성 코드를 구현하는 것이죠. +<<<<<<< HEAD 잠깐! 모든 함수는 생성자 함수가 될 수 있다는 점을 잊지 마시기 바랍니다. `new`를 붙여 실행한다면 어떤 함수라도 위에 언급된 알고리즘이 실행됩니다. 이름의 '첫 글자가 대문자'인 함수는 `new`를 붙여 실행해야 한다는 점도 잊지 마세요. 공동의 약속이니까요. ````smart header="new function() { ... }" 재사용할 필요가 없는 복잡한 객체를 만들어야 한다고 해봅시다. 많은 양의 코드가 필요할 겁니다. 이럴 땐 아래와 같이 코드를 익명 생성자 함수로 감싸주는 방식을 사용할 수 있습니다. +======= +Let's note once again -- technically, any function (except arrow functions, as they don't have `this`) can be used as a constructor. It can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`. + +````smart header="new function() { ... }" +If we have many lines of code all about creation of a single complex object, we can wrap them in an immediately called constructor function, like this: +>>>>>>> upstream/master ```js -let user = new function() { +// create a function and immediately call it with new +let user = new function() { this.name = "John"; this.isAdmin = false; @@ -80,7 +92,11 @@ let user = new function() { }; ``` +<<<<<<< HEAD 위 생성자 함수는 익명 함수이기 때문에 어디에도 저장되지 않습니다. 처음 만들 때부터 단 한 번만 호출할 목적으로 만들었기 때문에 재사용이 불가능합니다. 이렇게 익명 생성자 함수를 이용하면 재사용은 막으면서 코드를 캡슐화 할 수 있습니다. +======= +This constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. +>>>>>>> upstream/master ```` ## new.target과 생성자 함수 @@ -91,7 +107,11 @@ let user = new function() { `new.target` 프로퍼티를 사용하면 함수가 `new`와 함께 호출되었는지 아닌지를 알 수 있습니다. +<<<<<<< HEAD 일반적인 방법으로 함수를 호출했다면 `new.target`은 undefined를 반환합니다. 반면 `new`와 함께 호출한 경우엔 `new.target`은 함수 자체를 반환해줍니다. +======= +It is undefined for regular calls and equals the function if called with `new`: +>>>>>>> upstream/master ```js run function User() { @@ -169,8 +189,13 @@ alert( new SmallUser().name ); // 원숭이 `return`문이 있는 생성자 함수는 거의 없습니다. 여기선 튜토리얼의 완성도를 위해 특이 케이스를 소개해보았습니다. +<<<<<<< HEAD ````smart header="괄호 생략하기" 인수가 없는 생성자 함수는 괄호를 생략해 호출할 수 있습니다. +======= +````smart header="Omitting parentheses" +By the way, we can omit parentheses after `new`: +>>>>>>> upstream/master ```js let user = new User; // <-- 괄호가 없음 diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md index 192afcca48..63c0c2e803 100644 --- a/1-js/04-object-basics/07-optional-chaining/article.md +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -3,20 +3,38 @@ [recent browser="new"] +<<<<<<< HEAD 옵셔널 체이닝(optional chaining) `?.`을 사용하면 프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있습니다. ## 옵셔널 체이닝이 필요한 이유 +======= +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 +>>>>>>> upstream/master 이제 막 자바스크립트를 배우기 시작했다면 옵셔널 체이닝이 등장하게 된 배경 상황을 직접 겪어보지 않았을 겁니다. 몇 가지 사례를 재현하면서 왜 옵셔널 체이닝이 등장했는지 알아봅시다. +<<<<<<< HEAD 사용자가 여러 명 있는데 그중 몇 명은 주소 정보를 가지고 있지 않다고 가정해봅시다. 이럴 때 `user.address.street`를 사용해 주소 정보에 접근하면 에러가 발생할 수 있습니다. ```js run let user = {}; // 주소 정보가 없는 사용자 +======= +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 +>>>>>>> upstream/master alert(user.address.street); // TypeError: Cannot read property 'street' of undefined ``` +<<<<<<< HEAD 또 다른 사례론 브라우저에서 동작하는 코드를 개발할 때 발생할 수 있는 문제가 있습니다. 자바스크립트를 사용해 페이지에 존재하지 않는 요소에 접근해 요소의 정보를 가져오려 하면 문제가 발생하죠. ```js run @@ -27,23 +45,94 @@ let html = document.querySelector('.my-element').innerHTML; 명세서에 `?.`이 추가되기 전엔 이런 문제들을 해결하기 위해 `&&` 연산자를 사용하곤 했습니다. 예시: +======= +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 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` property 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. + +Here's how the same would look for `document.querySelector`: + +```js run +let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null; +``` + +We can see that the element search `document.querySelector('.elem')` is actually called twice here. Not good. + +For more deeply nested properties, it becomes even uglier, as more repetitions are required. + +E.g. let's get `user.address.street.name` in a similar fashion. + +```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. + +There's a little better way to write it, using the `&&` operator: +>>>>>>> upstream/master ```js run let user = {}; // 주소 정보가 없는 사용자 +<<<<<<< HEAD alert( user && user.address && user.address.street ); // undefined, 에러가 발생하지 않습니다. ``` 중첩 객체의 특정 프로퍼티에 접근하기 위해 거쳐야 할 구성요소들을 AND로 연결해 실제 해당 객체나 프로퍼티가 있는지 확인하는 방법을 사용했었죠. 그런데 이렇게 AND를 연결해서 사용하면 코드가 아주 길어진다는 단점이 있습니다. +======= +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! +>>>>>>> upstream/master ## 옵셔널 체이닝의 등장 +<<<<<<< HEAD `?.`은 `?.`'앞'의 평가 대상이 `undefined`나 `null`이면 평가를 멈추고 `undefined`를 반환합니다. +======= +The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`. +>>>>>>> upstream/master **설명이 장황해지지 않도록 지금부턴 평가후 결과가 `null`이나 `undefined`가 아닌 경우엔 값이 '있다' 혹은 '존재한다'라고 표현하겠습니다.** +<<<<<<< HEAD 이제 옵셔널 체이닝을 사용해 `user.address.street`에 안전하게 접근해봅시다. +======= +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 `?.`: +>>>>>>> upstream/master ```js run let user = {}; // 주소 정보가 없는 사용자 @@ -51,7 +140,19 @@ let user = {}; // 주소 정보가 없는 사용자 alert( user?.address?.street ); // undefined, 에러가 발생하지 않습니다. ``` +<<<<<<< HEAD `user?.address`로 주소를 읽으면 아래와 같이 `user` 객체가 존재하지 않더라도 에러가 발생하지 않습니다. +======= +The code is short and clean, there's no duplication at all. + +Here's an example with `document.querySelector`: + +```js run +let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element +``` + +Reading the address with `user?.address` works even if `user` object doesn't exist: +>>>>>>> upstream/master ```js run let user = null; @@ -62,16 +163,26 @@ alert( user?.address.street ); // undefined 위 예시를 통해 우리는 `?.`은 `?.` '앞' 평가 대상에만 동작되고, 확장은 되지 않는다는 사실을 알 수 있습니다. +<<<<<<< HEAD 참고로 위 예시에서 사용된 `user?.`는 `user`가 `null`이나 `undefined`인 경우만 처리할 수 있습니다. `user`가 `null`이나 `undefined`가 아니고 실제 값이 존재하는 경우엔 반드시 `user.address` 프로퍼티는 있어야 합니다. 그렇지 않으면 `user?.address.street`의 두 번째 점 연산자에서 에러가 발생합니다. +======= +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 `?.`. +>>>>>>> upstream/master ```warn header="옵셔널 체이닝을 남용하지 마세요." `?.`는 존재하지 않아도 괜찮은 대상에만 사용해야 합니다. +<<<<<<< HEAD 사용자 주소를 다루는 위 예시에서 논리상 `user`는 반드시 있어야 하는데 `address`는 필수값이 아닙니다. 그러니 `user.address?.street`를 사용하는 것이 바람직합니다. 실수로 인해 `user`에 값을 할당하지 않았다면 바로 알아낼 수 있도록 해야 합니다. 그렇지 않으면 에러를 조기에 발견하지 못하고 디버깅이 어려워집니다. +======= +For example, if according to our code logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. + +Then, if `user` happens to be undefined, we'll see a programming error about it and fix it. Otherwise, if we overuse `?.`, coding errors can be silenced where not appropriate, and become more difficult to debug. +>>>>>>> upstream/master ``` ````warn header="`?.`앞의 변수는 꼭 선언되어 있어야 합니다." @@ -81,25 +192,43 @@ alert( user?.address.street ); // undefined // ReferenceError: user is not defined user?.address; ``` +<<<<<<< HEAD `user?.anything`을 사용하려면 `let`이나 `const`, `var`를 사용해 `user`를 정의해야 하죠. 이렇게 옵셔널 체이닝은 선언이 완료된 변수를 대상으로만 동작합니다. +======= +The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables. +>>>>>>> upstream/master ```` ## 단락 평가 `?.`는 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈춥니다. 참고로 이런 평가 방법을 단락 평가(short-circuit)라고 부릅니다. +<<<<<<< HEAD 그렇기 때문에 함수 호출을 비롯한 `?.` 오른쪽에 있는 부가 동작은 `?.`의 평가가 멈췄을 때 더는 일어나지 않습니다. +======= +So, if there are any further function calls or operations to the right of `?.`, they won't be made. + +For instance: +>>>>>>> upstream/master ```js run let user = null; let x = 0; +<<<<<<< HEAD user?.sayHi(x++); // 아무 일도 일어나지 않습니다. +======= +user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++ +>>>>>>> upstream/master alert(x); // 0, x는 증가하지 않습니다. ``` +<<<<<<< HEAD ## ?.()와 ?.[] +======= +## Other variants: ?.(), ?.[] +>>>>>>> upstream/master `?.`은 연산자가 아닙니다. `?.`은 함수나 대괄호와 함께 동작하는 특별한 문법 구조체(syntax construct)입니다. @@ -108,15 +237,16 @@ alert(x); // 0, x는 증가하지 않습니다. 한 객체엔 메서드 `admin`이 있지만 다른 객체엔 없는 상황입니다. ```js run -let user1 = { +let userAdmin = { admin() { alert("관리자 계정입니다."); } -} +}; -let user2 = {}; +let userGuest = {}; *!* +<<<<<<< HEAD user1.admin?.(); // 관리자 계정입니다. user2.admin?.(); */!* @@ -125,22 +255,39 @@ user2.admin?.(); 두 상황 모두에서 user 객체는 존재하기 때문에 `admin` 프로퍼티는 `.`만 사용해 접근했습니다. 그리고 난 후 `?.()`를 사용해 `admin`의 존재 여부를 확인했습니다. `user1`엔 `admin`이 정의되어 있기 때문에 메서드가 제대로 호출되었습니다. 반면 `user2`엔 `admin`이 정의되어 있지 않았음에도 불구하고 메서드를 호출하면 에러 없이 그냥 평가가 멈추는 것을 확인할 수 있습니다. +======= +userAdmin.admin?.(); // I am admin +*/!* + +*!* +userGuest.admin?.(); // nothing happens (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. +>>>>>>> upstream/master `.`대신 대괄호 `[]`를 사용해 객체 프로퍼티에 접근하는 경우엔 `?.[]`를 사용할 수도 있습니다. 위 예시와 마찬가지로 `?.[]`를 사용하면 객체 존재 여부가 확실치 않은 경우에도 안전하게 프로퍼티를 읽을 수 있습니다. ```js run +let key = "firstName"; + let user1 = { firstName: "Violet" }; +<<<<<<< HEAD let user2 = null; // user2는 권한이 없는 사용자라고 가정해봅시다. let key = "firstName"; +======= +let user2 = null; +>>>>>>> upstream/master alert( user1?.[key] ); // Violet alert( user2?.[key] ); // undefined - -alert( user1?.[key]?.something?.not?.existing); // undefined ``` `?.`은 `delete`와 조합해 사용할 수도 있습니다. @@ -149,10 +296,17 @@ alert( user1?.[key]?.something?.not?.existing); // undefined delete user?.name; // user가 존재하면 user.name을 삭제합니다. ``` +<<<<<<< HEAD ```warn header="`?.`은 읽기나 삭제하기에는 사용할 수 있지만 쓰기에는 사용할 수 없습니다." `?.`은 할당 연산자 왼쪽에서 사용할 수 없습니다. +======= +````warn header="We can use `?.` for safe reading and deleting, but not writing" +The optional chaining `?.` has no use on the left side of an assignment. +>>>>>>> upstream/master +For example: ```js run +<<<<<<< HEAD // user가 존재할 경우 user.name에 값을 쓰려는 의도로 아래와 같이 코드를 작성해 보았습니다. user?.name = "Violet"; // SyntaxError: Invalid left-hand side in assignment @@ -166,11 +320,32 @@ user?.name = "Violet"; // SyntaxError: Invalid left-hand side in assignment 1. `obj?.prop` -- `obj`가 존재하면 `obj.prop`을 반환하고, 그렇지 않으면 `undefined`를 반환함 2. `obj?.[prop]` -- `obj`가 존재하면 `obj[prop]`을 반환하고, 그렇지 않으면 `undefined`를 반환함 3. `obj?.method()` -- `obj`가 존재하면 `obj.method()`를 호출하고, 그렇지 않으면 `undefined`를 반환함 +======= +let user = null; + +user?.name = "John"; // Error, doesn't work +// because it evaluates to: undefined = "John" +``` + +```` + +## 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`. +>>>>>>> upstream/master 여러 예시를 통해 살펴보았듯이 옵셔널 체이닝 문법은 꽤 직관적이고 사용하기도 쉽습니다. `?.` 왼쪽 평가 대상이 `null`이나 `undefined`인지 확인하고 `null`이나 `undefined`가 아니라면 평가를 계속 진행합니다. `?.`를 계속 연결해서 체인을 만들면 중첩 프로퍼티들에 안전하게 접근할 수 있습니다. +<<<<<<< HEAD `?.`은 `?.`왼쪽 평가대상이 없어도 괜찮은 경우에만 선택적으로 사용해야 합니다. 꼭 있어야 하는 값인데 없는 경우에 `?.`을 사용하면 프로그래밍 에러를 쉽게 찾을 수 없으므로 이런 상황을 만들지 말도록 합시다. +======= +Still, we should apply `?.` carefully, only where it's acceptable, according to our code logic, that the left part doesn't exist. So that it won't hide programming errors from us, if they occur. +>>>>>>> upstream/master diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md index c0fd2f3ff4..1e484b6707 100644 --- a/1-js/04-object-basics/08-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -1,9 +1,22 @@ # 심볼형 +<<<<<<< HEAD 자바스크립트는 객체 프로퍼티 키로 오직 문자형과 심볼형만을 허용합니다. 숫자형, 불린형 모두 불가능하고 오직 문자형과 심볼형만 가능하죠. 지금까지는 프로퍼티 키가 문자형인 경우만 살펴보았습니다. 이번 챕터에선 프로퍼티 키로 심볼값을 사용해 보면서, 심볼형 키를 사용할 때의 이점에 대해 살펴보도록 하겠습니다. +======= +By specification, only two primitive types may serve as object property keys: + +- string type, or +- symbol type. + +Otherwise, if one uses another type, such as number, it's autoconverted to string. So that `obj[1]` is the same as `obj["1"]`, and `obj[true]` is the same as `obj["true"]`. + +Until now we've been using only strings. + +Now let's explore symbols, see what they can do for us. +>>>>>>> upstream/master ## 심볼 @@ -12,18 +25,29 @@ `Symbol()`을 사용하면 심볼값을 만들 수 있습니다. ```js +<<<<<<< HEAD // id는 새로운 심볼이 됩니다. let id = Symbol(); ``` 심볼을 만들 때 심볼 이름이라 불리는 설명을 붙일 수도 있습니다. 심볼 이름은 디버깅 시 아주 유용합니다. +======= +let id = Symbol(); +``` + +Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes: +>>>>>>> upstream/master ```js // 심볼 id에는 "id"라는 설명이 붙습니다. let id = Symbol("id"); ``` +<<<<<<< HEAD 심볼은 유일성이 보장되는 자료형이기 때문에, 설명이 동일한 심볼을 여러 개 만들어도 각 심볼값은 다릅니다. 심볼에 붙이는 설명(심볼 이름)은 어떤 것에도 영향을 주지 않는 이름표 역할만을 합니다. +======= +Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesn't affect anything. +>>>>>>> upstream/master 설명이 같은 심볼 두 개를 만들고 이를 비교해보겠습니다. 동일 연산자(`==`)로 비교 시 `false`가 반환되는 것을 확인할 수 있습니다. @@ -38,8 +62,15 @@ alert(id1 == id2); // false 참고로 Ruby 등의 언어에서도 '심볼'과 유사한 개념을 사용하는데, 자바스크립트의 심볼은 이들 언어에 쓰이는 심볼과는 다르기 때문에 혼동하지 마시길 바랍니다. +<<<<<<< HEAD ````warn header="심볼은 문자형으로 자동 형 변환되지 않습니다." 자바스크립트에선 문자형으로의 암시적 형 변환이 비교적 자유롭게 일어나는 편입니다. `alert` 함수가 거의 모든 값을 인자로 받을 수 있는 이유가 이 때문이죠. 그러나 심볼은 예외입니다. 심볼형 값은 다른 자료형으로 암시적 형 변환(자동 형 변환)되지 않습니다. +======= +So, to summarize, a symbol is a "primitive unique value" with an optional description. Let's see where we can use them. + +````warn header="Symbols don't auto-convert to a string" +Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don't auto-convert. +>>>>>>> upstream/master 아래 예시에서 `alert`는 에러를 발생시킵니다. @@ -52,7 +83,12 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string 문자열과 심볼은 근본이 다르기 때문에 우연히라도 서로의 타입으로 변환돼선 안 됩니다. 자바스크립트에선 '언어 차원의 보호장치(language guard)'를 마련해 심볼형이 다른 형으로 변환되지 않게 막아줍니다. +<<<<<<< HEAD 심볼을 반드시 출력해줘야 하는 상황이라면 아래와 같이 `.toString()` 메서드를 명시적으로 호출해주면 됩니다. +======= +If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here: + +>>>>>>> upstream/master ```js run let id = Symbol("id"); *!* @@ -60,7 +96,12 @@ alert(id.toString()); // Symbol(id)가 얼럿 창에 출력됨 */!* ``` +<<<<<<< HEAD `symbol.description` 프로퍼티를 이용하면 설명만 보여주는 것도 가능합니다. +======= +Or get `symbol.description` property to show the description only: + +>>>>>>> upstream/master ```js run let id = Symbol("id"); *!* @@ -72,7 +113,12 @@ alert(id.description); // id ## '숨김' 프로퍼티 +<<<<<<< HEAD 심볼을 이용하면 '숨김(hidden)' 프로퍼티를 만들 수 있습니다. 숨김 프로퍼티는 외부 코드에서 접근이 불가능하고 값도 덮어쓸 수 없는 프로퍼티입니다. +======= + +Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite. +>>>>>>> upstream/master 서드파티 코드에서 가지고 온 `user`라는 객체가 여러 개 있고, `user`를 이용해 어떤 작업을 해야 하는 상황이라고 가정해 봅시다. `user`에 식별자를 붙여주도록 합시다. @@ -92,9 +138,15 @@ alert( user[id] ); // 심볼을 키로 사용해 데이터에 접근할 수 있 그런데 문자열 `"id"`를 키로 사용해도 되는데 `Symbol("id")`을 사용한 이유가 무엇일까요? +<<<<<<< HEAD `user`는 서드파티 코드에서 가지고 온 객체이므로 함부로 새로운 프로퍼티를 추가할 수 없습니다. 그런데 심볼은 서드파티 코드에서 접근할 수 없기 때문에, 심볼을 사용하면 서드파티 코드가 모르게 `user`에 식별자를 부여할 수 있습니다. 상황 하나를 더 가정해보겠습니다. 제3의 스크립트(자바스크립트 라이브러리 등)에서 `user`를 식별해야 하는 상황이 벌어졌다고 해보죠. `user`의 원천인 서드파티 코드, 현재 작성 중인 스크립트, 제3의 스크립트가 각자 서로의 코드도 모른 채 `user`를 식별해야 하는 상황이 벌어졌습니다. +======= +As `user` objects belong to another codebase, it's unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won't be aware of newly defined symbols, so it's safe to add symbols to the `user` objects. + +Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. +>>>>>>> upstream/master 제3의 스크립트에선 아래와 같이 `Symbol("id")`을 이용해 전용 식별자를 만들어 사용할 수 있습니다. @@ -109,7 +161,7 @@ user[id] = "제3 스크립트 id 값"; 만약 심볼 대신 문자열 `"id"`를 사용해 식별자를 만들었다면 충돌이 발생할 *가능성이* 있습니다. -```js run +```js let user = { name: "John" }; // 문자열 "id"를 사용해 식별자를 만들었습니다. @@ -157,11 +209,19 @@ let user = { for (let key in user) alert(key); // name과 age만 출력되고, 심볼은 출력되지 않습니다. */!* +<<<<<<< HEAD // 심볼로 직접 접근하면 잘 작동합니다. alert( "직접 접근한 값: " + user[id] ); ``` `Object.keys(user)`에서도 키가 심볼인 프로퍼티는 배제됩니다. '심볼형 프로퍼티 숨기기(hiding symbolic property)'라 불리는 이런 원칙 덕분에 외부 스크립트나 라이브러리는 심볼형 키를 가진 프로퍼티에 접근하지 못합니다. +======= +// the direct access by the symbol works +alert( "Direct: " + user[id] ); // Direct: 123 +``` + +[Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property. +>>>>>>> upstream/master 그런데 [Object.assign](mdn:js/Object/assign)은 키가 심볼인 프로퍼티를 배제하지 않고 객체 내 모든 프로퍼티를 복사합니다. @@ -206,12 +266,20 @@ alert( id === idAgain ); // true ```smart header="Ruby랑 비슷해 보이네요." Ruby 등의 몇몇 언어에선 이름이 같으면 심볼도 같습니다. +<<<<<<< HEAD 자바스크립트에선 전역 심볼에만 이런 특징이 적용됩니다. +======= +In JavaScript, as we can see, that's true for global symbols. +>>>>>>> upstream/master ``` ### Symbol.keyFor +<<<<<<< HEAD 전역 심볼을 찾을 때 사용되는 `Symbol.for(key)`에 반대되는 메서드도 있습니다. `Symbol.keyFor(sym)`를 사용하면 이름을 얻을 수 있습니다. +======= +We have seen that for global symbols, `Symbol.for(key)` returns a symbol by name. To do the opposite -- return a name by global symbol -- we can use: `Symbol.keyFor(sym)`: +>>>>>>> upstream/master 예시: @@ -227,7 +295,11 @@ alert( Symbol.keyFor(sym2) ); // id `Symbol.keyFor`는 전역 심볼 레지스트리를 뒤져서 해당 심볼의 이름을 얻어냅니다. 검색 범위가 전역 심볼 레지스트리이기 때문에 전역 심볼이 아닌 심볼에는 사용할 수 없습니다. 전역 심볼이 아닌 인자가 넘어오면 `Symbol.keyFor`는 `undefined`를 반환합니다. +<<<<<<< HEAD 전역 심볼이 아닌 모든 심볼은 `description` 프로퍼티가 있습니다. 일반 심볼에서 이름을 얻고 싶으면 `description` 프로퍼티를 사용하면 됩니다. +======= +That said, all symbols have the `description` property. +>>>>>>> upstream/master 예시: @@ -267,11 +339,21 @@ alert( localSymbol.description ); // name 심볼의 주요 유스 케이스는 다음과 같습니다. +<<<<<<< HEAD 1. 객체의 '숨김' 프로퍼티 -- 외부 스크립트나 라이브러리에 '속한' 객체에 새로운 프로퍼티를 추가해 주고 싶다면 심볼을 만들고, 이를 프로퍼티 키로 사용하면 됩니다. 키가 심볼인 경우엔 `for..in`의 대상이 되지 않아서 의도치 않게 프로퍼티가 수정되는 것을 예방할 수 있습니다. 외부 스크립트나 라이브러리는 심볼 정보를 갖고 있지 않아서 프로퍼티에 직접 접근하는 것도 불가능합니다. 심볼형 키를 사용하면 프로퍼티가 우연히라도 사용되거나 덮어씌워 지는 걸 예방할 수 있습니다. +======= +1. "Hidden" object properties. + + If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite. +>>>>>>> upstream/master 이런 특징을 이용하면 원하는 것을 객체 안에 '은밀하게' 숨길 수 있습니다. 외부 스크립트에선 우리가 숨긴 것을 절대 볼 수 없습니다. 2. 자바스크립트 내부에서 사용되는 시스템 심볼은 `Symbol.*`로 접근할 수 있습니다. 시스템 심볼을 이용하면 내장 메서드 등의 기본 동작을 입맛대로 변경할 수 있습니다. [iterable 객체](info:iterable)에선 `Symbol.iterator`를, [객체를 원시형으로 변환하기](info:object-toprimitive)에선 `Symbol.toPrimitive`이 어떻게 사용되는지 알아보겠습니다. +<<<<<<< HEAD 사실 심볼을 완전히 숨길 방법은 없습니다. 내장 메서드 [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols)를 사용하면 모든 심볼을 볼 수 있고, 메서드 [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys)는 심볼형 키를 포함한 객체의 *모든* 키를 반환해줍니다. 그런데 대부분의 라이브러리, 내장 함수 등은 이런 메서드를 사용하지 않습니다. +======= +Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. But most libraries, built-in functions and syntax constructs don't use these methods. +>>>>>>> upstream/master diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index 28792e21df..c443e6354b 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -3,19 +3,56 @@ `obj1 + obj2` 처럼 객체끼리 더하는 연산을 하거나, `obj1 - obj2` 처럼 객체끼리 빼는 연산을 하면 어떤 일이 일어날까요? `alert(obj)`로 객체를 출력할 때는 무슨 일이 발생할까요? +<<<<<<< HEAD 이 모든 경우에 자동 형 변환이 일어납니다. 객체는 원시값으로 변환되고, 그 후 의도한 연산이 수행됩니다. +======= +JavaScript doesn't allow you to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle addition (or other operators). + +In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value. + +That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object! + +E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board". + +So, because we can't technically do much here, there's no maths with objects in real projects. When it happens, with rare exceptions, it's because of a coding mistake. + +In this chapter we'll cover how an object converts to primitive and how to customize it. + +We have two purposes: + +1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally. +2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later. + +## Conversion rules +>>>>>>> upstream/master 챕터에선 객체의 형 변환은 다루지 않았습니다. 원시형 자료가 어떻게 문자, 숫자, 논리형으로 변환되는지만 알아보았죠. 이젠 메서드와 심볼에 대한 지식을 갖추었으니 본격적으로 이 공백을 메꿔봅시다. +<<<<<<< HEAD 1. 객체는 논리 평가 시 `true`를 반환합니다. 단 하나의 예외도 없죠. 따라서 객체는 숫자형이나 문자형으로만 형 변환이 일어난다고 생각하시면 됩니다. 2. 숫자형으로의 형 변환은 객체끼리 빼는 연산을 할 때나 수학 관련 함수를 적용할 때 일어납니다. 객체 `Date`끼리 차감하면(`date1 - date2`) 두 날짜의 시간 차이가 반환됩니다. `Date`에 대해선 에서 다룰 예정입니다. 3. 문자형으로의 형 변환은 대개 `alert(obj)`같이 객체를 출력하려고 할 때 일어납니다. +======= +1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions. +2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. +3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts. +>>>>>>> upstream/master -## ToPrimitive +We can implement string and numeric conversion by ourselves, using special object methods. +<<<<<<< HEAD 특수 객체 메서드를 사용하면 숫자형이나 문자형으로의 형 변환을 원하는 대로 조절할 수 있습니다. 객체 형 변환은 세 종류로 구분되는데, 'hint'라 불리는 값이 구분 기준이 됩니다. 'hint'가 무엇인지는 [명세서](https://tc39.github.io/ecma262/#sec-toprimitive)에 자세히 설명되어 있는데, '목표로 하는 자료형' 정도로 이해하시면 될 것 같습니다. +======= +Now let's get into technical details, because it's the only way to cover the topic in-depth. + +## Hints + +How does JavaScript decide which conversion to apply? + +There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +>>>>>>> upstream/master `"string"` : `alert` 함수같이 문자열을 기대하는 연산을 수행할 때는(객체-문자형 변환), hint가 `string`이 됩니다. @@ -43,10 +80,16 @@ let greater = user1 > user2; ``` + Most built-in mathematical functions also include such conversion. + `"default"` : 연산자가 기대하는 자료형이 '확실치 않을 때' hint는 `default`가 됩니다. 아주 드물게 발생합니다. +<<<<<<< HEAD 이항 덧셈 연산자 `+`는 피연산자의 자료형에 따라 문자열을 합치는 연산을 할 수도 있고 숫자를 더해주는 연산을 할 수도 있습니다. 따라서 `+`의 인수가 객체일 때는 hint가 `default`가 됩니다. +======= + For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them). So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. +>>>>>>> upstream/master 동등 연산자 `==`를 사용해 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교할 때도, 객체를 어떤 자료형으로 바꿔야 할지 확신이 안 서므로 hint는 default가 됩니다. @@ -60,6 +103,7 @@ 크고 작음을 비교할 때 쓰이는 연산자 `<`, `>` 역시 피연산자에 문자형과 숫자형 둘 다를 허용하는데, 이 연산자들은 hint를 'number'로 고정합니다. hint가 'default'가 되는 일이 없죠. 이는 하위 호환성 때문에 정해진 규칙입니다. +<<<<<<< HEAD 실제 일을 할 때는 이런 사항을 모두 외울 필요는 없습니다. `Date` 객체를 제외한 모든 내장 객체는 hint가 `"default"`인 경우와 `"number"`인 경우를 동일하게 처리하기 때문입니다. 우리도 커스텀 객체를 만들 땐 이런 규칙을 따르면 됩니다. ```smart header="`\"boolean\"` hint는 없습니다." @@ -67,26 +111,53 @@ hint는 총 세 가지입니다. 아주 간단하죠. 'boolean' hint는 존재하지 않습니다. 모든 객체는 그냥 `true`로 평가됩니다. 게다가 우리도 내장 객체에 사용되는 규칙처럼 `"default"`와 `"number"`를 동일하게 처리하면, 결국엔 두 종류의 형 변환(객체-문자형, 객체-숫자형)만 남게 됩니다. ``` +======= +In practice though, things are a bit simpler. + +All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same. + +Still, it's important to know about all 3 hints, soon we'll see why. +>>>>>>> upstream/master **자바스크립트는 형 변환이 필요할 때, 아래와 같은 알고리즘에 따라 원하는 메서드를 찾고 호출합니다.** +<<<<<<< HEAD 1. 객체에 `obj[Symbol.toPrimitive](hint)`메서드가 있는지 찾고, 있다면 메서드를 호출합니다. `Symbol.toPrimitive`는 시스템 심볼로, 심볼형 키로 사용됩니다. 2. 1에 해당하지 않고 hint가 `"string"`이라면, - `obj.toString()`이나 `obj.valueOf()`를 호출합니다(존재하는 메서드만 실행됨). 3. 1과 2에 해당하지 않고, hint가 `"number"`나 `"default"`라면 - `obj.valueOf()`나 `obj.toString()`을 호출합니다(존재하는 메서드만 실행됨). +======= +1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. +>>>>>>> upstream/master ## Symbol.toPrimitive 첫 번째 메서드부터 살펴봅시다. 자바스크립트엔 `Symbol.toPrimitive`라는 내장 심볼이 존재하는데, 이 심볼은 아래와 같이 목표로 하는 자료형(hint)을 명명하는 데 사용됩니다. ```js obj[Symbol.toPrimitive] = function(hint) { +<<<<<<< HEAD // 반드시 원시값을 반환해야 합니다. // hint는 "string", "number", "default" 중 하나가 될 수 있습니다. }; ``` 실제 돌아가는 예시를 살펴보는 게 좋을 것 같네요. `user` 객체에 객체-원시형 변환 메서드 `obj[Symbol.toPrimitive](hint)`를 구현해보겠습니다. +======= + // here goes the code to convert this object to a primitive + // it must return a primitive value + // hint = one of "string", "number", "default" +}; +``` + +If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed. + +For instance, here `user` object implements it: +>>>>>>> upstream/master ```js run let user = { @@ -105,11 +176,16 @@ alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` +<<<<<<< HEAD 이렇게 메서드를 구현해 놓으면 `user`는 hint에 따라 (자기 자신을 설명해주는) 문자열로 변환되기도 하고 (가지고 있는 돈의 액수를 나타내는) 숫자로 변환되기도 합니다. `user[Symbol.toPrimitive]`를 사용하면 메서드 하나로 모든 종류의 형 변환을 다룰 수 있습니다. +======= +As we can see from the code, `user` becomes a self-descriptive string or a money amount, depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases. +>>>>>>> upstream/master ## toString과 valueOf +<<<<<<< HEAD `toString`과 `valueOf`는 심볼이 생기기 이전부터 존재해 왔던 '평범한' 메서드입니다. 이 메서드를 이용하면 '구식'이긴 하지만 형 변환을 직접 구현할 수 있습니다. 객체에 `Symbol.toPrimitive`가 없으면 자바스크립트는 아래 규칙에 따라 `toString`이나 `valueOf`를 호출합니다. @@ -118,6 +194,16 @@ alert(user + 500); // hint: default -> 1500 - 그 외: `valueOf -> toString` 순 이 메서드들은 반드시 원시값을 반환해야합니다. `toString`이나 `valueOf`가 객체를 반환하면 그 결과는 무시됩니다. 마치 메서드가 처음부터 없었던 것처럼 되어버리죠. +======= +If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: + +- For the `"string"` hint: call `toString` method, and if it doesn't exist or if it returns an object instead of a primitive value, then call `valueOf` (so `toString` has the priority for string conversions). +- For other hints: call `valueOf`, and if it doesn't exist or if it returns an object instead of a primitive value, then call `toString` (so `valueOf` has the priority for maths). + +Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. + +These methods must return a primitive value. If `toString` or `valueOf` returns an object, then it's ignored (same as if there were no method). +>>>>>>> upstream/master 일반 객체는 기본적으로 `toString`과 `valueOf`에 적용되는 다음 규칙을 따릅니다. @@ -135,9 +221,15 @@ alert(user.valueOf() === user); // true 이런 이유 때문에 `alert`에 객체를 넘기면 `[object Object]`가 출력되는 것입니다. +<<<<<<< HEAD 여기서 `valueOf`는 튜토리얼의 완성도를 높이고 헷갈리는 것을 줄여주려고 언급했습니다. 앞서 본 바와 같이 `valueOf`는 객체 자신을 반환하기 때문에 그 결과가 무시됩니다. 왜 그런거냐고 이유를 묻지는 말아주세요. 그냥 역사적인 이유때문입니다. 우리는 그냥 이 메서드가 존재하지 않는다고 생각하면 됩니다. 이제 직접 이 메서드들을 사용한 예시를 구현해봅시다. +======= +The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. + +Let's implement these methods to customize the conversion. +>>>>>>> upstream/master 아래 `user`는 `toString`과 `valueOf`를 조합해 만들었는데, `Symbol.toPrimitive`를 사용한 위쪽 예시와 동일하게 동작합니다. @@ -182,18 +274,30 @@ alert(user + 500); // toString -> John500 객체에 `Symbol.toPrimitive`와 `valueOf`가 없으면, `toString`이 모든 형 변환을 처리합니다. +<<<<<<< HEAD ## 반환 타입 +======= +### A conversion can return any primitive type +>>>>>>> upstream/master 위에서 소개해드린 세 개의 메서드는 'hint'에 명시된 자료형으로의 형 변환을 보장해 주지 않습니다. +<<<<<<< HEAD `toString()`이 항상 문자열을 반환하리라는 보장이 없고, `Symbol.toPrimitive`의 hint가 `"number"`일 때 항상 숫자형 자료가 반환되리라는 보장이 없습니다. +======= +There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for the hint `"number"`. +>>>>>>> upstream/master 확신할 수 있는 단 한 가지는 객체가 아닌 원시값을 반환해 준다는 것뿐입니다. ```smart header="과거의 잔재" `toString`이나 `valueOf`가 객체를 반환해도 에러가 발생하지 않습니다. 다만 이때는 반환 값이 무시되고, 메서드 자체가 존재하지 않았던 것처럼 동작합니다. 이렇게 동작하는 이유는 과거 자바스크립트엔 '에러'라는 개념이 잘 정립되어있지 않았기 때문입니다. +<<<<<<< HEAD 반면에 `Symbol.toPrimitive`는 *무조건* 원시자료를 반환해야 합니다. 그렇지 않으면 에러가 발생합니다. +======= +In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error. +>>>>>>> upstream/master ``` ## 추가 형 변환 @@ -204,7 +308,15 @@ alert(user + 500); // toString -> John500 1. 객체는 원시형으로 변화됩니다. 변환 규칙은 위에서 설명했습니다. 2. 변환 후 원시값이 원하는 형이 아닌 경우엔 또다시 형 변환이 일어납니다. +<<<<<<< HEAD 예시: +======= +If we pass an object as an argument, then there are two stages of calculations: +1. The object is converted to a primitive (using the rules described above). +2. If necessary for further calculations, the resulting primitive is also converted. + +For instance: +>>>>>>> upstream/master ```js run let obj = { @@ -229,22 +341,36 @@ let obj = { } }; +<<<<<<< HEAD alert(obj + 2); // 22("2" + 2), 문자열이 반환되기 때문에 문자열끼리의 병합이 일어났습니다. +======= +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation +>>>>>>> upstream/master ``` ## 요약 원시값을 기대하는 내장 함수나 연산자를 사용할 때 객체-원시형으로의 형 변환이 자동으로 일어납니다. +<<<<<<< HEAD 객체-원시형으로의 형 변환은 hint를 기준으로 세 종류로 구분할 수 있습니다. - `"string"` (`alert` 같이 문자열을 필요로 하는 연산) - `"number"` (수학 연산) - `"default"` (드물게 발생함) 연산자별로 어떤 hint가 적용되는지는 명세서에서 찾아볼 수 있습니다. 연산자가 기대하는 피연산자를 '확신할 수 없을 때'에는 hint가 `"default"`가 됩니다. 이런 경우는 아주 드물게 발생합니다. 내장 객체는 대개 hint가 `"default"`일 때와 `"number"`일 때를 동일하게 처리합니다. 따라서 실무에선 hint가 `"default"`인 경우와 `"number"`인 경우를 합쳐서 처리하는 경우가 많습니다. +======= +There are 3 types (hints) of it: +- `"string"` (for `alert` and other operations that need a string) +- `"number"` (for maths) +- `"default"` (few operators, usually objects implement it the same way as `"number"`) + +The specification describes explicitly which operator uses which hint. +>>>>>>> upstream/master 객체-원시형 변환엔 다음 알고리즘이 적용됩니다. +<<<<<<< HEAD 1. 객체에 `obj[Symbol.toPrimitive](hint)`메서드가 있는지 찾고, 있다면 호출합니다. 2. 1에 해당하지 않고 hint가 `"string"`이라면, - `obj.toString()`이나 `obj.valueOf()`를 호출합니다. @@ -252,3 +378,14 @@ alert(obj + 2); // 22("2" + 2), 문자열이 반환되기 때문에 문자열끼 - `obj.valueOf()`나 `obj.toString()`을 호출합니다. `obj.toString()`만 사용해도 '모든 변환'을 다 다룰 수 있기 때문에, 실무에선 `obj.toString()`만 구현해도 충분한 경우가 많습니다. 반환 값도 '사람이 읽고 이해할 수 있는' 형식이기 때문에 실용성 측면에서 다른 메서드에 뒤처지지 않습니다. `obj.toString()`은 로깅이나 디버깅 목적으로도 자주 사용됩니다. +======= +1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. + +All these methods must return a primitive to work (if defined). + +In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. +>>>>>>> upstream/master diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md index 082913c118..b7f0539c59 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md @@ -15,4 +15,8 @@ str.test = 5; alert(str.test); ``` +<<<<<<< HEAD 아래 코드를 실행하면, 의도한 대로 문자열(str)에 프로퍼티(test)를 추가할 수 있을까요? 만약 가능하다면 얼럿 창엔 무엇이 출력될까요? +======= +What do you think, will it work? What will be shown? +>>>>>>> upstream/master diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index a07937a393..887d574aeb 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -39,8 +39,13 @@ john.sayHi(); // 친구야 반갑다! 자바스크립트 창안자(creator)는 다음과 같은 모순적인 상황을 해결해야만 했었습니다. +<<<<<<< HEAD - 문자열이나 숫자와 같은 원시값을 다루어야 하는 작업이 많은데, 메서드를 사용하면 작업을 수월하게 할 수 있을 것 같다는 생각이 듭니다. - 그런데 원시값은 가능한 한 빠르고 가벼워야 합니다. +======= +- There are many things one would want to do with a primitive, like a string or a number. It would be great to access them using methods. +- Primitives must be as fast and lightweight as possible. +>>>>>>> upstream/master 조금 어색해 보이지만, 자바스크립트 창안자는 아래와 같은 방법을 사용해 해결책을 모색하였습니다. @@ -48,7 +53,11 @@ john.sayHi(); // 친구야 반갑다! 2. 문자열, 숫자, 불린, 심볼의 메서드와 프로퍼티에 접근할 수 있도록 언어 차원에서 허용합니다. 3. 이를 가능하게 하기 위해, 원시값이 메서드나 프로퍼티에 접근하려 하면 추가 기능을 제공해주는 특수한 객체, "원시 래퍼 객체(object wrapper)"를 만들어 줍니다. 이 객체는 곧 삭제됩니다. +<<<<<<< HEAD "래퍼 객체"는 원시 타입에 따라 종류가 다양합니다. 각 래퍼 객체는 원시 자료형의 이름을 그대로 차용해, `String`,`Number`,`Boolean`, `Symbol`라고 부릅니다. 래퍼 객체 마다 제공하는 메서드 역시 다릅니다. +======= +The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. Thus, they provide different sets of methods. +>>>>>>> upstream/master 인수로 받은 문자열의 모든 글자를 대문자로 바꿔주는 메서드 [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase)를 예로 들어보겠습니다. @@ -104,9 +113,16 @@ if (zero) { // 변수 zero는 객체이므로, 조건문이 참이 됩니다. } ``` +<<<<<<< HEAD 그런데, `new`를 붙이지 않고 `String / Number / Boolean`을 사용하는 건 괜찮습니다. `new` 없이 사용하면 상식에 맞게 인수를 원하는 형의 원시값(문자열, 숫자, 불린 값)으로 바꿔줍니다. 아주 유용하죠. 예시: +======= +On the other hand, using the same functions `String/Number/Boolean` without `new` is totally fine and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive). + +For example, this is entirely valid: + +>>>>>>> upstream/master ```js let num = Number("123"); // 문자열을 숫자로 바꿔줌 ``` diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md index 4473c71b9e..73a98b976b 100644 --- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md +++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md @@ -28,6 +28,10 @@ alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000 ```js run +<<<<<<< HEAD alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(반올림됨) -> 6.4 +======= +alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 +>>>>>>> upstream/master ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 3b04f080a0..9e76f45893 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -2,9 +2,15 @@ 모던 자바스크립트는 숫자를 나타내는 두 가지 자료형을 지원합니다. +<<<<<<< HEAD 1. 일반적인 숫자는 '배정밀도 부동소수점 숫자(double precision floating point number)'로 알려진 64비트 형식의 [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision)에 저장됩니다. 튜토리얼 전체에서 이 형식을 사용하여 숫자를 표현할 예정입니다. 2. 임의의 길이를 가진 정수는 BigInt 숫자로 나타낼 수 있습니다. 일반적인 숫자는 253이상이거나 -253이하일 수 없다는 제약 때문에 BigInt라는 새로운 자료형이 만들어졌습니다. BigInt는 아주 특별한 경우에만 사용되므로, 별도의 챕터 에서 자세한 내용을 다루겠습니다. +======= +1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. + +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . +>>>>>>> upstream/master 자, 그럼 일반적인 숫자에 대해서 자세히 알아봅시다. @@ -16,13 +22,28 @@ let billion = 1000000000; ``` +<<<<<<< HEAD 그런데 이렇게 0을 많이 사용해 숫자를 표현하다 보면 잘못 입력하기 쉽기 때문에, 실제로는 이런 방법을 잘 사용하지 않습니다. 0을 많이 입력하는 게 귀찮기도 하지요. 그래서 대개는 10억(`billion`)을 나타낼 땐 `'1bn'`을 사용하고, 73억을 나타낼 땐 `'7.3bn'`을 사용합니다. 큰 숫자를 나타낼 땐 이런 방법이 주로 사용되죠. 자바스크립트에서도 숫자 옆에 `'e'`를 붙이고 0의 개수를 그 옆에 붙여주면 숫자를 줄일 수 있습니다. +======= +We also can use underscore `_` as the separator: + +```js +let billion = 1_000_000_000; +``` + +Here the underscore `_` plays the role of the "[syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above. + +In real life though, we try to avoid writing long sequences of zeroes. We're too lazy for that. We'll try to write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers. + +In JavaScript, we can shorten a number by appending the letter `"e"` to it and specifying the zeroes count: +>>>>>>> upstream/master ```js run let billion = 1e9; // 10억, 1과 9개의 0 +<<<<<<< HEAD alert( 7.3e9 ); // 73억 (7,300,000,000) ``` @@ -34,11 +55,25 @@ alert( 7.3e9 ); // 73억 (7,300,000,000) ``` 이제 아주 작은 숫자인 1마이크로초(백만 분의 1초)를 표현해보겠습니다. +======= +alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000) +``` + +In other words, `e` multiplies the number by `1` with the given zeroes count. ```js -let ms = 0.000001; +1e3 === 1 * 1000; // e3 means *1000 +1.23e6 === 1.23 * 1000000; // e6 means *1000000 ``` +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): +>>>>>>> upstream/master + +```js +let mсs = 0.000001; +``` + +<<<<<<< HEAD 작은 숫자를 표현할 때도 큰 숫자를 표현할 때처럼 `'e'`를 사용할 수 있습니다. 0을 명시적으로 쓰고 싶지 않다면 다음과 같이 숫자를 표현할 수 있죠. ```js @@ -46,15 +81,35 @@ let ms = 1e-6; // 1에서 왼쪽으로 6번 소수점 이동 ``` `0.000001`에서 0의 개수를 세면 6이므로 `0.000001`은 당연히 `1e-6`이 되죠. +======= +Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could write the same as: + +```js +let mcs = 1e-6; // five zeroes to the left from 1 +``` + +If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`. +>>>>>>> upstream/master 이렇게 `'e'` 우측에 음수가 있으면, 이 음수의 절댓값 만큼 10을 거듭제곱한 수로 나누는 것을 의미합니다. ```js +<<<<<<< HEAD // 10을 세 번 거듭제곱한 수로 나눔 1e-3 === 1 / 1000 // 0.001 // 10을 여섯 번 거듭제곱한 수로 나눔 1.23e-6 === 1.23 / 1000000 // 0.00000123 +======= +// -3 divides by 1 with 3 zeroes +1e-3 === 1 / 1000; // 0.001 + +// -6 divides by 1 with 6 zeroes +1.23e-6 === 1.23 / 1000000; // 0.00000123 + +// an example with a bigger number +1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times +>>>>>>> upstream/master ``` ### 16진수, 2진수, 8진수 @@ -92,13 +147,23 @@ alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` +<<<<<<< HEAD `base`는 `2`에서 `36`까지 쓸 수 있는데, 기본값은 `10`입니다. +======= +The `base` can vary from `2` to `36`. By default, it's `10`. +>>>>>>> upstream/master `base`별 유스 케이스는 다음과 같습니다. +<<<<<<< HEAD - **base=16** -- 16진수 색, 문자 인코딩 등을 표현할 때 사용합니다. 숫자는 `0`부터 `9`, 10 이상의 수는 `A`부터 `F`를 사용하여 나타냅니다. - **base=2** -- 비트 연산 디버깅에 주로 쓰입니다. 숫자는 `0` 또는 `1`이 될 수 있습니다. - **base=36** -- 사용할 수 있는 `base` 중 최댓값으로, `0..9`와 `A..Z`를 사용해 숫자를 표현합니다. 알파벳 전체가 숫자를 나타내는 데 사용되죠. `36` 베이스는 url을 줄이는 것과 같이 숫자로 된 긴 식별자를 짧게 줄일 때 유용합니다. 예시를 살펴봅시다. +======= +- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`. +- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`. +- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`: +>>>>>>> upstream/master ```js run alert( 123456..toString(36) ); // 2n9c @@ -107,9 +172,16 @@ alert( num.toString(2) ); // 11111111 ```warn header="점 두 개와 메서드 호출" `123456..toString(36)`에 있는 점 두 개는 오타가 아닙니다. 위 예시처럼 숫자를 대상으로 메서드 `toString`을 직접 호출하고 싶다면 숫자 다음에 점 두 개 `..`를 붙여야 합니다. +<<<<<<< HEAD `123456.toString(36)`처럼 점을 한 개만 사용하면, 첫 번째 점 이후는 소수부로 인식되어 에러가 발생할 수 있습니다. 점을 하나 더 추가하면 자바스크립트는 소수부가 없다고 판단하고 함수를 호출합니다. `(123456).toString(36)`도 가능합니다. +======= +If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method. + +Also could write `(123456).toString(36)`. + +>>>>>>> upstream/master ``` ## 어림수 구하기 @@ -125,7 +197,11 @@ alert( num.toString(2) ); // 11111111 : 소수점 첫째 자리에서 올림. `3.1`은 `4`, `-1.1`은 `-1`이 됩니다. `Math.round` +<<<<<<< HEAD : 소수점 첫째 자리에서 반올림. `3.1`은 `3`, `3.6`은 `4`, `-1.1`은 `-1`이 됩니다. +======= +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. +>>>>>>> upstream/master `Math.trunc` (Internet Explorer에서는 지원하지 않음) : 소수부를 무시. `3.1`은 `3`이 되고 `-1.1`은 `-1`이 됩니다. @@ -135,8 +211,10 @@ alert( num.toString(2) ); // 11111111 | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| |`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | |`3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | @@ -148,11 +226,15 @@ alert( num.toString(2) ); // 11111111 1. 곱하기와 나누기 +<<<<<<< HEAD 소수점 두 번째 자리 숫자까지만 남기고 싶은 경우, 숫자에 `100` 또는 `100`보다 큰 `10`의 거듭제곱 수를 곱한 후, 원하는 어림수 내장 함수를 호출하고 처음 곱한 수를 다시 나누면 됩니다. +======= + For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100`, call the rounding function and then divide it back. +>>>>>>> upstream/master ```js run let num = 1.23456; - alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 + alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 ``` 2. 소수점 `n` 번째 수까지의 어림수를 구한 후 이를 문자형으로 반환해주는 메서드인 [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed)를 사용합니다. @@ -169,20 +251,34 @@ alert( num.toString(2) ); // 11111111 alert( num.toFixed(1) ); // "12.4" ``` +<<<<<<< HEAD `toFixed`를 사용할 때 주의할 점은 이 메서드의 반환 값이 문자열이라는 것입니다. 소수부의 길이가 인수보다 작으면 끝에 0이 추가됩니다. +======= + Please note that the result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end: +>>>>>>> upstream/master ```js run let num = 12.34; alert( num.toFixed(5) ); // "12.34000", 소수부의 길이를 5로 만들기 위해 0이 추가되었습니다. ``` +<<<<<<< HEAD 참고로, `+num.toFixed(5)`처럼 단항 덧셈 연산자를 앞에 붙이거나 `Number()`를 호출하면 문자형의 숫자를 숫자형으로 변환할 수 있습니다. +======= + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. +>>>>>>> upstream/master ## 부정확한 계산 +<<<<<<< HEAD 숫자는 내부적으로 64비트 형식 [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision)으로 표현되기 때문에 숫자를 저장하려면 정확히 64비트가 필요합니다. 64비트 중 52비트는 숫자를 저장하는 데 사용되고, 11비트는 소수점 위치를(정수는 0), 1비트는 부호를 저장하는 데 사용됩니다. 그런데 숫자가 너무 커지면 64비트 공간이 넘쳐서 Infinity로 처리됩니다. +======= +Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign. + +If a number is really huge, it may overflow the 64-bit storage and become a special numeric value `Infinity`: +>>>>>>> upstream/master ```js run alert( 1e500 ); // Infinity @@ -190,7 +286,11 @@ alert( 1e500 ); // Infinity 원인을 이해하려면 집중이 필요하긴 하지만, 꽤 자주 발생하는 현상인 정밀도 손실(loss of precision)도 있습니다. +<<<<<<< HEAD 예시를 살펴봅시다. +======= +Consider this (falsy!) equality test: +>>>>>>> upstream/master ```js run alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* @@ -204,13 +304,27 @@ alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* alert( 0.1 + 0.2 ); // 0.30000000000000004 ``` +<<<<<<< HEAD 부정확한 비교 연산이 만들어내는 결과는 여기서 그치지 않습니다. 인터넷 쇼핑몰 사이트를 운영하고 있다고 가정해 봅시다. 사용자가 `$0.10`와 `$0.20` 짜리 물품을 장바구니에 넣었다고 상상해 보죠. 주문 총액이 `$0.30000000000000004`인 것을 보고 놀라지 않을 사용자는 없을 겁니다. +======= +Ouch! Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone. +>>>>>>> upstream/master 왜 이런 일이 발생하는 걸까요? 숫자는 0과 1로 이루어진 이진수로 변환되어 연속된 메모리 공간에 저장됩니다. 그런데 10진법을 사용하면 쉽게 표현할 수 있는 `0.1`, `0.2` 같은 분수는 이진법으로 표현하면 무한 소수가 됩니다. +<<<<<<< HEAD `0.1`은 1을 10으로 나눈 수인 `1/10`입니다. 10진법을 사용하면 이러한 숫자를 쉽게 표현할 수 있죠. `1/10`과 `1/3`을 비교해봅시다. `1/3`은 무한 소수 `0.33333(3)`이 됩니다. +======= +```js run +alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 +alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 +alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 +``` + +What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. +>>>>>>> upstream/master 이렇게 `10`의 거듭제곱으로 나눈 값은 10진법에서 잘 동작하지만 `3`으로 나누게 되면 10진법에서 제대로 동작하지 않습니다. 같은 이유로 2진법 체계에서 `2`의 거듭제곱으로 나눈 값은 잘 동작하지만 `1/10`같이 `2`의 거듭제곱이 아닌 값으로 나누게 되면 무한 소수가 되어버립니다. @@ -230,14 +344,18 @@ alert( 0.1.toFixed(20) ); // 0.10000000000000000555 ```smart header="자바스크립트뿐만이 아닙니다." 다른 언어에서도 같은 이슈가 있습니다. +<<<<<<< HEAD 자바스크립트와 동일한 숫자 형식을 사용하기 때문에 PHP, Java, C, Perl, Ruby에서도 똑같은 결과를 얻습니다. +======= +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. +>>>>>>> upstream/master ``` 문제를 해결하는 방법은 없을까요? 물론 있습니다. 가장 신뢰할만한 방법은 [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 +alert( sum.toFixed(2) ); // "0.30" ``` 이때 `toFixed`는 항상 문자열을 반환한다는 점에 유의해야 합니다. 문자열을 반환하기 때문에 소수점 다음에 오는 숫자가 항상 2개가 될 수 있습니다. 인터넷 쇼핑몰을 구축 중이고 `$0.30`를 보여줘야 할 때 유용하죠. 문자형으로 바뀐 숫자를 다시 숫자형으로 강제 변환하려면 단항 덧셈 연산자를 사용하면 됩니다. @@ -254,7 +372,11 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` +<<<<<<< HEAD 이렇게 10의 거듭제곱을 곱하고 다시 동일한 숫자로 나누는 전략은 오류를 줄여주긴 하지만 완전히 없애지는 못합니다. +======= +So, the multiply/divide approach reduces the error, but doesn't remove it totally. +>>>>>>> upstream/master 구현을 하다 보면 무한 소수가 나오는 경우를 완전히 차단해야 하는 경우가 생기곤 합니다. 달러가 아닌 센트 단위로 물품 가격을 저장하는 쇼핑몰을 담당하고 있는데, 행사 때문에 가격을 30% 할인해야 하는 경우가 그렇죠. 무한소수를 방지하는 완벽한 방법은 사실 없습니다. 필요할 때마다 '꼬리'를 잘라 어림수를 만드는 방법뿐이죠. @@ -276,7 +398,11 @@ alert( 9999999999999999 ); // 10000000000000000이 출력됩니다. 자바스크립트에선 숫자의 부호가 단일 비트에 저장되는데 0을 포함한 모든 숫자에 부호를 설정할 수도, 설정하지 않을 수도 있기 때문입니다. +<<<<<<< HEAD 대부분의 연산은 `0`과 `-0`을 동일하게 취급하기 때문에 두 0의 차이는 두드러지지 않는 편입니다. +======= +In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same. +>>>>>>> upstream/master ``` ## isNaN과 isFinite @@ -296,7 +422,11 @@ alert( 9999999999999999 ); // 10000000000000000이 출력됩니다. alert( isNaN("str") ); // true ``` +<<<<<<< HEAD 그런데 굳이 이 함수가 필요할까요? "`=== NaN` 비교를 하면 되지 않을까?"라는 생각이 들 수 있습니다. 안타깝게도 대답은 '필요하다'입니다. `NaN`은 `NaN` 자기 자신을 포함하여 그 어떤 값과도 같지 않다는 점에서 독특합니다. +======= + But do we need this function? Can't we just use the comparison `=== NaN`? Unfortunately not. The value `NaN` is unique in that it does not equal anything, including itself: +>>>>>>> upstream/master ```js run alert( NaN === NaN ); // false @@ -320,6 +450,7 @@ let num = +prompt("숫자를 입력하세요.", ''); alert( isFinite(num) ); ``` +<<<<<<< HEAD 빈 문자열이나 공백만 있는 문자열은 `isFinite`를 포함한 모든 숫자 관련 내장 함수에서 `0`으로 취급된다는 점에 유의하시기 바랍니다. ```smart header="`Object.is`와 비교하기" @@ -328,10 +459,52 @@ alert( isFinite(num) ); 1. `NaN`을 대상으로 비교할 때: `Object.is(NaN, NaN) === true`임. 2. `0`과 `-0`이 다르게 취급되어야 할 때: `Object.is(0, -0) === false`임. 숫자를 나타내는 비트가 모두 0이더라도 부호를 나타내는 비트는 다르므로 `0`과 `-0`은 사실 다른 값이긴 합니다. +======= +Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. + +````smart header="`Number.isNaN` and `Number.isFinite`" +[Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) and [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) methods are the more "strict" versions of `isNaN` and `isFinite` functions. They do not autoconvert their argument into a number, but check if it belongs to the `number` type instead. + +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. + + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Note the difference: + alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type + alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion + ``` + +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case, it returns `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false + + // Note the difference: + alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type + alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123 + ``` + +In a way, `Number.isNaN` and `Number.isFinite` are simpler and more straightforward than `isNaN` and `isFinite` functions. In practice though, `isNaN` and `isFinite` are mostly used, as they're shorter to write. +```` + +```smart header="Comparison with `Object.is`" +There is a special built-in method `Object.is` that compares values like `===`, but is more reliable for two edge cases: + +1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. +>>>>>>> upstream/master 이 두 에지 케이스를 제외하곤, `Object.is(a, b)`와 `a === b`의 결과는 같습니다. +<<<<<<< HEAD 이런 식의 비교는 자바스크립트 명세서에서 종종 찾아볼 수 있습니다. 내부 알고리즘에서 두 값을 비교해야 하는데, 비교 결과가 정확해야 하는 경우 `Object.is`를 사용하죠. `Object.is`에서 사용되는 비교방식은 명세서에서 [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)라고 불립니다. +======= +We mention `Object.is` here, because it's often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +>>>>>>> upstream/master ``` @@ -345,7 +518,11 @@ alert( +"100px" ); // NaN 엄격한 규칙이 적용되지 않는 유일한 예외는 문자열의 처음 또는 끝에 공백이 있어서 공백을 무시할 때입니다. +<<<<<<< HEAD 그런데 실무에선 CSS 등에서 `'100px'`, `'12pt'`와 같이 숫자와 단위를 함께 쓰는 경우가 흔합니다. 대다수 국가에서 `'19€'`처럼 금액 뒤에 통화 기호를 붙여 표시하기도 하죠. 숫자만 추출하는 방법이 필요해 보이네요. +======= +But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. +>>>>>>> upstream/master 내장 함수 `parseInt`와 `parseFloat`는 이런 경우를 위해 만들어졌습니다. @@ -383,7 +560,11 @@ alert( parseInt('2n9c', 36) ); // 123456 몇 가지 예시를 살펴봅시다. `Math.random()` +<<<<<<< HEAD : 0과 1 사이의 난수를 반환합니다(1은 제외). +======= +: Returns a random number from 0 to 1 (not including 1). +>>>>>>> upstream/master ```js run alert( Math.random() ); // 0.1234567894322 @@ -391,8 +572,13 @@ alert( parseInt('2n9c', 36) ); // 123456 alert( Math.random() ); // ... (무작위 수) ``` +<<<<<<< HEAD `Math.max(a, b, c...)` / `Math.min(a, b, c...)` : 인수 중 최대/최솟값을 반환합니다. +======= +`Math.max(a, b, c...)` and `Math.min(a, b, c...)` +: Returns the greatest and smallest from the arbitrary number of arguments. +>>>>>>> upstream/master ```js run alert( Math.max(3, 5, -10, 0, 1) ); // 5 @@ -400,13 +586,21 @@ alert( parseInt('2n9c', 36) ); // 123456 ``` `Math.pow(n, power)` +<<<<<<< HEAD : `n`을 power번 거듭제곱한 값을 반환합니다. +======= +: Returns `n` raised to the given power. +>>>>>>> upstream/master ```js run alert( Math.pow(2, 10) ); // 2의 10제곱 = 1024 ``` +<<<<<<< HEAD 이 외에도 삼각법을 포함한 다양한 함수와 상수가 `Math`에 있습니다. 자세한 내용은 [MDN 문서](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math)에서 읽어보시기 바랍니다. +======= +There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math). +>>>>>>> upstream/master ## 요약 @@ -421,7 +615,18 @@ alert( parseInt('2n9c', 36) ); // 123456 - `parseInt(str, base)`를 사용하면 `str`을 `base`진수로 바꿔줍니다(단, `2 ≤ base ≤ 36`). - `num.toString(base)`는 숫자를 `base`진수로 바꾸고, 이를 문자열 형태로 반환합니다. +<<<<<<< HEAD `12pt`나 `100px`과 같은 값을 숫자로 변환하는 것도 가능합니다. +======= +For regular number tests: + +- `isNaN(value)` converts its argument to a number and then tests it for being `NaN` +- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN` +- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity` + +For converting values like `12pt` and `100px` to a number: +>>>>>>> upstream/master - `parseInt/parseFloat`를 사용하면 문자열에서 숫자만 읽고, 읽은 숫자를 에러가 발생하기 전에 반환해주는 '약한' 형 변환을 사용할 수 있습니다. @@ -432,4 +637,8 @@ alert( parseInt('2n9c', 36) ); // 123456 이 외에도 다양한 수학 함수가 있습니다. +<<<<<<< HEAD - 수학 연산이 필요할 때 [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) 객체를 찾아보세요. 작은 객체이지만 기본적인 연산은 대부분 다룰 수 있습니다. +======= +- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs. +>>>>>>> upstream/master diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md index 8a965450cc..2a478f69dc 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/solution.md +++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md @@ -8,12 +8,16 @@ let newStr = str[0].toUpperCase() + str.slice(1); 그런데 이렇게 코드를 작성하면 `str`이 비어있는 문자열인 경우 `str[0]`이 `undefined`가 되는 문제가 발생합니다. `undefined`는 `toUpperCase()`메서드를 지원하지 않으므로 에러가 발생하죠. +<<<<<<< HEAD 두 가지 방법을 사용해 이런 예외사항을 처리 할 수 있습니다. 1. `str.charAt(0)`은 `str`이 비어있는 문자열이더라도 항상 문자열을 반환하므로, 이 메서드를 사용합니다. 2. 빈 문자열인지를 확인하는 코드를 작성합니다. 두 번째 방법을 사용하여 작성한 답안은 아래와 같습니다. +======= +The easiest way out is to add a test for an empty string, like this: +>>>>>>> upstream/master ```js run demo function ucFirst(str) { @@ -24,4 +28,3 @@ function ucFirst(str) { alert( ucFirst("john") ); // John ``` - diff --git a/1-js/05-data-types/03-string/3-truncate/solution.md b/1-js/05-data-types/03-string/3-truncate/solution.md index c8b36554b5..48364a0a07 100644 --- a/1-js/05-data-types/03-string/3-truncate/solution.md +++ b/1-js/05-data-types/03-string/3-truncate/solution.md @@ -1,6 +1,10 @@ 새로 만든 문자열의 길이는 `maxlength`가 되어야 하므로, 생략 부호 `"…"`가 차지할 길이를 생각하여 함수를 만들어야 합니다. +<<<<<<< HEAD 생략 부호는 유니코드에 등록된 독립된 글자임에 유의하여 답안을 작성해야 합니다. 점 세 개가 아님에 유의하시기 바랍니다. +======= +Note that there is actually a single Unicode character for an ellipsis. That's not three dots. +>>>>>>> upstream/master ```js run demo function truncate(str, maxlength) { diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md index f91459e7a6..ce1cbe6593 100644 --- a/1-js/05-data-types/03-string/3-truncate/task.md +++ b/1-js/05-data-types/03-string/3-truncate/task.md @@ -11,7 +11,7 @@ importance: 5 예시: ```js -truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" +truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…" -truncate("Hi everyone!", 20) = "Hi everyone!" +truncate("Hi everyone!", 20) == "Hi everyone!" ``` diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 1c1b3d662f..d7e2ea32f5 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -48,9 +48,15 @@ let guestList = "손님: // Error: Invalid or unexpected token * John"; ``` +<<<<<<< HEAD 작은따옴표나 큰따옴표로 문자열을 표현하는 방식은 자바스크립트가 만들어졌을 때부터 있었습니다. 이때는 문자열을 여러 줄에 걸쳐 작성할 생각조차 못 했던 시기였죠. 백틱은 그 이후에 등장한 문법이기 때문에 따옴표보다 다양한 기능을 제공합니다. 백틱은 '템플릿 함수(template function)'에서도 사용됩니다. func`string` 같이 첫 번째 백틱 바로 앞에 함수 이름(`func`)을 써주면, 이 함수는 백틱 안의 문자열 조각이나 표현식 평가 결과를 인수로 받아 자동으로 호출됩니다. 이런 기능을 '태그드 템플릿(tagged template)'이라 부르는데, 태그드 템플릿을 사용하면 사용자 지정 템플릿에 맞는 문자열을 쉽게 만들 수 있습니다. 태그드 템플릿과 템플릿 함수에 대한 자세한 내용은 MDN [문서](mdn:/JavaScript/Reference/Template_literals#Tagged_templates)에서 확인해보세요. 참고로 이 기능은 자주 사용되진 않습니다. +======= +Single and double quotes come from ancient times of language creation, when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile. + +Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. This feature is called "tagged templates", it's rarely seen, but you can read about it in the MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). +>>>>>>> upstream/master ## 특수 기호 @@ -59,10 +65,17 @@ let guestList = "손님: // Error: Invalid or unexpected token ```js run let guestList = "손님:\n * John\n * Pete\n * Mary"; +<<<<<<< HEAD alert(guestList); // 손님 리스트를 여러 줄에 걸쳐 작성함 ``` 따옴표를 이용해 만든 여러 줄 문자열과 백틱을 이용해 만든 여러 줄 문자열은 표현 방식만 다를 뿐 차이가 없습니다. +======= +alert(guestList); // a multiline list of guests, same as above +``` + +As a simpler example, these two lines are equal, just written differently: +>>>>>>> upstream/master ```js run let str1 = "Hello\nWorld"; // '줄 바꿈 기호'를 사용해 두 줄짜리 문자열을 만듦 @@ -74,12 +87,17 @@ World`; alert(str1 == str2); // true ``` +<<<<<<< HEAD 자바스크립트엔 줄 바꿈 문자를 비롯한 다양한 '특수' 문자들이 있습니다. 특수 문자 목록: +======= +There are other, less common special characters: +>>>>>>> upstream/master | 특수 문자 | 설명 | |-----------|-------------| +<<<<<<< HEAD |`\n`|줄 바꿈| |`\r`|캐리지 리턴(carriage return). Windows에선 캐리지 리턴과 줄 바꿈 특수 문자를 조합(`\r\n`)해 줄을 바꿉니다. 캐리지 리턴을 단독으론 사용하는 경우는 없습니다. | |`\'`, `\"`|따옴표| @@ -101,6 +119,24 @@ alert( "\u{1F60D}" ); // 😍, 웃는 얼굴 기호(긴 유니코드) 모든 특수 문자는 '이스케이프 문자(escape character)'라고도 불리는 역슬래시 (backslash character) `\`로 시작합니다. 역슬래시는 문자열 내에 따옴표를 넣을 때도 사용할 수 있습니다. +======= +|`\n`|New line| +|`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. | +|`\'`, `\"`, \\`|Quotes| +|`\\`|Backslash| +|`\t`|Tab| +|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- mentioned for completeness, coming from old times, not used nowadays (you can forget them right now). | + +As you can see, all special characters start with a backslash character `\`. It is also called an "escape character". + +Because it's so special, if we need to show an actual backslash `\` within the string, we need to double it: + +```js run +alert( `The backslash: \\` ); // The backslash: \ +``` + +So-called "escaped" quotes `\'`, `\"`, \\` are used to insert a quote into the same-quoted string. +>>>>>>> upstream/master 예시: @@ -110,12 +146,17 @@ alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! 위 예시에서 살펴본 바와 같이 문자열 내의 따옴표엔 `\`를 꼭 붙여줘야 합니다. 이렇게 하지 않으면 자바스크립트는 해당 따옴표가 문자열을 닫는 용도로 사용된 것이라 해석하기 때문입니다. +<<<<<<< HEAD 이스케이프 문자는 문자열을 감쌀 때 사용한 따옴표와 동일한 따옴표에만 붙여주면 됩니다. 문자열 내에서 좀 더 우아하게 따옴표를 사용하려면 아래와 같이 따옴표 대신 백틱으로 문자열을 감싸주면 됩니다. +======= +Of course, only the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead: +>>>>>>> upstream/master ```js run -alert( `I'm the Walrus!` ); // I'm the Walrus! +alert( "I'm the Walrus!" ); // I'm the Walrus! ``` +<<<<<<< HEAD 역슬래시 `\`는 문자열을 정확하게 읽기 위한 용도로 만들어졌으므로 `\`는 제 역할이 끝나면 사라집니다. 메모리에 저장되는 문자열엔 `\`가 없습니다. 앞선 예시들을 실행했을 때 뜨는 `alert` 창을 통해 이를 확인할 수 있습니다. 그렇다면 문자열 안에 역슬래시 `\`를 보여줘야 하는 경우엔 어떻게 해야 할까요? @@ -125,6 +166,9 @@ alert( `I'm the Walrus!` ); // I'm the Walrus! ```js run alert( `역슬래시: \\` ); // 역슬래시: \ ``` +======= +Besides these special characters, there's also a special notation for Unicode codes `\u…`, it's rarely used and is covered in the optional chapter about [Unicode](info:unicode). +>>>>>>> upstream/master ## 문자열의 길이 @@ -139,33 +183,55 @@ alert( `My\n`.length ); // 3 ```warn header="`length`는 프로퍼티입니다." 자바스크립트 이외의 언어를 사용했던 개발자들은 `str.length`가 아닌 `str.length()`로 문자열의 길이를 알아내려고 하는 경우가 있습니다. 하지만 원하는 대로 동작하지 않습니다. +<<<<<<< HEAD `length`는 함수가 아니고, 숫자가 저장되는 프로퍼티라는 점에 주의하시기 바랍니다. 뒤에 괄호를 붙일 필요가 없습니다. +======= +Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. Not `.length()`, but `.length`. +>>>>>>> upstream/master ``` ## 특정 글자에 접근하기 +<<<<<<< HEAD 문자열 내 특정 위치인 `pos`에 있는 글자에 접근하려면 `[pos]`같이 대괄호를 이용하거나 [str.charAt(pos)](mdn:js/String/charAt)라는 메서드를 호출하면 됩니다. 위치는 0부터 시작합니다. +======= +To get a character at position `pos`, use square brackets `[pos]` or call the method [str.at(pos)](mdn:js/String/at). The first character starts from the zero position: +>>>>>>> upstream/master ```js run let str = `Hello`; // 첫 번째 글자 alert( str[0] ); // H -alert( str.charAt(0) ); // H +alert( str.at(0) ); // H // 마지막 글자 alert( str[str.length - 1] ); // o +alert( str.at(-1) ); ``` +<<<<<<< HEAD 근래에는 대괄호를 이용하는 방식을 사용합니다. `charAt`은 하위 호환성을 위해 남아있는 메서드라고 생각하시면 됩니다. 두 접근 방식의 차이는 반환할 글자가 없을 때 드러납니다. 접근하려는 위치에 글자가 없는 경우 `[]`는 `undefined`를, `charAt`은 빈 문자열을 반환합니다. +======= +As you can see, the `.at(pos)` method has a benefit of allowing negative position. If `pos` is negative, then it's counted from the end of the string. + +So `.at(-1)` means the last character, and `.at(-2)` is the one before it, etc. + +The square brackets always return `undefined` for negative indexes, for instance: +>>>>>>> upstream/master ```js run let str = `Hello`; +<<<<<<< HEAD alert( str[1000] ); // undefined alert( str.charAt(1000) ); // '' (빈 문자열) +======= +alert( str[-2] ); // undefined +alert( str.at(-2) ); // l +>>>>>>> upstream/master ``` `for..of`를 사용하면 문자열을 구성하는 글자를 대상으로 반복 작업을 할 수 있습니다. @@ -214,7 +280,7 @@ alert( 'Interface'.toLowerCase() ); // interface 글자 하나의 케이스만 변경하는 것도 가능합니다. -```js +```js run alert( 'Interface'[0].toLowerCase() ); // 'i' ``` @@ -239,7 +305,11 @@ alert( str.indexOf('widget') ); // -1, indexOf는 대·소문자를 따지므로 alert( str.indexOf("id") ); // 1, "id"는 첫 번째 위치에서 발견됨 (Widget에서 id) ``` +<<<<<<< HEAD `str.indexOf(substr, pos)`의 두 번째 매개변수 `pos`는 선택적으로 사용할 수 있는데, 이를 명시하면 검색이 해당 위치부터 시작됩니다. +======= +The optional second parameter allows us to start searching from a given position. +>>>>>>> upstream/master 부분 문자열 `"id"`는 위치 `1`에서 처음 등장하는데, 두 번째 인수에 `2`를 넘겨 `"id"`가 두 번째로 등장하는 위치가 어디인지 알아봅시다. @@ -310,6 +380,7 @@ if (str.indexOf("Widget") != -1) { } ``` +<<<<<<< HEAD #### 비트 NOT 연산자를 사용한 기법 오래전부터 전해 오는 [비트(bitwise) NOT 연산자](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~`를 사용한 기법 하나를 소개해드리겠습니다. 비트 NOT 연산자는 피연산자를 32비트 정수로 바꾼 후(소수부는 모두 버려짐) 모든 비트를 반전합니다. @@ -349,6 +420,8 @@ if (~str.indexOf("Widget")) { 모던 자바스크립트에선 `.includes` 메서드(아래에서 배움)를 사용해 부분 문자열 포함 여부를 검사합니다. 이런 기법은 오래된 자바스크립트에서만 볼 수 있습니다. +======= +>>>>>>> upstream/master ### includes, startsWith, endsWith 비교적 근래에 나온 메서드인 [str.includes(substr, pos)](mdn:js/String/includes)는 `str`에 부분 문자열 `substr`이 있는지에 따라 `true`나 `false`를 반환합니다. @@ -371,8 +444,13 @@ alert( "Widget".includes("id", 3) ); // false, 세 번째 위치 이후엔 "id" 메서드 [str.startsWith](mdn:js/String/startsWith)와 [str.endsWith](mdn:js/String/endsWith)는 메서드 이름 그대로 문자열 `str`이 특정 문자열로 시작하는지(start with) 여부와 특정 문자열로 끝나는지(end with) 여부를 확인할 때 사용할 수 있습니다. ```js run +<<<<<<< HEAD alert( "Widget".startsWith("Wid") ); // true, "Widget"은 "Wid"로 시작합니다. alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다. +======= +alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" starts with "Wid" +alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" ends with "get" +>>>>>>> upstream/master ``` ## 부분 문자열 추출하기 @@ -407,9 +485,15 @@ alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다 ``` `str.substring(start [, end])` +<<<<<<< HEAD : `start`와 `end` *사이*에 있는 문자열을 반환합니다. `substring`은 `slice`와 아주 유사하지만 `start`가 `end`보다 커도 괜찮다는 데 차이가 있습니다. +======= +: Returns the part of the string *between* `start` and `end` (not including `end`). + + This is almost the same as `slice`, but it allows `start` to be greater than `end` (in this case it simply swaps `start` and `end` values). +>>>>>>> upstream/master 예시: @@ -445,18 +529,36 @@ alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다 alert( str.substr(-4, 2) ); // gi, 끝에서 네 번째 위치부터 글자 두 개 ``` +<<<<<<< HEAD 부분 문자열 추출과 관련된 메서드를 요약해 봅시다. +======= + This method resides in the [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) of the language specification. It means that only browser-hosted Javascript engines should support it, and it's not recommended to use it. In practice, it's supported everywhere. + +Let's recap these methods to avoid any confusion: +>>>>>>> upstream/master | 메서드 | 추출할 부분 문자열 | 음수 허용 여부(인수)| |--------|-----------|-----------| +<<<<<<< HEAD | `slice(start, end)` | `start`부터 `end`까지(`end`는 미포함) | 음수 허용 | | `substring(start, end)` | `start`와 `end` 사이 | 음수는 `0`으로 취급함 | | `substr(start, length)` | `start`부터 `length`개의 글자 | 음수 허용 | +======= +| `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives | +| `substring(start, end)` | between `start` and `end` (not including `end`)| negative values mean `0` | +| `substr(start, length)` | from `start` get `length` characters | allows negative `start` | +>>>>>>> upstream/master ```smart header="어떤 메서드를 선택해야 하나요?" 모두 사용해도 괜찮습니다. 그런데 `substr`에는 단점이 하나 있습니다. `substr`는 코어 자바스크립트 명세서(ECMA-262 - 옮긴이)가 아닌, 구식 스크립트에 대응하기 위해 남겨 둔 브라우저 전용 기능들을 명시해 놓은 부록 B(Annex B)에 정의되어있습니다. 거의 모든 곳에서 이 메서드가 동작하긴 하지만 브라우저 이외의 호스트 환경에서는 제대로 동작하지 않을 수 있습니다. +<<<<<<< HEAD 남은 두 메서드 중 `slice`는 음수 인수를 허용한다는 측면에서 `substring`보다 좀 더 유연합니다. 메서드 이름도 더 짧죠. 따라서 세 메서드 중 `slice`만 외워놓고 사용해도 충분할 것 같습니다. +======= +Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. + +So, for practical use it's enough to remember only `slice`. +>>>>>>> upstream/master ``` ## 문자열 비교하기 @@ -479,6 +581,7 @@ alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다 이런 예외사항 때문에 이름순으로 국가를 나열할 때 예상치 못한 결과가 나올 수 있습니다. 사람들은 `Österreich`가 `Zealand`보다 앞서 나올 것이라 예상하는데 그렇지 않죠. +<<<<<<< HEAD 자바스크립트 내부에서 문자열이 어떻게 표시되는지 상기하며 원인을 알아봅시다. 모든 문자열은 [UTF-16](https://en.wikipedia.org/wiki/UTF-16)을 사용해 인코딩되는데, UTF-16에선 모든 글자가 숫자 형식의 코드와 매칭됩니다. 코드로 글자를 얻거나 글자에서 연관 코드를 알아낼 수 있는 메서드는 다음과 같습니다. @@ -489,7 +592,20 @@ alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다 ```js run // 글자는 같지만 케이스는 다르므로 반환되는 코드가 다릅니다. alert( "z".codePointAt(0) ); // 122 +======= +To understand what happens, we should be aware that strings in Javascript are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. + +There are special methods that allow to get the character for the code and back: + +`str.codePointAt(pos)` +: Returns a decimal number representing the code for the character at position `pos`: + + ```js run + // different case letters have different codes +>>>>>>> upstream/master alert( "Z".codePointAt(0) ); // 90 + alert( "z".codePointAt(0) ); // 122 + alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value) ``` `String.fromCodePoint(code)` @@ -497,6 +613,7 @@ alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다 ```js run alert( String.fromCodePoint(90) ); // Z +<<<<<<< HEAD ``` `\u` 뒤에 특정 글자에 대응하는 16진수 코드를 붙이는 방식으로도 원하는 글자를 만들 수 있습니다. @@ -504,6 +621,9 @@ alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다 ```js run // 90을 16진수로 변환하면 5a입니다. alert( '\u005a' ); // Z +======= + alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument) +>>>>>>> upstream/master ``` 이제 이 배경지식을 가지고 코드 `65`와 `220` 사이(라틴계열 알파벳과 기타 글자들이 여기에 포함됨)에 대응하는 글자들을 출력해봅시다. @@ -515,6 +635,7 @@ for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); +// Output: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` @@ -525,16 +646,27 @@ alert( str ); 글자는 글자에 대응하는 숫자 형식의 코드를 기준으로 비교됩니다. 코드가 크면 대응하는 글자 역시 크다고 취급되죠. 따라서 `a`(코드:97)는 `Z`(코드:90) 보다 크다는 결론이 도출됩니다. +<<<<<<< HEAD - 알파벳 소문자의 코드는 대문자의 코드보다 크므로 소문자는 대문자 뒤에 옵니다. - `Ö` 같은 글자는 일반 알파벳과 멀리 떨어져 있습니다. `Ö`의 코드는 알파벳 소문자의 코드보다 훨씬 큽니다. ### 문자열 제대로 비교하기 +======= +- All lowercase letters go after uppercase letters because their codes are greater. +- Some letters like `Ö` stand apart from the main alphabet. Here, its code is greater than anything from `a` to `z`. + +### Correct comparisons [#correct-comparisons] +>>>>>>> upstream/master 언어마다 문자 체계가 다르기 때문에 문자열을 '제대로' 비교하는 알고리즘을 만드는 건 생각보다 간단하지 않습니다. 문자열을 비교하려면 일단 페이지에서 어떤 언어를 사용하고 있는지 브라우저가 알아야 합니다. +<<<<<<< HEAD 다행히도 모던 브라우저 대부분이 국제화 관련 표준인 [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf)를 지원합니다(IE10은 아쉽게도 [Intl.js](https://github.com/andyearnshaw/Intl.js/) 라이브러리를 사용해야 합니다). +======= +Luckily, modern browsers support the internationalization standard [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). +>>>>>>> upstream/master ECMA-402엔 언어가 다를 때 적용할 수 있는 문자열 비교 규칙과 이를 준수하는 메서드가 정의되어있습니다. @@ -552,6 +684,7 @@ alert( 'Österreich'.localeCompare('Zealand') ); // -1 `localeCompare`엔 선택 인수 두 개를 더 전달할 수 있습니다. 기준이 되는 언어를 지정(아무것도 지정하지 않았으면 호스트 환경의 언어가 기준 언어가 됨)해주는 인수와 대·소문자를 구분할지나 `"a"`와 `"á"`를 다르게 취급할지에 대한 것을 설정해주는 인수가 더 있죠. 자세한 사항은 관련 [페이지](mdn:js/String/localeCompare)에서 확인해 보시기 바랍니다. +<<<<<<< HEAD ## 문자열 심화 ```warn header="심화 학습" @@ -669,6 +802,17 @@ alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true - 소문자로 바꾸려면 `toLowerCase`, 대문자로 바꾸려면 `toUpperCase`를 사용하세요. - `indexOf`를 사용하면 부분 문자열의 위치를 얻을 수 있습니다. 부분 문자열 여부만 알고 싶다면 `includes/startsWith/endsWith`를 사용하면 됩니다. - 특정 언어에 적합한 비교 기준 사용해 문자열을 비교하려면 `localeCompare`를 사용하세요. 이 메서드를 사용하지 않으면 글자 코드를 기준으로 문자열이 비교됩니다. +======= +## Summary + +- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions `${…}`. +- We can use special characters, such as a line break `\n`. +- To get a character, use: `[]` or `at` method. +- To get a substring, use: `slice` or `substring`. +- To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`. +- To look for a substring, use: `indexOf`, or `includes/startsWith/endsWith` for simple checks. +- To compare strings according to the language, use: `localeCompare`, otherwise they are compared by character codes. +>>>>>>> upstream/master 이외에도 문자열에 쓸 수 있는 유용한 메서드 몇 가지가 있습니다. @@ -676,4 +820,10 @@ alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true - `str.repeat(n)` -- 문자열을 `n`번 반복합니다. - 이 외의 메서드는 [MDN 문서](mdn:js/String)에서 확인해보시기 바랍니다. +<<<<<<< HEAD 정규 표현식을 사용해 문자열을 찾거나 교체해주는 메서드도 여러 개 있는데 이는 아주 큰 주제이기 때문에 별도의 섹션 에서 다루겠습니다. +======= +Strings also have methods for doing search/replace with regular expressions. But that's big topic, so it's explained in a separate tutorial section . + +Also, as of now it's important to know that strings are based on Unicode encoding, and hence there're issues with comparisons. There's more about Unicode in the chapter . +>>>>>>> upstream/master diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md index 046b9f1deb..227515185f 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md @@ -57,9 +57,15 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 ``` +<<<<<<< HEAD 이렇게 구현하면 시간 복잡도가 [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation)이 됩니다. 이는 배열의 크기를 2배 늘리면 알고리즘은 4배나 더 오래 걸린다는 의미입니다. 크기가 큰 배열(1000, 10000 또는 그 이상의 요소를 가진 배열)에 위와 같은 알고리즘을 적용하면 매우 느릴 수 있습니다. +======= +The solution has a time complexity of [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer. + +For big arrays (1000, 10000 or more items) such algorithms can lead to serious sluggishness. +>>>>>>> upstream/master # 빠른 해답 @@ -91,4 +97,8 @@ alert( getMaxSubSum([-1, -2, -3]) ); // 0 이 알고리즘은 정확히 한번 배열을 순회하므로 시간 복잡도는 O(n)입니다. +<<<<<<< HEAD 알고리즘에 대한 상세한 정보는 [최대합 부분 배열 문제](http://en.wikipedia.org/wiki/Maximum_subarray_problem)에서 찾을 수 있습니다. 동작원리에 대해 확실히 이해가 되지 않았다면 위 예제의 알고리즘이 어떻게 동작하는지 찬찬히 살펴보세요. 글을 읽는 것보다 코드를 살펴보는게 훨씬 도움이 될 겁니다. +======= +You can find more detailed information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. +>>>>>>> upstream/master diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 5a0985fce9..589e0fdae9 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -6,11 +6,19 @@ importance: 5 배열과 관련된 다섯 가지 연산을 해봅시다. +<<<<<<< HEAD 1. 요소 "Jazz", "Blues"가 있는 `styles` 배열을 생성합니다. 2. "Rock-n-Roll"을 배열 끝에 추가합니다. 3. 배열 정 중앙에 있는 요소를 "Classics"로 바꿉니다. 가운데 요소를 찾는 코드는 요소가 홀수 개인 배열에서도 잘 작동해야 합니다. 4. 배열의 첫 번째 요소를 꺼내서 출력합니다. 5. "Rap"과 "Reggae"를 배열의 앞에 추가합니다. +======= +1. Create an array `styles` with items "Jazz" and "Blues". +2. Append "Rock-n-Roll" to the end. +3. Replace the value in the middle with "Classics". Your code for finding the middle value should work for any arrays with odd length. +4. Strip off the first value of the array and show it. +5. Prepend `Rap` and `Reggae` to the array. +>>>>>>> upstream/master 단계를 하나씩 거칠 때마다 배열 모습은 아래와 같이 변해야 합니다. diff --git a/1-js/05-data-types/04-array/3-call-array-this/task.md b/1-js/05-data-types/04-array/3-call-array-this/task.md index 8522685957..d0035a0a3a 100644 --- a/1-js/05-data-types/04-array/3-call-array-this/task.md +++ b/1-js/05-data-types/04-array/3-call-array-this/task.md @@ -11,7 +11,7 @@ let arr = ["a", "b"]; arr.push(function() { alert( this ); -}) +}); arr[2](); // ? ``` diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 1ea4c587d3..07e3dae52b 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -92,6 +92,38 @@ let fruits = [ trailing(길게 늘어지는) 쉼표를 사용하면 모든 줄의 생김새가 유사해지기 때문에 요소를 넣거나 빼기가 쉬워집니다. ```` +## Get last elements with "at" + +[recent browser="new"] + +Let's say we want the last element of the array. + +Some programming languages allow the use of negative indexes for the same purpose, like `fruits[-1]`. + +However, in JavaScript it won't work. The result will be `undefined`, because the index in square brackets is treated literally. + +We can explicitly calculate the last element index and then access it: `fruits[fruits.length - 1]`. + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +alert( fruits[fruits.length-1] ); // Plum +``` + +A bit cumbersome, isn't it? We need to write the variable name twice. + +Luckily, there's a shorter syntax: `fruits.at(-1)`: + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +// same as fruits[fruits.length-1] +alert( fruits.at(-1) ); // Plum +``` + +In other words, `arr.at(i)`: +- is exactly the same as `arr[i]`, if `i >= 0`. +- for negative values of `i`, it steps back from the end of the array. ## pop·push와 shift·unshift @@ -121,9 +153,15 @@ trailing(길게 늘어지는) 쉼표를 사용하면 모든 줄의 생김새가 스택을 사용하면 가장 나중에 집어넣은 요소가 먼저 나옵니다. 이런 특징을 줄여서 후입선출(Last-In-First-Out, LIFO)이라고 부릅니다. 반면 큐를 사용하면 먼저 집어넣은 요소가 먼저 나오기 때문에 큐는 선입선출(First-In-First-Out, FIFO) 자료구조라고 부릅니다. +<<<<<<< HEAD 자바스크립트 배열을 사용하면 큐와 스택 둘 다를 만들 수 있습니다. 이 자료구조들은 배열의 처음이나 끝에 요소를 더하거나 빼는 데 사용되죠. 이렇게 처음이나 끝에 요소를 더하거나 빼주는 연산을 제공하는 자료구조를 컴퓨터 과학 분야에선 [데큐(deque, Double Ended Queue)](https://en.wikipedia.org/wiki/Double-ended_queue)라고 부릅니다. +======= +Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements, both to/from the beginning or the end. + +In computer science, the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). +>>>>>>> upstream/master **아래는 배열 끝에 무언가를 해주는 메서드입니다.** @@ -138,6 +176,8 @@ trailing(길게 늘어지는) 쉼표를 사용하면 모든 줄의 생김새가 alert( fruits ); // 사과,오렌지 ``` + Both `fruits.pop()` and `fruits.at(-1)` return the last element of the array, but `fruits.pop()` also modifies the array by removing it. + `push` : 배열 끝에 요소를 추가합니다. @@ -193,7 +233,11 @@ alert( fruits ); 숫자형 키를 사용함으로써 배열은 객체 기본 기능 이외에도 순서가 있는 컬렉션을 제어하게 해주는 특별한 메서드를 제공합니다. `length`라는 프로퍼티도 제공하죠. 그렇지만 어쨌든 배열의 본질은 객체입니다. +<<<<<<< HEAD 이렇게 배열은 자바스크립트의 일곱 가지 원시 자료형에 해당하지 않고, 원시 자료형이 아닌 객체형에 속하기 때문에 객체처럼 동작합니다. +======= +Remember, there are only eight basic data types in JavaScript (see the [Data types](info:types) chapter for more info). Array is an object and thus behaves like an object. +>>>>>>> upstream/master 예시를 하나 살펴봅시다. 배열은 객체와 마찬가지로 참조를 통해 복사됩니다. @@ -209,7 +253,11 @@ arr.push("배"); // 참조를 이용해 배열을 수정합니다. alert( fruits ); // 바나나,배 - 요소가 두 개가 되었습니다. ``` +<<<<<<< HEAD 배열을 배열답게 만들어주는 것은 특수 내부 표현방식입니다. 자바스크립트 엔진은 아래쪽 그림에서처럼 배열의 요소를 인접한 메모리 공간에 차례로 저장해 연산 속도를 높입니다. 이 방법 이외에도 배열 관련 연산을 더 빠르게 해주는 최적화 기법은 다양합니다. +======= +...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast. +>>>>>>> upstream/master 그런데 개발자가 배열을 '순서가 있는 자료의 컬렉션'처럼 다루지 않고 일반 객체처럼 다루면 이런 기법들이 제대로 동작하지 않습니다. @@ -247,7 +295,11 @@ fruits.age = 25; // 임의의 이름을 사용해 프로퍼티를 만듭니다. fruits.shift(); // 배열 맨 앞의 요소를 빼줍니다. ``` +<<<<<<< HEAD `shift` 메서드를 호출한 것과 동일한 효과를 보려면 인덱스가 `0`인 요소를 제거하는 것만으론 충분하지 않습니다. 제거 대상이 아닌 나머지 요소들의 인덱스를 수정해 줘야 하죠. +======= +It's not enough to take and remove the element with the index `0`. Other elements need to be renumbered as well. +>>>>>>> upstream/master `shift` 연산은 아래 3가지 동작을 모두 수행해야 이뤄집니다. @@ -365,11 +417,19 @@ alert( arr[3] ); // undefined: 삭제된 기존 요소들이 복구되지 않습 let arr = *!*new Array*/!*("사과", "배", "기타"); ``` +<<<<<<< HEAD 대괄호 `[]`를 사용하면 더 짧은 문법으로 배열을 만들 수 있기 때문에 `new Array()`는 잘 사용되지 않는 편입니다. `new Array()`엔 다루기 까다로운 기능도 있어서 더욱더 그렇습니다. +======= +It's rarely used, because square brackets `[]` are shorter. Also, there's a tricky feature with it. +>>>>>>> upstream/master 숫자형 인수 하나를 넣어서 `new Array`를 호출하면 배열이 만들어지는데, 이 배열엔 *요소가 없는 반면 길이는 인수와 같아*집니다. +<<<<<<< HEAD 예시를 통해 `new Array()`의 이런 특징이 어떻게 실수를 유발할 수 있는지 알아봅시다. +======= +Let's see how one can shoot themselves in the foot: +>>>>>>> upstream/master ```js run let arr = new Array(2); // 이렇게 하면 배열 [2]가 만들어질까요? @@ -379,9 +439,13 @@ alert( arr[0] ); // undefined가 출력됩니다. 요소가 하나도 없는 배 alert( arr.length ); // 길이는 2입니다. ``` +<<<<<<< HEAD 위 예시에서 확인해 본 것처럼 `new Array(number)`를 이용해 만든 배열의 요소는 모두 `undefined` 입니다. 이런 뜻밖의 상황을 마주치지 않기 위해 `new Array`의 기능을 잘 알지 않는 한 대부분의 개발자가 대괄호를 써서 배열을 만듭니다. +======= +To avoid such surprises, we usually use square brackets, unless we really know what we're doing. +>>>>>>> upstream/master ## 다차원 배열 @@ -394,7 +458,11 @@ let matrix = [ [7, 8, 9] ]; +<<<<<<< HEAD alert( matrix[1][1] ); // 5, 중심에 있는 요소 +======= +alert( matrix[0][1] ); // 2, the second value of the first inner array +>>>>>>> upstream/master ``` ## toString @@ -429,10 +497,62 @@ alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` +<<<<<<< HEAD ## 요약 +======= +## Don't compare arrays with == + +Arrays in JavaScript, unlike some other programming languages, shouldn't be compared with operator `==`. + +This operator has no special treatment for arrays, it works with them as with any objects. + +Let's recall the rules: + +- Two objects are equal `==` only if they're references to the same object. +- If one of the arguments of `==` is an object, and the other one is a primitive, then the object gets converted to primitive, as explained in the chapter . +- ...With an exception of `null` and `undefined` that equal `==` each other and nothing else. + +The strict comparison `===` is even simpler, as it doesn't convert types. + +So, if we compare arrays with `==`, they are never the same, unless we compare two variables that reference exactly the same array. + +For example: +```js run +alert( [] == [] ); // false +alert( [0] == [0] ); // false +``` + +These arrays are technically different objects. So they aren't equal. The `==` operator doesn't do item-by-item comparison. + +Comparison with primitives may give seemingly strange results as well: + +```js run +alert( 0 == [] ); // true + +alert('0' == [] ); // false +``` + +Here, in both cases, we compare a primitive with an array object. So the array `[]` gets converted to primitive for the purpose of comparison and becomes an empty string `''`. + +Then the comparison process goes on with the primitives, as described in the chapter : + +```js run +// after [] was converted to '' +alert( 0 == '' ); // true, as '' becomes converted to number 0 + +alert('0' == '' ); // false, no type conversion, different strings +``` + +So, how to compare arrays? + +That's simple: don't use the `==` operator. Instead, compare them item-by-item in a loop or using iteration methods explained in the next chapter. + +## Summary +>>>>>>> upstream/master 배열은 특수한 형태의 객체로, 순서가 있는 자료를 저장하고 관리하는 용도에 최적화된 자료구조입니다. +<<<<<<< HEAD - 선언 방법: ```js @@ -444,11 +564,33 @@ alert( "1,2" + 1 ); // "1,21" ``` `new Array(number)`을 호출하면 길이가 `number`인 배열이 만들어지는데, 이 때 요소는 비어있습니다. +======= +The declaration: + +```js +// square brackets (usual) +let arr = [item1, item2...]; + +// new Array (exceptionally rare) +let arr = new Array(item1, item2...); +``` + +The call to `new Array(number)` creates an array with the given length, but without elements. +>>>>>>> upstream/master - `length` 프로퍼티는 배열의 길이를 나타내줍니다. 정확히는 숫자형 인덱스 중 가장 큰 값에 1을 더한 값입니다. 배열 메서드는 `length` 프로퍼티를 자동으로 조정해줍니다. - `length` 값을 수동으로 줄이면 배열 끝이 잘립니다. +<<<<<<< HEAD 다음 연산을 사용하면 배열을 데큐처럼 사용할 수 있습니다. +======= +Getting the elements: + +- we can get element by its index, like `arr[0]` +- also we can use `at(i)` method that allows negative indexes. For negative values of `i`, it steps back from the end of the array. If `i >= 0`, it works same as `arr[i]`. + +We can use an array as a deque with the following operations: +>>>>>>> upstream/master - `push(...items)` -- `items`를 배열 끝에 더해줍니다. - `pop()` -- 배열 끝 요소를 제거하고, 제거한 요소를 반환합니다. @@ -460,4 +602,12 @@ alert( "1,2" + 1 ); // "1,21" - `for (let item of arr)` -- 배열 요소에만 사용되는 모던한 문법입니다. - `for (let i in arr)` -- 배열엔 절대 사용하지 마세요. +<<<<<<< HEAD 챕터에선 배열에 요소를 더하거나 빼기, 원하는 요소를 추출하기, 배열 정렬하기 등과 관련된 다양한 메서드를 학습할 예정입니다. +======= +To compare arrays, don't use the `==` operator (as well as `>`, `<` and others), as they have no special treatment for arrays. They handle them as any objects, and it's not what we usually want. + +Instead you can use `for..of` loop to compare arrays item-by-item. + +We will continue with arrays and study more methods to add, remove, extract elements and sort arrays in the next chapter . +>>>>>>> upstream/master diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md index d3c8f8eb1a..7f0082357a 100644 --- a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -4,7 +4,7 @@ importance: 4 # Create keyed object from array -Let's say we received an array of users in the form `{id:..., name:..., age... }`. +Let's say we received an array of users in the form `{id:..., name:..., age:... }`. Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values. diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/task.md b/1-js/05-data-types/05-array-methods/2-filter-range/task.md index a18ae5c209..3158dc1ca6 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/task.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/task.md @@ -4,7 +4,11 @@ # 특정 범위에 속하는 요소 찾기 +<<<<<<< HEAD 배열 `arr`의 요소 중 `a`이상 `b` 이하 범위에 속하는 요소만 골라 새로운 배열에 집어넣고, 해당 요소를 출력해주는 함수 `filterRange(arr, a, b)`를 작성해봅시다. +======= +Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements with values higher or equal to `a` and lower or equal to `b` and return a result as an array. +>>>>>>> upstream/master 새로 작성하는 함수는 기존 배열 `arr`을 변경하면 안 되고, 반환되는 함수는 새로운 배열이어야 합니다. diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js index db32d9a115..241b74c6ed 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js @@ -4,13 +4,13 @@ describe("filterRangeInPlace", function() { let arr = [5, 3, 8, 1]; - filterRangeInPlace(arr, 1, 4); + filterRangeInPlace(arr, 2, 5); - assert.deepEqual(arr, [3, 1]); + assert.deepEqual(arr, [5, 3]); }); it("doesn't return anything", function() { assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4)); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md index c6fd32ad8e..33fae83d9a 100644 --- a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md @@ -25,7 +25,11 @@ alert( usersMapped[0].id ); // 1 alert( usersMapped[0].fullName ); // John Smith ``` +<<<<<<< HEAD 화살표 함수 우측에 괄호를 썼다는 점에 주목해주시기 바랍니다. +======= +Please note that in the arrow functions we need to use additional brackets. +>>>>>>> upstream/master 아래와 같이 괄호 없이 코드를 작성할 수 없습니다. ```js diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index e6bcda5fae..3dfe2c3413 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -36,15 +36,15 @@ alert( arr.length ); // 3 이런 기대를 충족하려면 특별한 메서드를 사용해야 합니다. -[arr.splice(start)](mdn:js/Array/splice)는 만능 스위스 맥가이버 칼 같은 메서드입니다. 요소를 자유자재로 다룰 수 있게 해주죠. 이 메서드를 사용하면 요소 추가, 삭제, 교체가 모두 가능합니다. +[arr.splice](mdn:js/Array/splice)는 만능 스위스 맥가이버 칼 같은 메서드입니다. 요소를 자유자재로 다룰 수 있게 해주죠. 이 메서드를 사용하면 요소 추가, 삭제, 교체가 모두 가능합니다. 문법은 다음과 같습니다. ```js -arr.splice(index[, deleteCount, elem1, ..., elemN]) +arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` -첫 번째 매개변수는 조작을 가할 첫 번째 요소를 가리키는 `인덱스(index)`입니다. 두 번째 매개변수는 `deleteCount`로, 제거하고자 하는 요소의 개수를 나타냅니다. `elem1, ..., elemN`은 배열에 추가할 요소를 나타냅니다. +splice 메서드는 `start` 인덱스부터 `deleteCount` 개의 요소를 제거한 뒤, 그 자리에 `elem1, ..., elemN` 요소를 삽입하여 `arr` 배열을 수정합니다. 그리고 제거된 요소를 담은 배열을 반환합니다. splice 메서드를 사용해 작성된 예시 몇 가지를 보여드리겠습니다. @@ -234,12 +234,13 @@ arr.forEach(function(item, index, array) { ### indexOf, lastIndexOf와 includes -[arr.indexOf](mdn:js/Array/indexOf)와 [arr.lastIndexOf](mdn:js/Array/lastIndexOf), [arr.includes](mdn:js/Array/includes)는 같은 이름을 가진 문자열 메서드와 문법이 동일합니다. 물론 하는 일도 같습니다. 연산 대상이 문자열이 아닌 배열의 요소라는 점만 다릅니다. +[arr.indexOf](mdn:js/Array/indexOf)와 [arr.includes](mdn:js/Array/includes)는 같은 이름을 가진 문자열 메서드와 문법이 유사합니다. 물론 하는 일도 같습니다. 연산 대상이 문자열이 아닌 배열의 요소라는 점만 다릅니다. - `arr.indexOf(item, from)`는 인덱스 `from`부터 시작해 `item(요소)`을 찾습니다. 요소를 발견하면 해당 요소의 인덱스를 반환하고, 발견하지 못했으면 `-1`을 반환합니다. -- `arr.lastIndexOf(item, from)`는 위 메서드와 동일한 기능을 하는데, 검색을 끝에서부터 시작한다는 점만 다릅니다. - `arr.includes(item, from)`는 인덱스 `from`부터 시작해 `item`이 있는지를 검색하는데, 해당하는 요소를 발견하면 `true`를 반환합니다. +일반적으로 위 두 메서드는 검색할 `item` 하나만 인수로 전달해 호출하며, 기본적으로 검색은 배열의 0번 인덱스부터 시작합니다. + 예시: ```js run @@ -252,19 +253,31 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -위 메서드들은 요소를 찾을 때 완전 항등 연산자 `===` 을 사용한다는 점에 유의하시기 바랍니다. 보시는 바와 같이 `false`를 검색하면 정확히 `false`만을 검색하지, 0을 검색하진 않습니다. +`indexOf` 메서드는 요소를 찾을 때 완전 항등 연산자 `===` 을 사용한다는 점에 유의하시기 바랍니다. 보시는 바와 같이 `false`를 검색하면 정확히 `false`만을 검색하지, 0을 검색하진 않습니다. -요소의 위치를 정확히 알고 싶은게 아니고 요소가 배열 내 존재하는지 여부만 확인하고 싶다면 `arr.includes`를 사용하는 게 좋습니다. +요소의 위치를 정확히 알고 싶은게 아니고 `item`이 배열 내 존재하는지 여부만 확인하고 싶다면 `arr.includes`를 사용하는 게 좋습니다. -`includes`는 `NaN`도 제대로 처리한다는 점에서 `indexOf/lastIndexOf`와 약간의 차이가 있습니다. +[arr.lastIndexOf](mdn:js/Array/lastIndexOf)는 `indexOf`와 동일한 기능을 하는데, 검색을 끝에서부터 시작한다는 점만 다릅니다. + +```js run +let fruits = ['Apple', 'Orange', 'Apple'] + +alert( fruits.indexOf('Apple') ); // 0 (first Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) +``` + +````smart header="`includes` 메서드는 `NaN`을 올바르게 처리합니다" +사소하지만 주목할만한 `includes`의 특징은 `indexOf`와 다르게 `NaN`을 올바르게 처리한다는 점입니다. ```js run const arr = [NaN]; -alert( arr.indexOf(NaN) ); // -1 (완전 항등 비교 === 는 NaN엔 동작하지 않으므로 0이 출력되지 않습니다.) +alert( arr.indexOf(NaN) ); // -1 (0이 출력되지 않습니다.) alert( arr.includes(NaN) );// true (NaN의 여부를 확인하였습니다.) ``` +이는 `includes`가 자바스크립트에 훨씬 나중에 추가되었고 내부적으로 더 최신의 비교 알고리즘을 사용하기 때문입니다. +```` -### find와 findIndex +### find와 findIndex, findLastIndex 객체로 이루어진 배열이 있다고 가정해 봅시다. 특정 조건에 부합하는 객체를 배열 내에서 어떻게 찾을 수 있을까요? @@ -304,7 +317,26 @@ alert(user.name); // John 그런데 위 예시에서 `find` 안의 함수가 인자를 하나만 가지고 있다는 점에 주목해주시기 바랍니다(`item => item.id == 1`). 이런 패턴이 가장 많이 사용되는 편입니다. 다른 인자들(`index`, `array`)은 잘 사용되지 않습니다. -[arr.findIndex](mdn:js/Array/findIndex)는 `find`와 동일한 일을 하나, 조건에 맞는 요소를 반환하는 대신 해당 요소의 인덱스를 반환한다는 점이 다릅니다. 조건에 맞는 요소가 없으면 `-1`이 반환됩니다. +[arr.findIndex](mdn:js/Array/findIndex)는 `find`와 문법이 동일하지만, 조건에 맞는 요소를 반환하는 대신 해당 요소의 인덱스를 반환한다는 점이 다릅니다. 조건에 맞는 요소가 없으면 `-1`이 반환됩니다. + +[arr.findLastIndex](mdn:js/Array/findLastIndex)는 `findIndex`와 유사하지만 `lastIndexOf`처럼 검색을 끝에서부터 시작한다는 점이 다릅니다. + +예제: + +```js run +let users = [ + {id: 1, name: "John"}, + {id: 2, name: "Pete"}, + {id: 3, name: "Mary"}, + {id: 4, name: "John"} +]; + +// 처음 등장하는 John의 인덱스를 찾습니다. +alert(users.findIndex(user => user.name == 'John')); // 0 + +// 마지막으로 등장하는 John의 인덱스를 찾습니다. +alert(users.findLastIndex(user => user.name == 'John')); // 3 +``` ### filter @@ -314,7 +346,6 @@ alert(user.name); // John `filter`는 `find`와 문법이 유사하지만, 조건에 맞는 요소 전체를 담은 배열을 반환한다는 점에서 차이가 있습니다. - ```js let results = arr.filter(function(item, index, array) { // 조건을 충족하는 요소는 results에 순차적으로 더해집니다. @@ -390,6 +421,7 @@ alert( arr ); // *!*1, 15, 2*/!* 기본 정렬 기준 대신 새로운 정렬 기준을 만들려면 `arr.sort()`에 새로운 함수를 넘겨줘야 합니다. 인수로 넘겨주는 함수는 반드시 값 두 개를 비교해야 하고 반환 값도 있어야 합니다. + ```js function compare(a, b) { if (a > b) return 1; // 첫 번째 값이 두 번째 값보다 큰 경우 @@ -420,7 +452,7 @@ alert(arr); // *!*1, 2, 15*/!* 여기서 잠시 멈춰 위 예시에서 어떤 일이 일어났는지 생각해 봅시다. 사실 `arr`엔 숫자, 문자열, 객체 등이 들어갈 수 있습니다. 알 수 없는 *무언가*로 구성된 집합이 되는 거죠. 이제 이 비 동질적인 집합을 정렬해야 한다고 가정해봅시다. 무언가를 정렬하려면 기준이 필요하겠죠? 이때 *정렬 기준을 정의해주는 함수(ordering function, 정렬 함수)* 가 필요합니다. `sort`에 정렬 함수를 인수로 넘겨주지 않으면 이 메서드는 사전편집 순으로 요소를 정렬합니다. -`arr.sort(fn)`는 포괄적인 정렬 알고리즘을 이용해 구현되어있습니다. 대개 최적화된 [퀵 소트(quicksort)](https://en.wikipedia.org/wiki/Quicksort)를 사용하는데, `arr.sort(fn)`는 주어진 함수를 사용해 정렬 기준을 만들고 이 기준에 따라 요소들을 재배열하므로 개발자는 내부 정렬 동작 원리를 알 필요가 없습니다. 우리가 해야 할 일은 정렬 함수 `fn`을 만들고 이를 인수로 넘겨주는 것뿐입니다. +`arr.sort(fn)`는 포괄적인 정렬 알고리즘을 이용해 구현되어있습니다. 대개 최적화된 [퀵 소트(quicksort)](https://en.wikipedia.org/wiki/Quicksort) 혹은 [팀 소트(Timsort)](https://en.wikipedia.org/wiki/Timsort)를 사용하는데, `arr.sort(fn)`는 주어진 함수를 사용해 정렬 기준을 만들고 이 기준에 따라 요소들을 재배열하므로 개발자는 내부 정렬 동작 원리를 알 필요가 없습니다. 우리가 해야 할 일은 정렬 함수 `fn`을 만들고 이를 인수로 넘겨주는 것뿐입니다. 정렬 과정에서 어떤 요소끼리 비교가 일어났는지 확인하고 싶다면 아래 코드를 활용하시면 됩니다. @@ -632,7 +664,6 @@ arr.reduce((sum, current) => sum + current); [arr.reduceRight](mdn:js/Array/reduceRight)는 `reduce`와 동일한 기능을 하지만 배열의 오른쪽부터 연산을 수행한다는 점이 다른 메서드입니다. - ## Array.isArray로 배열 여부 알아내기 자바스크립트에서 배열은 독립된 자료형으로 취급되지 않고 객체형에 속합니다. @@ -641,7 +672,7 @@ arr.reduce((sum, current) => sum + current); ```js run alert(typeof {}); // object -alert(typeof []); // object +alert(typeof []); // object (동일) ``` 그런데 배열은 자주 사용되는 자료구조이기 때문에 배열인지 아닌지를 감별해내는 특별한 메서드가 있다면 아주 유용할 겁니다. [Array.isArray(value)](mdn:js/Array/isArray)는 이럴 때 사용할 수 있는 유용한 메서드입니다. `value`가 배열이라면 `true`를, 배열이 아니라면 `false`를 반환해주죠. @@ -700,7 +731,7 @@ alert(soldiers[1].age); // 23 `thisArgs`에 `army`를 지정하지 않고 단순히 `users.filter(army.canJoin)`를 사용했다면 `army.canJoin`은 단독 함수처럼 취급되고, 함수 본문 내 `this`는 `undefined`가 되어 에러가 발생했을 겁니다. -`users.filter(user => army.canJoin(user))`를 사용하면 `users.filter(army.canJoin, army)`를 대체할 수 있긴 한데 `thisArg`를 사용하는 방식이 좀 더 이해하기 쉬우므로 더 자주 사용됩니다. +`thisArgs`를 사용하는 방식인 `users.filter(army.canJoin, army)`는 `users.filter(user => army.canJoin(user))`로 대체할 수 있습니다. 두 방식은 모두 동일하게 동작하지만, 전자보다 후자가 좀 더 이해하기 쉬우므로 더 자주 사용됩니다. ## 요약 @@ -729,10 +760,10 @@ alert(soldiers[1].age); // 23 - `sort(func)` -- 배열을 정렬하고 정렬된 배열을 반환함 - `reverse()` -- 배열을 뒤집어 반환함 - `split/join` -- 문자열을 배열로, 배열을 문자열로 변환함 - - `reduce(func, initial)` -- 요소를 차례로 돌면서 `func`을 호출함. 반환값은 다음 함수 호출에 전달함. 최종적으로 하나의 값이 도출됨 + - `reduce/reduceRight(func, initial)` -- 요소를 차례로 돌면서 `func`을 호출함. 반환값은 다음 함수 호출에 전달함. 최종적으로 하나의 값이 도출됨 - 기타 - - `Array.isArray(arr)` -- `arr`이 배열인지 여부를 판단함 + - `Array.isArray(arr)` -- `arr`이 배열인지 여부를 판단하고 `true` 혹은 `false`를 반환함 `sort`, `reverse`, `splice`는 기존 배열을 변형시킨다는 점에 주의하시기 바랍니다. @@ -740,12 +771,26 @@ alert(soldiers[1].age); // 23 - [arr.some(fn)](mdn:js/Array/some)과 [arr.every(fn)](mdn:js/Array/every)는 배열을 확인합니다. - 두 메서드는 `map`과 유사하게 모든 요소를 대상으로 함수를 호출합니다. `some`은 함수의 반환 값을 `true`로 만드는 요소가 하나라도 있는지 여부를 확인하고 `every`는 모든 요소가 함수의 반환 값을 `true`로 만드는지 여부를 확인합니다. 두 메서드 모두 조건을 충족하면 `true`를, 그렇지 않으면 `false`를 반환합니다. + 두 메서드는 `map`과 유사하게 모든 요소를 대상으로 함수 `fn`을 호출합니다. 두 메서드 모두 조건을 충족하면 `true`를, 그렇지 않으면 `false`를 반환합니다. + + 두 메서드는 `||`와 `&&` 연산자와 비슷하게 동작합니다. 함수 `fn`이 truthy(참 같은 값)를 반환하면 `arr.some()`은 즉시 `true`를 반환하고 나머지 요소에 대한 순회를 중단합니다. 만약 함수 `fn`이 falsy(거짓 같은 값)를 반환하면 `arr.every()`는 즉시 `false`를 반환하고 나머지 요소에 대한 순회를 중단합니다. + + `every`를 사용하여 다음과 같이 배열을 비교할 수 있습니다. + + ```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)은 `start`부터 `end`까지 `value`를 채워 넣습니다. - [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin)은 `start`부터 `end`까지 요소를 복사하고, 복사한 요소를 `target`에 붙여넣습니다. 기존 요소가 있다면 덮어씁니다. +- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap)은 다차원 배열을 새로운 평면 배열로 만듭니다. + 배열에 관한 모든 메서드는 [manual](mdn:js/Array)에서 찾아볼 수 있습니다. 배워야 할 메서드 종류가 너무 많아서 이걸 다 외워야 하나라는 생각이 들 수 있는데, 생각보다 쉬우니 너무 걱정하지 않으셨으면 좋겠습니다. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index ab37e2aa2a..cb20b848f5 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -47,7 +47,7 @@ range[Symbol.iterator] = function() { // 2. 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, 이때 다음 값도 정해집니다. return { current: this.from, - last: this.to, + last: this.to, // 3. for..of 반복문에 의해 반복마다 next()가 호출됩니다. next() { @@ -217,7 +217,7 @@ alert(arr.pop()); // World (메서드가 제대로 동작합니다.) 이터러블을 사용한 예시는 다음과 같습니다. -```js +```js run // range는 챕터 위쪽 예시에서 그대로 가져왔다고 가정합시다. let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (배열-문자열 형 변환이 제대로 동작합니다.) @@ -232,7 +232,7 @@ Array.from(obj[, mapFn, thisArg]) 예시: -```js +```js run // range는 챕터 위쪽 예시에서 그대로 가져왔다고 가정합시다. // 각 숫자를 제곱 diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index 33f7dfc481..e141bc1261 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -10,17 +10,17 @@ ## 맵 -[맵(Map)](mdn:js/Map)은 키가 있는 데이터를 저장한다는 점에서 `객체`와 유사합니다. 다만, `맵`은 키에 다양한 자료형을 허용한다는 점에서 차이가 있습니다. +[맵(Map)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)은 키가 있는 데이터를 저장한다는 점에서 `객체`와 유사합니다. 다만, `맵`은 키에 다양한 자료형을 허용한다는 점에서 차이가 있습니다. 맵에는 다음과 같은 주요 메서드와 프로퍼티가 있습니다. -- `new Map()` -- 맵을 만듭니다. -- `map.set(key, value)` -- `key`를 이용해 `value`를 저장합니다. -- `map.get(key)` -- `key`에 해당하는 값을 반환합니다. `key`가 존재하지 않으면 `undefined`를 반환합니다. -- `map.has(key)` -- `key`가 존재하면 `true`, 존재하지 않으면 `false`를 반환합니다. -- `map.delete(key)` -- `key`에 해당하는 값을 삭제합니다. -- `map.clear()` -- 맵 안의 모든 요소를 제거합니다. -- `map.size` -- 요소의 개수를 반환합니다. +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- 맵을 만듭니다. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- `key`를 이용해 `value`를 저장합니다. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- `key`에 해당하는 값을 반환합니다. `key`가 존재하지 않으면 `undefined`를 반환합니다. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- `key`가 존재하면 `true`, 존재하지 않으면 `false`를 반환합니다. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- `key`에 해당하는 값을 삭제합니다. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- 맵 안의 모든 요소를 제거합니다. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- 요소의 개수를 반환합니다. 예시: @@ -42,7 +42,7 @@ alert( map.size ); // 3 맵은 객체와 달리 키를 문자형으로 변환하지 않습니다. 키엔 자료형 제약이 없습니다. ```smart header="`map[key]`는 `Map`을 쓰는 바른 방법이 아닙니다." -`map[key] = 2`로 값을 설정하는 것 같이 `map[key]`를 사용할 수 있긴 합니다. 하지만 이 방법은 `map`을 일반 객체처럼 취급하게 됩니다. 따라서 여러 제약이 생기게 되죠. +`map[key] = 2`로 값을 설정하는 것 같이 `map[key]`를 사용할 수 있긴 합니다. 하지만 이 방법은 `map`을 일반 객체처럼 취급하게 됩니다. 따라서 객체의 프로퍼티 키를 문자열 또는 심볼로만 사용할 수 있는 등 여러 제약이 생기게 되죠. `map`을 사용할 땐 `map`전용 메서드 `set`, `get` 등을 사용해야만 합니다. ``` @@ -69,10 +69,12 @@ alert( visitsCountMap.get(john) ); // 123 ```js run let john = { name: "John" }; +let ben = { name: "Ben" }; let visitsCountObj = {}; // 객체를 하나 만듭니다. -visitsCountObj[john] = 123; // 객체(john)를 키로 해서 객체에 값(123)을 저장해봅시다. +visitsCountObj[ben] = 234; // 객체(ben)을 키로 사용하겠습니다. +visitsCountObj[john] = 123; // 객체(john)을 키로 사용하면 객체(ben)은 객체(john)으로 덮어써집니다. *!* // 원하는 값(123)을 얻으려면 아래와 같이 키가 들어갈 자리에 `object Object`를 써줘야합니다. @@ -80,7 +82,7 @@ alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` -`visitsCountObj`는 객체이기 때문에 모든 키를 문자형으로 변환시킵니다. 이 과정에서 `john`은 문자형으로 변환되어 `"[object Object]"`가 됩니다. +`visitsCountObj`는 객체이기 때문에 모든 `객체`키를 문자형으로 변환시킵니다. 이 과정에서 `john`과 `ben`은 문자형으로 변환되어 `"[object Object]"`가 됩니다. ```smart header="`맵`이 키를 비교하는 방식" `맵`은 [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero)라 불리는 알고리즘을 사용해 값의 등가 여부를 확인합니다. 이 알고리즘은 일치 연산자 `===`와 거의 유사하지만, `NaN`과 `NaN`을 같다고 취급하는 것에서 일치 연산자와 차이가 있습니다. 따라서 맵에선 `NaN`도 키로 쓸 수 있습니다. @@ -98,14 +100,13 @@ map.set('1', 'str1') ``` ```` - ## 맵의 요소에 반복 작업하기 다음 세 가지 메서드를 사용해 `맵`의 각 요소에 반복 작업을 할 수 있습니다. -- `map.keys()` -- 각 요소의 키를 모은 반복 가능한(iterable, 이터러블) 객체를 반환합니다. -- `map.values()` -- 각 요소의 값을 모은 이터러블 객체를 반환합니다. -- `map.entries()` -- 요소의 `[키, 값]`을 한 쌍으로 하는 이터러블 객체를 반환합니다. 이 이터러블 객체는 `for..of`반복문의 기초로 쓰입니다. +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- 각 요소의 키를 모은 반복 가능한(iterable, 이터러블) 객체를 반환합니다. +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- 각 요소의 값을 모은 이터러블 객체를 반환합니다. +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- 요소의 `[키, 값]`을 한 쌍으로 하는 이터러블 객체를 반환합니다. 이 이터러블 객체는 `for..of`반복문의 기초로 쓰입니다. 예시: @@ -161,7 +162,7 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -평범한 객체를 가지고 `맵`을 만들고 싶다면 내장 메서드 [Object.entries(obj)](mdn:js/Object/entries)를 활용해야 합니다. 이 메서드는 객체의 키-값 쌍을 요소(`[key, value]`)로 가지는 배열을 반환합니다. +평범한 객체를 가지고 `맵`을 만들고 싶다면 내장 메서드 [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries)를 활용해야 합니다. 이 메서드는 객체의 키-값 쌍을 요소(`[key, value]`)로 가지는 배열을 반환합니다. 예시: @@ -232,16 +233,16 @@ let obj = Object.fromEntries(map); // .entries()를 생략함 ## 셋 -`셋(Set)`은 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션입니다. 셋에 키가 없는 값이 저장됩니다. +[`셋(Set)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)은 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션입니다. 셋에 키가 없는 값이 저장됩니다. 주요 메서드는 다음과 같습니다. -- `new Set(iterable)` -- 셋을 만듭니다. `이터러블` 객체를 전달받으면(대개 배열을 전달받음) 그 안의 값을 복사해 셋에 넣어줍니다. -- `set.add(value)` -- 값을 추가하고 셋 자신을 반환합니다. -- `set.delete(value)` -- 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면 `true`, 아니면 `false`를 반환합니다. -- `set.has(value)` -- 셋 내에 값이 존재하면 `true`, 아니면 `false`를 반환합니다. -- `set.clear()` -- 셋을 비웁니다. -- `set.size` -- 셋에 몇 개의 값이 있는지 세줍니다. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- 셋을 만듭니다. `이터러블` 객체를 전달받으면(대개 배열을 전달받음) 그 안의 값을 복사해 셋에 넣어줍니다. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- 값을 추가하고 셋 자신을 반환합니다. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면 `true`, 아니면 `false`를 반환합니다. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- 셋 내에 값이 존재하면 `true`, 아니면 `false`를 반환합니다. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- 셋을 비웁니다. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- 셋에 몇 개의 값이 있는지 세줍니다. 셋 내에 동일한 값(value)이 있다면 `set.add(value)`을 아무리 많이 호출하더라도 아무런 반응이 없을 겁니다. 셋 내의 값에 중복이 없는 이유가 바로 이 때문이죠. @@ -271,7 +272,7 @@ for (let user of set) { } ``` -`셋` 대신 배열을 사용하여 방문자 정보를 저장한 후, 중복 값 여부는 배열 메서드인 [arr.find](mdn:js/Array/find)를 이용해 확인할 수도 있습니다. 하지만 `arr.find`는 배열 내 요소 전체를 뒤져 중복 값을 찾기 때문에, 셋보다 성능 면에서 떨어집니다. 반면, `셋`은 값의 유일무이함을 확인하는데 최적화되어있습니다. +`셋` 대신 배열을 사용하여 방문자 정보를 저장한 후, 중복 값 여부는 배열 메서드인 [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)를 이용해 확인할 수도 있습니다. 하지만 `arr.find`는 배열 내 요소 전체를 뒤져 중복 값을 찾기 때문에, 셋보다 성능 면에서 떨어집니다. 반면, `셋`은 값의 유일무이함을 확인하는데 최적화되어있습니다. ## 셋의 값에 반복 작업하기 @@ -294,38 +295,38 @@ set.forEach((value, valueAgain, set) => { `셋`에도 `맵`과 마찬가지로 반복 작업을 위한 메서드가 있습니다. -- `set.keys()` -- 셋 내의 모든 값을 포함하는 이터러블 객체를 반환합니다. -- `set.values()` -- `set.keys`와 동일한 작업을 합니다. `맵`과의 호환성을 위해 만들어진 메서드입니다. -- `set.entries()` -- 셋 내의 각 값을 이용해 만든 `[value, value]` 배열을 포함하는 이터러블 객체를 반환합니다. `맵`과의 호환성을 위해 만들어졌습니다. +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- 셋 내의 모든 값을 포함하는 이터러블 객체를 반환합니다. +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- `set.keys`와 동일한 작업을 합니다. `맵`과의 호환성을 위해 만들어진 메서드입니다. +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- 셋 내의 각 값을 이용해 만든 `[value, value]` 배열을 포함하는 이터러블 객체를 반환합니다. `맵`과의 호환성을 위해 만들어졌습니다. ## 요약 -`맵`은 키가 있는 값이 저장된 컬렉션입니다. +[`맵(Map)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) 은 키가 있는 값이 저장된 컬렉션입니다. 주요 메서드와 프로퍼티: -- `new Map([iterable])` -- 맵을 만듭니다. `[key,value]`쌍이 있는 `iterable`(예: 배열)을 선택적으로 넘길 수 있는데, 이때 넘긴 이터러블 객체는 맵 초기화에 사용됩니다. -- `map.set(key, value)` -- 키를 이용해 값을 저장합니다. -- `map.get(key)` -- 키에 해당하는 값을 반환합니다. `key`가 존재하지 않으면 `undefined`를 반환합니다. -- `map.has(key)` -- 키가 있으면 `true`, 없으면 `false`를 반환합니다. -- `map.delete(key)` -- 키에 해당하는 값을 삭제합니다. -- `map.clear()` -- 맵 안의 모든 요소를 제거합니다. -- `map.size` -- 요소의 개수를 반환합니다. +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- 맵을 만듭니다. `[key,value]`쌍이 있는 `iterable`(예: 배열)을 선택적으로 넘길 수 있는데, 이때 넘긴 이터러블 객체는 맵 초기화에 사용됩니다. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- 키를 이용해 값을 저장합니다. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- 키에 해당하는 값을 반환합니다. `key`가 존재하지 않으면 `undefined`를 반환합니다. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- 키가 있으면 `true`, 없으면 `false`를 반환합니다. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- 키에 해당하는 값을 삭제합니다. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- 맵 안의 모든 요소를 제거합니다. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- 요소의 개수를 반환합니다. 일반적인 `객체`와의 차이점: - 키의 타입에 제약이 없습니다. 객체도 키가 될 수 있습니다. - `size` 프로퍼티 등의 유용한 메서드나 프로퍼티가 있습니다. -`셋`은 중복이 없는 값을 저장할 때 쓰이는 컬렉션입니다. +[`셋(Set)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)은 중복이 없는 값을 저장할 때 쓰이는 컬렉션입니다. 주요 메서드와 프로퍼티: -- `new Set([iterable])` -- 셋을 만듭니다. `iterable` 객체를 선택적으로 전달받을 수 있는데(대개 배열을 전달받음), 이터러블 객체 안의 요소는 셋을 초기화하는데 쓰입니다. -- `set.add(value)` -- 값을 추가하고 셋 자신을 반환합니다. 셋 내에 이미 `value`가 있는 경우 아무런 작업을 하지 않습니다. -- `set.delete(value)` -- 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면 `true`, 아니면 `false`를 반환합니다. -- `set.has(value)` -- 셋 내에 값이 존재하면 `true`, 아니면 `false`를 반환합니다. -- `set.clear()` -- 셋을 비웁니다. -- `set.size` -- 셋에 몇 개의 값이 있는지 세줍니다. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- 셋을 만듭니다. `iterable` 객체를 선택적으로 전달받을 수 있는데(대개 배열을 전달받음), 이터러블 객체 안의 요소는 셋을 초기화하는데 쓰입니다. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- 값을 추가하고 셋 자신을 반환합니다. 셋 내에 이미 `value`가 있는 경우 아무런 작업을 하지 않습니다. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면 `true`, 아니면 `false`를 반환합니다. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- 셋 내에 값이 존재하면 `true`, 아니면 `false`를 반환합니다. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- 셋을 비웁니다. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- 셋에 몇 개의 값이 있는지 세줍니다. `맵`과 `셋`에 반복 작업을 할 땐, 해당 컬렉션에 요소나 값을 추가한 순서대로 반복 작업이 수행됩니다. 따라서 이 두 컬렉션은 정렬이 되어있지 않다고 할 수 없습니다. 그렇지만 컬렉션 내 요소나 값을 재 정렬하거나 (배열에서 인덱스를 이용해 요소를 가져오는 것처럼) 숫자를 이용해 특정 요소나 값을 가지고 오는 것은 불가능합니다. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 2548d04b62..baa3172de4 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -30,7 +30,8 @@ let array = [ john ]; john = null; // 참조를 null로 덮어씀 *!* -// john을 나타내는 객체는 배열의 요소이기 때문에 가비지 컬렉터의 대상이 되지 않습니다. +// john이 참조하는 객체가 배열 안에 저장되었습니다. +// 그렇기 때문에 가비지 컬렉터의 대상이 되지 않습니다. // array[0]을 이용하면 해당 객체를 얻는 것도 가능합니다. */!* alert(JSON.stringify(array[0])); @@ -59,13 +60,13 @@ for(let obj of map.keys()){ alert(map.size); ``` -이런 관점에서 `위크맵(WeakMap)`은 일반 `맵`과 전혀 다른 양상을 보입니다. 위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 됩니다. +이런 관점에서 [`위크맵(WeakMap)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)은 일반 `맵`과 전혀 다른 양상을 보입니다. 위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 됩니다. 예시를 이용해 이에 대해 자세히 알아보도록 합시다. ## 위크맵 -`맵`과 `위크맵`의 첫 번째 차이는 `위크맵`의 키가 반드시 객체여야 한다는 점입니다. 원시값은 위크맵의 키가 될 수 없습니다. +[`맵`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)과 [`위크맵`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)의 첫 번째 차이는 `위크맵`의 키가 반드시 객체여야 한다는 점입니다. 원시값은 위크맵의 키가 될 수 없습니다. ```js run let weakMap = new WeakMap(); @@ -99,10 +100,10 @@ john = null; // 참조를 덮어씀 `위크맵`이 지원하는 메서드는 단출합니다. -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +- [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) +- [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) +- [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) +- [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) 왜 이렇게 적은 메서드만 제공할까요? 원인은 가비지 컬렉션의 동작 방식 때문입니다. 위 예시의 `john`을 나타내는 객체처럼, 객체는 모든 참조를 잃게 되면 자동으로 가비지 컬렉션의 대상이 됩니다. 그런데 가비지 컬렉션의 *동작 시점*은 정확히 알 수 없습니다. @@ -187,6 +188,7 @@ function process(obj) { let result = /* 연산 수행 */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -226,6 +228,7 @@ function process(obj) { let result = /* 연산 수행 */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -247,11 +250,11 @@ obj = null; ## 위크셋 -이제 `위크셋(WeakSet)`에 대해 알아봅시다. +이제 [`위크셋(WeakSet)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet)에 대해 알아봅시다. - `위크셋`은 `셋`과 유사한데, 객체만 저장할 수 있다는 점이 다릅니다. 원시값은 저장할 수 없습니다. - 셋 안의 객체는 도달 가능할 때만 메모리에서 유지됩니다. -- `셋`과 마찬가지로 `위크셋`이 지원하는 메서드는 단출합니다. `add`, `has`, `delete`를 사용할 수 있고, `size`, `keys()`나 반복 작업 관련 메서드는 사용할 수 없습니다. +- `셋`과 마찬가지로 `위크셋`이 지원하는 메서드는 단출합니다. [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has), [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete)를 사용할 수 있고, `size`, `keys()`나 반복 작업 관련 메서드는 사용할 수 없습니다. '위크'맵과 유사하게 '위크'셋도 부차적인 데이터를 저장할 때 사용할 수 있습니다. 다만, 위크셋엔 위크맵처럼 복잡한 데이터를 저장하지 않습니다. 대신 "예"나 "아니오" 같은 간단한 답변을 얻는 용도로 사용됩니다. 물론 `위크셋`에 저장되는 값들은 객체이겠죠. @@ -285,10 +288,12 @@ john = null; ## 요약 -`위크맵`은 `맵`과 유사한 컬렉션입니다. `위크맵`을 구성하는 요소의 키는 오직 객체만 가능합니다. 키로 사용된 객체가 메모리에서 삭제되면 이에 대응하는 값 역시 삭제됩니다. +[`위크맵(WeakMap)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)은 `맵`과 유사한 컬렉션입니다. `위크맵`을 구성하는 요소의 키는 오직 객체만 가능합니다. 키로 사용된 객체가 메모리에서 삭제되면 이에 대응하는 값 역시 삭제됩니다. -`위크셋`은 `셋`과 유사한 컬렉션입니다. 위크셋엔 객체만 저장할 수 있습니다. 위크셋에 저장된 객체가 도달 불가능한 상태가 되면 해당 객체는 메모리에서 삭제됩니다. +[`위크셋(WeakSet)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet)은 `셋`과 유사한 컬렉션입니다. 위크셋엔 객체만 저장할 수 있습니다. 위크셋에 저장된 객체가 도달 불가능한 상태가 되면 해당 객체는 메모리에서 삭제됩니다. -두 자료구조 모두 구성 요소 전체를 대상으로 하는 메서드를 지원하지 않습니다. 구성 요소 하나를 대상으로 하는 메서드만 지원합니다. +`위크맵`과 `위크셋`의 가장 큰 장점은 객체에 대한 약한 참조이며, 가비지 컬렉터가 객체를 쉽게 청소할 수 있다는 점입니다. + +하지만 `clear`, `size`, `keys`, `values`와 같은 메서드를 지원하지 않는다는 단점이 있습니다. 객체엔 '주요' 자료를, `위크맵`과 `위크셋`엔 '부수적인' 자료를 저장하는 형태로 위크맵과 위크셋을 활용할 수 있습니다. 객체가 메모리에서 삭제되면, (그리고 오로지 `위크맵`과 `위크셋`의 키만 해당 객체를 참조하고 있다면) 위크맵이나 위크셋에 저장된 연관 자료들 역시 메모리에서 자동으로 삭제됩니다. diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index d4192513ec..c9ced331be 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -77,7 +77,7 @@ for (let value of Object.values(user)) { 하지만 `Object.entries`와 `Object.fromEntries`를 순차적으로 적용하면 객체에도 배열 전용 메서드 사용할 수 있습니다. 적용 방법은 다음과 같습니다. 1. `Object.entries(obj)`를 사용해 객체의 키-값 쌍이 요소인 배열을 얻습니다. -2. 1.에서 만든 배열에 `map` 등의 배열 전용 메서드를 적용합니다. +2. 1.에서 만든 배열에 `map` 등의 배열 전용 메서드를 적용해 키-값 쌍을 변환합니다. 3. 2.에서 반환된 배열에 `Object.fromEntries(array)`를 적용해 배열을 다시 객체로 되돌립니다. 이 방법을 사용해 가격 정보가 저장된 객체 prices의 프로퍼티 값을 두 배로 늘려보도록 합시다. @@ -91,12 +91,13 @@ let prices = { *!* let doublePrices = Object.fromEntries( - // 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌립니다. - Object.entries(prices).map(([key, value]) => [key, value * 2]) + // prices 객체를 배열로 변환하고 각 키-값 쌍에 map을 적용해 새로운 쌍으로 만듭니다. + // 그런 다음 fromEntries를 사용해 배열을 다시 객체로 되돌립니다. + Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) ); */!* alert(doublePrices.meat); // 8 -``` +``` -지금 당장은 어렵게 느껴지겠지만 한두 번 위 방법을 적용해 보면 객체에 배열 전용 메서드를 적용하는게 쉬워질 겁니다. +지금 당장은 어렵게 느껴지겠지만 한두 번 위 방법을 적용해 보면 객체에 배열 전용 메서드를 적용하는게 쉬워질 겁니다. 이러한 방법으로 변환에 대한 강력한 체인을 만들 수 있습니다. diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js index f4bd5c761c..6538af42b9 100644 --- a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js +++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js @@ -1,16 +1,14 @@ function topSalary(salaries) { - let max = 0; + let maxSalary = 0; let maxName = null; for(const [name, salary] of Object.entries(salaries)) { - if (max < salary) { - max = salary; + if (maxSalary < salary) { + maxSalary = salary; maxName = name; } } return maxName; -} - - +} \ No newline at end of file diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index e8076ec788..286e581286 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -2,11 +2,14 @@ `객체`와 `배열`은 자바스크립트에서 가장 많이 쓰이는 자료 구조입니다. -키를 가진 데이터 여러 개를 하나의 엔티티에 저장할 땐 객체를, 컬렉션에 데이터를 순서대로 저장할 땐 배열을 사용하죠. +- 키를 가진 데이터 여러 개를 하나의 엔티티에 저장할 땐 객체를 사용합니다. +- 컬렉션에 데이터를 순서대로 저장할 땐 배열을 사용합니다. -개발을 하다 보면 함수에 객체나 배열을 전달해야 하는 경우가 생기곤 합니다. 가끔은 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우가 생기기도 하죠. +개발을 하다 보면 함수에 객체나 배열을 전달해야 하는 경우가 생기곤 합니다. 가끔은 객체나 배열에 저장된 데이터 전체가 아닌 특정 요소나 프로퍼티만 필요한 경우가 생기기도 하죠. -이럴 때 객체나 배열을 변수로 '분해'할 수 있게 해주는 특별한 문법인 *구조 분해 할당(destructuring assignment)* 을 사용할 수 있습니다. 이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해(destructuring)는 그 진가를 발휘합니다. +이럴 때 객체나 배열을 변수로 '분해'할 수 있게 해주는 특별한 문법인 *구조 분해 할당(destructuring assignment)* 을 사용할 수 있습니다. + +이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해(destructuring)는 그 진가를 발휘합니다. 그 내용을 만나러 가보죠. ## 배열 분해하기 @@ -31,10 +34,14 @@ alert(surname); // Lee 아래 예시처럼 `split` 같은 반환 값이 배열인 메서드를 함께 활용해도 좋습니다. -```js +```js run let [firstName, surname] = "Bora Lee".split(' '); +alert(firstName); // Bora +alert(surname); // Lee ``` +보다시피 문법은 간단합니다. 하지만 몇 가지 특징이 있습니다. 아래 예제를 살펴봅시다. + ````smart header="'분해(destructuring)'는 '파괴(destructive)'를 의미하지 않습니다." 구조 분해 할당이란 명칭은 어떤 것을 복사한 이후에 변수로 '분해(destructurize)'해준다는 의미 때문에 붙여졌습니다. 이 과정에서 분해 대상은 수정 또는 파괴되지 않습니다. @@ -69,7 +76,7 @@ alert( title ); // Consul let [a, b, c] = "abc"; // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3]); ``` - +That works, because internally a destructuring assignment works by iterating over the right value. It's a kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values. ```` @@ -83,6 +90,7 @@ let user = {}; [user.name, user.surname] = "Bora Lee".split(' '); alert(user.name); // Bora +alert(user.surname); // Lee ``` ```` @@ -107,7 +115,7 @@ for (let [key, value] of Object.entries(user)) { } ``` -맵에도 물론 이 메서드를 활용할 수 있습니다. +`Map`은 이터러블이므로 코드를 더 간단히 작성할 수 있습니다. ```js run let user = new Map(); @@ -115,6 +123,7 @@ user.set("name", "John"); user.set("age", "30"); *!* +// Map은 [key, value] 쌍으로 순회하기 때문에 간편히 구조 분해 할당이 가능합니다. for (let [key, value] of user) { */!* alert(`${key}:${value}`); // name:John, then age:30 @@ -130,7 +139,9 @@ let guest = "Jane"; let admin = "Pete"; // 변수 guest엔 Pete, 변수 admin엔 Jane이 저장되도록 값을 교환함 +*!* [guest, admin] = [admin, guest]; +*/!* alert(`${guest} ${admin}`); // Pete Jane(값 교환이 성공적으로 이뤄졌습니다!) ``` @@ -142,23 +153,39 @@ alert(`${guest} ${admin}`); // Pete Jane(값 교환이 성공적으로 이뤄졌 ### '...'로 나머지 요소 가져오기 -배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 이어지는 나머지 값들은 한데 모아서 저장하고 싶을 때가 있습니다. 이럴 때는 점 세 개 `...`를 붙인 매개변수 하나를 추가하면 '나머지(rest)' 요소를 가져올 수 있습니다. +분해하고자 하는 배열의 길이가 할당하고자 하는 변수의 개수보다 크다면 길이를 '초과(extra)'한 요소는 생략됩니다. + +아래 예제에서는 두 개의 요소만 할당되고 나머지 요소들은 무시됩니다. ```js run -let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; +let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert(name1); // Julius alert(name2); // Caesar +// 이후의 요소는 할당되지 않습니다. +``` + +이어지는 나머지 값들을 모두 모아서 저장하고 싶다면, 점 세 개 `...`를 붙인 매개변수 하나를 추가하여 '나머지(rest)' 요소를 가져올 수 있습니다. + +```js run +let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* -// `rest`는 배열입니다. +// rest는 세 번째 요소부터 담은 배열입니다. alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 */!* ``` -`rest`는 나머지 배열 요소들이 저장된 새로운 배열이 됩니다. `rest` 대신에 다른 이름을 사용해도 되는데, 변수 앞의 점 세 개(`...`)와 변수가 가장 마지막에 위치해야 한다는 점은 지켜주시기 바랍니다. +`rest`는 나머지 배열 요소들이 저장된 새로운 배열이 됩니다. + +`rest` 대신에 다른 이름을 사용해도 되는데, 변수 앞의 점 세 개(`...`)와 변수가 가장 마지막에 위치해야 한다는 점은 지켜주시기 바랍니다. + +```js run +let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +// now titles = ["Consul", "of the Roman Republic"] +``` ### 기본값 @@ -187,7 +214,7 @@ alert(surname); // Anonymous (기본값) 복잡한 표현식이나 함수 호출도 기본값이 될 수 있습니다. 이렇게 기본식으로 표현식이나 함수를 설정하면 할당할 값이 없을 때 표현식이 평가되거나 함수가 호출됩니다. -기본값으로 두 개의 `prompt` 함수를 할당한 예시를 살펴봅시다. 값이 제공되지 않았을 때만 함수가 호출되므로, `prompt`는 한 번만 호출됩니다. +기본값으로 두 개의 `prompt` 함수를 할당한 예시를 살펴봅시다. ```js run // name의 prompt만 실행됨 @@ -197,7 +224,7 @@ alert(surname); // 김 (배열에서 받아온 값) alert(name); // prompt에서 받아온 값 ``` - +참고로 `prompt`는 누락된 값인 `surname`에 대해서만 실행됩니다. ## 객체 분해하기 @@ -229,7 +256,9 @@ alert(width); // 100 alert(height); // 200 ``` -프로퍼티 `options.title`과 `options.width`, `options.height`에 저장된 값이 상응하는 변수에 할당된 것을 확인할 수 있습니다. 참고로 순서는 중요하지 않습니다. 아래와 같이 작성해도 위 예시와 동일하게 동작합니다. +프로퍼티 `options.title`과 `options.width`, `options.height`에 저장된 값이 상응하는 변수에 할당된 것을 확인할 수 있습니다. + +참고로 순서는 중요하지 않습니다. 아래와 같이 작성해도 위 예시와 동일하게 동작합니다. ```js // let {...} 안의 순서가 바뀌어도 동일하게 동작함 @@ -534,7 +563,7 @@ showMenu(); // Menu 100 200 - 구조 분해 할당을 사용하면 객체나 배열을 변수로 연결할 수 있습니다. - 객체 분해하기: ```js - let {prop : varName = default, ...rest} = object + let {prop : varName = defaultValue, ...rest} = object ``` object의 프로퍼티 `prop`의 값은 변수 `varName`에 할당되는데, object에 prop이 없으면 `default`가 `varName`에 할당됩니다. @@ -544,7 +573,7 @@ showMenu(); // Menu 100 200 - 배열 분해하기: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = defaultValue, item2, ...rest] = array ``` array의 첫 번째 요소는 `item1`에, 두 번째 요소는 변수 `item2`에 할당되고, 이어지는 나머지 요소들은 배열 `rest` 저장됩니다. diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md index ef6ac03865..b572d16a00 100644 --- a/1-js/05-data-types/11-date/1-new-date/solution.md +++ b/1-js/05-data-types/11-date/1-new-date/solution.md @@ -4,6 +4,8 @@ Here's an example with numbers as date components: +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); @@ -13,6 +15,10 @@ We could also create a date from a string, like this: ```js run //new Date(datastring) +<<<<<<< HEAD let d2 = new Date("February 20, 2012 03:12:00"); +======= +let d2 = new Date("2012-02-20T03:12"); +>>>>>>> upstream/master alert( d2 ); ``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index d93b6843ba..a103c67d08 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -57,10 +57,17 @@ Date 객체를 활용하면 생성 및 수정 시간을 저장하거나 시간 `new Date(year, month, date, hours, minutes, seconds, ms)` : 주어진 인수를 조합해 만들 수 있는 날짜가 저장된 객체가 반환됩니다(지역 시간대 기준). 첫 번째와 두 번째 인수만 필수값입니다. +<<<<<<< HEAD - `year`는 반드시 네 자리 숫자여야 합니다. `2013`은 괜찮고 `98`은 괜찮지 않습니다. - `month`는 `0`(1월)부터 `11`(12월) 사이의 숫자여야 합니다. - `date`는 일을 나타내는데, 값이 없는 경우엔 1일로 처리됩니다. - `hours/minutes/seconds/ms`에 값이 없는 경우엔 `0`으로 처리됩니다. +======= + - The `year` should have 4 digits. For compatibility, 2 digits are also accepted and considered `19xx`, e.g. `98` is the same as `1998` here, but always using 4 digits is strongly encouraged. + - The `month` count starts with `0` (Jan), up to `11` (Dec). + - The `date` parameter is actually the day of month, if absent then `1` is assumed. + - If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`. +>>>>>>> upstream/master 예시: @@ -69,7 +76,11 @@ Date 객체를 활용하면 생성 및 수정 시간을 저장하거나 시간 new Date(2011, 0, 1); // hours를 비롯한 인수는 기본값이 0이므로 위와 동일 ``` +<<<<<<< HEAD 최소 정밀도는 1밀리초(1/1000초)입니다. +======= + The maximal precision is 1 ms (1/1000 sec): +>>>>>>> upstream/master ```js run let date = new Date(2011, 0, 1, 2, 3, 4, 567); @@ -348,7 +359,11 @@ let time1 = 0; let time2 = 0; *!* +<<<<<<< HEAD // 함수 bench를 각 함수(diffSubtract, diffGetTime)별로 10번씩 돌립니다. +======= +// run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating +>>>>>>> upstream/master for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); @@ -376,7 +391,11 @@ for (let i = 0; i < 10; i++) { ```warn header="세밀한 벤치마킹을 할 때는 주의하세요" 모던 자바스크립트 엔진은 최적화를 많이 합니다. 이로 인해 '만들어진 테스트'가 '실제 사례'와는 결과가 다를 수 있습니다. 특히 연산자, 내장 함수와 같이 아주 작은 것일수록 더 결과가 다를 수 있습니다. 그러니 진지하게 성능을 이해하고 싶다면 자바스크립트 엔진이 어떻게 동작하는지 공부하시길 바랍니다. 그러면 아마 세밀한 벤치마킹을 할 필요가 없을 겁니다. +<<<<<<< HEAD 에서 V8 엔진을 설명한 좋은 글들을 보실 수 있습니다. +======= +The great pack of articles about V8 can be found at . +>>>>>>> upstream/master ``` ## Date.parse와 문자열 @@ -385,10 +404,17 @@ for (let i = 0; i < 10; i++) { 단, 문자열의 형식은 `YYYY-MM-DDTHH:mm:ss.sssZ`처럼 생겨야 합니다. +<<<<<<< HEAD - `YYYY-MM-DD` -- 날짜(연-월-일) - `"T"` -- 구분 기호로 쓰임 - `HH:mm:ss.sss` -- 시:분:초.밀리초 - `'Z'`(옵션) -- `+-hh:mm` 형식의 시간대를 나타냄. `Z` 한 글자인 경우엔 UTC+0을 나타냄 +======= +- `YYYY-MM-DD` -- is the date: year-month-day. +- The character `"T"` is used as the delimiter. +- `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds. +- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` would mean UTC+0. +>>>>>>> upstream/master `YYYY-MM-DD`, `YYYY-MM`, `YYYY`같이 더 짧은 문자열 형식도 가능합니다. @@ -407,7 +433,7 @@ alert(ms); // 1327611110417 (타임스탬프) ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); -alert(date); +alert(date); ``` ## 요약 @@ -424,10 +450,17 @@ alert(date); 간혹 밀리초보다 더 정확한 시간 측정이 필요할 때가 있습니다. 자바스크립트는 마이크로초(1/1,000,000초)를 지원하진 않지만 대다수의 호스트 환경은 마이크로초를 지원합니다. 브라우저 환경의 메서드 [performance.now()](mdn:api/Performance/now)는 페이지 로딩에 걸리는 밀리초를 반환해주는데, 반환되는 숫자는 소수점 아래 세 자리까지 지원합니다. ```js run +<<<<<<< HEAD alert(`페이지 로딩이 ${performance.now()}밀리초 전에 시작되었습니다.`); // 얼럿 창에 "페이지 로딩이 34731.26000000001밀리초 전에 시작되었습니다."와 유사한 메시지가 뜰 텐데 // 여기서 '.26'은 마이크로초(260마이크로초)를 나타냅니다. // 소수점 아래 숫자 세 개 이후의 숫자는 정밀도 에러때문에 보이는 숫자이므로 소수점 아래 숫자 세 개만 유효합니다. +======= +alert(`Loading started ${performance.now()}ms ago`); +// Something like: "Loading started 34731.26000000001ms ago" +// .26 is microseconds (260 microseconds) +// more than 3 digits after the decimal point are precision errors, only the first 3 are correct +>>>>>>> upstream/master ``` Node.js에선 `microtime` 모듈 등을 사용해 마이크로초를 사용할 수 있습니다. 자바스크립트가 구동되는 대다수의 호스트 환경과 기기에서 마이크로초를 지원하고 있는데 `Date` 객체만 마이크로초를 지원하지 않습니다. diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index 35a0c0ee94..f87ea65377 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -27,7 +27,11 @@ alert(user); // {name: "John", age: 30} ## JSON.stringify +<<<<<<< HEAD [JSON](http://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation)은 값이나 객체를 나타내주는 범용 포맷으로, [RFC 4627](http://tools.ietf.org/html/rfc4627) 표준에 정의되어 있습니다. JSON은 본래 자바스크립트에서 사용할 목적으로 만들어진 포맷입니다. 그런데 라이브러리를 사용하면 자바스크립트가 아닌 언어에서도 JSON을 충분히 다룰 수 있어서, JSON을 데이터 교환 목적으로 사용하는 경우가 많습니다. 특히 클라이언트 측 언어가 자바스크립트일 때 말이죠. 서버 측 언어는 무엇이든 상관없습니다. +======= +The [JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](https://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever. +>>>>>>> upstream/master 자바스크립트가 제공하는 JSON 관련 메서드는 아래와 같습니다. @@ -41,7 +45,7 @@ let student = { age: 30, isAdmin: false, courses: ['html', 'css', 'js'], - wife: null + spouse: null }; *!* @@ -58,7 +62,7 @@ alert(json); "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], - "wife": null + "spouse": null } */ */!* @@ -104,9 +108,15 @@ JSON은 데이터 교환을 목적으로 만들어진 언어에 종속되지 않 `JSON.stringify` 호출 시 무시되는 프로퍼티는 아래와 같습니다. +<<<<<<< HEAD - 함수 프로퍼티 (메서드) - 심볼형 프로퍼티 (키가 심볼인 프로퍼티) - 값이 `undefined`인 프로퍼티 +======= +- Function properties (methods). +- Symbolic keys and values. +- Properties that store `undefined`. +>>>>>>> upstream/master ```js run let user = { @@ -276,6 +286,7 @@ name: John name: Alice place: [object Object] number: 23 +occupiedBy: [object Object] */ ``` @@ -327,7 +338,13 @@ alert(JSON.stringify(user, null, 2)); */ ``` +<<<<<<< HEAD 이처럼 매개변수 `space`는 로깅이나 가독성을 높이는 목적으로 사용됩니다. +======= +The third argument can also be a string. In this case, the string is used for indentation instead of a number of spaces. + +The `space` parameter is used solely for logging and nice-output purposes. +>>>>>>> upstream/master ## 커스텀 "toJSON" @@ -401,7 +418,7 @@ alert( JSON.stringify(meetup) ); 문법: ```js -let value = JSON.parse(str, [reviver]); +let value = JSON.parse(str[, reviver]); ``` str @@ -447,7 +464,11 @@ let json = `{ JSON은 주석을 지원하지 않는다는 점도 기억해 놓으시기 바랍니다. 주석을 추가하면 유효하지 않은 형식이 됩니다. +<<<<<<< HEAD 키를 큰따옴표로 감싸지 않아도 되고 주석도 지원해주는 [JSON5](http://json5.org/)라는 포맷도 있는데, 이 포맷은 자바스크립트 명세서에서 정의하지 않은 독자적인 라이브러리입니다. +======= +There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. +>>>>>>> upstream/master JSON 포맷이 까다로운 규칙을 가지게 된 이유는 개발자의 귀차니즘 때문이 아니고, 쉽고 빠르며 신뢰할 수 있을 만한 파싱 알고리즘을 구현하기 위해서입니다. diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index db5dcad57d..8e7eb926b0 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -37,4 +37,8 @@ alert( sumTo(100) ); 반복을 사용하는 방법은 두 번째로 빠릅니다. 재귀를 사용하는 방법과 반복문을 사용하는 방법 모두 같은 수의 숫자를 더하는 것에서 같지만, 재귀를 사용하는 방법은 중첩 호출과 실행 스택 관리가 추가로 필요하기 때문에 더 많은 자원을 소비합니다. 따라서 속도가 더 느리죠. +<<<<<<< HEAD 더 생각해보기 2: 몇몇 자바스크립트 엔진은 'tail call' 최적화를 지원합니다. 위 함수 `sumTo`처럼 함수가 가장 마지막으로 수행하는 연산이 재귀 호출이라면 외부 함수는 실행을 다시 시작할 필요가 없기 때문에 엔진은 실행 컨텍스트를 기억할 필요가 없어집니다. 메모리 부담이 사라지는 거죠. 그렇기 때문에 `sumTo(100000)`같은 계산이 가능한 것입니다. 그런데 자바스크립트 엔진이 tail call 최적화를 지원하지 않는다면(대부분의 엔진이 이를 지원하지 않습니다) 엔진에 설정된 스택 사이즈 제한을 넘었기 때문에 최대 스택 사이즈 초과 에러가 발생합니다. +======= +P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +>>>>>>> upstream/master diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md index 5f74d25835..b70ab9c538 100644 --- a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md +++ b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD 팩토리얼은 그 정의에 따라 `n!`을 `n * (n-1)!`로 바꿔쓸 수 있습니다. +======= +By definition, a factorial `n!` can be written as `n * (n-1)!`. +>>>>>>> upstream/master 따라서 `factorial(n)`의 결과는 `n`과 `factorial(n-1)`의 결과를 곱한 값이 되겠죠. 함수의 인수는 `n-1`에서 `1`이 될 때까지 점점 줄어들 겁니다. diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md index 4e3fe5eed5..7fa30e438d 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md @@ -33,7 +33,11 @@ printReverseList(list); # 반복문을 기반으로 하는 방법 +<<<<<<< HEAD 리스트를 원래 순서대로 출력하는 방법보다 역시 까다롭습니다. +======= +The loop variant is also a little bit more complicated than the direct output. +>>>>>>> upstream/master `list`의 마지막 값을 바로 구할 수 있는 방법이 없기 때문입니다. 마지막 값을 시작으로 '역행'할 수 없는 상황이죠. diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 9cb2855435..1b8f77d5d2 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -132,7 +132,11 @@ function pow(x, n) { +<<<<<<< HEAD 위 그림은 함수 실행이 시작되는 순간을 나타낸 것입니다. 지금 상태론 조건 `n == 1`을 만족하지 못하므로 실행 흐름은 `if`의 두 번째 분기로 넘어갑니다. +======= +That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`: +>>>>>>> upstream/master ```js run function pow(x, n) { @@ -188,7 +192,11 @@ alert( pow(2, 3) ); 이전 컨텍스트에 변수 정보, 코드가 일시 중단된 줄에 대한 정보가 저장되어있기 때문에 서브 호출이 끝났을 때 이전 컨텍스트가 문제없이 다시 시작됩니다. ```smart +<<<<<<< HEAD 예시엔 한 줄에 서브 호출 하나만 있기 때문에, 그림에서 '줄'이라는 단어를 사용했습니다. 하지만 한 줄에는 `pow(…) + pow(…) + somethingElse(…)` 같이 복수의 서브 호출이 있을 수 있습니다. +======= +Here in the picture we use the word "line", as in our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`. +>>>>>>> upstream/master 따라서 좀 더 정확히는 실행이 '서브 호출 바로 직후'에 시작된다고 이야기 할 수 있습니다. ``` @@ -285,7 +293,11 @@ function pow(x, n) { **재귀를 이용해 작성한 코드는 반복문을 사용한 코드로 다시 작성할 수 있습니다. 반복문을 사용하면 대개 함수 호출의 비용(메모리 사용)이 절약됩니다.** +<<<<<<< HEAD 하지만 코드를 다시 작성해도 큰 개선이 없는 경우가 있습니다. 조건에 따라 함수가 다른 재귀 서브 호출을 하고 그 결과를 합칠 때가 그렇습니다. 분기문이 복잡하게 얽혀있을 때도 메모리가 크게 절약되지 않습니다. 이런 경우엔 최적화가 필요하지 않을 수 있고 최적화에 드는 노력이 무용지물일 수 있습니다. +======= +...But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. +>>>>>>> upstream/master 재귀를 사용하면 코드가 짧아지고 코드 이해도가 높아지며 유지보수에도 이점이 있습니다. 모든 곳에서 메모리 최적화를 신경 써서 코드를 작성해야 하는 것은 아닙니다. 우리가 필요한 것은 좋은 코드입니다. 이런 이유 때문에 재귀를 사용합니다. @@ -535,7 +547,11 @@ list.next = list.next.next; list = {value, next -> list} ``` +<<<<<<< HEAD HTML 문서의 HTML 요소 트리나 위에서 다룬 부서를 나타내는 트리 역시 재귀적인 자료 구조로 만들었습니다. 이렇게 재귀적인 자료 구조를 사용하면 가지가 여러 개인데 각 가지가 여러 가지로 뻗쳐 나가는 형태로 자료 구조를 만들 수 있습니다. +======= + Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they have branches and every branch can have other branches. +>>>>>>> upstream/master 예시에서 구현한 `sumSalary`같은 재귀 함수를 사용하면 각 분기(가지)를 순회할 수 있습니다. diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md index ce8317e02c..8487ca3cb3 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -23,7 +23,11 @@ function sum(a, b) { alert( sum(1, 2, 3, 4, 5) ); ``` +<<<<<<< HEAD 함수를 정의할 땐 인수를 두 개만 받도록 하고, 실제 함수를 호출할 땐 이보다 더 많은 '여분의' 인수를 전달했지만, 에러가 발생하지 않았습니다. 다만 반환 값은 처음 두 개의 인수만을 사용해 계산됩니다. +======= +There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted, so the result in the code above is `3`. +>>>>>>> upstream/master 이렇게 여분의 매개변수는 그 값들을 담을 배열 이름을 마침표 세 개 `...`뒤에 붙여주면 함수 선언부에 포함시킬 수 있습니다. 이때 마침표 세 개 `...`는 "남아있는 매개변수들을 한데 모아 배열에 집어넣어라."는 것을 의미합니다. @@ -225,7 +229,11 @@ alert( Array.from(str) ); // H,e,l,l,o 이런 이유때문에 무언가를 배열로 바꿀 때는 전개 구문보다 `Array.from`이 보편적으로 사용됩니다. +<<<<<<< HEAD ## 배열과 객체의 복사본 만들기 +======= +## Copy an array/object +>>>>>>> upstream/master [참조에 의한 객체 복사](info:object-copy#cloning-and-merging-object-assign) 챕터에서 `Object.assign()`을 사용해 객체를 복사한 예시를 떠올려봅시다. @@ -233,8 +241,16 @@ alert( Array.from(str) ); // H,e,l,l,o ```js run let arr = [1, 2, 3]; +<<<<<<< HEAD let arrCopy = [...arr]; // 배열을 펼쳐서 각 요소를 분리후, 매개변수 목록으로 만든 다음에 // 매개변수 목록을 새로운 배열에 할당함 +======= + +*!* +let arrCopy = [...arr]; // spread the array into a list of parameters + // then put the result into a new array +*/!* +>>>>>>> upstream/master // 배열 복사본의 요소가 기존 배열 요소와 진짜 같을까요? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true @@ -252,8 +268,16 @@ alert(arrCopy); // 1, 2, 3 ```js run let obj = { a: 1, b: 2, c: 3 }; +<<<<<<< HEAD let objCopy = { ...obj }; // 객체를 펼쳐서 각 요소를 분리후, 매개변수 목록으로 만든 다음에 // 매개변수 목록을 새로운 객체에 할당함 +======= + +*!* +let objCopy = { ...obj }; // spread the object into a list of parameters + // then return the result in a new object +*/!* +>>>>>>> upstream/master // 객체 복사본의 프로퍼티들이 기존 객체의 프로퍼티들과 진짜 같을까요? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true @@ -267,7 +291,11 @@ alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3} ``` +<<<<<<< HEAD 이렇게 전개 구문을 사용하면 `let objCopy = Object.assign({}, obj);`, `let arrCopy = Object.assign([], arr);`보다 더 짧은 코드로 배열이나 객체를 복사할 수 있어서 사람들은 이 방법을 선호합니다. +======= +This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj)` or for an array `let arrCopy = Object.assign([], arr)` so we prefer to use it whenever we can. +>>>>>>> upstream/master ## 요약 diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg deleted file mode 100644 index c0a312ec7f..0000000000 --- a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg +++ /dev/null @@ -1 +0,0 @@ -outeri: 0i: 1i: 2i: 10...makeArmy() LexicalEnvironmentfor block LexicalEnvironment \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md index 0fb0b4a49a..9d99aa7178 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md @@ -1,12 +1,12 @@ -Let's examine what's done inside `makeArmy`, and the solution will become obvious. +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 in the loop via `shooters.push(function...)`. +2. Fills it with functions via `shooters.push(function)` in the loop. Every element is a function, so the resulting array looks like this: @@ -26,95 +26,104 @@ Let's examine what's done inside `makeArmy`, and the solution will become obviou ``` 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) -Then, later, the call to `army[5]()` will get the element `army[5]` from the array (it will be a function) and call it. - -Now why all such functions show the same? - -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. - -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 - }; - ... - } - ... -} -``` - -...We can see that it lives in the lexical environment associated with the current `makeArmy()` run. But when `army[5]()` is called, `makeArmy` has already finished its job, and `i` has the last value: `10` (the end of `while`). - -As a result, all `shooter` functions get from the outer lexical envrironment the same, last value `i=10`. - -We can fix it by moving the variable definition into the loop: - -```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 -``` - -Now it works correctly, because every time the code block in `for (let i=0...) {...}` is executed, a new Lexical Environment is created for it, with the corresponding variable `i`. - -So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. That's why now it works. - -![](lexenv-makearmy.svg) - -Here we rewrote `while` into `for`. - -Another trick could be possible, let's see it for better understanding of the subject: - -```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, 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? -army[0](); // 0 -army[5](); // 5 -``` +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. -The `while` loop, just like `for`, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a `shooter`. +Besides, there are indeed cases when one prefers `while` to `for`, and other scenarios, where such problems are real. -We copy `let j = i`. This makes a loop body local `j` and copies the value of `i` to it. Primitives are copied "by value", so we actually get a complete independent copy of `i`, belonging to the current loop iteration. diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/task.md b/1-js/06-advanced-functions/03-closure/10-make-army/task.md index 63275859a0..1ce14d497b 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/task.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/task.md @@ -14,22 +14,42 @@ function makeArmy() { let i = 0; while (i < 10) { +<<<<<<< HEAD let shooter = function() { // shooter 함수 alert( i ); // 몇 번째 shooter인지 출력해줘야 함 +======= + let shooter = function() { // create a shooter function, + alert( i ); // that should show its number +>>>>>>> upstream/master }; - shooters.push(shooter); + shooters.push(shooter); // and add it to the array i++; } + // ...and return the array of shooters return shooters; } let army = makeArmy(); +<<<<<<< HEAD army[0](); // 0번째 shooter가 10을 출력함 army[5](); // 5번째 shooter 역시 10을 출력함 // 모든 shooter가 자신의 번호 대신 10을 출력하고 있음 ``` 왜 모든 shooter가 동일한 숫자를 출력하는 걸까요? 제대로 된 번호가 출력될 수 있게 코드를 수정해 보세요. +======= +*!* +// all shooters show 10 instead of their numbers 0, 1, 2, 3... +army[0](); // 10 from the shooter number 0 +army[1](); // 10 from the shooter number 1 +army[2](); // 10 ...and so on. +*/!* +``` + +Why do all of the shooters show the same value? + +Fix the code so that they work as intended. +>>>>>>> upstream/master diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md index 93a67d7d23..9b255dc38f 100644 --- a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md +++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md @@ -1,5 +1,11 @@ +importance: 5 +<<<<<<< HEAD # if 문 안의 함수 +======= +--- +# Function in if +>>>>>>> upstream/master 아래 예시의 실행 결과를 예측해보세요. diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md index 7ff607f201..490d8ec50b 100644 --- a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -26,9 +26,15 @@ func(); ```js function func() { *!* +<<<<<<< HEAD // 엔진은 함수가 시작될 때 로컬 변수 x의 존재를 알고 있지만 // let 문이 실행될 때까지 x는 '초기화되지 않은' 상태(dead zone)이기 때문에 // 에러가 발생합니다. +======= + // the local variable x is known to the engine from the beginning of the function, + // but "uninitialized" (unusable) until let ("dead zone") + // hence the error +>>>>>>> upstream/master */!* console.log(x); // ReferenceError: Cannot access 'x' before initialization diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js index e3c335e03a..802f28c4d8 100644 --- a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js @@ -23,7 +23,7 @@ describe("byField", function(){ { name: "John", age: 20, surname: "Johnson"}, ]; let ageSortedAnswer = users.sort(byField("age")); - assert.deepEqual(ageSortedKey, ageSortedKey); + assert.deepEqual(ageSortedKey, ageSortedAnswer); }); it("sorts users by surname", function(){ diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 225e510094..5e9d7aefe1 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -7,7 +7,11 @@ 그런데 함수가 생성된 이후에 외부 변수가 변경되면 어떤 일이 발생할까요? 함수는 새로운 값을 가져올까요? 아니면 생성 시점 이전의 값을 가져올까요? +<<<<<<< HEAD 매개변수를 통해 함수를 넘기고 이 함수를 저 멀리 떨어진 코드에서 호출할 땐 어떤 일이 발생할까요? 함수는 호출되는 곳을 기준으로 외부 변수에 접근할까요? +======= +And what if a function is passed along as an argument and called from another place of code, will it get access to outer variables at the new place? +>>>>>>> upstream/master 이젠 이런 간단한 시나리오부터 시작해 좀 더 복잡한 시나리오를 다룰 수 있도록 지식을 확장해 봅시다. @@ -146,7 +150,11 @@ alert( counter() ); // 2 그런데 `makeCounter`를 살펴보다 보면 "`counter`를 여러 개 만들었을 때, 이 함수들은 서로 독립적일까? 함수와 중첩 함수 내 `count` 변수엔 어떤 값이 할당될까?" 같은 의문이 들기 마련입니다. +<<<<<<< HEAD 이런 의문을 해결할 수 있게 되면 자바스크립트 숙련도가 크게 올라갑니다. 좀 더 복잡한 시나리오도 다룰 수 있게 되죠. 자, 긴말할 필요 없이 바로 시작해봅시다. +======= +Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. +>>>>>>> upstream/master ## 렉시컬 환경 @@ -314,7 +322,11 @@ let counter = makeCounter(); 함수 호출이 끝나면 함수에 대응하는 렉시컬 환경이 메모리에서 제거됩니다. 함수와 관련된 변수들은 이때 모두 사라지죠. 함수 호출이 끝나면 관련 변수를 참조할 수 없는 이유가 바로 여기에 있습니다. 자바스크립트에서 모든 객체는 도달 가능한 상태일 때만 메모리에 유지됩니다. +<<<<<<< HEAD 그런데 호출이 끝난 후에도 여전히 도달 가능한 중첩 함수가 있을 수 있습니다. 이때는 이 중첩함수의 `[[Environment]]` 프로퍼티에 외부 함수 렉시컬 환경에 대한 정보가 저장됩니다. 도달 가능한 상태가 되는 것이죠. +======= +However, if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment. +>>>>>>> upstream/master 함수 호출은 끝났지만 렉시컬 환경이 메모리에 유지되는 이유는 바로 이 때문입니다. @@ -333,7 +345,11 @@ let g = f(); // g.[[Environment]]에 f() 호출 시 만들어지는 // 렉시컬 환경 정보가 저장됩니다. ``` +<<<<<<< HEAD 그런데 이렇게 중첩함수를 사용할 때는 주의할 점이 있습니다. `f()`를 여러 번 호출하고 그 결과를 어딘가에 저장하는 경우, 호출 시 만들어지는 각 렉시컬 환경 모두가 메모리에 유지된다는 점입니다. 아래 예시를 실행하면 3개의 렉시컬 환경이 만들어지는데, 각 렉시컬 환경은 메모리에서 삭제되지 않습니다. +======= +Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. In the code below, all 3 of them: +>>>>>>> upstream/master ```js function f() { @@ -371,7 +387,11 @@ g = null; // 도달할 수 없는 상태가 되었으므로 메모리에서 삭 그러나 실제로는 자바스크립트 엔진이 이를 지속해서 최적화합니다. 자바스크립트 엔진은 변수 사용을 분석하고 외부 변수가 사용되지 않는다고 판단되면 이를 메모리에서 제거합니다. +<<<<<<< HEAD **디버깅 시, 최적화 과정에서 제거된 변수를 사용할 수 없다는 점은 V8 엔진(Chrome, Edge, Opera에서 쓰임)의 주요 부작용입니다.** +======= +**An important side effect in V8 (Chrome, Edge, Opera) is that such variable will become unavailable in debugging.** +>>>>>>> upstream/master Chrome 브라우저에서 개발자 도구를 열고 아래의 코드를 실행해봅시다. @@ -413,6 +433,12 @@ let g = f(); g(); ``` +<<<<<<< HEAD 이런 V8만의 부작용을 미리 알아 놓는 것이 좋습니다. Chrome이나 Edge, Opera에서 디버깅하는 경우라면 언젠간 이 이슈를 만나게 될 테니까요. 이 부작용은 버그라기보다는 V8만의 특별한 기능이라고 생각하시면 될 것 같습니다. 미래에 이 기능은 변경될 수 있습니다. 최적화 과정에서 외부 변수가 어떻게 처리되었는지 확인하고 싶다면 이 페이지로 돌아오셔서 예시를 실행해 보세요. +======= +This feature of V8 is good to know. If you are debugging with Chrome/Edge/Opera, sooner or later you will meet it. + +That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You can always check for it by running the examples on this page. +>>>>>>> upstream/master diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index eeeffc5359..74adb83aab 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -4,7 +4,11 @@ ```smart header="오래된 스크립트를 읽는 데 도움을 주는 글입니다." 이번 주제에선 작성된 지 오래된 스크립트를 읽는 데 도움을 줄 만한 내용을 다룹니다. +<<<<<<< HEAD 새로운 코드를 작성할 때는 이 방법을 쓰시면 안 됩니다. +======= +That's not how we write new code. +>>>>>>> upstream/master ``` [변수](info:variables)를 다룬 첫 번째 장에서 변수 선언 방법 세 가지를 배운 바 있습니다. @@ -28,7 +32,11 @@ alert(message); // 안녕하세요. ## var는 블록 스코프가 없습니다. +<<<<<<< HEAD `var`로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프입니다. 블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근 가능합니다. +======= +Variables, declared with `var`, are either function-scoped or global-scoped. They are visible through blocks. +>>>>>>> upstream/master 예시: @@ -52,19 +60,25 @@ if (true) { } *!* -alert(test); // Error: test is not defined +alert(test); // ReferenceError: test is not defined */!* ``` 반복문에서도 유사한 일이 일어납니다. `var`는 블록이나 루프 수준의 스코프를 형성하지 않기 때문이죠. -```js +```js run for (var i = 0; i < 10; i++) { + var one = 1; // ... } *!* +<<<<<<< HEAD alert(i); // 10, 반복문이 종료되었지만 'i'는 전역 변수이므로 여전히 접근 가능합니다. +======= +alert(i); // 10, "i" is visible after loop, it's a global variable +alert(one); // 1, "one" is visible after loop, it's a global variable +>>>>>>> upstream/master */!* ``` @@ -80,10 +94,17 @@ function sayHi() { } sayHi(); +<<<<<<< HEAD alert(phrase); // Error: phrase is not defined ``` 위에서 살펴본 바와 같이, `var`는 `if`, `for` 등의 코드 블록을 관통합니다. 아주 오래전의 자바스크립트에선 블록 수준 렉시컬 환경이 만들어 지지 않았기 때문입니다. `var`는 구식 자바스크립트의 잔재이죠. +======= +alert(phrase); // ReferenceError: phrase is not defined +``` + +As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript, blocks had no Lexical Environments, and `var` is a remnant of that. +>>>>>>> upstream/master ## var는 변수의 중복 선언을 허용합니다 @@ -168,7 +189,7 @@ sayHi(); ```js run function sayHi() { - alert(phrase); + alert(phrase); *!* var phrase = "Hello"; @@ -203,11 +224,19 @@ sayHi(); 이처럼 모든 `var` 선언은 함수 시작 시 처리되기 때문에, `var`로 선언한 변수는 어디서든 참조할 수 있습니다. 하지만 변수에 무언가를 할당하기 전까진 값이 undefined이죠. +<<<<<<< HEAD 바로 위의 두 예시에서 `alert`를 호출하기 전에 변수 `phrase`는 선언이 끝난 상태이기 때문에 에러 없이 얼럿 창이 뜹니다. 그러나 값이 할당되기 전이기 때문에 얼럿 창엔 `undefined`가 출력되죠. ### 즉시 실행 함수 표현식 과거엔 `var`만 사용할 수 있었습니다. 그런데 `var`의 스코프는 블록 레벨 수준이 아니죠. 개발자들은 `var`도 블록 레벨 스코프를 가질 수 있게 여러가지 방안을 고려하게 됩니다. 이때 만들어진 것이 '즉시 실행 함수 표현식(immediately-invoked function expressions)'입니다. 즉시 실행 함수 표현식은 `IIFE`라고 부르기도 합니다. +======= +In both examples above, `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`. + +## IIFE + +In the past, as there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE). +>>>>>>> upstream/master 즉시 실행 함수 표현식을 요즘에는 자주 쓰지 않습니다. 하지만 오래된 스크립트에서 만날 수 있기 때문에 즉시 실행 함수 표현식이 무엇인지 알아 둘 필요가 있습니다. @@ -216,13 +245,14 @@ IIFE는 다음과 같이 생겼습니다. ```js run (function() { - let message = "Hello"; + var message = "Hello"; alert(message); // Hello })(); ``` +<<<<<<< HEAD 함수 표현식이 만들어지고 바로 호출되면서, 해당 함수가 바로 실행되었습니다. 이 함수는 자신만의 변수를 갖고있네요. 즉시 실행 함수를 만들 땐, 함수 표현식을 괄호로 둘러쌓아 (function {...})와 같은 형태로 만듭니다. 이렇게 괄호로 둘러싸지 않으면 에러가 발생합니다. 자바스크립트는 'function'이라는 키워드를 만나면 함수 선언문이 시작될 것이라 예상합니다. 그런데 함수 선언문으로 함수를 만들 땐 반드시 함수의 이름이 있어야 합니다. 따라서 아래와 예시를 실행하면 에러가 발생합니다. @@ -230,8 +260,17 @@ IIFE는 다음과 같이 생겼습니다. ```js run // 함수를 선언과 동시에 실행하려고 함 function() { // <-- Error: Function statements require a function name +======= +Here, a Function Expression is created and immediately called. So the code executes right away and has its own private variables. - let message = "Hello"; +The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript engine encounters `"function"` in the main code, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error: + +```js run +// Tries to declare and immediately call a function +function() { // <-- SyntaxError: Function statements require a function name +>>>>>>> upstream/master + + var message = "Hello"; alert(message); // Hello @@ -254,12 +293,21 @@ function go() { ```js run // IIFE를 만드는 방법 +<<<<<<< HEAD (function() { alert("함수를 괄호로 둘러싸기"); }*!*)*/!*(); (function() { alert("전체를 괄호로 둘러싸기"); +======= +*!*(*/!*function() { + alert("Parentheses around the function"); +}*!*)*/!*(); + +*!*(*/!*function() { + alert("Parentheses around the whole thing"); +>>>>>>> upstream/master }()*!*)*/!*; *!*!*/!*function() { @@ -277,8 +325,13 @@ function go() { `var`로 선언한 변수는 `let`이나 `const`로 선언한 변수와 다른 두 가지 주요한 특성을 보입니다. +<<<<<<< HEAD 1. `var`로 선언한 변수는 블록 스코프가 아닌 함수 수준 스코프를 갖습니다. 2. `var` 선언은 함수가 시작되는 시점(전역 공간에선 스크립트가 시작되는 시점)에서 처리됩니다. +======= +1. `var` variables have no block scope, their visibility is scoped to current function, or global, if declared outside function. +2. `var` declarations are processed at function start (script start for globals). +>>>>>>> upstream/master 이 외에도 전역 객체와 관련된 특성 하나가 더 있는데, 이에 대해선 다음 챕터에서 다루도록 하겠습니다. diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md index dc79a6caf8..b8a446ce27 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -5,7 +5,11 @@ 브라우저 환경에선 전역 객체를 `window`, Node.js 환경에선 `global`라고 부르는데, 각 호스트 환경마다 부르는 이름은 다릅니다. +<<<<<<< HEAD 전역 객체의 이름을 `globalThis`로 표준화하자는 내용이 최근에 자바스크립트 명세에 추가되었기 때문에 모든 호스트 환경이 이를 따라야 하죠. Chromium 기반이 아닌 몇몇 브라우저는 아직 `globalThis`를 지원하진 않지만, 이에 대한 폴리필(polyfill)을 쉽게 만들 수 있습니다. +======= +Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers. +>>>>>>> upstream/master 본 튜토리얼은 브라우저 환경에서 구동되기 때문에 `window`라는 전역 객체를 사용하도록 하겠습니다. 다른 호스트 환경에서 작업하고 계신다면 `window`대신 `globalThis`를 사용하시면 됩니다. @@ -25,7 +29,13 @@ var gVar = 5; alert(window.gVar); // 5 (var로 선언한 변수는 전역 객체 window의 프로퍼티가 됩니다) ``` +<<<<<<< HEAD 하위 호환성 때문에 이런 방식으로 전역 객체를 사용해도 동작은 하지만, 이 방법은 쓰지 않으시길 바랍니다. [모듈](info:modules)을 사용하는 모던 자바스크립트는 이런 방식을 지원하지 않습니다. +======= +Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions). + +Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such a thing doesn't happen. +>>>>>>> upstream/master `var` 대신 `let`을 사용하면 위 예시와는 달리 전역 객체를 통해 변수에 접근할 수 없습니다. @@ -81,7 +91,14 @@ if (!window.Promise) { 전역 객체엔 `Array`와 같은 내장 객체, `window.innerHeight`(뷰포트의 높이를 반환함)같은 브라우저 환경 전용 변수 등이 저장되어 있습니다. - 전역 객체는 `globalThis`라는 보편적인 이름으로 불립니다. +<<<<<<< HEAD 하지만 '관습'에 따라 브라우저에서는 `window`, Node.js에서는 `global`이라는 이름으로 불릴 때가 많습니다. `globalThis`는 제안 목록에 추가 된 지 얼마 안 된 기능이기 때문에, 비 크로미움 기반 브라우저에선 지원하지 않습니다(폴리필을 구현하면 사용할 수 있습니다). - 프로젝트 전체에서 꼭 필요한 변수만 전역 객체에 저장하도록 하고, 전역 변수는 가능한 한 최소한으로 사용합시다. - [모듈](info:modules)을 사용하고 있지 않다면, 브라우저에서 `var`로 선언한 전역 변수는 전역 객체의 프로퍼티가 됩니다. - 이해하기 쉽고 요구사항 변경에 쉽게 대응할 수 있는 코드를 구현하려면, `window.x`처럼 전역 객체의 프로퍼티에 직접 접근합시다. +======= + ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). +- We should store values in the global object only if they're truly global for our project. And keep their number at minimum. +- In-browser, unless we're using [modules](info:modules), global functions and variables declared with `var` become a property of the global object. +- To make our code future-proof and easier to understand, we should access properties of the global object directly, as `window.x`. +>>>>>>> upstream/master diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md index 8c12729c51..cb3aeef838 100644 --- a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md @@ -5,7 +5,7 @@ 답안은 아래와 같습니다. -```js run +```js demo run function sum(a) { let currentSum = a; @@ -52,4 +52,8 @@ function f(b) { } ``` +<<<<<<< HEAD 이렇게 자기 자신을 호출하지 않고 반환만 하면 다음 호출에서 함수 `f`를 사용할 수 있고, 자기 자신을 또다시 반환해 원하는 만큼 이 과정을 반복할 수 있습니다. `toString` 은 `currentSum` 을 반환해주므로 반환된 함수(객체)를 숫자 혹은 문자열로도 사용할 수 있죠. `Symbol.toPrimitive`나 `valueOf`를 사용해 객체를 숫자나 문자열로 변환할 수도 있습니다. +======= +This `f` will be used in the next call, again return itself, as many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. +>>>>>>> upstream/master diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md index d2a7895892..17b5c71922 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -326,7 +326,11 @@ welcome(); // Hello, Guest (중첩 호출이 제대로 동작함) `"func"`이라는 이름은 함수 지역 수준(function-local)에 존재하므로 외부 렉시컬 환경에서 찾지 않아도 됩니다. 외부 렉시컬 환경에선 보이지도 않죠. 함수 표현식에 붙인 이름은 현재 함수만 참조하도록 명세서에 정의되어있기 때문입니다. +<<<<<<< HEAD 이렇게 기명 함수 표현식을 이용하면 `sayHi`나 `welcome` 같은 외부 변수의 변경과 관계없이 `func`이라는 '내부 함수 이름'을 사용해 언제든 함수 표현식 내부에서 자기 자신을 호출할 수 있습니다. +======= +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to call itself reliably. +>>>>>>> upstream/master ```smart header="함수 선언문엔 내부 이름을 지정할 수 없습니다." 지금까지 살펴본 '내부 이름'은 함수 표현식에만 사용할 수 있고, 함수 선언문엔 사용할 수 없습니다. 함수 선언문엔 '내부' 이름을 지정할 수 있는 문법이 없습니다. @@ -347,7 +351,11 @@ welcome(); // Hello, Guest (중첩 호출이 제대로 동작함) 함수엔 다양한 프로퍼티를 추가할 수 있습니다. 널리 쓰이는 자바스크립트 라이브러리 상당수에서 이런 커스텀 프로퍼티를 잘 활용하고 있습니다. +<<<<<<< HEAD 이런 라이브러리들은 '주요' 함수 하나를 만들고 여기에 다양한 '헬퍼' 함수를 붙이는 식으로 구성됩니다. [jQuery](https://jquery.com)는 이름이 `$`인 주요 함수로 이루어져 있습니다. [lodash](https://lodash.com)는 주요 함수 `_`에 `_.clone`, `_.keyBy`등의 프로퍼티를 추가하는 식으로 구성되죠. 자세한 정보는 lodash [공식 문서](https://lodash.com/docs)에서 찾아볼 수 있습니다. 이렇게 함수 하나에 다양한 헬퍼 함수를 붙여 라이브러리를 만들면 라이브러리 하나가 전역 변수 하나만 사용하므로 전역 공간을 더럽히지 않는다는 장점이 있습니다. 이름 충돌도 방지할 수 있죠. +======= +They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want to learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. +>>>>>>> upstream/master 이렇게 객체로서의 함수 특징을 이용해 커스텀 프로퍼티를 만들면 함수는 자기 자신을 이용해 원하는 일을 수행할 수 있고, 함수 자기 자신에 딸린 프로퍼티의 기능도 사용할 수 있다는 장점이 있습니다. diff --git a/1-js/06-advanced-functions/07-new-function/article.md b/1-js/06-advanced-functions/07-new-function/article.md index 5e02efb18b..011d028244 100644 --- a/1-js/06-advanced-functions/07-new-function/article.md +++ b/1-js/06-advanced-functions/07-new-function/article.md @@ -92,7 +92,7 @@ getFunc()(); // getFunc의 렉시컬 환경에 있는 값 *!*"test"*/!*가 출 그런데 스크립트가 프로덕션 서버에 반영되기 전, *압축기(minifier)* 에 의해 압축될 때 문제가 발생합니다. 압축기는 스크립트에서 주석이나 여분의 공백 등을 없애 코드 크기를 줄여주는 특수한 프로그램인데 압축기가 지역 변수 이름을 짧게 바꾸면서 문제가 발생하죠. -구체적으로 어떤 부분이 문제가 되는지 예시를 통해 알아봅시다. 함수 내부에 `let userName`라는 변수가 있으면 이 지역변수는 압축기에 의해 `let a` 등(짧은 이름)으로 대체되는데, 이때 `userName` 모두가 `a`로 교체됩니다. `userName`은 지역변수이고, 함수 외부에선 함수 내부에 있는 변수에 접근할 수 없기 때문에 이렇게 해도 전혀 문제가 없죠. 압축기는 단순히 변수를 찾아서 바꾸지 않고 코드 구조를 분석해 기존에 작성한 코드의 기능을 망가뜨리지 않으면서 영리하게 제 역할을 수행합니다. +구체적으로 어떤 부분이 문제가 되는지 예시를 통해 알아봅시다. 함수 내부에 `let userName`라는 변수가 있으면 이 지역변수는 압축기에 의해 `let a` 등(해당 글자가 이미 사용 중이라면 다른 짧은 이름)으로 대체되는데, 이때 `userName` 모두가 `a`로 교체됩니다. `userName`은 지역변수이고, 함수 외부에선 함수 내부에 있는 변수에 접근할 수 없기 때문에 이렇게 해도 전혀 문제가 없죠. 압축기는 단순히 찾아바꾸기가 아니라 코드 구조를 분석해 기존 코드의 기능을 망가뜨리지 않으면서 영리하게 제 역할을 수행합니다. 이런 동작 방식 때문에 `new Function` 문법으로 만든 함수 내부에서 외부 변수에 접근하려고 하면 `userName`은 이미 이름이 변경되었기 때문에 찾을 수 없게 됩니다. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index b44ebbcad2..65f4e55721 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -27,7 +27,7 @@ let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) : 실행 전 대기 시간으로, 단위는 밀리초(millisecond, 1000밀리초 = 1초)이며 기본값은 0입니다. `arg1`, `arg2`... -: 함수에 전달할 인수들로, IE9 이하에선 지원하지 않습니다. +: 함수에 전달할 인수입니다. 예시를 통해 `setTimeout`을 어떻게 쓸 수 있는지 알아봅시다. 아래 코드를 실행하면 1초 후에 `sayHi()`가 호출됩니다. @@ -102,7 +102,7 @@ alert(timerId); // 위 타이머 식별자와 동일함 (취소 후에도 식별 다시 한번 말씀드리자면, 스케줄링에 관한 명세는 따로 존재하지 않습니다. 명세가 없기 때문에 호스트 환경마다 약간의 차이가 있을 수밖에 없습니다. -참고로 브라우저는 HTML5의 [timers section](https://www.w3.org/TR/html5/webappapis.html#timers)을 준수하고 있습니다. +참고로 브라우저는 HTML Living Standard의 [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers)을 준수하고 있습니다. ## setInterval @@ -218,7 +218,7 @@ setTimeout(function run() { ![](settimeout-interval.svg) -**중첩 `setTimeout`을 사용하면 명시한 지연(여기서는 100ms)이 보장됩니다.** +**중첩 `setTimeout`을 사용하면 이전 호출이 끝난 후 다음 호출이 시작되기까지 명시한 지연(여기서는 100ms)이 보장됩니다.** 이렇게 지연 간격이 보장되는 이유는 이전 함수의 실행이 종료된 이후에 다음 함수 호출에 대한 계획이 세워지기 때문입니다. @@ -255,8 +255,8 @@ alert("Hello"); 대기 시간이 0인 setTimeout을 활용한 브라우저 환경에서의 유스 케이스는 에서 자세히 다루도록 하겠습니다. -````smart header="브라우저 환경에서 실제 대기 시간은 0이 아닙니다." -브라우저는 [HTML5 표준](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers)에서 정한 중첩 타이머 실행 간격 관련 제약을 준수합니다. 해당 표준엔 "다섯 번째 중첩 타이머 이후엔 대기 시간을 최소 4밀리초 이상으로 강제해야 한다."라는 제약이 명시되어있습니다. +````smart header="실제로 지연시간이 0인 경우는 없습니다(브라우저 환경)." +브라우저는 [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers)에서 정한 중첩 타이머 실행 간격 관련 제약을 준수합니다. 해당 표준엔 "다섯 번째 중첩 타이머 이후엔 대기 시간을 최소 4밀리초 이상으로 강제해야 한다."라는 제약을 명시합니다. 예시를 보며 이 제약 사항을 이해해봅시다. 예시 내 `setTimeout`은 지연 없이 함수 run을 다시 호출할 수 있게 스케줄링 되어 있습니다. 배열 `times`에는 실제 지연 간격에 대한 정보가 기록되도록 해놓았는데, 배열 times에 어떤 값이 저장되는지 알아봅시다. @@ -281,13 +281,13 @@ setTimeout(function run() { 이는 오래전부터 있던 제약인데, 구식 스크립트 중 일부는 아직 이 제약에 의존하는 경우가 있어서 명세서를 변경하지 못하고 있는 상황입니다. -한편, 서버 측엔 이런 제약이 없습니다. Node.js의 [process.nextTick](https://nodejs.org/api/process.html)과 [setImmediate](https://nodejs.org/api/timers.html)를 이용하면 비동기 작업을 지연 없이 실행할 수 있습니다. 위에서 언급된 제약은 브라우저에 한정됩니다. +한편, 서버 측엔 이런 제약이 없습니다. Node.js의 [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args)와 같은 방법으로 비동기 작업을 즉시 실행할 수 있습니다. 위에서 언급된 제약은 브라우저에 한정합니다. ```` ## 요약 - `setInterval(func, delay, ...args)`과 `setTimeout(func, delay, ...args)`은 `delay`밀리초 후에 `func`을 규칙적으로, 또는 한번 실행하도록 해줍니다. -- `setInterval·setTimeout`을 호출하고 반환받은 값을 `clearInterval·clearTimeout`에 넘겨주면 스케줄링을 취소할 수 있습니다. +- `setTimeout·setInterval`을 호출하고 반환받은 값을 `clearTimeout·clearInterval`에 넘겨주면 스케줄링을 취소할 수 있습니다. - 중첩 `setTimeout`을 사용하면 `setInterval`을 사용한 것보다 유연하게 코드를 작성할 수 있습니다. 여기에 더하여 *지연 간격* 보장이라는 장점도 있습니다. - 대기 시간이 0인 setTimeout(`setTimeout(func, 0)` 혹은 `setTimeout(func)`)을 사용하면 '현재 스크립트의 실행이 완료된 후 가능한 한 빠르게' 원하는 함수를 호출할 수 있습니다. - 지연 없이 중첩 `setTimeout`을 5회 이상 호출하거나 지연 없는 `setInterval`에서 호출이 5회 이상 이뤄지면, 4밀리초 이상의 지연 간격이 강제로 더해집니다. 이는 브라우저에만 적용되는 사항이며, 하위 호환성을 위해 유지되고 있습니다. @@ -297,6 +297,6 @@ setTimeout(function run() { 아래와 같은 상황에서 브라우저 내 타이머가 느려지면 지연 간격이 보장되지 않습니다. - CPU가 과부하 상태인 경우 - 브라우저 탭이 백그라운드 모드인 경우 -- 노트북이 배터리에 의존해서 구동 중인 경우 +- 노트북이 배터리 절약 모드인 경우 이런 상황에서 타이머의 최소 지연 시간은 300밀리초에서 심하면 1,000밀리초까지 늘어납니다. 연장 시간은 브라우저나 구동 중인 운영 체제의 성능 설정에 따라 다릅니다. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 347a5e64f2..5b0fcc5f87 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -21,9 +21,9 @@ Here's the code for it (uses the debounce decorator from the [Lodash library](ht ```js let f = _.debounce(alert, 1000); -f("a"); +f("a"); setTimeout( () => f("b"), 200); -setTimeout( () => f("c"), 500); +setTimeout( () => f("c"), 500); // debounced function waits 1000ms after the last call and then runs: alert("c") ``` diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index cf851f771f..6950664be1 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -12,11 +12,10 @@ function throttle(func, ms) { savedThis = this; return; } + isThrottled = true; func.apply(this, arguments); // (1) - isThrottled = true; - setTimeout(function() { isThrottled = false; // (3) if (savedArgs) { diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index 6df7af1327..cbd4731960 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -8,7 +8,7 @@ Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. -The difference with debounce is that it's completely different decorator: +Compared to the debounce decorator, the behavior is completely different: - `debounce` runs the function once after the "cooldown" period. Good for processing the final result. - `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index 78b9b05dff..8e0c5bdcfb 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -36,11 +36,11 @@ function cachingDecorator(func) { slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1)이 저장되었습니다. -alert( "다시 호출: " + slow(1) ); // 동일한 결과 +alert( slow(1) ); // slow(1)를 캐싱하고 결과를 반환합니다. +alert( "다시 호출: " + slow(1) ); // 캐시에서 slow(1)의 결과를 가져옵니다. -alert( slow(2) ); // slow(2)가 저장되었습니다. -alert( "다시 호출: " + slow(2) ); // 윗줄과 동일한 결과 +alert( slow(2) ); // slow(2)를 캐싱하고 결과를 반환합니다. +alert( "다시 호출: " + slow(2) ); // 캐시에서 slow(2)의 결과를 가져옵니다. ``` `cachingDecorator`같이 인수로 받은 함수의 행동을 변경시켜주는 함수를 *데코레이터(decorator)* 라고 부릅니다. @@ -301,17 +301,17 @@ func.apply(context, args) 따라서 아래 코드 두 줄은 거의 같은 역할을 합니다. ```js -func.call(context, ...args); // 전개 구문을 사용해 인수가 담긴 배열을 전달하는 것과 -func.apply(context, args); // call을 사용하는 것은 동일합니다. +func.call(context, ...args); +func.apply(context, args); ``` -그런데 약간의 차이가 있긴 합니다. +위 코드는 `func`을 동일한 컨텍스트와 인수로 호출합니다. + +그런데 `args`에 관해 약간의 차이가 있긴 합니다. - 전개 구문 `...`은 *이터러블* `args`을 분해 해 `call`에 전달할 수 있도록 해줍니다. - `apply`는 오직 *유사 배열* 형태의 `args`만 받습니다. -이 차이만 빼면 두 메서드는 완전히 동일하게 동작합니다. 인수가 이터러블 형태라면 `call`을, 유사 배열 형태라면 `apply`를 사용하면 됩니다. - 배열같이 이터러블이면서 유사 배열인 객체엔 둘 다를 사용할 수 있는데, 대부분의 자바스크립트 엔진은 내부에서 `apply`를 최적화 하기 때문에 `apply`를 사용하는 게 좀 더 빠르긴 합니다. 이렇게 컨텍스트와 함께 인수 전체를 다른 함수에 전달하는 것을 *콜 포워딩(call forwarding)* 이라고 합니다. diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md index 36a6b6bdea..8372c465a7 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md @@ -1,5 +1,5 @@ -에러는 `ask`가 함수 `loginOk`, `loginFail`을 객체 없이 가지고 오기 때문에 발생합니다. +에러는 `askPassword`가 함수 `loginOk`, `loginFail`을 객체 없이 가지고 오기 때문에 발생합니다. ask는 `loginOk`, `loginFail`을 호출할 때 `this=undefined`라고 자연스레 가정합니다. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 2d87339856..91832c4da1 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -123,7 +123,7 @@ funcUser(); // John */!* ``` -여기서 `func.bind(user)`는 `func`의 `this`를 `user`로 '바인딩한 변형'이라고 생각하시면 됩니다. +여기서 `func.bind(user)`는 `this=user`로 고정된 `func`의 '바인딩한 변형'이라고 생각하면 됩니다. 인수는 원본 함수 `func`에 '그대로' 전달됩니다. @@ -200,7 +200,7 @@ for (let key in user) { } ``` -자바스크립트 라이브러리를 사용해도 대규모 바인딩을 할 수 있습니다. lodash 라이브러리의 [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll)이 그 예입니다. +자바스크립트 라이브러리를 사용해도 대규모 바인딩을 할 수 있습니다. lodash 라이브러리의 [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll)이 그 예입니다. ```` ## 부분 적용 @@ -245,7 +245,7 @@ alert( double(5) ); // = mul(2, 5) = 10 이런 방식을 [부분 적용(partial application)](https://en.wikipedia.org/wiki/Partial_application)이라고 부릅니다. 부분 적용을 사용하면 기존 함수의 매개변수를 고정하여 새로운 함수를 만들 수 있습니다. -위 예시에선 `this`를 사용하지 않았다는 점에 주목하시기 바랍니다. `bind`엔 컨텍스트를 항상 넘겨줘야 하므로 `null`을 사용했습니다. +위 예시에선 `this`를 사용하지 않았다는 점에 주목하기 바랍니다. `bind`엔 컨텍스트를 항상 넘겨줘야 하므로 `null`을 사용했습니다. 부분 적용을 사용해 3을 곱해주는 함수 `triple`을 만들어보겠습니다. diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md index e1ec18ce3e..e159ee3742 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -51,8 +51,8 @@ let group = { showList() { *!* this.students.forEach(function(student) { - // TypeError: Cannot read property 'title' of undefined - alert(this.title + ': ' + student) + // Error: Cannot read property 'title' of undefined + alert(this.title + ': ' + student); }); */!* } @@ -87,7 +87,7 @@ group.showList(); ```js run function defer(f, ms) { return function() { - setTimeout(() => f.apply(this, arguments), ms) + setTimeout(() => f.apply(this, arguments), ms); }; } diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index 6cbcc9d577..f88420d7ef 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -19,7 +19,7 @@ 자 이제 본격적으로 프로퍼티 플래그에 대해 다뤄봅시다. 먼저 플래그를 얻는 방법을 알아보겠습니다. -[Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor)메서드를 사용하면 특정 프로퍼티에 대한 정보를 *모두* 얻을 수 있습니다. +[Object.getOwnPropertyDescriptor](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) 메서드를 사용하면 특정 프로퍼티에 대한 정보를 *모두* 얻을 수 있습니다. 문법: ```js @@ -54,7 +54,7 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -메서드 [Object.defineProperty](mdn:js/Object/defineProperty)를 사용하면 플래그를 변경할 수 있습니다. +메서드 [Object.defineProperty](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)를 사용하면 플래그를 변경할 수 있습니다. 문법: @@ -121,7 +121,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' 이제 `defineProperty`를 사용해 `writable` 플래그를 `true`로 변경하지 않는 한 그 누구도 객체의 이름을 변경할 수 없게 되었습니다. ```smart header="에러는 엄격 모드에서만 발생합니다." -비 엄격 모드에선 읽기 전용 프로퍼티에 값을 쓰려고 해도 에러가 발생하지 않습니다. 다만 이때 값을 변경하는 것은 불가능합니다. 비 엄격 모드에선 이와 같이 플래그에서 정한 규칙을 위반하는 행위는 에러 없이 그냥 무시됩니다. +비 엄격 모드에선 읽기 전용 프로퍼티에 값을 써도 에러가 발생하지 않습니다. 다만 이때 값을 변경하는 것은 불가능합니다. 비 엄격 모드에선 이와 같이 플래그에서 정한 규칙을 위반하는 행위는 에러 없이 그냥 무시됩니다. ``` 아래 예시는 위 예시와 동일하게 동작합니다. 다만 아래 예시에선 `defineProperty` 메서드를 사용해 프로퍼티를 밑바닥부터 만들어 보았습니다. @@ -192,7 +192,7 @@ alert(Object.keys(user)); // name 구성 가능하지 않음을 나타내는 플래그(non-configurable flag)인 `configurable:false`는 몇몇 내장 객체나 프로퍼티에 기본으로 설정되어있습니다. -어떤 프로퍼티의 `configurable` 플래그가 `false`로 설정되어 있다면 해당 프로퍼티는 객체에서 지울 수 없습니다. +어떤 프로퍼티의 `configurable` 플래그가 `false`로 설정되어 있다면 해당 프로퍼티는 객체에서 지울 수 없고, 플래그 값도 수정할 수 없습니다. 내장 객체 `Math`의 `PI` 프로퍼티가 대표적인 예입니다. 이 프로퍼티는 쓰기와 열거, 구성이 불가능합니다. @@ -212,49 +212,67 @@ alert( JSON.stringify(descriptor, null, 2 ) ); 개발자가 코드를 사용해 `Math.PI` 값을 변경하거나 덮어쓰는 것도 불가능합니다. ```js run -Math.PI = 3; // Error +Math.PI = 3; // Error, because it has writable: false // 수정도 불가능하지만 지우는 것 역시 불가능합니다. ``` +`Math.PI`를 다시 `writable`로 바꾸는 것도 불가능합니다. + +```js run +// Error, because of configurable: false +Object.defineProperty(Math, "PI", { writable: true }); +``` + +`Math.PI`로는 아무것도 할 수 없습니다. + `configurable` 플래그를 `false`로 설정하면 돌이킬 방법이 없습니다. `defineProperty`를 써도 값을 `true`로 되돌릴 수 없죠. -`configurable:false`가 만들어내는 구체적인 제약사항은 아래와 같습니다. -1. `configurable` 플래그를 수정할 수 없음 -2. `enumerable` 플래그를 수정할 수 없음. -3. `writable: false`의 값을 `true`로 바꿀 수 없음(`true`를 `false`로 변경하는 것은 가능함). -4. 접근자 프로퍼티 `get/set`을 변경할 수 없음(새롭게 만드는 것은 가능함). +**참고: `configurable: false`는 프로퍼티 플래그 변경과 삭제를 막지만, 값 변경은 허용합니다.** -이런 특징을 이용하면 아래와 같이 "영원히 변경할 수 없는" 프로퍼티(`user.name`)를 만들 수 있습니다. +아래 예시에서 `user.name`은 구성 가능하지 않은 프로퍼티지만 writable이므로 값을 변경할 수 있습니다. ```js run -let user = { }; +let user = { + name: "John" +}; + +Object.defineProperty(user, "name", { + configurable: false +}); + +user.name = "Pete"; // 정상 작동 +delete user.name; // Error +``` + +아래 예시에서는 내장 객체 `Math.PI`처럼 `user.name`을 "변경할 수 없는" 상수로 만듭니다. + +```js run +let user = { + name: "John" +}; Object.defineProperty(user, "name", { - value: "John", writable: false, configurable: false }); -*!* -// user.name 프로퍼티의 값이나 플래그를 변경할 수 없습니다. -// 아래와 같이 변경하려고 하면 에러가 발생합니다. -// user.name = "Pete" -// delete user.name -// Object.defineProperty(user, "name", { value: "Pete" }) -Object.defineProperty(user, "name", {writable: true}); // Error -*/!* +// user.name과 플래그를 변경할 수 없습니다. +// 아래는 모두 동작하지 않습니다. +user.name = "Pete"; +delete user.name; +Object.defineProperty(user, "name", { value: "Pete" }); ``` -```smart header="\"non-configurable\"은 \"non-writable\"과 다릅니다." -`configurable` 플래그가 `false`이더라도 `writable` 플래그가 `true`이면 프로퍼티 값을 변경할 수 있습니다. +```smart header="가능한 플래그 변경: writable true -> false" +플래그 변경에는 한 가지 예외가 있습니다. -`configurable: false`는 플래그 값 변경이나 프로퍼티 삭제를 막기 위해 만들어졌지, 프로퍼티 값 변경을 막기 위해 만들어진 게 아닙니다. +구성 가능하지 않은 프로퍼티라도 `writable: true`를 `false`로 변경할 수 있습니다. 이렇게 하면 값 변경을 막을 수 있습니다. 반대로 false를 true로 변경하는 것은 불가능합니다. ``` ## Object.defineProperties -[Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) 메서드를 사용하면 프로퍼티 여러 개를 한 번에 정의할 수 있습니다. +[Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) 메서드를 사용하면 프로퍼티 여러 개를 한 번에 정의할 수 있습니다. 문법: @@ -280,7 +298,7 @@ Object.defineProperties(user, { ## Object.getOwnPropertyDescriptors -[Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors) 메서드를 사용하면 프로퍼티 설명자를 전부 한꺼번에 가져올 수 있습니다. +[Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors) 메서드를 사용하면 프로퍼티 설명자를 한꺼번에 가져올 수 있습니다. 이 메서드를 `Object.defineProperties`와 함께 사용하면 객체 복사 시 플래그도 함께 복사할 수 있습니다. @@ -298,7 +316,7 @@ for (let key in user) { 그런데 이 방법은 플래그는 복사하지 않습니다. 플래그 정보도 복사하려면 `Object.defineProperties`를 사용하시기 바랍니다. -위 샘플 코드처럼 `for..in`을 사용해 객체를 복사하면 심볼형 프로퍼티도 놓치게 됩니다. 하지만 `Object.getOwnPropertyDescriptors`는 심볼형 프로퍼티를 포함한 프로퍼티 설명자 *전체*를 반환합니다. +또 다른 점은 `for..in`은 심볼형 프로퍼티와 열거가 불가능한 프로퍼티를 무시합니다. 하지만 `Object.getOwnPropertyDescriptors`는 심볼형 프로퍼티와 열거가 불가능한 프로퍼티를 포함한 프로퍼티 설명자 *전체*를 반환합니다. ## 객체 수정을 막아주는 다양한 메서드 @@ -306,24 +324,24 @@ for (let key in user) { 아래 메서드를 사용하면 한 객체 내 프로퍼티 *전체*를 대상으로 하는 제약사항을 만들 수 있습니다. -[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) +[Object.preventExtensions(obj)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) : 객체에 새로운 프로퍼티를 추가할 수 없게 합니다. -[Object.seal(obj)](mdn:js/Object/seal) -: 새로운 프로퍼티 추가나 기존 프로퍼티 삭제를 막아줍니다. 프로퍼티 전체에 `configurable: false`를 설정하는 것과 동일한 효과입니다. +[Object.seal(obj)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) +: 새로운 프로퍼티 추가나 기존 프로퍼티 삭제를 막아줍니다. 기존 프로퍼티 전체에 `configurable: false`를 설정합니다. -[Object.freeze(obj)](mdn:js/Object/freeze) -: 새로운 프로퍼티 추가나 기존 프로퍼티 삭제, 수정을 막아줍니다. 프로퍼티 전체에 `configurable: false, writable: false`를 설정하는 것과 동일한 효과입니다. +[Object.freeze(obj)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) +: 새로운 프로퍼티 추가나 기존 프로퍼티 삭제, 수정을 막아줍니다. 기존 프로퍼티 전체에 `configurable: false, writable: false`를 설정합니다. 아래 메서드는 위 세 가지 메서드를 사용해서 설정한 제약사항을 확인할 때 사용할 수 있습니다. -[Object.isExtensible(obj)](mdn:js/Object/isExtensible) +[Object.isExtensible(obj)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) : 새로운 프로퍼티를 추가하는 게 불가능한 경우 `false`를, 그렇지 않은 경우 `true`를 반환합니다. -[Object.isSealed(obj)](mdn:js/Object/isSealed) +[Object.isSealed(obj)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed) : 프로퍼티 추가, 삭제가 불가능하고 모든 프로퍼티가 `configurable: false`이면 `true`를 반환합니다. -[Object.isFrozen(obj)](mdn:js/Object/isFrozen) +[Object.isFrozen(obj)](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen) : 프로퍼티 추가, 삭제, 변경이 불가능하고 모든 프로퍼티가 `configurable: false, writable: false`이면 `true`를 반환합니다. 위 메서드들은 실무에선 잘 사용되지 않습니다. diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index eada70304b..b98d54e5b3 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -12,7 +12,11 @@ ![prototype](object-prototype-empty.svg) +<<<<<<< HEAD 프로토타입의 동작 방식은 '신비스러운' 면이 있습니다. `object`에서 프로퍼티를 읽으려고 하는데 해당 프로퍼티가 없으면 자바스크립트는 자동으로 프로토타입에서 프로퍼티를 찾기 때문이죠. 프로그래밍에선 이런 동작 방식을 '프로토타입 상속'이라 부릅니다. 언어 차원에서 지원하는 편리한 기능이나 개발 테크닉 중 프로토타입 상속에 기반해 만들어진 것들이 많습니다. +======= +When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. +>>>>>>> upstream/master `[[Prototype]]` 프로퍼티는 내부 프로퍼티이면서 숨김 프로퍼티이지만 다양한 방법을 사용해 개발자가 값을 설정할 수 있습니다. @@ -27,10 +31,11 @@ let rabbit = { }; *!* -rabbit.__proto__ = animal; +rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal */!* ``` +<<<<<<< HEAD ```smart header="`__proto__`는 `[[Prototype]]`용 getter·setter입니다." `__proto__`는 `[[Prototype]]`과 *다릅니다*. `__proto__`는 `[[Prototype]]`의 getter(획득자)이자 setter(설정자) 입니다. @@ -41,6 +46,9 @@ rabbit.__proto__ = animal; ``` 객체 `rabbit`에서 프로퍼티를 얻고싶은데 해당 프로퍼티가 없다면, 자바스크립트는 자동으로 `animal`이라는 객체에서 프로퍼티를 얻습니다. +======= +Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`. +>>>>>>> upstream/master 예시: @@ -63,7 +71,11 @@ alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true ``` +<<<<<<< HEAD `(*)`로 표시한 줄에선 `animal`이 `rabbit`의 프로토타입이 되도록 설정하였습니다. +======= +Here the line `(*)` sets `animal` to be the prototype of `rabbit`. +>>>>>>> upstream/master `(**)`로 표시한 줄에서 `alert` 함수가 `rabbit.eats` 프로퍼티를 읽으려 했는데, `rabbit`엔 `eats`라는 프로퍼티가 없습니다. 이때 자바스크립트는 `[[Prototype]]`이 참조하고 있는 객체인 `animal`에서 `eats`를 얻어냅니다. 다음 그림을 밑에서부터 위로 살펴보세요. @@ -131,14 +143,36 @@ alert(longEar.jumps); // true (rabbit에서 상속받음) ![](proto-animal-rabbit-chain.svg) +<<<<<<< HEAD 프로토타입 체이닝엔 두 가지 제약사항이 있습니다. +======= +Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`. + +There are only two limitations: +>>>>>>> upstream/master 1. 순환 참조(circular reference)는 허용되지 않습니다. `__proto__`를 이용해 닫힌 형태로 다른 객체를 참조하면 에러가 발생합니다. 2. `__proto__`의 값은 객체나 `null`만 가능합니다. 다른 자료형은 무시됩니다. 여기에 더하여 객체엔 오직 하나의 `[[Prototype]]`만 있을 수 있다는 당연한 제약도 있습니다. 객체는 두 개의 객체를 상속받지 못합니다. +<<<<<<< HEAD ## 프로토타입은 읽기 전용이다 +======= +```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" +It's a common mistake of novice developers not to know the difference between these two. + +Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language. + +The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later. + +By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it. + +As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples. +``` + +## Writing doesn't use prototype +>>>>>>> upstream/master 프로토타입은 프로퍼티를 읽을 때만 사용합니다. @@ -199,8 +233,13 @@ alert(admin.fullName); // John Smith (*) // setter 함수가 실행됩니다! admin.fullName = "Alice Cooper"; // (**) +<<<<<<< HEAD alert(admin.fullName); // Alice Cooper, setter에 의해 추가된 admin의 프로퍼티(name, surname)에서 값을 가져옴 alert(user.fullName); // John Smith, 본래 user에 있었던 프로퍼티 값 +======= +alert(admin.fullName); // Alice Cooper, state of admin modified +alert(user.fullName); // John Smith, state of user protected +>>>>>>> upstream/master ``` 프로토타입 `user`엔 getter 함수 `get fullName`이 있기 때문에 `(*)`로 표시한 줄에선 `get fullName`이 호출되었습니다. 마찬가지로 프로토타입에 이미 setter 함수(`set fullName`)가 정의되어 있기 때문에 `(**)`로 표시한 줄의 할당 연산이 실행되면 객체 `user`에 프로퍼티가 추가되는게 아니라 프로토타입에 있는 setter 함수가 호출됩니다. @@ -282,7 +321,11 @@ for(let prop in rabbit) alert(prop); // jumps, eats */!* ``` +<<<<<<< HEAD [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty)를 이용하면 상속 프로퍼티를 순회 대상에서 제외할 수 있습니다. 이 내장 메서드는 `key`에 대응하는 프로퍼티가 상속 프로퍼티가 아니고 `obj`에 직접 구현되어있는 프로퍼티일 때만 `true`를 반환합니다. +======= +If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. +>>>>>>> upstream/master `obj.hasOwnProperty(key)`를 응용하면 아래 예시에서처럼 상속 프로퍼티를 걸러낼 수 있고, 상속 프로퍼티만을 대상으로 무언가를 할 수도 있습니다. diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 08bd62393a..b50b38b9ef 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -37,8 +37,21 @@ alert( user2.name ); // undefined 그 이유는 `new user.constructor('Pete')`가 아래와 같이 동작하기 때문입니다. +<<<<<<< HEAD 1. `new user.constructor('Pete')`는 `user`에서 `constructor`를 찾는데 아무것도 찾지 못합니다. 2. 객체에서 원하는 프로퍼티를 찾지 못했기 때문에 프로토타입에서 검색을 시작합니다. `user`의 프로토타입은 `User.prototype`인데, `User.prototype`은 빈 객체입니다. 3. `User.prototype`은 일반 객체 `{}`이고, 일반 객체의 프로토타입은 `Object.prototype`입니다. `Object.prototype.constructor == Object`이므로 `Object`가 사용됩니다. 결국에 `let user2 = new user.constructor('Pete');`는 `let user2 = new Object('Pete')`가 됩니다. 그런데 `Object`의 생성자는 인수를 무시하고 항상 빈 객체를 생성합니다. 따라서 `let user2 = new Object('Pete')`는 `let user2 = {}`와 같다고 생각할 수 있습니다. `user2.name`이 `undefined`인 이유가 여기에 있습니다. +======= +1. First, it looks for `constructor` in `user`. Nothing. +2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!). +3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`. +4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used. + +Finally, at the end, we have `let user2 = new Object('Pete')`. + +Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`. + +(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all). +>>>>>>> upstream/master diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index d655f06df7..780a3dc077 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -2,7 +2,11 @@ `prototype` 프로퍼티는 자바스크립트 내부에서도 광범위하게 사용됩니다. 모든 내장 생성자 함수에서 `prototype` 프로퍼티를 사용합니다. +<<<<<<< HEAD 이번 챕터에선 내장 객체의 프로토타입에 대해 자세히 살펴본 후, 어떻게 내장 객체의 프로토타입 프로퍼티를 응용할 수 있는지 알아보겠습니다. +======= +First we'll look at the details, and then how to use it for adding new capabilities to built-in objects. +>>>>>>> upstream/master ## Object.prototype diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index 7758d6092c..8abf89cd74 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -28,4 +28,8 @@ alert(dictionary); // "apple,__proto__" 설명자를 사용하여 프로퍼티를 만들면 기본적으로 플래그가 `false`입니다. 따라서 위 코드에서 `dictionary.toString`은 열거할 수 없습니다. +<<<<<<< HEAD 다음 챕터 [](info:property-descriptors)를 참고하시기 바랍니다. +======= +See the chapter [](info:property-descriptors) for review. +>>>>>>> upstream/master diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index 42fdd142ee..b7042fb203 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -3,6 +3,7 @@ 이 절의 첫 번째 챕터에서 프로토타입을 설정하기 위한 모던한 방법이 있다고 언급했습니다. +<<<<<<< HEAD `__proto__`는 브라우저를 대상으로 개발하고 있다면 다소 구식이기 때문에 더는 사용하지 않는 것이 좋습니다. 표준에도 관련 내용이 명시되어있습니다. 대신 아래와 같은 모던한 메서드들을 사용하는 것이 좋습니다. @@ -12,6 +13,20 @@ - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- `obj`의 `[[Prototype]]`이 `proto`가 되도록 설정합니다. 앞으론 아래 예시처럼 `__proto__` 대신 메서드를 사용하도록 합시다. +======= +Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only). + +The modern methods to get/set a prototype are: + +- [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`. + +The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`. + +Although, there's a special method for this too: + +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. +>>>>>>> upstream/master 예시: @@ -22,7 +37,7 @@ let animal = { // 프로토타입이 animal인 새로운 객체를 생성합니다. *!* -let rabbit = Object.create(animal); +let rabbit = Object.create(animal); // same as {__proto__: animal} */!* alert(rabbit.eats); // true @@ -36,7 +51,13 @@ Object.setPrototypeOf(rabbit, {}); // rabbit의 프로토타입을 {}으로 바 */!* ``` +<<<<<<< HEAD 메서드를 소개할 때 잠시 언급한 것처럼 `Object.create`에는 프로퍼티 설명자를 선택적으로 전달할 수 있습니다. 설명자를 이용해 새 객체에 프로퍼티를 추가해 보겠습니다. +======= +The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors. + +We can provide additional properties to the new object there, like this: +>>>>>>> upstream/master ```js run let animal = { @@ -57,19 +78,29 @@ alert(rabbit.jumps); // true `Object.create`를 사용하면 `for..in`을 사용해 프로퍼티를 복사하는 것보다 더 효과적으로 객체를 복제할 수 있습니다. ```js -let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); +let clone = Object.create( + Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) +); ``` `Object.create`를 호출하면 `obj`의 모든 프로퍼티를 포함한 완벽한 사본이 만들어집니다, 사본엔 열거 가능한 프로퍼티와 불가능한 프로퍼티, 데이터 프로퍼티, getter, setter 등 모든 프로퍼티가 복제됩니다. `[[Prototype]]`도 복제되죠. +<<<<<<< HEAD ## 비하인드 스토리 `[[Prototype]]`을 다루는 방법은 다양합니다. 목표는 하나인데 목표를 이루기 위한 수단은 여러 가지이죠. 왜 그럴까요? +======= + +## Brief history + +There're so many ways to manage `[[Prototype]]`. How did that happen? Why? +>>>>>>> upstream/master 역사적인 이유가 있습니다. +<<<<<<< HEAD - 생성자 함수의 `"prototype"` 프로퍼티는 아주 오래전부터 사용되고 있었습니다. - 그런데 2012년, 명세서에 `Object.create`가 추가되었습니다. `Object.create`를 사용하면 주어진 프로토타입을 사용해 객체를 만들 수 있긴 하지만, 프로토타입을 얻거나 설정하는것은 불가능했습니다. 그래서 브라우저는 비표준 접근자인 `__proto__`를 구현해 언제나 프로토타입을 얻거나 설정할 수 있도록 하였습니다. - 이후 2015년에 `Object.setPrototypeOf`와 `Object.getPrototypeOf`가 표준에 추가되면서 `__proto__`와 동일한 기능을 수행할 수 있게 되었습니다. 그런데 이 시점엔 `__proto__`를 사용하는 곳이 너무 많아서 `__proto__`는 사실상 표준(de-facto standard)이 되어버렸죠. 이 내용은 명세서의 부록 B(Annex B)에 추가되어 있습니다. 부록 B의 내용은 브라우저 이외의 호스트 환경에선 선택사항이라는것을 의미합니다. @@ -77,6 +108,22 @@ let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescr 이런 역사적인 이유 때문에 지금은 여러 방식을 원하는 대로 쓸 수 있게되었습니다. 이쯤되면 "왜 `__proto__`가 함수 `getPrototypeOf`, `setPrototypeOf`로 대체되었을까?"라는 의문이 떠오를 수 있습니다. 흥미로운 질문이죠. 답은 `__proto__`가 왜 나쁜지 이해하면 얻을 수 있습니다. 아래 내용을 계속 읽으면서 답을 찾아봅시다. +======= +The prototypal inheritance was in the language since its dawn, but the ways to manage it evolved over time. + +- The `prototype` property of a constructor function has worked since very ancient times. It's the oldest way to create objects with a given prototype. +- 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. Some browsers implemented the non-standard `__proto__` accessor that allowed the user to get/set a prototype at any time, to give more flexibility to developers. +- 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. +- Later, in the year 2022, it was officially allowed to use `__proto__` in object literals `{...}` (moved out of Annex B), but not as a getter/setter `obj.__proto__` (still in Annex B). + +Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? + +Why was `__proto__` partially rehabilitated and its usage allowed in `{...}`, but not as a getter/setter? + +That's an interesting question, requiring us to understand why `__proto__` is bad. + +And soon we'll get the answer. +>>>>>>> upstream/master ```warn header="속도가 중요하다면 기존 객체의 `[[Prototype]]`을 변경하지 마세요." 원한다면 언제나 `[[Prototype]]`을 얻거나 설정할 수 있습니다. 기술적 제약이 있는 건 아니죠. 하지만 대개는 객체를 생성할 때만 `[[Prototype]]`을 설정하고 이후엔 수정하지 않습니다. `rabbit`이 `animal`을 상속받도록 설정하고 난 이후엔 상속 관계를 잘 변경하지 않습니다. @@ -101,25 +148,58 @@ obj[key] = "...값..."; alert(obj[key]); // "...값..."이 아닌 [object Object]가 출력됩니다. ``` +<<<<<<< HEAD 프롬프트 창에 `__proto__`를 입력하면 값이 제대로 할당되지 않는것을 확인할 수 있습니다. `__proto__` 프로퍼티는 특별한 프로퍼티라는 것을 이미 알고 있기 때문에 그렇게 놀랄만한 일은 아니긴 합니다. 참고로 `__proto__`는 항상 객체이거나 `null`이어야 합니다. 문자열은 프로토타입이 될 수 없습니다. +======= +Here, if the user types in `__proto__`, the assignment in line 4 is ignored! + +That could surely be surprising for a non-developer, but pretty understandable for us. The `__proto__` property is special: it must be either an object or `null`. A string can not become a prototype. That's why assigning a string to `__proto__` is ignored. +>>>>>>> upstream/master 개발자가 위 예시와 같은 코드를 작성할 땐 이런 결과를 의도하면서 구현하진 않았을 겁니다. 키가 무엇이 되었든, 키-값 쌍을 저장하려고 하는데 키가 `__proto__`일 때 값이 제대로 저장되지 않는 건 명백한 버그이죠. +<<<<<<< HEAD 예시에선 이 버그가 그리 치명적이진 않습니다. 그런데 할당 값이 객체일 때는 프로토타입이 바뀔 수 있다는 치명적인 버그가 발생할 수 있습니다. 프로토타입이 바뀌면 예상치 못한 일이 발생할 수 있기 때문입니다. +======= +Here the consequences are not terrible. But in other cases we may be storing objects instead of strings in `obj`, and then the prototype will indeed be changed. As a result, the execution will go wrong in totally unexpected ways. +>>>>>>> upstream/master 개발자들은 대개 프로토타입이 중간에 바뀌는 시나리오는 배제한 채 개발을 진행합니다. 이런 고정관념 때문에 프로토타입이 중간에 바뀌면서 발생한 버그는 그 원인을 쉽게 찾지 못합니다. 서버 사이드에서 자바스크립트를 사용 할 땐 이런 버그가 취약점이 되기도 합니다. +<<<<<<< HEAD `toString`을 비롯한 내장 메서드에 할당을 할 때도 같은 이유 때문에 예상치 못한 일이 일어날 수 있습니다. +======= +Unexpected things also may happen when assigning to `obj.toString`, as it's a built-in object method. +>>>>>>> upstream/master 그럼 우리는 이런 문제를 어떻게 예방할 수 있을까요? +<<<<<<< HEAD 객체 대신 `맵`을 사용하면 됩니다. 그런데 자바스크립트를 만든 사람들이 아주 오래전부터 이런 문제를 고려했기 때문에 `객체`를 써도 문제를 예방할 수 있습니다. 객체를 써서 문제를 예방하는 방법을 알아봅시다. 아시다시피 `__proto__`는 객체의 프로퍼티가 아니라 `Object.prototype`의 접근자 프로퍼티입니다. +======= +First, we can just switch to using `Map` for storage instead of plain objects, then everything's fine: + +```js run +let map = new Map(); + +let key = prompt("What's the key?", "__proto__"); +map.set(key, "some value"); + +alert(map.get(key)); // "some value" (as intended) +``` + +...But `Object` syntax is often more appealing, as it's more concise. + +Fortunately, we *can* use objects, because language creators gave thought to that problem long ago. + +As we know, `__proto__` is not a property of an object, but an accessor property of `Object.prototype`: +>>>>>>> upstream/master ![](object-prototype-2.svg) @@ -132,6 +212,7 @@ alert(obj[key]); // "...값..."이 아닌 [object Object]가 출력됩니다. ```js run *!* let obj = Object.create(null); +// or: obj = { __proto__: null } */!* let key = prompt("입력하고자 하는 key는 무엇인가요?", "__proto__"); @@ -173,6 +254,7 @@ alert(Object.keys(chineseDictionary)); // hello,bye ## 요약 +<<<<<<< HEAD 프로토타입에 직접 접근할 땐 다음과 같은 모던한 메서드를 사용할 수 있습니다. - [Object.create(proto, [descriptors])](mdn:js/Object/create) -- `[[Prototype]]`이 `proto`인 객체를 만듭니다. 참조 값은 `null`일 수 있고 프로퍼티 설명자를 넘기는 것도 가능합니다. @@ -184,11 +266,25 @@ alert(Object.keys(chineseDictionary)); // hello,bye 이를 방지하려면 `Object.create(null)`을 사용해 `__proto__`가 없는 '아주 단순한 객체'를 만들거나, `맵`을 사용하는게 좋습니다. 한편, `Object.create`를 사용하면 객체의 얕은 복사본(shallow-copy)을 만들 수 있습니다. +======= +- To create an object with the given prototype, use: -```js -let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); -``` + - literal syntax: `{ __proto__: ... }`, allows to specify multiple properties + - or [Object.create(proto[, descriptors])](mdn:js/Object/create), allows to specify property descriptors. + The `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)); + ``` + +- Modern methods to get/set the prototype are: +>>>>>>> upstream/master + + - [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). + +<<<<<<< HEAD 지금까지 우리는 `__proto__`는 `[[Prototype]]`의 getter, setter라는 점과 `__proto__`는 다른 메서드처럼 `Object.prototype`에 정의되어 있다는 것을 확인해 보았습니다. `Object.create(null)`을 사용하면 프로토타입이 없는 객체를 만들 수 있습니다. 이런 객체는 `"__proto__"`를 키로 사용해도 문제를 일으키지 않기 때문에 커스텀 사전을 만들 때 유용합니다. @@ -202,3 +298,12 @@ let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescr - [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty) -- 상속받지 않고 `obj` 자체에 구현된 키 중 이름이 `key`인 것이 있으면 `true`를 반환합니다. `Object.keys`를 비롯하여 객체의 프로퍼티를 반환하는 메서드들은 객체가 '직접 소유한' 프로퍼티만 반환합니다. 상속 프로퍼티는 `for..in`을 사용해 얻을 수 있습니다. +======= +- Getting/setting the prototype using the built-in `__proto__` getter/setter isn't recommended, it's now in the Annex B of the specification. + +- We also covered prototype-less objects, created with `Object.create(null)` or `{__proto__: null}`. + + These objects are used as dictionaries, to store any (possibly user-generated) keys. + + Normally, objects inherit built-in methods and `__proto__` getter/setter from `Object.prototype`, making corresponding keys "occupied" and potentially causing side effects. With `null` prototype, objects are truly empty. +>>>>>>> upstream/master diff --git a/1-js/09-classes/01-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md index 336aadc05b..05b587ae3e 100644 --- a/1-js/09-classes/01-class/1-rewrite-to-class/task.md +++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md @@ -4,6 +4,10 @@ importance: 5 # 클래스로 다시 작성하기 +<<<<<<< HEAD 함수 스타일로 작성된 `Clock` 클래스를 '클래스' 문법으로 다시 써봅시다. +======= +The `Clock` class (see the sandbox) is written in functional style. Rewrite it in the "class" syntax. +>>>>>>> upstream/master 참고: 콘솔창을 열어 시계가 동작하는 모습을 확인해보세요. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 0e1def2be6..0de9cbe0ae 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -49,9 +49,15 @@ let user = new User("John"); user.sayHi(); ``` +<<<<<<< HEAD `new User("John")`를 호출하면 다음과 같은 일이 일어납니다. 1. 새로운 객체가 생성됩니다. 2. 넘겨받은 인수와 함께 `constructor`가 자동으로 실행됩니다. 이때 인수 `"John"`이 `this.name`에 할당됩니다. +======= +When `new User("John")` is called: +1. A new object is created. +2. The `constructor` runs with the given argument and assigns it to `this.name`. +>>>>>>> upstream/master 이런 과정을 거친 후에 `user.sayHi()` 같은 객체 메서드를 호출할 수 있습니다. @@ -109,8 +115,13 @@ alert(typeof User); // function // 정확히는 생성자 메서드와 동일합니다. alert(User === User.prototype.constructor); // true +<<<<<<< HEAD // 클래스 내부에서 정의한 메서드는 User.prototype에 저장됩니다. alert(User.prototype.sayHi); // alert(this.name); +======= +// The methods are in User.prototype, e.g: +alert(User.prototype.sayHi); // the code of the sayHi method +>>>>>>> upstream/master // 현재 프로토타입에는 메서드가 두 개입니다. alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi @@ -118,7 +129,11 @@ alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ## 클래스는 단순한 편의 문법이 아닙니다 +<<<<<<< HEAD 어떤 사람들은 `class`라는 키워드 없이도 클래스 역할을 하는 함수를 선언할 수 있기 때문에 `클래스`는 '편의 문법'에 불과하다고 이야기합니다. 참고로 기능은 동일하나 기존 문법을 쉽게 읽을 수 있게 만든 문법을 편의 문법(syntactic sugar, 문법 설탕)이라고 합니다. +======= +Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same thing without using the `class` keyword at all: +>>>>>>> upstream/master ```js run // class User와 동일한 기능을 하는 순수 함수를 만들어보겠습니다. @@ -144,7 +159,11 @@ user.sayHi(); 그런데 두 방법에는 중요한 차이가 몇 가지 있습니다. +<<<<<<< HEAD 1. `class`로 만든 함수엔 특수 내부 프로퍼티인 `[[IsClassConstructor]]: true`가 이름표처럼 붙습니다. 이것만으로도 두 방법엔 분명한 차이가 있음을 알 수 있습니다. +======= +1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually. +>>>>>>> upstream/master 자바스크립트는 다양한 경우에 `[[IsClassConstructor]]: true`를 활용합니다. 클래스 생성자를 `new`와 함께 호출하지 않으면 에러가 발생하는데 이 때 `[[IsClassConstructor]]: true`가 사용됩니다. @@ -218,7 +237,7 @@ function makeClass(phrase) { return class { sayHi() { alert(phrase); - }; + } }; } diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 8c626fa2c2..9b1ea710b9 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -55,7 +55,11 @@ rabbit.run(5); // 흰 토끼 은/는 속도 5로 달립니다. rabbit.hide(); // 흰 토끼 이/가 숨었습니다! ``` +<<<<<<< HEAD 클래스 `Rabbit`을 사용해 만든 객체는 `rabbit.hide()` 같은 `Rabbit`에 정의된 메서드에도 접근할 수 있고, `rabbit.run()` 같은 `Animal`에 정의된 메서드에도 접근할 수 있습니다. +======= +Object of `Rabbit` class have access both to `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. +>>>>>>> upstream/master 키워드 `extends`는 프로토타입을 기반으로 동작합니다. `extends`는 `Rabbit.prototype.[[Prototype]]`을 `Animal.prototype`으로 설정합니다. 그렇기 때문에 `Rabbit.prototype`에서 메서드를 찾지 못하면 `Animal.prototype`에서 메서드를 가져옵니다. @@ -76,8 +80,8 @@ rabbit.hide(); // 흰 토끼 이/가 숨었습니다! ```js run function f(phrase) { return class { - sayHi() { alert(phrase) } - } + sayHi() { alert(phrase); } + }; } *!* @@ -106,7 +110,11 @@ class Rabbit extends Animal { } ``` +<<<<<<< HEAD 개발을 하다 보면 부모 메서드 전체를 교체하지 않고, 부모 메서드를 토대로 일부 기능만 변경하고 싶을 때가 생깁니다. 부모 메서드의 기능을 확장하고 싶을 때도 있죠. 이럴 때 커스텀 메서드를 만들어 작업하게 되는데, 이미 커스텀 메서드를 만들었더라도 이 과정 전·후에 부모 메서드를 호출하고 싶을 때가 있습니다. +======= +Usually, however, we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +>>>>>>> upstream/master 키워드 `super`는 이럴 때 사용합니다. @@ -150,8 +158,13 @@ class Rabbit extends Animal { let rabbit = new Rabbit("흰 토끼"); +<<<<<<< HEAD rabbit.run(5); // 흰 토끼가 속도 5로 달립니다. rabbit.stop(); // 흰 토끼가 멈췄습니다. 흰 토끼가 숨었습니다! +======= +rabbit.run(5); // White Rabbit runs with speed 5. +rabbit.stop(); // White Rabbit stands still. White Rabbit hides! +>>>>>>> upstream/master ``` `Rabbit`은 이제 실행 중간에 부모 클래스에 정의된 메서드 `super.stop()`을 호출하는 `stop`을 가지게 되었네요. @@ -159,7 +172,12 @@ rabbit.stop(); // 흰 토끼가 멈췄습니다. 흰 토끼가 숨었습니다! ````smart header="화살표 함수엔 `super`가 없습니다." 에서 살펴본 바와 같이, 화살표 함수는 `super`를 지원하지 않습니다. +<<<<<<< HEAD `super`에 접근하면 아래 예시와 같이 `super`를 외부 함수에서 가져옵니다. +======= +If accessed, it's taken from the outer function. For instance: + +>>>>>>> upstream/master ```js class Rabbit extends Animal { stop() { @@ -176,8 +194,12 @@ setTimeout(function() { super.stop() }, 1000); ``` ```` +<<<<<<< HEAD ## 생성자 오버라이딩 +======= +## Overriding constructor +>>>>>>> upstream/master 생성자 오버라이딩은 좀 더 까다롭습니다. @@ -280,9 +302,13 @@ alert(rabbit.earLength); // 10 */!* ``` +<<<<<<< HEAD ### 클래스 필드 오버라이딩: 까다로운 내용 +======= +### Overriding class fields: a tricky note +>>>>>>> upstream/master ```warn header="사전 공지" 이 내용은 자바스크립트 이외의 언어에서 클래스를 사용해 본 경험이 있다는 전제하에 진행됩니다. @@ -300,7 +326,7 @@ alert(rabbit.earLength); // 10 ```js run class Animal { - name = 'animal' + name = 'animal'; constructor() { alert(this.name); // (*) @@ -317,13 +343,21 @@ new Rabbit(); // animal */!* ``` +<<<<<<< HEAD `Animal`을 상속받는 `Rabbit`에서 `name` 필드를 오버라이딩 했습니다. +======= +Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value. +>>>>>>> upstream/master `Rabbit`에는 따로 생성자가 정의되어 있지 않기 때문에 `Rabbit`을 사용해 인스턴스를 만들면 `Animal`의 생성자가 호출됩니다. 흥미로운 점은 `new Animal()`과 `new Rabbit()`을 실행할 때 두 경우 모두 `(*)`로 표시한 줄에 있는 `alert` 함수가 실행되면서 얼럿 창에 `animal`이 출력된다는 점입니다. +<<<<<<< HEAD 이를 통해 우리는 **'부모 생성자는 자식 클래스에서 오버라이딩한 값이 아닌, 부모 클래스 안의 필드 값을 사용한다'** 는 사실을 알 수 있습니다. +======= +**In other words, the parent constructor always uses its own field value, not the overridden one.** +>>>>>>> upstream/master 상속을 받고 필드 값을 오버라이딩했는데 새로운 값 대신 부모 클래스 안에 있는 기존 필드 값을 사용하다니 이상하지 않나요? @@ -360,24 +394,40 @@ new Rabbit(); // rabbit 그런데 클래스 필드는 자식 클래스에서 필드를 오버라이딩해도 부모 생성자가 오버라이딩한 필드 값을 사용하지 않습니다. 부모 생성자는 항상 부모 클래스에 있는 필드의 값을 사용합니다. +<<<<<<< HEAD 왜 이런 차이가 있을까요? 이유는 필드 초기화 순서 때문입니다. 클래스 필드는 다음과 같은 규칙에 따라 초기화 순서가 달라집니다. - 아무것도 상속받지 않는 베이스 클래스는 생성자 실행 이전에 초기화됨 - 부모 클래스가 있는 경우엔 `super()` 실행 직후에 초기화됨 +======= +Why is there a difference? + +Well, the reason is the field initialization order. The class field is initialized: +- Before constructor for the base class (that doesn't extend anything), +- Immediately after `super()` for the derived class. +>>>>>>> upstream/master 위 예시에서 `Rabbit`은 하위 클래스이고 `constructor()`가 정의되어 있지 않습니다. 이런 경우 앞서 설명한 바와 같이 생성자는 비어있는데 그 안에 `super(...args)`만 있다고 보면 됩니다. 따라서 `new Rabbit()`을 실행하면 `super()`가 호출되고 그 결과 부모 생성자가 실행됩니다. 그런데 이때 하위 클래스 필드 초기화 순서에 따라 하위 클래스 `Rabbit`의 필드는 `super()` 실행 후에 초기화됩니다. 부모 생성자가 실행되는 시점에 `Rabbit`의 필드는 아직 존재하지 않죠. 이런 이유로 필드를 오버라이딩 했을 때 `Animal`에 있는 필드가 사용된 것입니다. +<<<<<<< HEAD 이렇게 자바스크립트는 오버라이딩시 필드와 메서드의 동작 방식이 미묘하게 다릅니다. +======= +This subtle difference between fields and methods is specific to JavaScript. +>>>>>>> upstream/master 다행히도 이런 문제는 오버라이딩한 필드를 부모 생성자에서 사용할 때만 발생합니다. 이런 차이가 왜 발생하는지 모르면 결과를 해석할 수 없는 상황이 발생하기 때문에 별도의 공간을 만들어 필드 오버라이딩시 내부에서 벌어지는 일에 대해 자세히 알아보았습니다. 개발하다가 필드 오버라이딩이 문제가 되는 상황이 발생하면 필드 대신 메서드를 사용하거나 getter나 setter를 사용해 해결하면 됩니다. +<<<<<<< HEAD ## super 키워드와 [[HomeObject]] +======= +## Super: internals, [[HomeObject]] +>>>>>>> upstream/master ```warn header="어렵습니다." 튜토리얼을 처음 읽는 분이라면 이번 절은 넘어가서도 좋습니다. @@ -545,7 +595,11 @@ longEar.eat(); // 귀가 긴 토끼 이/가 먹이를 먹습니다. ```js run let animal = { sayHi() { +<<<<<<< HEAD console.log(`나는 동물입니다.`); +======= + alert(`I'm an animal`); +>>>>>>> upstream/master } }; @@ -559,7 +613,11 @@ let rabbit = { let plant = { sayHi() { +<<<<<<< HEAD console.log("나는 식물입니다."); +======= + alert("I'm a plant"); +>>>>>>> upstream/master } }; diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md index e09872f2e7..7b5ee6ca2c 100644 --- a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md @@ -21,14 +21,22 @@ alert( rabbit.hasOwnProperty('name') ); // true 그런데 이게 끝이 아닙니다. +<<<<<<< HEAD 위와 같이 수정 해도, 여전히 `"class Rabbit extends Object"`와 `class Rabbit`는 다른점이 있습니다. +======= +Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`. +>>>>>>> upstream/master 아시다시피 'extends' 문법은 두 개의 프로토타입을 설정합니다. 1. 생성자 함수의 `"prototype"` 사이(일반 메서드용) 2. 생성자 함수 자체 사이(정적 메서드용) +<<<<<<< HEAD 예시의 `class Rabbit extends Object`는 다음과 같은 관계를 만들죠. +======= +In the case of `class Rabbit extends Object` it means: +>>>>>>> upstream/master ```js run class Rabbit extends Object {} @@ -37,7 +45,11 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true ``` +<<<<<<< HEAD 따라서 `Rabbit`은 아래와 같이 `Rabbit`을 통해 `Object`의 정적 메서드에 접근할 수 있습니다. +======= +So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this: +>>>>>>> upstream/master ```js run class Rabbit extends Object {} @@ -67,7 +79,11 @@ alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error 이런 이유 때문에 `Rabbit`에서 `Object`의 정적 메서드를 사용할 수 없습니다. +<<<<<<< HEAD 한편, `Function.prototype`은 `call`, `bind` 등의 '일반' 함수 메서드를 가집니다. 내장 객체, `Object`의 생성자는 `Object.__proto__ === Function.prototype` 관계를 갖기 때문에 `Function.prototype`에 정의된 일반 함수 메서드는 두 경우 모두에 사용할 수 있습니다. +======= +By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. +>>>>>>> upstream/master 이해를 돕기 위한 그림: diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index 90cd8094c3..a5e9916e7a 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -1,9 +1,15 @@ # 정적 메서드와 정적 프로퍼티 +<<<<<<< HEAD `"prototype"`이 아닌 클래스 함수 자체에 메서드를 설정할 수도 있습니다. 이런 메서드를 *정적(static)* 메서드라고 부릅니다. 정적 메서드는 아래와 같이 클래스 안에서 `static` 키워드를 붙여 만들 수 있습니다. +======= +We can also assign a method to the class as a whole. Such methods are called *static*. + +In a class declaration, they are prepended by `static` keyword, like this: +>>>>>>> upstream/master ```js run class User { @@ -31,9 +37,17 @@ User.staticMethod(); // true `User.staticMethod()`가 호출될 때 `this`의 값은 클래스 생성자인 `User` 자체가 됩니다(점 앞 객체). +<<<<<<< HEAD 정적 메서드는 어떤 특정한 객체가 아닌 클래스에 속한 함수를 구현하고자 할 때 주로 사용됩니다. 객체 `Article`이 여러 개 있고 이들을 비교해줄 함수가 필요하다고 가정해 봅시다. 가장 먼저 아래와 같이 `Article.compare`를 추가하는 방법이 떠오를 겁니다. +======= +Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it. + +For instance, we have `Article` objects and need a function to compare them. + +A natural solution would be to add `Article.compare` static method: +>>>>>>> upstream/master ```js run class Article { @@ -63,9 +77,17 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` +<<<<<<< HEAD 여기서 `Article.compare`는 article(글)을 비교해주는 수단으로, 글 전체를 '위에서' 바라보며 비교를 수행합니다. `Article.compare`이 글 하나의 메서드가 아닌 클래스의 메서드여야 하는 이유가 여기에 있습니다. 이번에 살펴볼 예시는 '팩토리' 메서드를 구현한 코드입니다. 다양한 방법을 사용해 조건에 맞는 article 인스턴스를 만들어야 한다고 가정해 봅시다. +======= +Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. + +Another example would be a so-called "factory" method. + +Let's say, we need multiple ways to create an article: +>>>>>>> upstream/master 1. 매개변수(`title`, `date` 등)를 이용해 관련 정보가 담긴 article 생성 2. 오늘 날짜를 기반으로 비어있는 article 생성 @@ -73,7 +95,11 @@ alert( articles[0].title ); // CSS 첫 번째 방법은 생성자를 사용해 구현할 수 있습니다. 두 번째 방법은 클래스에 정적 메서드를 만들어 구현할 수 있습니다. +<<<<<<< HEAD 아래 `Article.createTodays()`같이 말이죠. +======= +Such as `Article.createTodays()` here: +>>>>>>> upstream/master ```js run class Article { @@ -100,12 +126,32 @@ alert( article.title ); // Today's digest 정적 메서드는 아래 예시와 같이 항목 검색, 저장, 삭제 등을 수행해주는 데이터베이스 관련 클래스에도 사용됩니다. ```js +<<<<<<< HEAD // Article은 article을 관리해주는 특별 클래스라고 가정합시다. // article 삭제에 쓰이는 정적 메서드 Article.remove({id: 12345}); ``` ## 정적 프로퍼티 +======= +// assuming Article is a special class for managing articles +// static method to remove the article by id: +Article.remove({id: 12345}); +``` + +````warn header="Static methods aren't available for individual objects" +Static methods are callable on classes, not on individual objects. + +E.g. such code won't work: + +```js +// ... +article.createTodays(); /// Error: article.createTodays is not a function +``` +```` + +## Static properties +>>>>>>> upstream/master [recent browser=Chrome] @@ -125,7 +171,11 @@ alert( Article.publisher ); // Ilya Kantor Article.publisher = "Ilya Kantor"; ``` +<<<<<<< HEAD ## 정적 프로퍼티와 메서드 상속 +======= +## Inheritance of static properties and methods [#statics-and-inheritance] +>>>>>>> upstream/master 정적 프로퍼티와 메서드는 상속됩니다. diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 30265bbf51..3a1f58082b 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -96,7 +96,13 @@ class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { +<<<<<<< HEAD if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다."); +======= + if (value < 0) { + value = 0; + } +>>>>>>> upstream/master this._waterAmount = value; } @@ -113,11 +119,19 @@ class CoffeeMachine { // 커피 머신 생성 let coffeeMachine = new CoffeeMachine(100); +<<<<<<< HEAD // 물 추가 coffeeMachine.waterAmount = -10; // Error: 물의 양은 음수가 될 수 없습니다. ``` 이제 물의 양을 0 미만으로 설정하면 실패합니다. +======= +// add water +coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10 +``` + +Now the access is under control, so setting the water amount below zero becomes impossible. +>>>>>>> upstream/master ## 읽기 전용 프로퍼티 @@ -159,7 +173,11 @@ class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { +<<<<<<< HEAD if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다."); +======= + if (value < 0) value = 0; +>>>>>>> upstream/master this._waterAmount = value; } @@ -190,7 +208,11 @@ private 프로퍼티와 메서드는 제안(proposal) 목록에 등재된 문법 private 프로퍼티와 메서드는 `#`으로 시작합니다. `#`이 붙으면 클래스 안에서만 접근할 수 있습니다. +<<<<<<< HEAD 물 용량 한도를 나타내는 private 프로퍼티 `#waterLimit`과 남아있는 물의 양을 확인해주는 private 메서드 `#checkWater`를 구현해봅시다. +======= +For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: +>>>>>>> upstream/master ```js run class CoffeeMachine { @@ -199,19 +221,34 @@ class CoffeeMachine { */!* *!* +<<<<<<< HEAD #checkWater(value) { if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다."); if (value > this.#waterLimit) throw new Error("물이 용량을 초과합니다."); +======= + #fixWaterAmount(value) { + if (value < 0) return 0; + if (value > this.#waterLimit) return this.#waterLimit; +>>>>>>> upstream/master } */!* + setWaterAmount(value) { + this.#waterLimit = this.#fixWaterAmount(value); + } + } let coffeeMachine = new CoffeeMachine(); *!* +<<<<<<< HEAD // 클래스 외부에서 private에 접근할 수 없음 coffeeMachine.#checkWater(); // Error +======= +// can't access privates from outside of the class +coffeeMachine.#fixWaterAmount(123); // Error +>>>>>>> upstream/master coffeeMachine.#waterLimit = 1000; // Error */!* ``` @@ -232,7 +269,11 @@ class CoffeeMachine { } set waterAmount(value) { +<<<<<<< HEAD if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다."); +======= + if (value < 0) value = 0; +>>>>>>> upstream/master this.#waterAmount = value; } } diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index e6fa8dc46d..b011ca1ff5 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -2,7 +2,11 @@ `instanceof` 연산자를 사용하면 객체가 특정 클래스에 속하는지 아닌지를 확인할 수 있습니다. `instanceof`는 상속 관계도 확인해줍니다. +<<<<<<< HEAD 확인 기능은 다양한 곳에서 쓰이는데, 이번 챕터에선 `instanceof`를 사용해 인수의 타입에 따라 이를 다르게 처리하는 *다형적인(polymorphic)* 함수를 만드는데 사용해보겠습니다. +======= +Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type. +>>>>>>> upstream/master ## instanceof 연산자 [#ref-instanceof] @@ -55,8 +59,13 @@ alert( arr instanceof Object ); // true 예시: ```js run +<<<<<<< HEAD // canEat 프로퍼티가 있으면 animal이라고 판단할 수 있도록 // instanceOf의 로직을 직접 설정합니다. +======= + // set up instanceof check that assumes that + // anything with canEat property is an animal +>>>>>>> upstream/master class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; @@ -68,7 +77,11 @@ alert( arr instanceof Object ); // true alert(obj instanceof Animal); // true, Animal[Symbol.hasInstance](obj)가 호출됨 ``` +<<<<<<< HEAD 2. 그런데, 대부분의 클래스엔 `Symbol.hasInstance`가 구현되어있지 않습니다. 이럴 땐 일반적인 로직이 사용됩니다. `obj instanceOf Class`는 `Class.prototype`이 `obj` 프로토타입 체인 상의 프로토타입 중 하나와 일치하는지 확인합니다. +======= +2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceof Class` checks whether `Class.prototype` is equal to one of the prototypes in the `obj` prototype chain. +>>>>>>> upstream/master 비교는 차례 차례 진행됩니다. ```js @@ -93,7 +106,7 @@ alert( arr instanceof Object ); // true alert(rabbit instanceof Animal); // true */!* - // rabbit.__proto__ === Rabbit.prototype + // rabbit.__proto__ === Animal.prototype (no match) *!* // rabbit.__proto__.__proto__ === Animal.prototype (일치!) */!* diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 5f86b2beb8..785d10b688 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -69,7 +69,11 @@ let sayMixin = { }; let sayHiMixin = { +<<<<<<< HEAD __proto__: sayMixin, // (Object.create를 사용해 프로토타입을 설정할 수도 있습니다.) +======= + __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here) +>>>>>>> upstream/master sayHi() { *!* @@ -103,7 +107,11 @@ new User("Dude").sayHi(); // Hello Dude! 이는 `sayHi`와 `sayBye`가 생성된 곳이 `sayHiMixin`이기 때문입니다. 따라서 메서드를 복사했더라도, 이 메서드들의 내부 프로퍼티인 `[[HomeObject]]`는 위 그림처럼 `sayHiMixin`을 참조합니다. +<<<<<<< HEAD 메서드의 `super`가 `[[HomeObject]].[[Prototype]]`내에서 부모 메서드를 찾기 때문에, 메서드는 `User.[[Prototype]]`이 아닌 `sayHiMixin.[[Prototype]]`을 검색합니다. +======= +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. +>>>>>>> upstream/master ## 이벤트 믹스인 diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md index ad3197d848..6d5f89159f 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md @@ -2,7 +2,7 @@ `try..catch`에 '빠져나오게 하는' 코드가 있다면 함수의 행동이 달라집니다. -아래 예시와 같이 `try..catch` 내부에 `return`이 있을 때가 대표적인 예입니다. `finally` 절은 `return`문을 통해 `try..catch`를 빠져나가는 경우를 포함하여 `try..catch`가 종료되는 *모든* 상황에서 실행됩니다. `try..catch`가 종료되었지만, 함수 호출 코드가 제어권을 갖기 직전에 실행되죠. +아래 예시와 같이 `try..catch` 내부에 `return`이 있을 때가 대표적인 예입니다. `finally` 절은 `return`문을 통해 `try..catch`를 빠져나가는 경우를 포함하여 `try..catch`가 종료되는 _모든_ 상황에서 실행됩니다. `try..catch`가 종료되었지만, 함수 호출 코드가 제어권을 갖기 직전에 실행되죠. ```js run function f() { @@ -11,7 +11,7 @@ function f() { *!* return "결과"; */!* - } catch (e) { + } catch (err) { /// ... } finally { alert('초기화!'); @@ -28,11 +28,11 @@ function f() { try { alert('시작'); throw new Error("에러 발생!"); - } catch (e) { + } catch (err) { // ... if("에러를 핸들링 할 수 없다면") { *!* - throw e; + throw err; */!* } diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 2a43345582..a8e8611fab 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -45,7 +45,7 @@ try..catch 동작 알고리즘은 다음과 같습니다. alert('try 블록 끝'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('에러가 없으므로, catch는 무시됩니다.'); // (3) @@ -64,7 +64,7 @@ try..catch 동작 알고리즘은 다음과 같습니다. alert('try 블록 끝(절대 도달하지 않음)'); // (2) - } catch(err) { + } catch (err) { alert(`에러가 발생했습니다!`); // *!*(3) <--*/!* @@ -80,7 +80,7 @@ try..catch 동작 알고리즘은 다음과 같습니다. ```js run try { {{{{{{{{{{{{ -} catch(e) { +} catch (err) { alert("유효하지 않은 코드이기 때문에, 자바스크립트 엔진은 이 코드를 이해할 수 없습니다."); } ``` @@ -99,7 +99,7 @@ try { setTimeout(function() { noSuchVariable; // 스크립트는 여기서 죽습니다. }, 1000); -} catch (e) { +} catch (err) { alert( "작동 멈춤" ); } ``` @@ -125,7 +125,7 @@ setTimeout(function() { ```js try { // ... -} catch(err) { // <-- '에러 객체', err 대신 다른 이름으로도 쓸 수 있음 +} catch (err) { // <-- '에러 객체', err 대신 다른 이름으로도 쓸 수 있음 // ... } ``` @@ -150,7 +150,7 @@ try { *!* lalala; // 에러, 변수가 정의되지 않음! */!* -} catch(err) { +} catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at ... (호출 스택) @@ -217,12 +217,12 @@ try { */!* alert( user.name ); // 이 코드는 동작하지 않습니다. -} catch (e) { +} catch (err) { *!* // 에러가 발생하면 제어 흐름이 catch 문으로 넘어옵니다. alert( "데이터에 에러가 있어 재요청을 시도합니다." ); - alert( e.name ); - alert( e.message ); + alert( err.name ); + alert( err.message ); */!* } ``` @@ -245,7 +245,7 @@ try { alert( user.name ); // 이름이 없습니다! */!* -} catch (e) { +} catch (err) { alert( "실행되지 않습니다." ); } ``` @@ -294,11 +294,11 @@ alert(error.message); // 이상한 일이 발생했습니다. o_O ```js run try { JSON.parse("{ 잘못된 형식의 json o_O }"); -} catch(e) { +} catch(err) { *!* - alert(e.name); // SyntaxError + alert(err.name); // SyntaxError */!* - alert(e.message); // Unexpected token b in JSON at position 2 + alert(err.message); // Unexpected token b in JSON at position 2 } ``` @@ -323,8 +323,8 @@ try { alert( user.name ); -} catch(e) { - alert( "JSON Error: " + e.message ); // JSON Error: 불완전한 데이터: 이름 없음 +} catch(err) { + alert( "JSON Error: " + err.message ); // JSON Error: 불완전한 데이터: 이름 없음 } ``` @@ -345,7 +345,7 @@ try { user = JSON.parse(json); // <-- user 앞에 let을 붙이는 걸 잊었네요. // ... -} catch(err) { +} catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (실제론 JSON Error가 아닙니다.) } @@ -370,7 +370,7 @@ try { ```js run try { user = { /*...*/ }; -} catch(err) { +} catch (err) { *!* if (err instanceof ReferenceError) { */!* @@ -399,13 +399,13 @@ try { alert( user.name ); -} catch(e) { +} catch (err) { *!* - if (e instanceof SyntaxError) { - alert( "JSON Error: " + e.message ); + if (err instanceof SyntaxError) { + alert( "JSON Error: " + err.message ); } else { - throw e; // 에러 다시 던지기 (*) + throw err; // 에러 다시 던지기 (*) } */!* @@ -427,11 +427,11 @@ function readData() { *!* blabla(); // 에러! */!* - } catch (e) { + } catch (err) { // ... - if (!(e instanceof SyntaxError)) { + if (!(err instanceof SyntaxError)) { *!* - throw e; // 알 수 없는 에러 다시 던지기 + throw err; // 알 수 없는 에러 다시 던지기 */!* } } @@ -439,16 +439,16 @@ function readData() { try { readData(); -} catch (e) { +} catch (err) { *!* - alert( "External catch got: " + e ); // 에러를 잡음 + alert( "External catch got: " + err ); // 에러를 잡음 */!* } ``` `readData`는 `SyntaxError`만 처리할 수 있지만, 함수 바깥의 `try..catch`에서는 예상치 못한 에러도 처리할 수 있게 되었습니다. -## try..catch..finally +## try...catch...finally 잠깐! 에러 핸들링은 여기서 끝이 아닙니다. @@ -464,7 +464,7 @@ try { ```js *!*try*/!* { ... 코드를 실행 ... -} *!*catch*/!*(e) { +} *!*catch*/!*(err) { ... 에러 핸들링 ... } *!*finally*/!* { ... 항상 실행 ... @@ -477,7 +477,7 @@ try { try { alert( 'try 블록 시작' ); if (confirm('에러를 만드시겠습니까?')) 이상한_코드(); -} catch (e) { +} catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); @@ -513,7 +513,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -550,7 +550,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -563,7 +563,7 @@ alert( func() ); // finally 안의 alert가 실행되고 난 후, 실행됨 ``` ```` -````smart header="`try..finally`" +````smart header="`try...finally`" `catch` 절이 없는 `try..finally` 구문도 상황에 따라 유용하게 쓸 수 있습니다. `try..finally` 안에선 에러를 처리하고 싶지 않지만, 시작한 프로세스가 마무리되었는지 확실히 하고 싶은 경우에 사용합니다. @@ -632,7 +632,7 @@ window.onerror = function(message, url, line, col, error) { 그런데 전역 핸들러 `window.onerror`는 죽어버린 스크립트를 복구하려는 목적으로는 잘 사용하지 않습니다. 프로그래밍 에러가 발생한 경우 `window.onerror`만으로 스크립트를 복구하는 건 사실상 불가능하죠. `window.onerror`는 개발자에게 에러 메시지를 보내는 용도로 사용합니다. -`window.onerror`말고 같은 에러 로깅 관련 상용 서비스가 여러 가지 있습니다. +`window.onerror`말고 같은 에러 로깅 관련 상용 서비스가 여러 가지 있습니다. 이런 서비스들은 다음과 같은 프로세스로 동작합니다. diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md index 6962138d14..4490e0958e 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -13,6 +13,7 @@ 사용자 데이터가 저장된 JSON을 읽는 함수 `readUser(json)`가 있다고 해봅시다. 유효한 `json`은 다음과 같은 형태이어야 합니다. + ```js let json = `{ "name": "John", "age": 30 }`; ``` @@ -21,9 +22,9 @@ let json = `{ "name": "John", "age": 30 }`; 따라서 `readUser(json)`은 JSON 형식의 데이터를 읽을 수 있을 뿐만 아니라, 데이터를 '검증'할 수 있어야 합니다. 필수 프로퍼티가 없거나, 위 형식에 맞지 않으면 에러를 발생시킬 수 있어야 하죠. 그런데 이때 발생하는 에러는 `SyntaxError`가 아닙니다. JSON 형식은 맞지만, 자체 기준에 맞지 않기 때문에 발생한 에러이므로 전혀 다른 종류의 에러이죠. 지금부턴 이 에러를 `ValidationError`라고 부르겠습니다. 자 이제 `ValidationError`를 위한 클래스를 만들어봅시다. -`ValidationError` 클래스엔 문제가 되는 필드 정보가 저장되어야 합니다. 내장 클래스 `Error`를 상속받아 `ValidationError` 클래스를 만들어봅시다. +`ValidationError` 클래스는 `Error` 클래스를 상속받아야 합니다. -그 전에 먼저 잠시 슈도 코드로 `Error` 클래스가 어떻게 생겼는지 살펴보겠습니다. +`Error` 클래스는 자바스크립트 내장 클래스인데, 상속 구조를 이해하기 위해 슈도 코드로 살펴보겠습니다. ```js // 자바스크립트 자체 내장 에러 클래스 Error의 '슈도 코드' @@ -38,7 +39,7 @@ class Error { 이제 `ValidationError`에서 `Error`를 상속받아보겠습니다. -```js run untrusted +```js run *!* class ValidationError extends Error { */!* @@ -117,9 +118,9 @@ try { // (err instanceof SyntaxError) 대신 사용 가능 } else if (err.name == "SyntaxError") { // (*) // ... -``` +``` -그런데 에러 유형 확인은 `err.name`보다는 `instanceof`를 사용하는 게 훨씬 좋습니다. 나중에 `ValidationError`를 확장하여 `PropertyRequiredError` 같은 새로운 확장 에러를 만들게 될 텐데, `instanceof`는 새로운 상속 클래스에서도 동작하기 때문입니다. +그런데 에러 유형 확인은 `err.name`보다는 `instanceof`를 사용하는 게 훨씬 좋습니다. 나중에 `ValidationError`를 확장하여 `PropertyRequiredError` 같은 새로운 확장 에러를 만들게 될 텐데, `instanceof`는 새로운 상속 클래스에서도 동작하기 때문입니다. `catch`에 알려지지 않은 에러가 있을 때 이 에러는 재 던지기 된다는 점(`(**)`) 또한 주목해서 봐주시기 바랍니다. `catch` 블록에선 유효성 검사와 문법 오류만 처리하고, 다른 종류의 에러는 밖으로 던져야 합니다. @@ -233,8 +234,8 @@ try { throw err; // 알 수 없는 에러는 다시 던지기 함 } } -``` - +``` + 위 스키마엔 두 종류의 에러만 나와 있네요. 그런데 에러의 종류는 더 추가될 수 있습니다. 이쯤 되면 "미래에 `readUser` 기능이 커지면서 에러 종류가 많아질 텐데 그때마다 에러 종류에 따라 에러 처리 분기문을 매번 추가해야 하나?"라는 의문이 생길 수 있습니다. @@ -319,7 +320,7 @@ try { 이제 `readUser`는 위에서 설명한 것처럼 동작합니다. Syntax 에러나 Validation 에러가 발생한 경우 해당 에러 자체를 다시 던지기 하지 않고 `ReadError`를 던지게 됩니다. -이렇게 되면, `readUser`를 호출하는 바깥 코드에선 `instanceof ReadError` 딱 하나만 확인하면 됩니다. 에러 처리 분기문을 여러 개 만들 필요가 없어집니다. +이렇게 되면, `readUser`를 호출하는 바깥 코드에선 `instanceof ReadError` 딱 하나만 확인하면 됩니다. 에러 처리 분기문을 여러 개 만들 필요가 없어집니다. '예외 감싸기'란 이름은 종류별 에러를 좀 더 추상적인 에러, `ReadError`에 하나로 모아(wrap) 처리하기 때문에 붙여졌습니다. 이런 기법은 객체 지향 프로그래밍에서 널리 쓰이는 패턴입니다. diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index 8751bf4fbc..b9ff1c6387 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -77,6 +77,8 @@ function loadScript(src, *!*callback*/!*) { } ``` +`onload` 이벤트는 글에서 자세히 다룹니다. 기본적으로 스크립트가 로드되고 실행된 후 함수를 호출합니다. + 새롭게 불러온 스크립트에 있는 함수를 콜백 함수 안에서 호출하면 원하는 대로 외부 스크립트 안의 함수를 사용할 수 있습니다. ```js @@ -102,7 +104,7 @@ function loadScript(src, callback) { *!* loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { alert(`${script.src}가 로드되었습니다.`); - alert( _ ); // 스크립트에 정의된 함수 + alert( _ ); // _는 로드된 스크립트에 선언된 함수입니다 }); */!* ``` @@ -146,7 +148,7 @@ loadScript('/my/script.js', function(script) { }); */!* - }) + }); }); ``` @@ -223,7 +225,7 @@ loadScript('1.js', function(error, script) { }); } - }) + }); } }); ``` @@ -256,7 +258,7 @@ loadScript('1.js', function(error, script) { } }); } - }) + }); } }); --> @@ -296,7 +298,7 @@ function step3(error, script) { } else { // 모든 스크립트가 로딩되면 다른 동작을 수행합니다. (*) } -}; +} ``` 어떤가요? 새롭게 작성한 코드는 각 동작을 분리해 최상위 레벨의 함수로 만들었기 때문에 깊은 중첩이 없습니다. 그리고 콜백 기반 스타일 코드와 동일하게 동작하죠. diff --git a/1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html b/1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html index 3229daf89f..6052f009ee 100644 --- a/1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html +++ b/1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html @@ -10,7 +10,7 @@ text-align: center; } .circle { - transition-property: width, height, margin-left, margin-top; + transition-property: width, height; transition-duration: 2s; position: fixed; transform: translateX(-50%) translateY(-50%); diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index 783473f2e4..d0bd7ee865 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -1,10 +1,10 @@ # 프라미스 -본인을 아주 유명한 가수라고 가정해 봅시다. 그리고 탑 가수인 본인이 밤, 낮으로 다음 싱글 앨범이 언제 나오는지 물어보는 팬들을 상대해야 한다고 해 봅시다. +본인을 아주 유명한 가수라고 가정해 봅시다. 그리고 탑 가수인 본인이 밤, 낮으로 다음 곡이 언제 나오는지 물어보는 팬들을 상대해야 한다고 해 봅시다. 당신은 앨범이 출시되면 팬들이 자동으로 소식을 받아볼 수 있도록 해 부하를 덜 겁니다. 구독 리스트를 하나 만들어 팬들에게 전달해 이메일 주소를 적게 하고, 앨범이 준비되면 리스트에 있는 팬들에게 메일을 보내 앨범 관련 소식을 바로 받아볼 수 있게 하는 식으로 말이죠. 이렇게 하면 녹음 스튜디오에 화재가 발생해서 출시 예정인 앨범이 취소되는 불상사가 발생해도 관련 소식을 팬들에게 전달 할 수 있습니다. -이제 모두가 행복해졌습니다. 밤낮으로 질문을 하는 팬이 사라졌고, 팬들은 앨범 출시를 놓치지 않을 수 있게 되었으니까요. +이제 모두가 행복해졌습니다. 밤낮으로 질문을 하는 팬이 사라졌고, 팬들은 새 곡 출시를 놓치지 않을 수 있게 되었으니까요. 이 비유는 코드를 작성하면서 자주 만나는 상황을 실제 일어날 법한 일로 바꾼 것입니다. 바로 아래 같은 상황 말이죠. @@ -35,8 +35,8 @@ executor의 인수 `resolve`와 `reject`는 자바스크립트에서 자체 제 한편, `new Promise` 생성자가 반환하는 `promise` 객체는 다음과 같은 내부 프로퍼티를 갖습니다. -- `state` — 처음엔 `"pending"`(보류)이었다 `resolve`가 호출되면 `"fulfilled"`, `reject`가 호출되면 `"rejected"`로 변합니다. -- `result` — 처음엔 `undefined`이었다 `resolve(value)`가 호출되면 `value`로, `reject(error)`가 호출되면 `error`로 변합니다. +- `state` — 처음엔 `"pending"`(보류)이었다가 `resolve`가 호출되면 `"fulfilled"`, `reject`가 호출되면 `"rejected"`로 변합니다. +- `result` — 처음엔 `undefined`이었다가 `resolve(value)`가 호출되면 `value`로, `reject(error)`가 호출되면 `error`로 변합니다. 따라서 executor는 아래 그림과 같이 `promise`의 상태를 둘 중 하나로 변화시킵니다. @@ -60,7 +60,7 @@ let promise = new Promise(function(resolve, reject) { 1. executor는 `new Promise`에 의해 자동으로 그리고 즉각적으로 호출됩니다. 2. executor는 인자로 `resolve`와 `reject` 함수를 받습니다. 이 함수들은 자바스크립트 엔진이 미리 정의한 함수이므로 개발자가 따로 만들 필요가 없습니다. 다만, `resolve`나 `reject` 중 하나는 반드시 호출해야 합니다. - executor '처리'가 시작 된 지 1초 후, `resolve("완료")`이 호출되고 결과가 만들어집니다. 이때 `promise` 객체의 상태는 다음과 같이 변합니다. + executor '처리'가 시작된 지 1초 후, `resolve("완료")`이 호출되고 결과가 만들어집니다. 이때 `promise` 객체의 상태는 다음과 같이 변합니다. ![](promise-resolve-1.svg) @@ -127,9 +127,9 @@ let promise = new Promise(function(resolve, reject) { 프라미스 객체의 `state`, `result` 프로퍼티는 내부 프로퍼티이므로 개발자가 직접 접근할 수 없습니다. `.then`/`.catch`/`.finally` 메서드를 사용하면 접근 가능한데, 자세한 내용은 아래에서 살펴보겠습니다. ``` -## 소비자: then, catch, finally +## 소비자: then, catch -프라미스 객체는 executor('제작 코드' 혹은 '가수')와 결과나 에러를 받을 소비 함수('팬')를 이어주는 역할을 합니다. 소비함수는 `.then`, `.catch`, `.finally` 메서드를 사용해 등록(구독)됩니다. +프라미스 객체는 executor('제작 코드' 혹은 '가수')와 결과나 에러를 받을 소비 함수('팬')를 이어주는 역할을 합니다. 소비함수는 `.then`, `.catch` 메서드를 사용해 등록(구독)됩니다. ### then @@ -212,59 +212,88 @@ promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력 `.catch(f)`는 문법이 간결하다는 점만 빼고 `.then(null,f)`과 완벽하게 같습니다. -### finally +## Cleanup: finally `try {...} catch {...}`에 finally 절이 있는 것처럼, 프라미스에도 `finally`가 있습니다. 프라미스가 처리되면(이행이나 거부) `f`가 항상 실행된다는 점에서 `.finally(f)` 호출은 `.then(f, f)`과 유사합니다. +`finally`의 목적은 이전 작업이 완료된 후 정리·마무리 작업을 수행하는 핸들러를 설정하는 것입니다. + 쓸모가 없어진 로딩 인디케이터(loading indicator)를 멈추는 경우같이, 결과가 어떻든 마무리가 필요하면 `finally`가 유용합니다. -사용법은 아래와 같습니다. +예를 들면 로딩 인디케이터를 멈추거나 더 이상 필요 없는 연결을 닫는 작업 등이 있습니다. + +파티 뒷정리 담당자라고 생각해보세요. 파티가 즐거웠든 아니든, 친구가 얼마나 왔든 상관없이 파티가 끝나면 뒷정리는 해야 하니까요. + +코드는 아래와 같은 형태가 됩니다. ```js new Promise((resolve, reject) => { - /* 시간이 걸리는 어떤 일을 수행하고, 그 후 resolve, reject를 호출함 */ + /* 시간이 걸리는 작업을 수행한 후 resolve 또는 reject를 호출 */ }) *!* // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨 .finally(() => 로딩 인디케이터 중지) + // 다음 단계로 넘어가기 전에 로딩 인디케이터는 항상 중지됨 + */!* .then(result => result와 err 보여줌 => error 보여줌) ``` -그런데 finally는 `.then(f, f)`과 완전히 같진 않습니다. 차이점은 다음과 같습니다. +그런데 `finally(f)`는 `.then(f, f)`과 완전히 같진 않습니다. + +차이점은 다음과 같습니다. -1. `finally` 핸들러엔 인수가 없습니다. `finally`에선 프라미스가 이행되었는지, 거부되었는지 알 수 없습니다. `finally`에선 절차를 마무리하는 '보편적' 동작을 수행하기 때문에 성공·실패 여부를 몰라도 됩니다. -2. `finally` 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달합니다. +1. `finally` 핸들러에는 인수가 없습니다. `finally`에서는 프라미스가 이행되었는지 거부되었는지 알 수 없습니다. 하지만 이는 문제가 되지 않습니다. `finally`의 목적은 보통 '공통적인' 마무리 작업을 수행하는 것이기 때문입니다. - result가 `finally`를 거쳐 `then`까지 전달되는 것을 확인해봅시다. + 위 예시를 보면 `finally` 핸들러에는 인수가 없습니다. 프라미스 결과는 다음 핸들러에서 처리됩니다. + +2. `finally` 핸들러는 결과나 에러를 다음 핸들러로 그대로 전달합니다. + + 결과가 `finally`를 거쳐 `then`까지 전달되는 것을 확인해봅시다. + ```js run new Promise((resolve, reject) => { - setTimeout(() => resolve("결과"), 2000) + setTimeout(() => resolve("값"), 2000); }) - .finally(() => alert("프라미스가 준비되었습니다.")) - .then(result => alert(result)); // <-- .then에서 result를 다룰 수 있음 + .finally(() => alert("프라미스가 준비되었습니다.")) // 가장 먼저 실행 + .then(result => alert(result)); // <-- .then이 "값" 출력 ``` - 프라미스에서 에러가 발생하고 이 에러가 `finally`를 거쳐 `catch`까지 전달되는 것을 확인해봅시다. + 보다시피 첫 번째 프라미스가 반환한 `값`은 `finally`를 거쳐 다음 `then`으로 전달됩니다. + `finally`는 프라미스의 결과를 처리하는 용도가 아니기 때문에 이런 동작은 결과와 관계없이 마무리 작업이 필요할 때 유용합니다. + + 아래는 에러가 `finally`를 거쳐 `catch`로 전달되는 예시입니다. + ```js run new Promise((resolve, reject) => { throw new Error("에러 발생!"); }) - .finally(() => alert("프라미스가 준비되었습니다.")) - .catch(err => alert(err)); // <-- .catch에서 에러 객체를 다룰 수 있음 - ``` - `finally`는 프라미스 결과를 처리하기 위해 만들어 진 게 아닙니다. 프라미스 결과는 `finally`를 통과해서 전달되죠. 이런 특징은 아주 유용하게 사용되기도 합니다. + .finally(() => alert("프라미스가 준비되었습니다.")) // 먼저 실행됨 + .catch(err => alert(err)); // <-- .catch에서 에러를 출력함 + ``` + +3. `finally` 핸들러는 아무것도 반환하지 않아야 합니다. 무언가를 반환하더라도 반환값은 무시됩니다. + + 단, `finally` 핸들러에서 에러를 던지는 경우는 예외입니다. 이 경우 에러는 이전 결과 대신 다음 핸들러로 전달됩니다. + +요약하면 다음과 같습니다. + +- `finally` 핸들러는 이전 핸들러의 결과를 받지 않습니다(인수가 없습니다). 결과는 대신 다음 적절한 핸들러로 전달됩니다. +- `finally` 핸들러가 무언가를 반환하면 무시됩니다. +- `finally`에서 에러가 발생하면 가장 가까운 에러 핸들러로 전달됩니다. + +이런 특징 덕분에 `finally`를 원래 목적대로 공통적인 정리 작업에 사용한다면 모든 것이 올바르게 동작합니다. - 프라미스 체이닝과 핸들러 간 결과 전달에 대해선 다음 챕터에서 더 이야기 나누도록 하겠습니다. +````smart header="처리된 프라미스에도 핸들러를 등록할 수 있습니다" +프라미스가 대기 상태라면 `.then/catch/finally` 핸들러는 결과를 기다립니다. -3. `.finally(f)`는 함수 `f`를 중복해서 쓸 필요가 없기 때문에 `.then(f, f)`보다 문법 측면에서 더 편리합니다. +핸들러를 등록할 때 프라미스가 이미 처리된 상태일 수도 있습니다. -````smart header="처리된 프라미스의 핸들러는 즉각 실행됩니다." -프라미스가 대기 상태일 때, `.then/catch/finally` 핸들러는 프라미스가 처리되길 기다립니다. 반면, 프라미스가 이미 처리상태라면 핸들러가 즉각 실행됩니다. +이런 경우 핸들러는 즉시 실행됩니다. ```js run // 아래 프라미스는 생성과 동시에 이행됩니다. @@ -273,17 +302,19 @@ let promise = new Promise(resolve => resolve("완료!")); promise.then(alert); // 완료! (바로 출력됨) ``` -가수와 팬, 구독리스트 시나리오보다 프라미스가 더 복잡하다고 말한 이유가 바로 이런 기능 때문입니다. 가수가 신곡을 발표한 이후에 구독 리스트에 이름을 올리는 팬은 신곡 발표 여부를 알 수 없습니다. 구독 리스트에 이름을 올리는 것이 선행되어야 새로운 소식을 받을 수 있기 때문이죠. +가수와 팬, 구독 리스트 시나리오보다 프라미스가 더 강력하게 만드는 이유가 바로 이런 기능 때문입니다. 가수가 신곡을 발표한 이후에 구독 리스트에 이름을 올리는 팬은 신곡 발표 여부를 알 수 없습니다. 구독 리스트에 이름을 올리는 것이 선행되어야 새로운 소식을 받을 수 있기 때문이죠. -그런데 프라미스는 핸들러를 언제든 추가할 수 있다는 점에서 구독리스트 시나리오보다 더 유연합니다. 결과가 나와 있는 상태에서도 핸들러를 등록하면 결과를 바로 받을 수 있습니다. +그런데 프라미스는 핸들러를 언제든 추가할 수 있다는 점에서 구독 리스트 시나리오보다 더 유연합니다. 결과가 나와 있는 상태에서도 핸들러를 등록하면 결과를 바로 받을 수 있습니다. ```` -이제 실제 동작하는 예시를 보며 프라미스로 어떻게 비동기 동작을 처리하는지 살펴봅시다. ## 예시: loadScript [#loadscript] +이제 실제 동작하는 예시를 보며 프라미스로 어떻게 비동기 동작을 처리하는지 살펴봅시다. + 이전 챕터에서 스크립트 로딩에 사용되는 함수 `loadScript`를 작성해 보았습니다. + 복습 차원에서 콜백 기반으로 작성한 함수를 다시 살펴봅시다. ```js diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md index 31c61e30a3..085117f311 100644 --- a/1-js/11-async/03-promise-chaining/article.md +++ b/1-js/11-async/03-promise-chaining/article.md @@ -36,15 +36,15 @@ new Promise(function(resolve, reject) { 위 예시는 아래와 같은 순서로 실행됩니다. 1. 1초 후 최초 프라미스가 이행됩니다. -- `(*)` -2. 이후 첫번째 `.then` 핸들러가 호출됩니다. --`(**)` -3. 2에서 반환한 값은 다음 `.then` 핸들러에 전달됩니다. -- `(***)` +2. 이후 첫 번째 `.then` 핸들러가 호출됩니다. 결괏값이 2인 새로운 프라미스를 생성합니다. --`(**)` +3. 다음 `.then`은 이전 결과를 받아 처리하고(두 배로 증가) 다음 핸들러에 전달합니다. -- `(***)` 4. 이런 과정이 계속 이어집니다. `result`가 핸들러 체인을 따라 전달되므로, `alert` 창엔 `1`, `2`, `4`가 순서대로 출력됩니다. ![](promise-then-chain.svg) -프라미스 체이닝이 가능한 이유는 `promise.then`을 호출하면 프라미스가 반환되기 때문입니다. 반환된 프라미스엔 당연히 `.then`을 호출할 수 있습니다. +프라미스 체이닝이 가능한 이유는 `.then`을 호출할 때마다 새로운 프라미스가 반환되기 때문입니다. 반환된 프라미스엔 당연히 `.then`을 호출할 수 있습니다. 한편 핸들러가 값을 반환할 때엔 이 값이 프라미스의 `result`가 됩니다. 따라서 다음 `.then`은 이 값을 이용해 호출됩니다. @@ -72,7 +72,7 @@ promise.then(function(result) { }); ``` -예시의 프라미스는 하나인데 여기에 등록된 핸들러는 여러 개 입니다. 이 핸들러들은 `result`를 순차적으로 전달하지 않고 독립적으로 처리합니다. +예시의 프라미스는 하나인데 여기에 등록된 핸들러는 여러 개입니다. 이 핸들러들은 결과를 순차적으로 전달하지 않고 독립적으로 처리합니다. 그림으로 표현하면 다음과 같습니다. 프라미스 체이닝을 묘사한 위 그림과 비교해 보세요. @@ -333,8 +333,7 @@ function loadJson(url) { } function loadGithubUser(name) { - return fetch(`https://api.github.com/users/${name}`) - .then(response => response.json()); + return loadJson(`https://api.github.com/users/${name}`); } function showAvatar(githubUser) { @@ -355,7 +354,7 @@ function showAvatar(githubUser) { loadJson('/article/promise-chaining/user.json') .then(user => loadGithubUser(user.name)) .then(showAvatar) - .then(githubUser => alert(`Finished showing ${githubUser.name}`)); + .then(githubUser => alert(`${githubUser.name}의 이미지를 성공적으로 출력하였습니다.`)); // ... ``` diff --git a/1-js/11-async/04-promise-error-handling/article.md b/1-js/11-async/04-promise-error-handling/article.md index cc48896c52..269608a13d 100644 --- a/1-js/11-async/04-promise-error-handling/article.md +++ b/1-js/11-async/04-promise-error-handling/article.md @@ -199,6 +199,7 @@ Node.js같은 기타 호스트 환경에도 처리하지 못한 에러를 다루 ## 요약 - `.catch` 는 프라미스에서 발생한 모든 에러를 다룹니다. `reject()`가 호출되거나 에러가 던져지면 `.catch`에서 이를 처리합니다. +- `.then`도 두 번째 인수(에러 핸들러)가 주어지면 같은 방식으로 에러를 잡을 수 있습니다. - `.catch`는 에러를 처리하고 싶은 지점에 정확히 위치시켜야 합니다. 물론 어떻게 에러를 처리할지 알고 있어야 하죠. 핸들러에선 에러를 분석하고(커스텀 에러 클래스가 이때 도움이 됩니다) 알 수 없는 에러(프로그래밍 실수로 발생한 에러일 확률이 높습니다)는 다시 던질 수 있습니다. -- 에러 발생 시, 회복할 방법이 없다면 `.catch`를 사용하지 않아도 괜찮습니다. +- 에러가 발생했을 때 복구할 방법이 없다면 `.catch`를 아예 사용하지 않아도 됩니다. - `unhandledrejection` 이벤트 핸들러를 사용해 처리되지 않은 에러를 추적하고, 이를 사용자(혹은 서버에)에게 알려서 애플리케이션이 아무런 설명도 없이 '그냥 죽는걸' 방지합시다. 브라우저 환경에선 예방에 `unhandledrejection`을, 다른 환경에선 유사한 핸들러를 사용할 수 있습니다. diff --git a/1-js/11-async/05-promise-api/article.md b/1-js/11-async/05-promise-api/article.md index 950b303e28..aac7859044 100644 --- a/1-js/11-async/05-promise-api/article.md +++ b/1-js/11-async/05-promise-api/article.md @@ -1,6 +1,6 @@ # 프라미스 API -`Promise` 클래스에는 5가지 정적 메서드가 있습니다. 이번 챕터에선 다섯 메서드의 유스 케이스에 대해서 빠르게 알아보겠습니다. +`Promise` 클래스에는 6가지 정적 메서드가 있습니다. 이번 챕터에선 여섯 메서드의 유스 케이스에 대해서 빠르게 알아보겠습니다. ## Promise.all @@ -13,12 +13,12 @@ 문법: ```js -let promise = Promise.all([...promises...]); +let promise = Promise.all(iterable); ``` -`Promise.all`은 요소 전체가 프라미스인 배열(엄밀히 따지면 이터러블 객체이지만, 대개는 배열임)을 받고 새로운 프라미스를 반환합니다. +`Promise.all`은 이터러블(보통은 프라미스 배열)을 받고 새로운 프라미스를 반환합니다. -배열 안 프라미스가 모두 처리되면 새로운 프라미스가 이행되는데, 배열 안 프라미스의 결괏값을 담은 배열이 새로운 프라미스의 `result`가 됩니다. +배열 안 프라미스가 모두 성공적으로 처리되면 새로운 프라미스가 이행되는데, 배열 안 프라미스의 결괏값을 담은 배열이 새로운 프라미스의 결과가 됩니다. 아래 `Promise.all`은 3초 후에 처리되고, 반환되는 프라미스의 `result`는 배열 `[1, 2, 3]`이 됩니다. @@ -176,15 +176,14 @@ Promise.allSettled(urls.map(url => fetch(url))) 브라우저가 `Promise.allSettled`를 지원하지 않는다면 폴리필을 구현하면 됩니다. ```js -if(!Promise.allSettled) { - Promise.allSettled = function(promises) { - return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({ - status: 'fulfilled', - value - }), reason => ({ - status: 'rejected', - reason - })))); +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); }; } ``` @@ -215,10 +214,47 @@ Promise.race([ ]).then(alert); // 1 ``` -첫 번째 프라미스가 가장 빨리 처리상태가 되기 때문에 첫 번째 프라미스의 결과가 result 값이 됩니다. 이렇게 `Promise.race`를 사용하면 '경주(race)의 승자'가 나타난 순간 다른 프라미스의 결과 또는 에러는 무시됩니다. +첫 번째 프라미스가 가장 빨리 처리상태가 되기 때문에 첫 번째 프라미스의 결과가 `Promise.race` 전체의 결과가 됩니다. 이렇게 `Promise.race`를 사용하면 '경주(race)의 승자'가 나타난 순간 다른 프라미스의 결과 또는 에러는 무시됩니다. + + +## Promise.any + +`Promise.race`와 비슷하지만, 가장 먼저 이행된 프라미스의 결과만 기다린다는 점이 다릅니다. 전달받은 프라미스가 모두 거부되면 반환된 프라미스는 [`AggregateError`](mdn:js/AggregateError)와 함께 거부됩니다. `AggregateError`는 모든 프라미스 에러를 `errors` 프로퍼티에 저장하는 특별한 에러 객체입니다. + +문법은 다음과 같습니다. + +```js +let promise = Promise.any(iterable); +``` + +아래 예시의 결과는 `1`입니다. + +```js run +Promise.any([ + new Promise((resolve, reject) => setTimeout(() => reject(new Error("이런!")), 1000)), + new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)), + new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) +]).then(alert); // 1 +``` + +첫 번째 프라미스가 가장 빨리 처리되었지만 거부되었기 때문에 두 번째 프라미스가 결과가 됩니다. 가장 먼저 이행된 프라미스가 '경주에서 승리'하면 이후 결과는 모두 무시됩니다. + +모든 프라미스가 실패하는 예시는 다음과 같습니다. + +```js run +Promise.any([ + new Promise((resolve, reject) => setTimeout(() => reject(new Error("아야!")), 1000)), + new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러!")), 2000)) +]).catch(error => { + console.log(error.constructor.name); // AggregateError + console.log(error.errors[0]); // Error: 아야! + console.log(error.errors[1]); // Error: 에러! +}); +``` +위 예시에서 볼 수 있듯 실패한 프라미스의 에러 객체는 `AggregateError` 객체의 `errors` 프로퍼티에서 확인할 수 있습니다. -## Promise.resolve와 Promise.reject +## Promise.resolve/reject 프라미스 메서드 `Promise.resolve`와 `Promise.reject`는 `async/await` 문법([뒤에서](info:async-await) 다룸)이 생긴 후로 쓸모없어졌기 때문에 근래에는 거의 사용하지 않습니다. @@ -273,14 +309,15 @@ let promise = new Promise((resolve, reject) => reject(error)); ## 요약 -`Promise` 클래스에는 5가지 정적 메서드가 있습니다. +`Promise` 클래스에는 6가지 정적 메서드가 있습니다. 1. `Promise.all(promises)` -- 모든 프라미스가 이행될 때까지 기다렸다가 그 결괏값을 담은 배열을 반환합니다. 주어진 프라미스 중 하나라도 실패하면 `Promise.all`는 거부되고, 나머지 프라미스의 결과는 무시됩니다. 2. `Promise.allSettled(promises)` -- 최근에 추가된 메서드로 모든 프라미스가 처리될 때까지 기다렸다가 그 결과(객체)를 담은 배열을 반환합니다. 객체엔 다음과 같은 정보가 담깁니다. - `status`: `"fulfilled"` 또는 `"rejected"` - `value`(프라미스가 성공한 경우) 또는 `reason`(프라미스가 실패한 경우) 3. `Promise.race(promises)` -- 가장 먼저 처리된 프라미스의 결과 또는 에러를 담은 프라미스를 반환합니다. -4. `Promise.resolve(value)` -- 주어진 값을 사용해 이행 상태의 프라미스를 만듭니다. -5. `Promise.reject(error)` -- 주어진 에러를 사용해 거부 상태의 프라미스를 만듭니다. +4. `Promise.any(promises)` -- 최근에 추가된 메서드로 가장 먼저 이행되는 프라미스를 기다렸다가 그 결괏값을 반환합니다. 주어진 프라미스가 모두 거부되면 [`AggregateError`](mdn:js/AggregateError)가 `Promise.any`의 에러가 됩니다. +5. `Promise.resolve(value)` -- 주어진 값을 사용해 이행 상태의 프라미스를 만듭니다. +6. `Promise.reject(error)` -- 주어진 에러를 사용해 거부 상태의 프라미스를 만듭니다. -실무에선 다섯 메서드 중 `Promise.all`을 가장 많이 사용합니다. +실무에선 이 중 `Promise.all`을 가장 많이 사용합니다. diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index 4de252d9f5..1e12bdfa97 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -4,7 +4,9 @@ 기능을 구현 하다 보면 콜백보다는 프라미스가 더 편리하기 때문에 콜백 기반 함수와 라이브러리를 프라미스를 반환하는 함수로 바꾸는 게 좋은 경우가 종종 생길 겁니다. - 챕터에서 사용했던 `loadScript(src, callback)` 예시를 사용해 프라미스화에 대해 좀 더 자세히 알아봅시다. +이해를 돕기 위해 예시를 하나 살펴봅시다. + +예를 들어 챕터에서 사용했던 `loadScript(src, callback)`이 있다고 해봅시다. ```js run function loadScript(src, callback) { @@ -21,35 +23,42 @@ function loadScript(src, callback) { // loadScript('path/script.js', (err, script) => {...}) ``` -`loadScript(src, callback)`를 이제 프라미스화해봅시다. 새로운 함수 `loadScriptPromise(src)`는 `loadScript`와 동일하게 동작하지만 `callback`을 제외한 `src`만 인수로 받아야 하고, 프라미스를 반환해야 합니다. +이 함수는 주어진 `src`의 스크립트를 불러온 뒤, 에러가 발생하면 `callback(err)`를 호출하고 로딩에 성공하면 `callback(null, script)`를 호출합니다. 앞서 살펴본 것처럼 콜백을 사용할 때 널리 쓰이는 방식입니다. + +이제 이 함수를 프라미스화해봅시다. + +새 함수 `loadScriptPromise(src)`를 만들겠습니다. 이 함수는 스크립트를 불러온다는 점에서 기존 함수와 동일하게 동작하지만, 콜백을 사용하는 대신 프라미스를 반환합니다. + +다시 말해 `callback` 없이 `src`만 전달하면 프라미스를 반환받습니다. 로딩에 성공하면 이 프라미스는 `script`와 함께 이행되고, 실패하면 에러와 함께 거부됩니다. +구현은 다음과 같습니다. ```js let loadScriptPromise = function(src) { return new Promise((resolve, reject) => { loadScript(src, (err, script) => { - if (err) reject(err) + if (err) reject(err); else resolve(script); }); - }) -} + }); +}; // 사용법: // loadScriptPromise('path/script.js').then(...) ``` -새롭게 구현한 `loadScriptPromise`는 프라미스 기반 코드와 잘 융화됩니다. +예시에서 볼 수 있듯이 새 함수는 기존 함수 `loadScript`를 감싸는 래퍼입니다. 새 함수는 자체 콜백을 넘겨 `loadScript`를 호출하고, 이 콜백은 결과에 따라 프라미스의 `resolve`나 `reject`를 호출합니다. -예시에서 볼 수 있듯이, `loadScriptPromise`는 기존 함수 `loadScript`에 모든 일을 위임합니다. `loadScript`의 콜백은 스크립트 로딩 상태에 따라 `이행` 혹은 `거부`상태의 프라미스를 반환합니다. +이제 `loadScriptPromise`는 프라미스 기반 코드에 잘 어울립니다. 콜백보다 프라미스를 더 선호한다면, 그리고 곧 그럴 만한 이유를 더 살펴볼 예정인데, 이제 이 함수를 사용하면 됩니다. -그런데 실무에선 함수 하나가 아닌 여러 개의 함수를 프라미스화 해야 할 겁니다. 헬퍼 함수를 만드는 게 좋을 것 같네요. 프라미스화를 적용 할 함수 `f`를 받고 래퍼 함수를 반환하는 함수 `promisify(f)`를 만들어봅시다. +실무에서는 함수 하나가 아니라 여러 함수를 프라미스화해야 할 수도 있으므로 헬퍼를 사용하는 편이 좋습니다. -`promisify(f)`가 반환하는 래퍼 함수는 위 예시와 동일하게 동작할 겁니다. 프라미스를 반환하고 호출을 기존 함수 `f`에 전달하여 커스텀 콜백 내의 결과를 추적해야 하죠. +이 헬퍼를 `promisify(f)`라고 부르겠습니다. `promisify(f)`는 프라미스화할 함수 `f`를 받고 래퍼 함수를 반환합니다. ```js function promisify(f) { - return function (...args) { // 래퍼 함수를 반환함 + return function (...args) { // 래퍼 함수를 반환함 (*) return new Promise((resolve, reject) => { - function callback(err, result) { // f에 사용할 커스텀 콜백 + function callback(err, result) { // f에 사용할 커스텀 콜백 (**) if (err) { reject(err); } else { @@ -62,18 +71,25 @@ function promisify(f) { f.call(this, ...args); // 기존 함수를 호출합니다. }); }; -}; +} // 사용법: let loadScriptPromise = promisify(loadScript); loadScriptPromise(...).then(...); ``` -위 예시는 프라미스화 할 함수가 인수가 두 개(`(err, result)`)인 콜백을 받을 것이라 가정하고 작성되었습니다. 십중팔구 이런 상황일 것이고, 커스텀 콜백은 이 상황에 딱 들어맞습니다. `promisify`가 잘 동작하는 것은 말할 것도 없겠죠. +코드가 조금 복잡해 보일 수 있지만, 본질적으로는 위에서 `loadScript` 함수를 프라미스화할 때 작성한 코드와 같습니다. + +`promisify(f)`를 호출하면 `f`를 감싸는 래퍼, 즉 `(*)`로 표시한 함수가 반환됩니다. 이 래퍼는 프라미스를 반환하고 원래 함수 `f`로 호출을 전달하며, 커스텀 콜백 `(**)`에서 결과를 추적합니다. + +여기서 `promisify`는 원래 함수가 정확히 두 개의 인수 `(err, result)`를 받는 콜백을 기대한다고 가정합니다. 이런 경우가 가장 흔합니다. 그러면 커스텀 콜백이 바로 알맞은 형식이 되고, 이런 경우 `promisify`는 잘 동작합니다. 그런데 함수 `f`가 두 개를 초과하는 인수를 가진 콜백, `callback(err, res1, res2, ...)`을 받는다면 어떤 일이 발생할까요? -이런 경우를 대비하여 좀 더 진화한 헬퍼 함수, `promisify`를 만들어 봅시다. 새롭게 만든 함수를 `promisify(f, true)`형태로 호출하면, 프라미스 결과는 콜백의 성공 케이스(`results`)를 담은 배열, `[res1, res2, ...]`이 됩니다. +헬퍼를 개선해봅시다. 좀 더 발전된 버전의 `promisify`를 만들어 보겠습니다. + +- `promisify(f)`로 호출하면 위 버전과 비슷하게 동작해야 합니다. +- `promisify(f, true)`로 호출하면 콜백 결과를 담은 배열로 이행되는 프라미스를 반환해야 합니다. 이는 인수가 많은 콜백을 위한 동작입니다. ```js // 콜백의 성공 결과를 담은 배열을 얻게 해주는 promisify(f, true) @@ -94,19 +110,21 @@ function promisify(f, manyArgs = false) { f.call(this, ...args); }); }; -}; +} // 사용법: f = promisify(f, true); -f(...).then(arrayOfResults => ..., err => ...) +f(...).then(arrayOfResults => ..., err => ...); ``` -`callback(result)`같이 `err`이 없는 형태나 지금까지 언급하지 않은 형태의 이색적인 콜백도 있을 수 있는데, 이런 경우엔 헬퍼 함수를 사용하지 않고 직접 프라미스화 하면 됩니다. +위 코드와 본질적으로는 같습니다. 다만 `manyArgs`가 참 같은 값인지에 따라 `resolve`를 하나의 인수로 호출할지, 전체 인수로 호출할지만 달라집니다. + +`callback(result)`처럼 `err`가 아예 없는 콜백 등 더 특이한 콜백 형식의 경우, 헬퍼를 사용하지 않고 해당 함수를 직접 프라미스화하면 됩니다. 본 챕터에서 설명한 헬퍼 함수보다 더 유용한 형태의 프라미스화를 도와주는 함수를 제공하는 모듈도 많습니다. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify)가 대표적인 예입니다. Node.js에선 내장 함수 `util.promisify`를 사용해 프라미스화를 할 수 있습니다. ```smart -프라미스화는 곧 배우게 될 `async/await`와 함께 사용하면 더 좋습니다. 다만, 콜백을 완전히 대체하지는 못한다는 사실을 기억해 두시기 바랍니다. +프라미스화는 특히 이후 챕터에서 다룰 `async/await`와 함께 사용할 때 매우 유용한 접근법이지만, 콜백을 완전히 대체할 수는 없습니다. 프라미스는 하나의 결과만 가질 수 있지만, 콜백은 여러 번 호출할 수 있기 때문입니다. diff --git a/1-js/11-async/07-microtask-queue/article.md b/1-js/11-async/07-microtask-queue/article.md index 81f089bcc9..3ff39f9ab3 100644 --- a/1-js/11-async/07-microtask-queue/article.md +++ b/1-js/11-async/07-microtask-queue/article.md @@ -103,7 +103,7 @@ window.addEventListener('unhandledrejection', event => alert(event.reason)); ## 요약 -모든 프라미스 동작은 '마이크로태스크 큐'(ES8 용어)라 불리는 내부 '프라미스 잡(promise job)' 큐에 들어가서 처리되기 때문에 프라미스 핸들링은 항상 비동기로 처리됩니다. +모든 프라미스 동작은 '마이크로태스크 큐'(V8 용어)라 불리는 내부 '프라미스 잡(promise job)' 큐에 들어가서 처리되기 때문에 프라미스 핸들링은 항상 비동기로 처리됩니다. 따라서 `.then/catch/finally` 핸들러는 항상 현재 코드가 종료되고 난 후에 호출됩니다. diff --git a/1-js/11-async/08-async-await/01-rewrite-async/solution.md b/1-js/11-async/08-async-await/01-rewrite-async/solution.md index 57b2392915..773741e0e1 100644 --- a/1-js/11-async/08-async-await/01-rewrite-async/solution.md +++ b/1-js/11-async/08-async-await/01-rewrite-async/solution.md @@ -13,7 +13,7 @@ async function loadJson(url) { // (1) throw new Error(response.status); } -loadJson('no-such-user.json') +loadJson('https://javascript.info/no-such-user.json') .catch(alert); // Error: 404 (4) ``` diff --git a/1-js/11-async/08-async-await/01-rewrite-async/task.md b/1-js/11-async/08-async-await/01-rewrite-async/task.md index 2a592a512e..1651af9f07 100644 --- a/1-js/11-async/08-async-await/01-rewrite-async/task.md +++ b/1-js/11-async/08-async-await/01-rewrite-async/task.md @@ -12,9 +12,9 @@ function loadJson(url) { } else { throw new Error(response.status); } - }) + }); } -loadJson('no-such-user.json') +loadJson('https://javascript.info/no-such-user.json') .catch(alert); // Error: 404 ``` diff --git a/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md b/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md index 59b8714eda..3a4adec9a2 100644 --- a/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md +++ b/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md @@ -1,5 +1,5 @@ -속임수랄게 없는 문제입니다. `demoGithubUser`안의 `.catch`를 `try...catch`로 교체하고 필요한 곳에 `async/await`를 추가하면 됩니다. +속임수랄게 없는 문제입니다. `demoGithubUser`안의 `.catch`를 `try..catch`로 교체하고 필요한 곳에 `async/await`를 추가하면 됩니다. ```js run class HttpError extends Error { @@ -46,4 +46,4 @@ async function demoGithubUser() { } demoGithubUser(); -``` \ No newline at end of file +``` diff --git a/1-js/11-async/08-async-await/02-rewrite-async-2/task.md b/1-js/11-async/08-async-await/02-rewrite-async-2/task.md index 227cadbf32..fd04715536 100644 --- a/1-js/11-async/08-async-await/02-rewrite-async-2/task.md +++ b/1-js/11-async/08-async-await/02-rewrite-async-2/task.md @@ -1,7 +1,7 @@ # async와 await를 사용해서 '다시 던지기' 예시 재작성하기 - 챕터에서 다뤘던 '다시 던지기(rethrow)' 관련 예시를 기억하실 겁니다. 이 예시를 `.then/catch` 대신 `async/await`를 사용해 다시 작성해 봅시다. +아래는 '다시 던지기(rethrow)' 예시입니다. 이 예시를 `.then/catch` 대신 `async/await`를 사용해 다시 작성해 봅시다. 그리고 `demoGithubUser` 안의 반복(recursion)은 반복문(loop)을 사용해 작성하도록 합시다. `async/await`를 사용하면 쉽게 작성할 수 있습니다. @@ -22,7 +22,7 @@ function loadJson(url) { } else { throw new HttpError(response); } - }) + }); } // 유효한 사용자를 찾을 때까지 반복해서 username을 물어봄 diff --git a/1-js/11-async/08-async-await/03-async-from-regular/task.md b/1-js/11-async/08-async-await/03-async-from-regular/task.md index 7c1ed56e10..6d3841b56f 100644 --- a/1-js/11-async/08-async-await/03-async-from-regular/task.md +++ b/1-js/11-async/08-async-await/03-async-from-regular/task.md @@ -1,7 +1,7 @@ # async가 아닌 함수에서 async 함수 호출하기 -'일반' 함수가 하나 있는데, 여기서 `async` 함수를 어떻게 하면 호출하고, 그 결과를 사용할 수 있을까요? +'일반' 함수 `f`가 있습니다. `f` 안에서 `async` 함수 `wait()`를 호출하고 그 결과를 사용하려면 어떻게 해야 할까요? ```js async function wait() { diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/solution.md b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md new file mode 100644 index 0000000000..69129c7296 --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md @@ -0,0 +1,113 @@ + +문제의 원인은 `Promise.all`이 프라미스 중 하나라도 거부되면 즉시 거부되지만, 나머지 프라미스를 취소하지는 않는다는 데 있습니다. + +위 예시에서는 두 번째 쿼리가 실패하므로 `Promise.all`이 거부되고, `try...catch` 블록이 이 에러를 잡습니다. 한편, 다른 프라미스는 *영향을 받지 않고 독립적으로 실행을 계속합니다*. 예시에서는 잠시 후 세 번째 쿼리가 자체적으로 에러를 던집니다. 이 에러는 어디에서도 잡히지 않으므로 콘솔에서 확인할 수 있습니다. + +이 문제는 Node.js 같은 서버 측 환경에서 특히 위험합니다. 잡히지 않은 에러로 인해 프로세스가 중단될 수 있기 때문입니다. + +어떻게 고칠 수 있을까요? + +가장 이상적인 해결책은 쿼리 중 하나가 실패했을 때 아직 끝나지 않은 쿼리를 모두 취소하는 것입니다. 이렇게 하면 잠재적인 에러를 피할 수 있습니다. + +하지만 안타깝게도 `database.query` 같은 서비스 호출은 취소 기능을 지원하지 않는 서드파티 라이브러리로 구현된 경우가 많습니다. 이런 경우 호출을 취소할 방법이 없습니다. + +대안으로 `Promise.all`을 감싸는 래퍼 함수를 직접 작성할 수 있습니다. 이 함수는 각 프라미스에 커스텀 `then/catch` 핸들러를 붙여 상태를 추적합니다. 결과를 모으다가 에러가 발생하면 그 이후 프라미스는 모두 무시합니다. + +```js +function customPromiseAll(promises) { + return new Promise((resolve, reject) => { + const results = []; + let resultsCount = 0; + let hasError = false; // 첫 번째 에러가 발생하면 true로 바꿉니다. + + promises.forEach((promise, index) => { + promise + .then(result => { + if (hasError) return; // 이미 에러가 발생했다면 이 프라미스를 무시합니다. + results[index] = result; + resultsCount++; + if (resultsCount === promises.length) { + resolve(results); // 모든 결과가 준비되면 성공입니다. + } + }) + .catch(error => { + if (hasError) return; // 이미 에러가 발생했다면 이 프라미스를 무시합니다. + hasError = true; // 에러가 발생했습니다. + reject(error); // 거부 상태로 실패 처리합니다. + }); + }); + }); +} +``` + +이 방식에도 문제가 있습니다. 쿼리가 아직 처리 중일 때 `disconnect()`를 호출하는 것은 대개 바람직하지 않습니다. + +특히 일부 쿼리가 중요한 업데이트를 처리한다면 모든 쿼리가 끝까지 완료되어야 할 수 있습니다. + +따라서 실행을 계속 진행하고 마지막에 연결을 끊기 전에 모든 프라미스가 처리될 때까지 기다려야 합니다. + +다른 구현을 살펴봅시다. 이 구현은 `Promise.all`과 비슷하게 동작합니다. 첫 번째 에러와 함께 거부되지만, 모든 프라미스가 처리될 때까지 기다립니다. + +```js +function customPromiseAllWait(promises) { + return new Promise((resolve, reject) => { + const results = new Array(promises.length); + let settledCount = 0; + let firstError = null; + + promises.forEach((promise, index) => { + Promise.resolve(promise) + .then(result => { + results[index] = result; + }) + .catch(error => { + if (firstError === null) { + firstError = error; + } + }) + .finally(() => { + settledCount++; + if (settledCount === promises.length) { + if (firstError !== null) { + reject(firstError); + } else { + resolve(results); + } + } + }); + }); + }); +} +``` + +이제 `await customPromiseAllWait(...)`는 모든 쿼리가 처리될 때까지 실행을 멈춥니다. + +실행 흐름을 예측할 수 있게 보장하므로 더 안정적인 방식입니다. + +마지막으로 모든 에러를 처리하고 싶다면 `Promise.allSettled`를 사용하거나, 모든 에러를 하나의 [AggregateError](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) 객체에 모아 그 객체로 거부하는 래퍼를 작성할 수 있습니다. + +```js +// 모든 프라미스가 처리될 때까지 기다립니다. +// 에러가 없으면 결과를 반환합니다. +// 에러가 하나라도 있으면 모든 에러가 담긴 AggregateError를 던집니다. +function allOrAggregateError(promises) { + return Promise.allSettled(promises).then(results => { + const errors = []; + const values = []; + + results.forEach((res, i) => { + if (res.status === 'fulfilled') { + values[i] = res.value; + } else { + errors.push(res.reason); + } + }); + + if (errors.length > 0) { + throw new AggregateError(errors, 'One or more promises failed'); + } + + return values; + }); +} +``` diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/task.md b/1-js/11-async/08-async-await/04-promise-all-failure/task.md new file mode 100644 index 0000000000..d5ccd3c02a --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/task.md @@ -0,0 +1,79 @@ + +# 위험한 Promise.all + +`Promise.all`은 여러 작업을 병렬로 처리할 때 아주 유용합니다. 여러 서비스에 병렬로 요청을 보내야 할 때 특히 빛을 발합니다. + +하지만 숨어 있는 위험이 있습니다. 이번 과제에서는 예시를 통해 어떤 위험이 있는지 살펴보고 이를 피하는 방법을 알아봅시다. + +데이터베이스 같은 원격 서비스에 연결한다고 가정해 봅시다. + +`connect()`와 `disconnect()`라는 두 함수가 있습니다. + +연결된 상태에서는 `database.query(...)`를 사용해 요청을 보낼 수 있습니다. `database.query(...)`는 보통 결과를 반환하지만, 에러를 던질 수도 있는 비동기 함수입니다. + +간단히 구현하면 다음과 같습니다. + +```js +let database; + +function connect() { + database = { + async query(isOk) { + if (!isOk) throw new Error('Query failed'); + } + }; +} + +function disconnect() { + database = null; +} + +// 사용법: +// connect() +// ... +// database.query(true)는 성공한 호출을 흉내 냅니다. +// database.query(false)는 실패한 호출을 흉내 냅니다. +// ... +// disconnect() +``` + +이제 문제가 되는 부분을 살펴봅시다. + +연결한 뒤 쿼리 세 개를 병렬로 보내고, 이후 연결을 끊는 코드를 작성했습니다. 각 쿼리는 100ms, 200ms, 300ms처럼 서로 다른 시간이 걸립니다. + +```js +// `ms` 밀리초 뒤에 비동기 함수 `fn`을 호출하는 헬퍼 함수 +function delay(fn, ms) { + return new Promise((resolve, reject) => { + setTimeout(() => fn().then(resolve, reject), ms); + }); +} + +async function run() { + connect(); + + try { + await Promise.all([ + // 병렬 작업 세 개는 각각 100ms, 200ms, 300ms로 서로 다른 시간이 걸립니다. + // 이 효과를 내기 위해 `delay` 헬퍼를 사용합니다. +*!* + delay(() => database.query(true), 100), + delay(() => database.query(false), 200), + delay(() => database.query(false), 300) +*/!* + ]); + } catch(error) { + console.log('에러를 처리했습니다. 정말 그럴까요?'); + } + + disconnect(); +} + +run(); +``` + +세 쿼리 중 두 개는 실패합니다. 그래도 `Promise.all` 호출을 `try..catch` 블록으로 감싸 두었으니 충분해 보입니다. + +하지만 이 방법으로는 부족합니다! 실제로 이 스크립트는 콘솔에 잡히지 않은 에러를 남깁니다. + +왜 이런 일이 생길까요? 어떻게 피할 수 있을까요? \ No newline at end of file diff --git a/1-js/11-async/08-async-await/article.md b/1-js/11-async/08-async-await/article.md index 67d793156f..664f7fcc48 100644 --- a/1-js/11-async/08-async-await/article.md +++ b/1-js/11-async/08-async-await/article.md @@ -71,7 +71,7 @@ f(); `await`는 `promise.then`보다 좀 더 세련되게 프라미스의 `result` 값을 얻을 수 있도록 해주는 문법입니다. `promise.then`보다 가독성 좋고 쓰기도 쉽습니다. -````warn header="일반 함수엔 `await`을 사용할 수 없습니다." +````warn header="일반 함수에는 `await`을 사용할 수 없습니다." `async` 함수가 아닌데 `await`을 사용하면 문법 에러가 발생합니다. ```js run @@ -121,16 +121,22 @@ showAvatar(); 코드가 깔끔해지고 읽기도 쉬워졌습니다. 프라미스를 사용한 것보다 훨씬 낫네요. -````smart header="`await`는 최상위 레벨 코드에서 작동하지 않습니다." -`await`을 이제 막 사용하기 시작한 분들은 최상위 레벨 코드(top-level code)에 `await`을 사용할 수 없다는 사실을 잊곤 합니다. 아래와 같은 코드는 동작하지 않습니다. +````smart header="최신 브라우저에서는 모듈 안에서 최상위 레벨 `await`를 사용할 수 있습니다." +최신 브라우저에서는 모듈 안이라면 최상위 레벨의 `await`가 문제없이 작동합니다. 모듈은 글에서 다룰 예정입니다. -```js run -// 최상위 레벨 코드에선 문법 에러가 발생함 +예를 들면 다음과 같습니다. + +```js run module +// 이 코드는 모듈 안의 최상위 레벨에서 실행된다고 가정합니다 let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); + +console.log(user); ``` -하지만 익명 async 함수로 코드를 감싸면 최상위 레벨 코드에도 `await`를 사용할 수 있습니다. +모듈을 사용하지 않거나 [오래된 브라우저](https://caniuse.com/mdn-javascript_operators_await_top_level)를 지원해야 한다면, 익명 async 함수로 감싸는 범용적인 방법을 사용할 수 있습니다. + +다음과 같이 말이죠. ```js (async () => { @@ -139,6 +145,7 @@ let user = await response.json(); ... })(); ``` + ```` ````smart header="`await`는 \'thenable\' 객체를 받습니다." @@ -156,7 +163,7 @@ class Thenable { // 1000밀리초 후에 이행됨(result는 this.num*2) setTimeout(() => resolve(this.num * 2), 1000); // (*) } -}; +} async function f() { // 1초 후, 변수 result는 2가 됨 @@ -184,7 +191,7 @@ class Waiter { new Waiter() .wait() - .then(alert); // 1 + .then(alert); // 1 (this is the same as (result => alert(result))) ``` `async` 메서드와 `async` 함수는 프라미스를 반환하고 `await`를 사용할 수 있다는 점에서 동일합니다. diff --git a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md index fb82dbb923..a07dd2d80c 100644 --- a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md +++ b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md @@ -3,7 +3,7 @@ function* pseudoRandom(seed) { let value = seed; while(true) { - value = value * 16807 % 2147483647 + value = value * 16807 % 2147483647; yield value; } diff --git a/1-js/12-generators-iterators/1-generators/article.md b/1-js/12-generators-iterators/1-generators/article.md index 0eb6dbd283..ac5d80ad18 100644 --- a/1-js/12-generators-iterators/1-generators/article.md +++ b/1-js/12-generators-iterators/1-generators/article.md @@ -43,6 +43,7 @@ alert(generator); // [object Generator] `next()`는 제너레이터의 주요 메서드입니다. `next()`를 호출하면 가장 가까운 `yield `문을 만날 때까지 실행이 지속됩니다(`value`를 생략할 수도 있는데, 이 경우엔 `undefined`가 됩니다). 이후, `yield `문을 만나면 실행이 멈추고 산출하고자 하는 값인 `value`가 바깥 코드에 반환됩니다(yield는 '생산하다, 산출하다'라는 뜻을 가짐 - 옮긴이). `next()`는 항상 아래 두 프로퍼티를 가진 객체를 반환합니다. + - `value`: 산출 값 - `done`: 함수 코드 실행이 끝났으면 `true`, 아니라면 `false` @@ -95,7 +96,7 @@ alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*} ```smart header="`function* f(…)`가 맞나요 아니면 `function *f(…)`가 맞나요?" 둘 다 맞습니다. -그런데 `*`는 제너레이터 `함수`를 나타내므로 대개는 첫 번째 문법이 선호됩니다. `*`는 종류를 나타내는 것이지 이름을 나타내는 것이 아니기 때문입니다. 그러므로 `*`는 `function`에 붙이도록 합시다. +그런데 `*`는 제너레이터 `함수`를 나타내므로 대개는 첫 번째 문법이 선호됩니다. `*`는 종류를 나타내는 것이지 이름을 나타내는 것이 아니기 때문입니다. 그러므로 `*`는 `function`에 붙이도록 합시다. ``` ## 제너레이터와 이터러블 @@ -202,7 +203,7 @@ let range = { to: 5, *[Symbol.iterator]() { // [Symbol.iterator]: function*()를 짧게 줄임 - for(let value = this.from; value <= this.to; value++) { + for (let value = this.from; value <= this.to; value++) { yield value; } } @@ -212,6 +213,7 @@ alert( [...range] ); // 1, 2, 3, 4, 5 ``` `range[Symbol.iterator]()`는 제너레이터를 반환하고, 제너레이터 메서드는 `for..of`가 동작하는데 필요한 사항(아래 설명)을 충족시키므로 예시가 잘 동작합니다. + - `.next()` 메서드가 있음 - 반환 값의 형태는 `{value: ..., done: true/false}`이어야 함 @@ -238,11 +240,12 @@ function* generateSequence(start, end) { ``` 그리고 이 함수를 기반으로 좀 더 복잡한 값을 연속해서 생성하는 함수를 만들어봅시다. 값 생성 규칙은 다음과 같습니다. + - 처음엔 숫자 `0`부터 `9`까지를 생성합니다(문자 코드 48부터 57까지), - 이어서 알파벳 대문자 `A`부터 `Z`까지를 생성합니다(문자 코드 65부터 90까지). - 이어서 알파벳 소문자 `a`부터 `z`까지를 생성합니다(문자 코드 97부터 122까지). -이런 규칙을 충족하는 연속 값은 비밀번호를 만들 때 응용할 수 있습니다(물론 특수 문자도 추가 할 수 있습니다). +이런 규칙을 충족하는 연속 값은 비밀번호를 만들 때 응용할 수 있습니다(물론 특수 문자도 추가할 수 있습니다). 일반 함수로는 여러 개의 함수를 만들고 그 호출 결과를 어딘가에 저장한 후 다시 그 결과들을 조합해야 원하는 기능을 구현할 수 있습니다. @@ -279,7 +282,7 @@ for(let code of generatePasswordCodes()) { alert(str); // 0..9A..Za..z ``` -`yield*` 지시자는 실행을 다른 제너레이터에 *위임합니다(delegate)*. 여기서 '위임'은 `yield* gen`이 제너레이터 `gen`을 대상으로 반복을 수행하고, 산출 값들을 바깥으로 전달한다는 것을 의미합니다. 마치 외부 제너레이터에 의해 값이 산출된 것처럼 말이죠. +`yield*` 지시자는 실행을 다른 제너레이터에 _위임합니다(delegate)_. 여기서 '위임'은 `yield* gen`이 제너레이터 `gen`을 대상으로 반복을 수행하고, 산출 값들을 바깥으로 전달한다는 것을 의미합니다. 마치 외부 제너레이터에 의해 값이 산출된 것처럼 말이죠. 중첩 제너레이터(`generateSequence`)의 코드를 직접 써줘도 결과는 같습니다. @@ -347,7 +350,7 @@ generator.next(4); // --> 결과를 제너레이터 안으로 전달합니다. 2. 그 후, 위 그림에서 보듯이 `yield`의 결과가 제너레이터를 호출하는 외부 코드에 있는 변수, `question`에 할당됩니다. 3. 마지막 줄, `generator.next(4)`에서 제너레이터가 다시 시작되고 `4`는 `result`에 할당됩니다(`let result = 4`). -외부 코드에선 `next(4)`를 즉시 호출하지 않고 있다는 점에 주목해 주시기 바랍니다. 제너레이터가 기다려주기 때문에 호출을 나중에 해도 문제가 되지 않습니다. +외부 코드에선 `next(4)`를 즉시 호출하지 않고 있다는 점에 주목해 주시기 바랍니다. 제너레이터가 기다려주기 때문에 호출을 나중에 해도 문제가 되지 않습니다. 예시: @@ -448,6 +451,28 @@ try { 이렇게 제너레이터 바깥에서 에러를 잡지 못하면 에러는 제너레이터 호출 코드 바깥으로 떨어져 나갑니다. 여기서도 에러를 잡지 못하면 스크립트가 죽겠죠. +## generator.return + +`generator.return(value)`는 제너레이터 실행을 종료시키고 주어진 `value`를 반환합니다. + +```js +function* gen() { + yield 1; + yield 2; + yield 3; +} + +const g = gen(); + +g.next(); // { value: 1, done: false } +g.return('foo'); // { value: "foo", done: true } +g.next(); // { value: undefined, done: true } +``` + +이미 종료된 제너레이터에서 `generator.return()`을 다시 호출해도 인수로 전달한 값을 그대로 반환합니다([MDN](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Generator/return)). + +대부분의 경우 모든 반환 값이 필요하기 때문에 이 메서드를 잘 사용하지는 않지만, 특정 상황에서 제너레이터를 중지하고 싶을 때 유용할 수 있습니다. + ## 요약 - 제너레이터는 제너레이터 함수 `function* f(…) {…}`을 사용해 만듭니다. diff --git a/1-js/12-generators-iterators/2-async-iterators-generators/article.md b/1-js/12-generators-iterators/2-async-iterators-generators/article.md index ef917fa374..8b9a1b5ba9 100644 --- a/1-js/12-generators-iterators/2-async-iterators-generators/article.md +++ b/1-js/12-generators-iterators/2-async-iterators-generators/article.md @@ -1,36 +1,48 @@ - # async 이터레이터와 제너레이터 비동기 이터레이터(asynchronous iterator)를 사용하면 비동기적으로 들어오는 데이터를 필요에 따라 처리할 수 있습니다. 네트워크를 통해 데이터가 여러 번에 걸쳐 들어오는 상황을 처리할 수 있게 되죠. 비동기 이터레이터에 더하여 비동기 제너레이터(asynchronous generator)를 사용하면 이런 데이터를 좀 더 편리하게 처리할 수 있습니다. 먼저 간단한 예시를 살펴보며 문법을 익힌 후, 실무에서 벌어질 법한 사례를 가지고 async 이터레이터와 제너레이터가 어떻게 사용되는지 알아보겠습니다. -## async 이터레이터 +## 이터러블 복습 + +이터러블에 대해 상기해봅시다. -비동기 이터레이터는 일반 이터레이터와 유사하며, 약간의 문법적인 차이가 있습니다. +아래와 같은 `range` 객체가 있다고 해보죠. + +```js +let range = { + from: 1, + to: 5 +}; +``` - 챕터에서 살펴본 바와 같이 '일반' 이터러블은 객체입니다. +이제 이 객체에 `for(value of range)`와 같은 `for..of` 반복문을 적용해 `1`부터 `5`까지의 값을 차례로 얻으려고 합니다. + +다시 말해, 객체에 *순회 기능*을 추가하고 싶은 것이죠. + +`Symbol.iterator`라는 특수한 메서드를 사용하면 이 기능을 구현할 수 있습니다. + +- `for..of` 반복문이 시작될 때, 자바스크립트 엔진은 이 메서드를 호출합니다. 이때 이 메서드는 `next`라는 메서드가 담긴 객체를 반환해야 합니다. +- 각 이터레이션마다 `next()` 메서드를 호출해 다음 값을 가져옵니다. +- `next()`는 `{done: true/false, value: <반복값>}` 형태의 객체를 반환해야 하며, `done: true`는 반복의 끝을 의미합니다. + +이터러블 `range`를 직접 구현하면 다음과 같습니다. ```js run let range = { from: 1, to: 5, - // for..of 최초 실행 시, Symbol.iterator가 호출됩니다. *!* - [Symbol.iterator]() { + [Symbol.iterator]() { // for..of 최초 실행 시, Symbol.iterator가 호출됩니다. */!* - // Symbol.iterator메서드는 이터레이터 객체를 반환합니다. - // 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, - // 다음 값은 next()에서 정해집니다. return { current: this.from, last: this.to, - // for..of 반복문에 의해 각 이터레이션마다 next()가 호출됩니다. *!* - next() { // (2) - // next()는 객체 형태의 값, {done:.., value :...}를 반환합니다. + next() { // 매 이터레이션마다 호출되어 다음 값을 가져옵니다. */!* if (this.current <= this.last) { return { done: false, value: this.current++ }; @@ -47,40 +59,44 @@ for(let value of range) { } ``` -일반 이터레이터에 대한 설명은 에서 자세히 다루고 있으니, 꼭 살펴보시기 바랍니다. +일반 이터레이터에 대한 설명은 에서 자세히 다루고 있으니, 혹시 이해되지 않는 부분이 있다면 해당 챕터를 참고하시길 바랍니다. + +## async 이터러블 + +비동기 이터레이션은 값이 비동기적으로 도착하는 경우에 필요합니다. 예를 들어 `setTimeout` 이후나 다른 종류의 지연이 발생하는 상황이 이에 해당합니다. + +가장 흔한 사례는 다음 값을 받아오기 위해 네트워크 요청이 필요한 경우입니다. 이에 대한 실전 예시는 조금 뒤에서 다루겠습니다. 이제, 이터러블 객체를 비동기적으로 만들려면 어떤 작업이 필요한지 알아봅시다. -1. `Symbol.iterator` 대신, `Symbol.asyncIterator`를 사용해야 합니다. -2. `next()`는 프라미스를 반환해야 합니다. + +1. `Symbol.iterator` 대신, `Symbol.asyncIterator`를 사용해야 합니다. +2. `next()`는 다음 값을 담은 프라미스를 반환해야 합니다. + - `async` 키워드를 사용하면 이 과정을 간단히 처리할 수 있으므로, `async next()` 형태로 작성하면 됩니다. 3. 비동기 이터러블 객체를 대상으로 하는 반복 작업은 `for await (let item of iterable)` 반복문을 사용해 처리해야 합니다. + - 이때 `await` 키워드가 들어간다는 점에 유의하세요. 익숙한 예시인 이터러블 객체 `range`를 토대로, 일초마다 비동기적으로 값을 반환하는 이터러블 객체를 만들어보겠습니다. +앞서 작성한 코드 몇 군데를 수정하기만 하면 됩니다. + ```js run let range = { from: 1, to: 5, - // for await..of 최초 실행 시, Symbol.asyncIterator가 호출됩니다. *!* [Symbol.asyncIterator]() { // (1) */!* - // Symbol.asyncIterator 메서드는 이터레이터 객체를 반환합니다. - // 이후 for await..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, - // 다음 값은 next()에서 정해집니다. return { current: this.from, last: this.to, - // for await..of 반복문에 의해 각 이터레이션마다 next()가 호출됩니다. *!* async next() { // (2) - // next()는 객체 형태의 값, {done:.., value :...}를 반환합니다. - // (객체는 async에 의해 자동으로 프라미스로 감싸집니다.) */!* *!* - // 비동기로 무언가를 하기 위해 await를 사용할 수 있습니다. + // 주의: async next() 내부에서는 "await"를 사용할 수 있습니다. await new Promise(resolve => setTimeout(resolve, 1000)); // (3) */!* @@ -110,33 +126,39 @@ let range = { 1. 객체를 비동기적으로 반복 가능하도록 하려면, `Symbol.asyncIterator`메서드가 반드시 구현되어 있어야 합니다. -- `(1)` 2. `Symbol.asyncIterator`는 프라미스를 반환하는 메서드인 `next()`가 구현된 객체를 반환해야 합니다. -- `(2)` 3. `next()`는 `async` 메서드일 필요는 없습니다. 프라미스를 반환하는 메서드라면 일반 메서드도 괜찮습니다. 다만, `async`를 사용하면 `await`도 사용할 수 있기 때문에, 여기선 편의상 `async`메서드를 사용해 일 초의 딜레이가 생기도록 했습니다. -- `(3)` -4. 반복 작업을 하려면 'for' 뒤에 'await'를 붙인 `for await(let value of range)`를 사용하면 됩니다. `for await(let value of range)`가 실행될 때 `range[Symbol.asyncIterator]()`가 일회 호출되는데, 그 이후엔 각 값을 대상으로 `next()`가 호출됩니다. -- `(4)` +4. 반복 작업을 하려면 'for' 뒤에 'await'를 붙인 `for await(let value of range)`를 사용하면 됩니다. `for await(let value of range)`가 실행될 때 `range[Symbol.asyncIterator]()`가 한 번 호출되는데, 그 이후엔 각 값을 대상으로 `next()`가 호출됩니다. -- `(4)` 일반 이터레이터와 async 이터레이터를 간략하게 비교하면 다음과 같습니다. -| | 이터레이터 | async 이터레이터 | -|-------|-----------|-----------------| -| 이터레이터를 제공해주는 메서드 | `Symbol.iterator` | `Symbol.asyncIterator` | -| `next()`가 반환하는 값 | 모든 값 | `Promise` | -| 반복 작업을 위해 사용하는 반복문 | `for..of` | `for await..of` | - +| | 이터레이터 | async 이터레이터 | +| -------------------------------- | ----------------- | ---------------------- | +| 이터레이터를 제공해주는 메서드 | `Symbol.iterator` | `Symbol.asyncIterator` | +| `next()`가 반환하는 값 | 모든 값 | `Promise` | +| 반복 작업을 위해 사용하는 반복문 | `for..of` | `for await..of` | ````warn header="전개 구문 `...`은 비동기적으로 동작하지 않습니다." 일반적인 동기 이터레이터가 필요한 기능은 비동기 이터레이터와 함께 사용할 수 없습니다. 전개 구문은 일반 이터레이터가 필요로 하므로 아래와 같은 코드는 동작하지 않습니다. + ```js alert( [...range] ); // Symbol.iterator가 없기 때문에 에러 발생 ``` 전개 구문은 `await`가 없는 `for..of`와 마찬가지로, `Symbol.asyncIterator`가 아닌 `Symbol.iterator`를 찾기 때문에 에러가 발생하는 것은 당연합니다. + ```` -## async 제너레이터 +## 제너레이터 복습 + +이번엔 제너레이터를 상기해봅시다. 제너레이터를 활용하면 반복 코드를 훨씬 간결하게 작성할 수 있으며, 실제로 이터러블을 만들 때는 대부분 제너레이터를 사용합니다. + +핵심만 간단히 말하면, 제너레이터는 '값을 생성(yield)하는 함수'입니다. 자세한 설명은 [](info:generators) 챕터를 참고하세요. -앞서 배운 바와 같이 자바스크립트에선 제너레이터를 사용할 수 있는데, 제너레이터는 이터러블 객체입니다. +제너레이터는 별표가 붙은 `function*`으로 선언하고, `yield`로 값을 생성하며, `for..of`로 반복할 수 있습니다. + +다음 예시는 시작(`start`) 값부터 끝(`end`) 값까지 연속된 값을 생성하는 코드입니다. -[](info:generators) 챕터에서 살펴본 `start`부터 `end`까지의 연속된 숫자를 생성해주는 제너레이터를 떠올려 봅시다. ```js run function* generateSequence(start, end) { @@ -146,15 +168,58 @@ function* generateSequence(start, end) { } for(let value of generateSequence(1, 5)) { - alert(value); // 1, then 2, then 3, then 4, then 5 + alert(value); // 1, 2, 3, 4, 5 +} +``` + +이미 배웠듯이, 반복 가능한 객체를 만들려면 객체에 `Symbol.iterator`를 추가해야 합니다. + +```js +let range = { + from: 1, + to: 5, +*!* + [Symbol.iterator]() { + return + } +*/!* +} +``` + +그런데 `Symbol.iterator`는 위 예시와 같이 `next`가 구현된 일반 객체를 반환하는 것 보다, 제너레이터를 반환하도록 구현하는 경우가 더 많습니다. + +```js run +let range = { + from: 1, + to: 5, + + *[Symbol.iterator]() { // [Symbol.iterator]: function*()를 짧게 줄임 + for(let value = this.from; value <= this.to; value++) { + yield value; + } + } +}; + +for(let value of range) { + alert(value); // 1, 2, 3, 4, 5 } ``` -일반 제너레이터에선 `await`를 사용할 수 없습니다. 그리고 모든 값은 동기적으로 생산됩니다. `for..of` 어디에서도 딜레이를 줄 만한 곳이 없죠. 일반 제너레이터는 동기적 문법입니다. +자세한 내용은 [](info:generators) 챕터를 참고하세요. + +일반 제너레이터에선 `await`를 사용할 수 없습니다. `for..of` 구문의 특성상 모든 값은 동기적으로 만들어집니다. + +비동기적으로 값을 생성해야 할 때는 어떻게 해야 할까요? 예를 들어 네트워크 요청을 통해 값을 가져오는 경우처럼 말이죠. + +이럴 때는 비동기 제너레이터를 사용하면 됩니다. + +## async 제너레이터 -그런데 제너레이터 본문에서 `await`를 사용해야만 하는 상황이 발생하면 어떻게 해야 할까요? 아래와 같이 네트워크 요청을 해야 하는 상황이 발생하면 말이죠. +비동기적으로 값의 시퀀스를 생성하는 객체가 필요한 대부분의 경우, 비동기 제너레이터를 사용하면 됩니다. -물론 가능합니다. 아래 예시와 같이 `async`를 제너레이터 함수 앞에 붙여주면 됩니다. +문법은 간단합니다. `function*` 앞에 `async`를 붙이기만 하면 됩니다. 이렇게 하면 제너레이터가 비동기적으로 동작합니다. + +이후 `for await (...)`으로 반복할 수 있습니다. 아래처럼 말이죠. ```js run *!*async*/!* function* generateSequence(start, end) { @@ -181,11 +246,12 @@ for(let value of generateSequence(1, 5)) { })(); ``` -이제 `for await...of`로 반복이 가능한 async 제너레이터를 사용할 수 있게 되었습니다. +제너레이터가 비동기이므로 내부에서 `await`를 사용할 수 있고, 프라미스나 네트워크 요청 등도 자유롭게 활용할 수 있습니다. -async 제너레이터를 만드는 것은 실제로도 상당히 간단합니다. `async` 키워드를 붙이기만 하면 제너레이터 안에서 프라미스와 기타 async 함수를 기반으로 동작하는 `await`를 사용할 수 있습니다. +````smart header="내부 동작의 차이" +제너레이터의 세부 동작을 잘 기억하고 있다면, 일반 제너레이터와 비동기 제너레이터 간 내부적인 차이가 있다는 것을 눈치챘을 것입니다. -async 제너레이터의 `generator.next()` 메서드는 비동기적이 되고, 프라미스를 반환한다는 점은 일반 제너레이터와 async 제너레이터엔 또 다른 차이입니다. +비동기 제너레이터는 `generator.next()` 메서드가 비동기로 동작하며 프라미스를 반환합니다. 일반 제너레이터에서는 `result = generator.next()`를 사용해 값을 얻습니다. 반면 async 제너레이터에서는 아래와 같이 `await`를 붙여줘야 합니다. @@ -193,46 +259,16 @@ async 제너레이터의 `generator.next()` 메서드는 비동기적이 되고, result = await generator.next(); // result = {value: ..., done: true/false} ``` -## async 이터러블 - -이미 배웠듯이, 반복 가능한 객체를 만들려면 객체에 `Symbol.iterator`를 추가해야 합니다. - -```js -let range = { - from: 1, - to: 5, -*!* - [Symbol.iterator]() { - return - } -*/!* -} -``` - -그런데 `Symbol.iterator`는 위 예시와 같이 `next`가 구현된 일반 객체를 반환하는 것 보다, 제너레이터를 반환하도록 구현하는 경우가 더 많습니다. - -[](info:generators) 챕터의 예시를 다시 상기해 봅시다. - -```js run -let range = { - from: 1, - to: 5, +이것이 async 제너레이터가 `for await...of`와 함께 동작하는 이유입니다. +```` - *[Symbol.iterator]() { // [Symbol.iterator]: function*()를 짧게 줄임 - for(let value = this.from; value <= this.to; value++) { - yield value; - } - } -}; +### async 이터러블 range -for(let value of range) { - alert(value); // 1, 2, 3, 4, 5 -} -``` +일반 제너레이터를 `Symbol.iterator`로 사용하면 반복 코드를 더욱 간결하게 만들 수 있습니다. -위 예시에서 커스텀 객체 `range`는 반복 가능하고, 제너레이터 `*[Symbol.iterator]`엔 값을 나열해주는 로직이 구현되어 있습니다. +마찬가지로 async 제너레이터를 `Symbol.asyncIterator`로 사용하면 비동기 반복을 구현할 수 있습니다. -지금 상태에서 제너레이터에 비동기 동작을 추가하려면, `Symbol.iterator`를 async `Symbol.asyncIterator`로 바꿔야 합니다. +예를 들어 동기적인 `Symbol.iterator`를 비동기적인 `Symbol.asyncIterator`로 교체하면, `range` 객체가 1초 간격으로 비동기적으로 값을 생성합니다. ```js run let range = { @@ -263,39 +299,48 @@ let range = { 이제 1초의 간격을 두고 값을 얻을 수 있습니다. -## 실제 사례 +```smart +기술적으로는 하나의 객체에 `Symbol.iterator`와 `Symbol.asyncIterator`를 모두 추가해, 동기(`for..of`)와 비동기(`for await..of`) 반복을 모두 지원하도록 만들 수도 있습니다. + +다만 실제로 그렇게 할 일은 거의 없습니다. +``` + +## 실제 사례: 페이지네이션이 적용된 데이터 지금까진 아주 간단한 예시들만 살펴보며, async 제너레이터에 대한 기초를 다졌습니다. 이제 실무에서 접할법한 유스 케이스를 살펴보겠습니다. 상당히 많은 온라인 서비스가 페이지네이션(pagination)을 구현해 데이터를 전송합니다. 사용자 목록이 필요해서 서버에 요청을 보내면, 서버는 일정 숫자(예를 들어 100명의 사용자) 단위로 사용자를 끊어 정보를 '한 페이지'로 구성한 후, 다음 페이지를 볼 수 있는 URL과 함께 응답합니다. -이런 패턴은 사용자 목록 전송뿐만 아니라, 다양한 서비스에서 찾아볼 수 있습니다. GitHub에서 커밋 이력을 볼 때도 페이지네이션이 사용됩니다. +이런 패턴은 사용자 목록 전송뿐만 아니라, 다양한 서비스에서 찾아볼 수 있습니다. + +GitHub에서 커밋 이력을 볼 때도 페이지네이션이 사용됩니다. - 클라이언트는 `https://api.github.com/repos//commits` 형태의 URL로 요청을 보냅니다. - GitHub에선 커밋 30개의 정보가 담긴 JSON과 함께, 다음 페이지에 대한 정보를 `Link` 헤더에 담아 응답합니다. - 더 많은 커밋 정보가 필요하면 헤더에 담긴 링크를 사용해 다음 요청을 보냅니다. 원하는 정보를 얻을 때까지 이런 과정을 반복합니다. -실제 GitHub API는 복잡하지만, 여기선 커밋 정보가 담긴 이터러블 객체를 만들어서 아래와 같이 객체를 대상으로 반복 작업을 할 수 있게 해주는 간단한 API를 만들어 보도록 하겠습니다. +이제 코드로 커밋 정보를 더 간단히 가져오는 방법을 구현해봅시다. -```js -let repo = 'javascript-tutorial/en.javascript.info'; // 커밋 정보를 얻어올 GitHub 리포지토리 +필요할 때마다 요청을 보내 커밋 정보를 가져오는 함수 `fetchCommits(repo)`를 만들어 API를 구성해봅시다. `fetchCommits(repo)`에서 페이지네이션 관련 일들을 모두 처리하도록 하면 원하는 대로 비동기 이터레이션인 `for await..of`에서 각 커밋을 처리할 수 있을 겁니다. -for await (let commit of fetchCommits(repo)) { +사용 방법은 다음과 같습니다. + +```js +for await (let commit of fetchCommits("username/repository")) { // 여기서 각 커밋을 처리함 } ``` -필요할 때마다 요청을 보내 커밋 정보를 가져오는 함수 `fetchCommits(repo)`를 만들어 API를 구성하도록 하겠습니다. `fetchCommits(repo)`에서 페이지네이션 관련 일들을 모두 처리하도록 하면 원하는 대로 `for await..of`에서 각 커밋을 처리할 수 있을 겁니다. - -async 제너레이터를 이용하면 쉽게 함수를 구현할 수 있습니다. +아래는 async 제너레이터로 구현한 함수입니다. ```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는 모든 요청에 user-agent헤더를 강제 합니다. + const response = await fetch(url, { + // (1) + headers: { 'User-Agent': 'Our script' }, // GitHub는 모든 요청에 user-agent 헤더를 강제 합니다. }); const body = await response.json(); // (2) 응답은 JSON 형태로 옵니다(커밋이 담긴 배열). @@ -306,38 +351,47 @@ async function* fetchCommits(repo) { url = nextPage; - for(let commit of body) { // (4) 페이지가 끝날 때까지 커밋을 하나씩 반환(yield)합니다. + for (let commit of body) { // (4) 페이지가 끝날 때까지 커밋을 하나씩 반환(yield)합니다. yield commit; } } } ``` -1. 다운로드는 [fetch](info:fetch) 메서드로 하겠습니다. `fetch`를 사용하면 인증 정보나 헤더 등을 함께 실어 요청할 수 있습니다. GitHub에서 강제하는 `User-Agent`를 헤더에 실어 보내겠습니다. -2. `fetch` 전용 메서드인 `response.json()`을 사용해 요청 결과를 JSON으로 파싱합니다. -3. 응답의 `Link` 헤더에서 다음 페이지의 URL을 얻습니다. 헤더에서 `https://api.github.com/repositories/93253246/commits?page=2`형태의 URL만 추출하기 위해 정규표현식을 사용하였습니다. -4. 커밋을 하나씩 반환하는데, 전체 다 반환되면 다음 `while(url)` 반복문이 트리거 되어 서버에 다시 요청을 보냅니다. +동작 방식을 좀 더 자세히 설명하면 다음과 같습니다. + +1. 커밋을 내려받을 때는 브라우저의 [fetch](info:fetch) 메서드를 사용합니다. + - 시작 URL은 `https://api.github.com/repos//commits`이며, 다음 페이지 URL은 응답의 `Link` 헤더에 담겨 옵니다. + - `fetch` 메서드를 사용하면 필요 시 인증 정보나 헤더를 함께 전송할 수 있습니다. GitHub는 `User-Agent` 헤더를 필수로 요구합니다. +2. 커밋은 JSON 형식으로 반환됩니다. +3. 응답의 `Link` 헤더에서 다음 페이지의 URL을 추출합니다. 헤더의 형식이 특수하여 정규표현식을 사용합니다([정규표현식](info:regular-expressions) 챕터에서 자세히 다룹니다). + - 다음 페이지 URL은 `https://api.github.com/repositories/93253246/commits?page=2`와 같은 형태로, GitHub가 자체적으로 생성합니다. +4. 커밋을 하나씩 반환하는데, 전체 다 반환되면 다음 while(url) 반복문이 트리거 되어 서버에 다시 요청을 보냅니다. 사용법은 다음과 같습니다(콘솔 창을 열어 각 커밋의 author를 확인해보세요). ```js run (async () => { - let count = 0; - for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) { - + for await (const commit of fetchCommits( + 'javascript-tutorial/en.javascript.info', + )) { console.log(commit.author.login); - if (++count == 100) { // 100번째 커밋에서 멈춥니다. + if (++count == 100) { + // 100번째 커밋에서 멈춥니다. break; } } - })(); + +// 주의: 외부 샌드박스(외부 실행 환경)에서 실행할 경우, 앞서 살펴본 fetchCommits 함수 코드를 여기에 복사·붙여넣기 해야 정상적으로 동작합니다. ``` -처음에 구상했던 API가 구현되었습니다. 페이지네이션과 관련된 내부 메커니즘은 바깥에서 볼 수 없고, 우리는 단순히 async 제너레이터를 사용해 원하는 커밋을 반환받기만 하면 됩니다. +처음에 구상했던 API가 구현되었습니다. + +페이지네이션과 관련된 내부 메커니즘은 바깥에서 볼 수 없고, 우리는 단순히 async 제너레이터를 사용해 원하는 커밋을 반환받기만 하면 됩니다. ## 요약 @@ -354,12 +408,11 @@ async function* fetchCommits(repo) { 일반 제너레이터와 async 제너레이터의 문법 차이는 다음과 같습니다. - | | generators | async generator | |-------|-----------|-----------------| | 선언 | `function*` | `async function*` | | `next()`가 반환하는 값 | `{value:…, done: true/false}` | `{value:…, done: true/false}`를 감싸는 `Promise` | -웹 개발을 하다 보면 띄엄띄엄 들어오는 데이터 스트림을 다뤄야 하는 경우가 자주 생깁니다. 용량이 큰 파일을 다운로드하거나 업로드 할 때와 같이 말이죠. +웹 개발을 하다 보면 띄엄띄엄 들어오는 데이터 스트림을 다뤄야 하는 경우가 자주 생깁니다. 용량이 큰 파일을 다운로드하거나 업로드 할 때와 같이 말이죠. 이런 데이터를 처리할 때 async 제너레이터를 사용할 수 있습니다. 참고로 브라우저 등의 몇몇 호스트 환경은 데이터 스트림을 처리할 수 있게 해주는 API인 Streams을 제공하기도 합니다. Streams API에서 제공하는 특별한 인터페이스를 사용하면, 데이터를 변경하여 한 스트림에서 다른 스트림으로 데이터를 전달할 수 있습니다. 따라서 한쪽에서 받은 데이터를 다른 쪽에 즉각 전달하는 게 가능해집니다. diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index 8b4af84e52..2390d95d8f 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -95,6 +95,8 @@ sayHi('John'); // Hello, John! 브라우저 환경에서도 ` + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 0000000000..ea55b44781 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,24 @@ +const startMessagesBtn = document.querySelector('.start-messages'); // (1) +const closeWindowBtn = document.querySelector('.window__button'); // (2) +const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) + +startMessagesBtn.addEventListener('click', () => { // (4) + startMessages(windowElementRef); + startMessagesBtn.disabled = true; +}); + +closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) + + +const startMessages = (element) => { + const timerId = setInterval(() => { // (6) + if (element.deref()) { // (7) + const payload = document.createElement("p"); + payload.textContent = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 0000000000..2a507dbcdb --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg @@ -0,0 +1,32 @@ + + + + + + + + user + + name: "John" + Object + + <global> + + + + + + + + + + + + + + + + admin + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 0000000000..6cc199a128 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg @@ -0,0 +1,33 @@ + + + + + + + + + + <global> + + + name: "John" + Object + + + + + + + + + + + + admin + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 0000000000..949a14f9f3 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 0000000000..1177d6580d --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,77 @@ + + + + + + + name: "John" + Object + + admin + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + + + + + + + + + + + + + + + WeakRef object + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 0000000000..e738f8e7ed --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + key + value + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + Deleted by FinalizationRegistry cleanup callback + + + + + + + + + + + + + + + WeakRef object + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png new file mode 100644 index 0000000000..fc33a023af Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png new file mode 100644 index 0000000000..7d8bb01e88 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif new file mode 100644 index 0000000000..b81966dda4 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg new file mode 100644 index 0000000000..ba60f1e860 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif new file mode 100644 index 0000000000..d34bda4d73 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg new file mode 100644 index 0000000000..b2655540f9 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif new file mode 100644 index 0000000000..51f8745188 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg new file mode 100644 index 0000000000..5f98aec14d Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 0000000000..e6c9e39606 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css @@ -0,0 +1,285 @@ +:root { + --mineralGreen: 60, 98, 85; + --viridianGreen: 97, 135, 110; + --swampGreen: 166, 187, 141; + --fallGreen: 234, 231, 177; + --brinkPink: #FA7070; + --silverChalice: 178, 178, 178; + --white: 255, 255, 255; + --black: 0, 0, 0; + + --topBarHeight: 64px; + --itemPadding: 32px; + --containerGap: 8px; +} + +@keyframes zoom-in { + 0% { + transform: scale(1, 1); + } + + 100% { + transform: scale(1.30, 1.30); + } +} + +body, html { + margin: 0; + padding: 0; +} + +.app { + min-height: 100vh; + background-color: rgba(var(--viridianGreen), 0.5); +} + +.header { + height: var(--topBarHeight); + padding: 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(var(--mineralGreen), 1); +} + +.header-text { + color: white; +} + +.container { + display: flex; + gap: 24px; + padding: var(--itemPadding); +} + +.item { + width: 50%; +} + +.item--scrollable { + overflow-y: scroll; + height: calc(100vh - var(--topBarHeight) - (var(--itemPadding) * 2)); +} + +.thumbnails-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + align-items: center; +} + +.thumbnail-item { + width: calc(25% - var(--containerGap)); + cursor: pointer; + position: relative; +} + +.thumbnail-item:hover { + z-index: 1; + animation: zoom-in 0.1s forwards; +} + +.thumbnail-item--selected { + outline: 3px solid rgba(var(--fallGreen), 1); + outline-offset: -3px; +} + +.badge { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + position: absolute; + right: 8px; + bottom: 8px; + border-radius: 50%; + border: 2px solid rgba(var(--fallGreen), 1); + background-color: rgba(var(--swampGreen), 1); +} + +.check { + display: inline-block; + transform: rotate(45deg); + border-bottom: 2px solid white; + border-right: 2px solid white; + width: 6px; + height: 12px; +} + +.img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + padding: 0 0 16px 0; + gap: 8px; +} + +.select { + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--swampGreen), 0.5); + background-color: rgba(var(--swampGreen), 1); +} + +.select:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.btn { + outline: none; + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--black), 0.5); +} + +.btn--primary { + background-color: rgba(var(--mineralGreen), 1); +} + +.btn--primary:hover:not([disabled]) { + background-color: rgba(var(--mineralGreen), 0.85); +} + +.btn--secondary { + background-color: rgba(var(--viridianGreen), 0.5); +} + +.btn--secondary:hover:not([disabled]) { + background-color: rgba(var(--swampGreen), 0.25); +} + +.btn--success { + background-color: rgba(var(--fallGreen), 1); +} + +.btn--success:hover:not([disabled]) { + background-color: rgba(var(--fallGreen), 0.85); +} + +.btn:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.previewContainer { + margin-bottom: 16px; + display: flex; + width: 100%; + height: 40vh; + overflow: scroll; + border: 3px solid rgba(var(--black), 1); +} + +.previewContainer--disabled { + background-color: rgba(var(--black), 0.1); + cursor: not-allowed; +} + +.canvas { + margin: auto; + display: none; +} + +.canvas--ready { + display: block; +} + +.spinnerContainer { + display: flex; + gap: 8px; + flex-direction: column; + align-content: center; + align-items: center; + margin: auto; +} + +.spinnerContainer--hidden { + display: none; +} + +.spinnerText { + margin: 0; + color: rgba(var(--mineralGreen), 1); +} + +.spinner { + display: inline-block; + width: 50px; + height: 50px; + margin: auto; + border: 3px solid rgba(var(--mineralGreen), 0.3); + border-radius: 50%; + border-top-color: rgba(var(--mineralGreen), 0.9); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loggerContainer { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 8px 8px 8px; + width: 100%; + min-height: 30vh; + max-height: 30vh; + overflow: scroll; + border-left: 3px solid rgba(var(--black), 0.25); +} + +.logger-title { + display: flex; + align-items: center; + padding: 8px; + position: sticky; + height: 40px; + min-height: 40px; + top: 0; + left: 0; + background-color: rgba(var(--viridianGreen), 1); + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.logger-item { + font-size: 14px; + padding: 8px; + border: 2px solid #5a5a5a; + color: white; +} + +.logger--primary { + background-color: #13315a; +} + +.logger--success { + background-color: #385a4e; +} + +.logger--error { + background-color: #5a1a24; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 0000000000..7ce52f927f --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,49 @@ + + + + + + + Photo Library Collage + + + + +
+
+

+ Photo Library Collage +

+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 0000000000..983b34d9ab --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js @@ -0,0 +1,228 @@ +import { + createImageFile, + loadImage, + weakRefCache, + LAYOUTS, + images, + THUMBNAIL_PARAMS, + stateObj, +} from "./utils.js"; + +export const state = new Proxy(stateObj, { + set(target, property, value) { + const previousValue = target[property]; + + target[property] = value; + + if (previousValue !== value) { + handleStateChange(target); + } + + return true; + }, +}); + +// Elements. +const thumbnailsContainerEl = document.querySelector(".thumbnails-container"); +const selectEl = document.querySelector(".select"); +const previewContainerEl = document.querySelector(".previewContainer"); +const canvasEl = document.querySelector(".canvas"); +const createCollageBtn = document.querySelector(".btn-create-collage"); +const startOverBtn = document.querySelector(".btn-start-over"); +const downloadBtn = document.querySelector(".btn-download"); +const spinnerContainerEl = document.querySelector(".spinnerContainer"); +const spinnerTextEl = document.querySelector(".spinnerText"); +const loggerContainerEl = document.querySelector(".loggerContainer"); + +// Renders. +// Render thumbnails previews. +images.forEach((img) => { + const thumbnail = document.createElement("div"); + thumbnail.classList.add("thumbnail-item"); + + thumbnail.innerHTML = ` + + `; + + thumbnail.addEventListener("click", (e) => handleSelection(e, img)); + + thumbnailsContainerEl.appendChild(thumbnail); +}); +// Render layouts select. +LAYOUTS.forEach((layout) => { + const option = document.createElement("option"); + option.value = JSON.stringify(layout); + option.innerHTML = layout.name; + selectEl.appendChild(option); +}); + +const handleStateChange = (state) => { + if (state.loading) { + selectEl.disabled = true; + createCollageBtn.disabled = true; + startOverBtn.disabled = true; + downloadBtn.disabled = true; + previewContainerEl.classList.add("previewContainer--disabled"); + spinnerContainerEl.classList.remove("spinnerContainer--hidden"); + spinnerTextEl.innerText = "Loading..."; + canvasEl.classList.remove("canvas--ready"); + } else if (!state.loading) { + selectEl.disabled = false; + createCollageBtn.disabled = false; + startOverBtn.disabled = false; + downloadBtn.disabled = false; + previewContainerEl.classList.remove("previewContainer--disabled"); + spinnerContainerEl.classList.add("spinnerContainer--hidden"); + canvasEl.classList.add("canvas--ready"); + } + + if (!state.selectedImages.size) { + createCollageBtn.disabled = true; + document.querySelectorAll(".badge").forEach((item) => item.remove()); + } else if (state.selectedImages.size && !state.loading) { + createCollageBtn.disabled = false; + } + + if (!state.collageRendered) { + downloadBtn.disabled = true; + } else if (state.collageRendered) { + downloadBtn.disabled = false; + } +}; +handleStateChange(state); + +const handleSelection = (e, imgName) => { + const imgEl = e.currentTarget; + + imgEl.classList.toggle("thumbnail-item--selected"); + + if (state.selectedImages.has(imgName)) { + state.selectedImages.delete(imgName); + state.selectedImages = new Set(state.selectedImages); + imgEl.querySelector(".badge")?.remove(); + } else { + state.selectedImages = new Set(state.selectedImages.add(imgName)); + + const badge = document.createElement("div"); + badge.classList.add("badge"); + badge.innerHTML = ` +
+ `; + imgEl.prepend(badge); + } +}; + +// Make a wrapper function. +let getCachedImage; +(async () => { + getCachedImage = await weakRefCache(loadImage); +})(); + +const calculateGridRows = (blobsLength) => + Math.ceil(blobsLength / state.currentLayout.columns); + +const drawCollage = (images) => { + state.drawing = true; + + let context = canvasEl.getContext("2d"); + + /** + * Calculate canvas dimensions based on the current layout. + * */ + context.canvas.width = + state.currentLayout.itemWidth * state.currentLayout.columns; + context.canvas.height = + calculateGridRows(images.length) * state.currentLayout.itemHeight; + + let currentRow = 0; + let currentCanvasDx = 0; + let currentCanvasDy = 0; + + for (let i = 0; i < images.length; i++) { + /** + * Get current row of the collage. + * */ + if (i % state.currentLayout.columns === 0) { + currentRow += 1; + currentCanvasDx = 0; + + if (currentRow > 1) { + currentCanvasDy += state.currentLayout.itemHeight; + } + } + + context.drawImage( + images[i], + 0, + 0, + images[i].width, + images[i].height, + currentCanvasDx, + currentCanvasDy, + state.currentLayout.itemWidth, + state.currentLayout.itemHeight, + ); + + currentCanvasDx += state.currentLayout.itemWidth; + } + + state.drawing = false; + state.collageRendered = true; +}; + +const createCollage = async () => { + state.loading = true; + + const images = []; + + for (const image of state.selectedImages.values()) { + const blobImage = await getCachedImage(image.img); + + const url = URL.createObjectURL(blobImage); + const img = await createImageFile(url); + + images.push(img); + URL.revokeObjectURL(url); + } + + state.loading = false; + + drawCollage(images); +}; + +/** + * Clear all settled data to start over. + * */ +const startOver = () => { + state.selectedImages = new Set(); + state.collageRendered = false; + const context = canvasEl.getContext("2d"); + context.clearRect(0, 0, canvasEl.width, canvasEl.height); + + document + .querySelectorAll(".thumbnail-item--selected") + .forEach((item) => item.classList.remove("thumbnail-item--selected")); + + loggerContainerEl.innerHTML = '

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 0000000000..f0140c116a --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index e850e97838..2dd6ebbe3c 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -18,14 +18,33 @@ DOM 노드는 종류에 따라 대응하는 내장 클래스가 다릅니다. 각 클래스는 다음과 같은 특징을 지닙니다. -- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- 루트에 있는 '추상(abstract)' 클래스로, 이 클래스에 대응하는 객체는 실제로 만들어지지 않습니다. EventTarget가 모든 DOM 노드의 베이스에 있기때문에 DOM 노드에서 '이벤트'를 사용할 수 있습니다. 자세한 내용은 곧 다룰 예정입니다. -- [Node](http://dom.spec.whatwg.org/#interface-node) -- 역시 '추상' 클래스로, DOM 노드의 베이스 역할을 합니다. getter 역할을 하는 `parentNode`, `nextSibling`, `childNodes` 등의 주요 트리 탐색 기능을 제공합니다. `Node` 클래스의 객체는 절대 생성되지 않습니다. 하지만 이 클래스를 상속받는 클래스는 여럿 있습니다. 텍스트 노드를 위한 `Text` 클래스와 요소 노드를 위한 `Element` 클래스, 주석 노드를 위한 `Comment`클래스는 `Node`클래스를 상속받습니다. -- [Element](http://dom.spec.whatwg.org/#interface-element) -- DOM 요소를 위한 베이스 클래스입니다. `nextElementSibling`, `children` 이나 `getElementsByTagName`, `querySelector` 같이 요소 전용 탐색을 도와주는 프로퍼티나 메서드가 이를 기반으로 합니다. 브라우저는 HTML뿐만 아니라 XML, SVG도 지원하는데 `Element` 클래스는 이와 관련된 `SVGElement`, `XMLElement`, `HTMLElement` 클래스의 베이스 역할을 합니다. +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- 루트에 있는 '추상(abstract)' 클래스로, 이 클래스에 대응하는 객체는 실제로 만들어지지 않습니다. + + EventTarget가 모든 DOM 노드의 베이스에 있기때문에 DOM 노드에서 '이벤트'를 사용할 수 있습니다. 자세한 내용은 곧 다룰 예정입니다. + +- [Node](http://dom.spec.whatwg.org/#interface-node) -- 역시 '추상' 클래스로, DOM 노드의 베이스 역할을 합니다. + + getter 역할을 하는 `parentNode`, `nextSibling`, `childNodes` 등의 주요 트리 탐색 기능을 제공합니다. `Node` 클래스의 객체는 절대 생성되지 않습니다. 하지만 이 클래스를 상속받는 클래스는 여럿 있습니다. 텍스트 노드를 위한 `Text` 클래스와 요소 노드를 위한 `Element` 클래스, 주석 노드를 위한 `Comment`클래스는 `Node`클래스를 상속받습니다. + +- [Document](https://dom.spec.whatwg.org/#interface-document) -- 역사적인 이유로 `HTMLDocument`에 상속되는 경우가 많지만(최신 명세서는 이를 요구하지 않습니다), 문서 전체를 나타냅니다. + + `document` 전역 객체가 이 클래스에 속하며, DOM의 진입점 역할을 합니다. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- '추상' 클래스로, 아래 클래스들이 이를 상속받습니다. + - [Text](https://dom.spec.whatwg.org/#interface-text) -- 요소 내부의 텍스트에 대응하는 클래스입니다. 예를 들어 `

Hello

` 내 `Hello`가 이에 해당합니다. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- 주석에 대응하는 클래스입니다. 화면에 표시되지 않지만, 각 주석은 DOM의 구성원이 됩니다. + +- [Element](http://dom.spec.whatwg.org/#interface-element) -- DOM 요소를 위한 베이스 클래스입니다. + + `nextElementSibling`, `children` 이나 `getElementsByTagName`, `querySelector` 같이 요소 전용 탐색을 도와주는 프로퍼티나 메서드가 이를 기반으로 합니다. 브라우저는 HTML뿐만 아니라 XML, SVG도 지원하는데 `Element` 클래스는 이와 관련된 `SVGElement`, `XMLElement`, `HTMLElement` 클래스의 베이스 역할을 합니다. + - [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- HTML 요소 노드의 베이스 역할을 하는 클래스입니다. 아래 나열한 클래스들은 실제 HTML 요소에 대응하고 `HTMLElement`를 상속받습니다. - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- `` 요소에 대응하는 클래스 - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- `` 요소에 대응하는 클래스 - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- `` 요소에 대응하는 클래스 - - 이외에도 다른 클래스가 많은데, 각 태그에 해당하는 클래스는 고유한 프로퍼티와 메서드를 지원합니다. + - 이외에도 다양한 클래스가 있습니다. + +이외에도 다른 클래스가 많은데, 각 태그에 해당하는 클래스는 고유한 프로퍼티와 메서드를 지원합니다. 반면 ``, `
`, `
` 같은 일부 요소는 고유한 프로퍼티나 메서드를 갖지 않으므로, `HTMLElement` 클래스의 인스턴스입니다. 이렇게 특정 노드에서 사용할 수 있는 프로퍼티와 메서드는 상속을 기반으로 결정됩니다. @@ -128,7 +147,7 @@ interface HTMLInputElement: HTMLElement { ```html run - diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index daa79f0eb9..0b7961be0c 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -1,6 +1,6 @@ # 스타일과 클래스 -자바스크립트를 사용해 어떻게 스타일과 클래스를 다룰 수 있는지 알아보기 전에, 중요한 규칙을 하나 집고 넘어가야 할 것 같습니다. 핵심만 요약했기 때문에 충분할진 모르겠지만 꼭 언급하고 넘어가야 하기 때문입니다. +자바스크립트를 사용해 어떻게 스타일과 클래스를 다룰 수 있는지 알아보기 전에, 중요한 규칙을 하나 짚고 넘어가야 할 것 같습니다. 핵심만 요약했기 때문에 충분할진 모르겠지만 꼭 언급하고 넘어가야 하기 때문입니다. 요소에 스타일을 적용할 수 있는 방법은 두 가지가 있습니다. @@ -86,7 +86,7 @@ elem.style.top = top; // 예시: '456px' 프로퍼티 `elem.style`은 속성 `"style"`에 쓰인 값에 대응되는 객체입니다. `elem.style.width="100px"`은 `style` 속성값을 문자열 `width:100px`로 설정한 것과 같죠. -여러 단어를 이어서 만든 프로퍼티는 다음와 같이 카멜 표기법을 사용해 이름 짓습니다. +여러 단어를 이어서 만든 프로퍼티는 다음과 같이 카멜 표기법을 사용해 이름 짓습니다. ```js no-beautify background-color => elem.style.backgroundColor @@ -128,8 +128,16 @@ setTimeout(() => document.body.style.display = "", 1000); // 1초 후 다시 원 이렇게 `style.display`에 빈 문자열을 할당하면 브라우저는 마치 처음부터 `style.display` 프로퍼티가 없었던 것처럼 CSS 클래스와 브라우저 내장 스타일을 페이지에 적용합니다. +이를 위한 특별한 메서드인 `elem.style.removeProperty('style property')`도 있습니다. 아래와 같이 프로퍼티를 제거할 수 있습니다. + +```js run +document.body.style.background = 'red'; // 배경을 빨간색으로 설정 + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // 1초 후 배경 제거 +``` + ````smart header="`style.cssText`로 완전히 다시 쓰기" -개별 스타일 프로퍼티를 적용할 때는 보통 `style.*`를 사용합니다. 그런데 `div.style` 은 객체이고 읽기 전용이기 때문에 `div.style="color: red; width: 100px"`같은 방식으론 전체 스타일을 설정할 수 없습니다. +개별 스타일 프로퍼티를 적용할 때는 보통 `style.*`를 사용합니다. 그런데 `div.style` 은 객체이고 읽기 전용이기 때문에 `div.style="color: red; width: 100px"`같은 방식으론 전체 스타일을 설정할 수 없습니다. 문자열을 사용해 전체 스타일을 설정하려면 프로퍼티 `style.cssText`를 사용해야 합니다. diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index 1bf3e861e3..58f633c5e3 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -17,8 +17,8 @@ width: 300px; height: 200px; border: 25px solid #E8C48F; - padding: 20px; - overflow: auto; + padding: 20px; + overflow: auto; } ``` diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 6018859b93..3847af8ab9 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -42,7 +42,7 @@ alert( document.documentElement.clientWidth ); // 스크롤바가 차지하는 이론상 `document.documentElement`는 문서의 루트 요소에 상응하고, 루트 요소엔 콘텐츠 전부가 들어가기 때문에 우리는 문서의 전체 크기를 `document.documentElement`의 `scrollWidth`와 `scrollHeight`를 사용해 재면 되지 않냐고 생각합니다. -그런데 전체 페이지를 대상으로 했을 때, `document.documentElement`의 프로피터들은 우리가 예상한 대로 동작하지 않습니다. Chrome이나 Safari, Opera에서 스크롤이 없는 경우 `documentElement.scrollHeight`는 `documentElement.clientHeight`보다 작을 때가 있죠. 예상하기엔 같은 값이어야 하는데도 말입니다. +그런데 전체 페이지를 대상으로 했을 때, `document.documentElement`의 프로퍼티들은 우리가 예상한 대로 동작하지 않습니다. Chrome이나 Safari, Opera에서 스크롤이 없는 경우 `documentElement.scrollHeight`는 `documentElement.clientHeight`보다 작을 때가 있죠. 예상하기엔 같은 값이어야 하는데도 말입니다. 정확한 문서 전체 높이 값을 얻으려면 아래 여섯 프로퍼티가 반환하는 값 중 최댓값을 골라야 합니다. @@ -73,7 +73,13 @@ alert('가로 스크롤에 의해 가려진 왼쪽 영역 너비: ' + window.pag 참고로 이 두 프로퍼티는 읽기만 가능합니다. -## scrollTo, scrollBy로 스크롤 상태 변경하기 [#window-scroll] +```smart header="`scrollX`와 `scrollY`라는 `window` 프로퍼티로도 사용 가능합니다" +역사적인 이유로 두 프로퍼티가 모두 존재하지만 동일합니다. +- `window.pageXOffset`은 `window.scrollX`의 별칭입니다. +- `window.pageYOffset`은 `window.scrollY`의 별칭입니다. +``` + +## scrollTo, scrollBy, scrollIntoView로 스크롤 상태 변경하기 [#window-scroll] ```warn 자바스크립트를 사용해 스크롤을 움직이려면 DOM이 완전히 만들어진 상태이어야 합니다. diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index cd3d1c8cb4..da60f1f5ea 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -199,7 +199,7 @@ setTimeout(() => message.remove(), 5000); CSS와 비교하자면 창 기준 좌표는 `position:fixed`에 해당하고 문서 기준 좌표는 맨 위 기준 `position:absolute`와 비슷합니다. -문서 내 특정 좌표에 무언가를 위치시키고 싶을 땐 `position:absolute`와 `top, `left`를 사용하면 스크롤 이동에 상관없이 해당 요소를 한 좌표에 머물게 할 수 있습니다. 그러려면 우선 정확한 좌표가 필요합니다. +문서 내 특정 좌표에 무언가를 위치시키고 싶을 땐 `position:absolute`와 `top`, `left`를 사용하면 스크롤 이동에 상관없이 해당 요소를 한 좌표에 머물게 할 수 있습니다. 그러려면 우선 정확한 좌표가 필요합니다. 그런데 요소의 문서 기준 좌표를 제공하는 표준 메서드가 아직 없습니다. 하지만 아주 쉽게 코드를 작성할 수 있습니다. diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html index 2c60733165..baf8676644 100644 --- a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html @@ -10,7 +10,7 @@ +

Mouse over here to see the date

+ diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css index 9b3d3b82d4..a84cd5e7e8 100644 --- a/2-ui/3-event-details/6-pointer-events/slider.view/style.css +++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css @@ -8,6 +8,7 @@ } .thumb { + touch-action: none; width: 10px; height: 25px; border-radius: 3px; diff --git a/2-ui/3-event-details/7-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md index 617852ccfd..12fe632016 100644 --- a/2-ui/3-event-details/7-keyboard-events/article.md +++ b/2-ui/3-event-details/7-keyboard-events/article.md @@ -107,7 +107,7 @@ So, `event.code` may match a wrong character for unexpected layout. Same letters To reliably track layout-dependent characters, `event.key` may be a better way. -On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch. +On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch. Do we want to handle layout-dependant keys? Then `event.key` is the way to go. @@ -139,22 +139,25 @@ For instance, the `` below expects a phone number, so it does not accept ```html autorun height=60 run ``` -Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. +The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`. -Let's relax it a little bit: +As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters) +Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side effect of the strict filter `checkPhoneKey`. These keys make it return `false`. + +Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`: ```html autorun height=60 run @@ -162,7 +165,9 @@ function checkPhoneKey(key) { Now arrows and deletion works well. -...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid. +Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable. + +The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together. ## Legacy @@ -170,6 +175,12 @@ In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `whic There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more. +## Mobile Keyboards + +When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values). + +While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices. + ## Summary Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS. diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html index 4010628301..a0d5a4f40e 100644 --- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html @@ -28,7 +28,7 @@ - + diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js index 5eba24c7ad..d97f7a7b50 100644 --- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js @@ -5,6 +5,8 @@ let lastTime = Date.now(); function handle(e) { if (form.elements[e.type + 'Ignore'].checked) return; + area.scrollTop = 1e6; + let text = e.type + ' key=' + e.key + ' code=' + e.code + diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index fff8f2234c..f99bd482c3 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -8,11 +8,11 @@ 폼은 특수한 컬렉션인 `document.forms`의 구성원입니다. -`document.forms`는 이름과 순서가 있는 '기명 컬렉션(named collection)'입니다. 개발자는 이 이름이나 순서를 사용해 문서 내의 폼에 접근할 수 있습니다. +`document.forms`는 이름과 순서가 있는 *'기명 컬렉션(named collection)'* 입니다. 개발자는 이 이름이나 순서를 사용해 문서 내의 폼에 접근할 수 있습니다. ```js no-beautify -document.forms.my - 이름이 'my'인 폼 -document.forms[0] - 문서 내의 첫 번째 폼 +document.forms.my; // 이름이 'my'인 폼 +document.forms[0]; // 문서 내의 첫 번째 폼 ``` 이름이나 순서를 사용해 원하는 폼을 가져온 다음에는 기명 컬렉션 `form.elements`를 사용해 폼의 요소를 얻을 수 있습니다. @@ -36,9 +36,9 @@ document.forms[0] - 문서 내의 첫 번째 폼 ``` -그런데 개발을 하다 보면 이름이 같은 요소 여러 개를 다뤄야 하는 경우가 생기기도 합니다. 라디오 버튼을 다룰 때 이런 상황이 자주 발생하죠. +그런데 개발을 하다 보면 이름이 같은 요소 여러 개를 다뤄야 하는 경우가 생기기도 합니다. 라디오 버튼이나 체크박스를 다룰 때 이런 상황이 자주 발생하죠. -이때 `form.elements[name]`은 컬렉션이 된다는 사실을 이용할 수 있습니다. 예시를 살펴봅시다. +이때 `form.elements[name]`는 *컬렉션*이 된다는 사실을 이용할 수 있습니다. 예시를 살펴봅시다. ```html run height=40
@@ -155,7 +155,7 @@ alert(ageElems[0]); // [object HTMLInputElement] ### input과 textarea -input과 textarea 요소의 값은 `input.value` (string) 또는 `input.checked`(boolean)을 사용해 얻을 수 있습니다. +input과 textarea 요소의 값은 `input.value` (string)로 얻을 수 있고, 체크박스와 라디오 버튼의 경우 `input.checked`(boolean)를 사용해 선택 여부를 확인할 수 있습니다. 이렇게 말이죠. @@ -177,18 +177,16 @@ input.checked = true; // 체크박스나 라디오 버튼에서 쓸 수 있습 ``의 값을 설정할 수 있습니다. -1. 조건에 맞는 ` 가 생성됩니다. ``` -이번엔 같은 요소를 선택된 상태로 생성합니다. +이번엔 같은 요소를 선택된 상태로 생성해 봅시다. ```js let option = new Option("Text", "value", true, true); @@ -288,9 +289,9 @@ Option을 사용해 만든 요소에는 다음과 같은 프로퍼티가 있습 `element.form` : 요소는 `form` 프로퍼티에서 자신이 속한 폼을 참조합니다. -각 요소의 값은 `input.value`, `textarea.value`, `select.value` 등으로 접근할 수 있습니다. 체크박스와 라디오 버튼에서는 `input.checked`를 사용할 수 있습니다. +각 요소의 값은 `input.value`, `textarea.value`, `select.value` 등으로 접근할 수 있습니다. 체크박스와 라디오 버튼에서는 값이 선택되었는지 확인하려면 `input.checked`를 사용합니다. -``에서는 인덱스 `select.selectedIndex`나 option 컬렉션 `select.options`를 통해 값을 구할 수도 있습니다. 지금까지는 폼 관련 기본을 다뤘습니다. 이 튜토리얼에서 앞으로 더 많은 예시를 만날 것입니다. diff --git a/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md b/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md index 0ab3782354..7acfed58a1 100644 --- a/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md @@ -6,7 +6,7 @@ importance: 5 셀을 클릭하면 해당 셀을 수정할 수 있게 해주는 테이블을 만들어보세요. -- 셀 클릭 시 셀 안에 textarea가 나타나면서 셀에 들어 있는 콘텐츠(HTML)를 '수정'할 수 있어야 합니다. 이때 textarea 크기는 기존 cell 크기와 같아야 합니다. +- 셀 클릭 시 셀 안에 textarea가 나타나면서 셀에 들어 있는 콘텐츠(HTML)를 '수정'할 수 있어야 합니다. 이때 textarea 크기는 기존 cell 크기와 같아야 하며, 전체 배치가 그대로 유지되어야 합니다. - 셀 수정 시 셀 아래쪽에 '완료'와 '취소' 버튼이 노출되도록 하고, 각 버튼을 누르면 수정이 종료, 취소될 수 있게 합니다. - 한 번에 하나의 셀만 수정할 수 있어야 합니다. ``가 '수정 중'일 때는 다른 셀을 눌러도 클릭 이벤트가 무시되어야 합니다. - 테이블엔 더 많은 셀이 추가될 수 있으므로 이벤트 위임을 사용하세요. diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md index 206eff5ad0..64578038fc 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md @@ -10,4 +10,4 @@ importance: 4 참고1: `#mouse` 요소 이외의 어느 곳에도 이벤트 핸들러를 달지 마세요. -참고2: HTML과 CSS를 수정하지 마세요. 작성할 자바스크립트는 어떤 요소에서도 동작할 수 있는 범용성이 있어야 합니다. \ No newline at end of file +참고2: HTML과 CSS를 수정하지 마세요. 작성할 자바스크립트는 어떤 요소에서도 동작할 수 있는 범용성이 있어야 합니다. diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md index 5ccf706e97..d3d6571f3a 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -90,17 +90,19 @@ 여기서 주의해야 할 점은 `onblur`는 요소가 포커스를 잃고 난 *후*에 발생하기 때문에 `onblur` 안에서 `event.preventDefault()`를 호출해 포커스를 잃게 하는걸 '막을 수 없다'라는 사실입니다. -```warn header="JavaScript-initiated focus loss" -A focus loss can occur for many reasons. +하지만 실제로 이런 기능을 구현하기 전엔 신중하게 생각해야 합니다. 일반적으로 사용자에게 *에러를 표시하는 것*은 필요하지만, 폼 작성을 *방해해서는 안 되기* 때문입니다. 사용자가 다른 항목을 먼저 작성하고 싶을 수도 있습니다. -One of them is when the visitor clicks somewhere else. But also JavaScript itself may cause it, for instance: +```warn header="자바스크립트에 의해 포커스를 잃는 경우" +포커스를 잃는 데에는 여러 가지 이유가 있습니다. -- An `alert` moves focus to itself, so it causes the focus loss at the element (`blur` event), and when the `alert` is dismissed, the focus comes back (`focus` event). -- If an element is removed from DOM, then it also causes the focus loss. If it is reinserted later, then the focus doesn't return. +그중 하나는 방문자가 다른 곳을 클릭했을 때입니다. 하지만 자바스크립트 자체도 원인이 될 수 있습니다. 예를 들면 다음과 같습니다. -These features sometimes cause `focus/blur` handlers to misbehave -- to trigger when they are not needed. +- `alert`는 포커스를 자기 자신으로 이동시키므로 기존 요소가 포커스를 잃게 만듭니다(`blur` 이벤트). 그리고 `alert`가 닫히면 포커스가 다시 돌아옵니다(`focus` 이벤트). +- 요소가 DOM에서 제거되는 경우에도 포커스를 잃게 됩니다. 나중에 요소가 다시 삽입되더라도 포커스는 돌아오지 않습니다. -The best recipe is to be careful when using these events. If we want to track user-initiated focus-loss, then we should avoid causing it ourselves. +이러한 특징 때문에 때때로 `focus/blur` 핸들러가 필요하지 않을 때에도 작동하는 경우가 있습니다. + +따라서 이런 이벤트를 사용할 때는 주의해야 합니다. 사용자 동작으로 포커스를 잃는 상황을 추적하려면 자바스크립트로 요소가 포커스를 잃게 만드는 일을 피해야 합니다. ``` ## tabindex를 사용해서 모든 요소 포커스 하기 @@ -215,7 +217,7 @@ The best recipe is to be careful when using these events. If we want to track us ## 요약 -`focus`와 `blur` 이벤트는 각각 요소가 포커스를 받을 때, 잃을 때 발생합니다. +`focus`와 `blur` 이벤트는 각각 요소가 포커스를 받을 때와 잃을 때 발생합니다. 두 이벤트를 사용할 땐 다음을 유의해야 합니다. - `focus`와 `blur` 이벤트는 버블링 되지 않습니다. 캡처링이나 `focusin`, `focusout`을 사용하면 이벤트 위임 효과를 볼 수 있습니다. diff --git a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html index c3decee0b1..2779c91bed 100644 --- a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html +++ b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html @@ -96,7 +96,7 @@ let years = form.months.value / 12; if (!years) return; - let result = Math.round(initial * (1 + interest * years)); + let result = Math.round(initial * (1 + interest) ** years); let height = result / form.money.value * 100 + 'px'; document.getElementById('height-after').style.height = height; diff --git a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md index 5102f2574e..ee2a0282d0 100644 --- a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md +++ b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md @@ -17,5 +17,5 @@ importance: 5 // initial: 계산 전의 잔고 // interest: 이자율. 0.05는 연 5%의 이자율을 의미합니다. // years: 예금 유치 기간으로, 연 단위 -let result = Math.round(initial * (1 + interest * years)); +let result = Math.round(initial * (1 + interest) ** years); ``` diff --git a/2-ui/4-forms-controls/3-events-change-input/article.md b/2-ui/4-forms-controls/3-events-change-input/article.md index bb220d54d9..4023a50910 100644 --- a/2-ui/4-forms-controls/3-events-change-input/article.md +++ b/2-ui/4-forms-controls/3-events-change-input/article.md @@ -58,29 +58,53 @@ `cut`, `copy`, `paste` 이벤트는 각각 값을 잘라내기·복사하기·붙여넣기 할 때 발생합니다. -세 이벤트는 [ClipboardEvent](https://www.w3.org/TR/clipboard-apis/#clipboard-event-interfaces) 클래스의 하위 클래스이며 복사하거나 붙여넣기 한 데이터에 접근할 수 있도록 해줍니다. +세 이벤트는 [ClipboardEvent](https://www.w3.org/TR/clipboard-apis/#clipboard-event-interfaces) 클래스의 하위 클래스이며 잘라내기·복사하기·붙여넣기 된 데이터에 접근할 수 있도록 해줍니다. `input` 이벤트와는 달리 세 이벤트는 `event.preventDefault()`를 사용해 기본 동작을 막을 수 있습니다. 이렇게 하면 아무것도 복사·붙여넣기 할 수 없습니다. -예를 들어 아래 코드는 잘라내기·복사하기·붙여넣기 동작을 시도하면 모든 동작들이 중단되고 얼럿창을 통해 중단된 이벤트 이름을 보여줍니다. +예를 들어 아래 코드는 잘라내기·복사하기·붙여넣기 동작을 시도하면 모든 동작이 중단되고 얼럿창을 통해 잘라내기·복사하기·붙여넣기를 시도하는 텍스트를 표시합니다. ```html autorun height=40 run ``` -알아두세요! 텍스트뿐만 아니라 모든 것을 복사·붙여넣기 할 수 있습니다. 예를 들어 OS 파일 매니저에서 파일을 복사해 붙여넣을 수 있습니다. +(붙여넣기는 얼럿창에 나오지만 잘라내기·복사하기는 '-'로 보입니다 - 옮긴이) + +알아두세요! 잘라내기·복사하기 이벤트 핸들러 내부에서 `event.clipboardData.getData(...)`를 호출하면 빈 문자열을 반환합니다. 엄밀히 말하면 아직 데이터가 클립보드에 들어가 있지 않기 때문입니다. 또한 `event.preventDefault()`를 사용하면 아예 복사조차 하지 못합니다. + +따라서 위 예제는 `document.getSelection()`을 사용하여 선택한 텍스트를 가져옵니다. 문서 선택(document selection)에 대한 자세한 내용은 에서 다룹니다. + +텍스트뿐만 아니라 모든 것을 복사하기·붙여넣기 할 수 있습니다. 예를 들어 OS 파일 매니저에서 파일을 복사해 붙여넣을 수 있습니다. + +이는 `clipboardData`가 드래그 앤 드롭과 복사하기·붙여넣기에 공통으로 사용되는 `DataTransfer` 인터페이스를 구현하기 때문입니다. 이 인터페이스는 현재 논의 범위를 벗어나므로 자세히 다루지는 않지만 관련 메서드는 [DataTransfer 명세서](https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)에서 확인할 수 있습니다. + +또한 클립보드에 접근하는 별도의 비동기 API인 `navigator.clipboard`도 있습니다 ([Firefox에서는 지원되지 않습니다](https://caniuse.com/async-clipboard)). 자세한 내용은 [클립보드 API 및 이벤트](https://www.w3.org/TR/clipboard-apis/) 명세서를 참조하면 됩니다. + + +### 안전 제한 사항 + +클립보드는 '전역' OS 레벨입니다. 사용자는 여러 애플리케이션을 오가며 다양한 내용을 복사하기·붙여넣기 할 수 있는데 브라우저 페이지가 그 내용을 모두 볼 수 있어서는 안 됩니다. + +그래서 대부분의 브라우저는 복사하기·붙여넣기 같은 특정 사용자 동작이 일어나는 범위 안에서만 클립보드 읽기·쓰기에 끊김없이 접근할 수 있도록 허용합니다. + +Firefox를 제외한 모든 브라우저에서는 `dispatchEvent`로 '사용자 지정(custom)' 클립보드 이벤트를 생성하는 것이 금지되어 있습니다. 그런 이벤트를 보내는(dispatch) 데 성공하더라도 명세에는 이러한 '합성(synthetic)' 이벤트가 클립보드에 접근해서는 안 된다고 명확히 규정합니다. -클립보드에서 읽기·쓰기, 파일 등 다양한 데이터 타입에서 작동하는 메서드 목록 [명세서](https://www.w3.org/TR/clipboard-apis/#dfn-datatransfer)를 확인할 수 있습니다. +이벤트 핸들러 안에서 `event.clipboardData`를 저장해두었다가 나중에 접근하려고 해도 마찬가지로 동작하지 않습니다. -다만 클립보드는 '전역' OS 레벨입니다. `onclick` 이벤트 핸들러처럼 대부분의 브라우저는 안전을 위해 특정 사용자 동작의 범위에서만 클립보드의 읽기·쓰기에 대한 접근을 허용합니다. +다시 말해 [event.clipboardData](https://www.w3.org/TR/clipboard-apis/#clipboardevent-clipboarddata)는 오직 사용자가 직접 발생시킨 이벤트의 핸들러 안에서만 동작합니다. -또한 Firefox를 제외한 모든 브라우저에서 `dispatchEvent`를 사용하여 '커스텀' 클립보드 이벤트를 생성하는 것을 금지하고 있습니다. +반면 [navigator.clipboard](https://www.w3.org/TR/clipboard-apis/#h-navigator-clipboard)는 더 최근에 등장한 API로 어떤 콘텍스트에서든 사용할 수 있도록 설계되었습니다. 필요한 경우 사용자에게 권한을 요청합니다. ## 요약 @@ -90,4 +114,4 @@ |---------|----------|-------------| | `change` | 값이 변경될 때 이벤트 발생 | 텍스트 입력의 경우 포커스를 잃을 때 실행 | | `input` | 텍스트가 입력될 때마다 이벤트 발생 | `change`와 달리 즉시 실행 | -| `cut`, `copy`, `paste` | 잘라내기·복사하기·붙여넣기 할 때 이벤트 발생 | 브라우저 기본 동작을 막아 이벤트 실행을 막을 수 있음. `event.clipboardData` 프로퍼티를 사용하면 클립보드에 저장된 데이터를 읽고 쓸 수 있음 | +| `cut`, `copy`, `paste` | 잘라내기·복사하기·붙여넣기 할 때 이벤트 발생 | 브라우저 기본 동작을 막아 이벤트 실행을 막을 수 있음.
`event.clipboardData` 프로퍼티를 사용하면 클립보드에 저장된 데이터를 읽고 쓸 수 있음
(Firefox를 제외한 모든 브라우저는 `navigator.clipboard` 도 지원) | diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/article.md b/2-ui/5-loading/01-onload-ondomcontentloaded/article.md index 8663044646..710a38a9f2 100644 --- a/2-ui/5-loading/01-onload-ondomcontentloaded/article.md +++ b/2-ui/5-loading/01-onload-ondomcontentloaded/article.md @@ -33,7 +33,7 @@ document.addEventListener("DOMContentLoaded", ready); function ready() { alert('DOM이 준비되었습니다!'); - // 이미지가 로드되지 않은 상태이기 때문에 사이즈는 0x0입니다. + // 이미지가 아직 로드되지 않았기 때문에(캐시된 경우 제외) 크기는 0x0입니다. alert(`이미지 사이즈: ${img.offsetWidth}x${img.offsetHeight}`); } @@ -117,7 +117,7 @@ Firefox와 Chrome, Opera의 폼 자동완성(form autofill)은 `DOMContentLoaded window.onload = function() { // window.addEventListener('load', (event) => {와 동일합니다. alert('페이지 전체가 로드되었습니다.'); - // 이번엔 이미지가 제대로 불러와 진 후에 얼럿창이 실행됩니다. + // 이번엔 이미지가 제대로 불러와진 후에 얼럿창이 실행됩니다. alert(`이미지 사이즈: ${img.offsetWidth}x${img.offsetHeight}`); }; @@ -145,7 +145,7 @@ let analyticsData = { /* 분석 정보가 담긴 객체 */ }; window.addEventListener("unload", function() { navigator.sendBeacon("/analytics", JSON.stringify(analyticsData)); -}; +}); ``` - 요청은 POST 메서드로 전송됩니다. @@ -185,6 +185,26 @@ window.onbeforeunload = function() { 이렇게 문자열을 반환하도록 해도 얼럿창에 문자열이 보이지 않게 된 이유는 몇몇 사이트 관리자들이 오해가 생길 법하거나 성가신 메시지를 띄우면서 `beforeunload`를 남용했기 때문입니다. 오래된 브라우저에서 위 예시를 실행하고 새로 고침을 누르면 "저장되지 않은..." 메시지가 뜨긴 합니다. 하지만 모던 브라우저에선 `beforeunload` 이벤트를 취소할 때 보이는 메시지를 커스터마이징 할 수 없습니다. +````warn header="The `event.preventDefault()` doesn't work from a `beforeunload` handler" +That may sound weird, but most browsers ignore `event.preventDefault()`. + +Which means, following code may not work: +```js run +window.addEventListener("beforeunload", (event) => { + // doesn't work, so this event handler doesn't do anything + event.preventDefault(); +}); +``` + +Instead, in such handlers one should set `event.returnValue` to a string to get the result similar to the code above: +```js run +window.addEventListener("beforeunload", (event) => { + // works, same as returning from window.onbeforeunload + event.returnValue = "There are unsaved changes. Leave now?"; +}); +``` +```` + ## readyState 문서가 완전히 로드된 후에 `DOMContentLoaded` 핸들러를 설정하면 어떤 일이 발생할까요? @@ -245,7 +265,7 @@ document.addEventListener('readystatechange', () => console.log(document.readySt - + diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html index a4685a7165..27df709393 100644 --- a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html +++ b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html @@ -9,8 +9,8 @@ [20] readyState:interactive [21] DOMContentLoaded [30] iframe onload - [40] readyState:complete [40] img onload + [40] readyState:complete [40] window onload --> diff --git a/2-ui/5-loading/02-script-async-defer/article.md b/2-ui/5-loading/02-script-async-defer/article.md index e0c0502c59..550dc7351e 100644 --- a/2-ui/5-loading/02-script-async-defer/article.md +++ b/2-ui/5-loading/02-script-async-defer/article.md @@ -37,7 +37,7 @@ ## defer -브라우저는 `defer` 속성이 있는 스크립트(이하 defer 스크립트 또는 지연 스크립트)를 '백그라운드'에서 다운로드 합니다. 따라서 지연 스크립트를 다운로드 하는 도중에도 HTML 파싱이 멈추지 않습니다. 그리고 `defer` 스크립트 실행은 페이지 구성이 끝날 때까지 *지연* 됩니다. +브라우저는 `defer` 속성이 있는 스크립트(이하 defer 스크립트 또는 지연 스크립트)를 '백그라운드'에서 다운로드합니다. 따라서 지연 스크립트를 다운로드하는 도중에도 HTML 처리를 계속 진행하며 DOM을 생성합니다. 그리고 defer 스크립트는 DOM 구성이 완료된 뒤 실행됩니다. 위쪽 예시와 동일한 코드인데 스크립트에 `defer`만 붙여보겠습니다. @@ -59,7 +59,7 @@

...스크립트 앞 콘텐츠...

@@ -70,7 +70,7 @@ 1. 페이지 콘텐츠는 바로 출력됩니다. 2. `DOMContentLoaded` 이벤트는 지연 스크립트 실행을 기다립니다. 따라서 얼럿창은 DOM 트리가 완성되고 지연 스크립트가 실행된 후에 뜹니다. -지연 스크립트는 일반 스크립트와 마찬가지로 HTML에 추가된 순(상대순, 요소순)으로 실행됩니다. +**지연 스크립트는 일반 스크립트와 마찬가지로 HTML에 추가된 순(상대순, 요소순)으로 실행됩니다.** 따라서 길이가 긴 스크립트가 앞에, 길이가 짧은 스크립트가 뒤에 있어도 짧은 스크립트는 긴 스크립트가 실행될 때까지 기다립니다. @@ -79,29 +79,34 @@ ``` -```smart header="작은 스크립트는 먼저 다운되지만, 실행은 나중에 됩니다." -브라우저는 성능을 위해 페이지에 어떤 스크립트들이 있는지 쭉 살펴본 후에야 스크립트를 병렬적으로 다운로드합니다. 위 예시에서도 스크립트 다운로드가 병렬적으로 진행되었습니다. 그런데 이 때 크기가 작은 `small.js`이 `long.js`보다 먼저 다운로드 될 수 있습니다. +브라우저는 성능을 위해 페이지에 어떤 스크립트들이 있는지 쭉 살펴본 후에야 스크립트를 병렬적으로 다운로드합니다. 위 예시에서도 스크립트 다운로드가 병렬적으로 진행되었습니다. 그런데 이때 크기가 작은 `small.js`가 `long.js`보다 먼저 다운로드될 수 있습니다. -하지만 명세서에서 스크립트를 문서에 추가한 순서대로 실행하라고 정의했기 때문에 `small.js`는 `long.js` 다음에 실행됩니다. -``` +하지만 `defer` 속성은 브라우저에 '막지 마'라고 지시하는 것 외에도 상대 순서를 유지합니다. 따라서 `small.js`가 먼저 다운로드 되더라도 `long.js` 다음에 실행됩니다. + +이는 자바스크립트 라이브러리를 로드한 다음 해당 라이브러리에 의존하는 스크립트를 로드해야 하는 경우에 중요할 수 있습니다. ```smart header="`defer` 속성은 외부 스크립트에만 유효합니다." ` ``` +```smart header="The `async` attribute is only for external scripts" +Just like `defer`, the `async` attribute is ignored if the ` +``` + +Here we take the first child of `

` (that's the text node) and specify the text positions inside it: + +![](range-hello-1.svg) + +### Selecting element nodes + +**Alternatively, if `node` is an element node, then `offset` must be the child number.** -```html +That's handy for making ranges that contain nodes as a whole, not stop somewhere inside their text. + +For example, we have a more complex document fragment: + +```html autorun

Example: italic and bold

``` -Here's its DOM structure, note that here text nodes are important for us: +Here's its DOM structure with both element and text nodes:
@@ -72,10 +102,21 @@ let selectPDomtree = { drawHtmlTree(selectPDomtree, 'div.select-p-domtree', 690, 320); -Let's select `"Example: italic"`. That's two first children of `

` (counting text nodes): +Let's make a range for `"Example: italic"`. + +As we can see, this phrase consists of exactly two children of `

`, with indexes `0` and `1`: ![](range-example-p-0-1.svg) +- The starting point has `

` as the parent `node`, and `0` as the offset. + + So we can set it as `range.setStart(p, 0)`. +- The ending point also has `

` as the parent `node`, but `2` as the offset (it specifies the range up to, but not including `offset`). + + So we can set it as `range.setEnd(p, 2)`. + +Here's the demo. If you run it, you can see that the text gets selected: + ```html run

Example: italic and bold

@@ -87,18 +128,15 @@ Let's select `"Example: italic"`. That's two first children of `

` (cou range.setEnd(p, 2); */!* - // toString of a range returns its content as text (without tags) - alert(range); // Example: italic + // toString of a range returns its content as text, without tags + console.log(range); // Example: italic - // apply this range for document selection (explained later) + // apply this range for document selection (explained later below) document.getSelection().addRange(range); ``` -- `range.setStart(p, 0)` -- sets the start at the 0th child of `

` (that's the text node `"Example: "`). -- `range.setEnd(p, 2)` -- spans the range up to (but not including) 2nd child of `

` (that's the text node `" and "`, but as the end is not included, so the last selected node is ``). - -Here's a more flexible test stand where you try more variants: +Here's a more flexible test stand where you can set range start/end numbers and explore other variants: ```html run autorun

Example: italic and bold

@@ -114,26 +152,28 @@ From – To ` first child (taking all but two first letters of "Example: ") @@ -148,14 +188,20 @@ We need to create a range, that: range.setStart(p.firstChild, 2); range.setEnd(p.querySelector('b').firstChild, 3); - alert(range); // ample: italic and bol + console.log(range); // ample: italic and bol // use this range for selection (explained later) window.getSelection().addRange(range); ``` -The range object has following properties: +As you can see, it's fairly easy to make a range of whatever we want. + +If we'd like to take nodes as a whole, we can pass elements in `setStart/setEnd`. Otherwise, we can work on the text level. + +## Range properties + +The range object that we created in the example above has following properties: ![](range-example-p-2-b-3-range.svg) @@ -168,9 +214,12 @@ The range object has following properties: - `commonAncestorContainer` -- the nearest common ancestor of all nodes within the range, - in the example above: `

` -## Range methods -There are many convenience methods to manipulate ranges. +## Range selection methods + +There are many convenient methods to manipulate ranges. + +We've already seen `setStart` and `setEnd`, here are other similar methods. Set range start: @@ -184,15 +233,19 @@ Set range end (similar methods): - `setEndBefore(node)` set end at: right before `node` - `setEndAfter(node)` set end at: right after `node` -**As it was demonstrated, `node` can be both a text or element node: for text nodes `offset` skips that many of characters, while for element nodes that many child nodes.** +Technically, `setStart/setEnd` can do anything, but more methods provide more convenience. + +In all these methods, `node` can be both a text or element node: for text nodes `offset` skips that many of characters, while for element nodes that many child nodes. -Others: +Even more methods to create ranges: - `selectNode(node)` set range to select the whole `node` - `selectNodeContents(node)` set range to select the whole `node` contents - `collapse(toStart)` if `toStart=true` set end=start, otherwise set start=end, thus collapsing the range - `cloneRange()` creates a new range with the same start/end -To manipulate the content within the range: +## Range editing methods + +Once the range is created, we can manipulate its content using these methods: - `deleteContents()` -- remove range content from the document - `extractContents()` -- remove range content from the document and return as [DocumentFragment](info:modifying-document#document-fragment) @@ -204,7 +257,7 @@ With these methods we can do basically anything with selected nodes. Here's the test stand to see them in action: -```html run autorun height=260 +```html run refresh autorun height=260 Click buttons to run methods on the selection, "resetExample" to reset it.

Example: italic and bold

@@ -237,7 +290,7 @@ Click buttons to run methods on the selection, "resetExample" to reset it. let newNode = document.createElement('u'); try { range.surroundContents(newNode); - } catch(e) { alert(e) } + } catch(e) { console.log(e) } }, resetExample() { p.innerHTML = `Example: italic and bold`; @@ -259,16 +312,16 @@ Click buttons to run methods on the selection, "resetExample" to reset it. ``` -There also exist methods to compare ranges, but these are rarely used. When you need them, please refer to the [spec](https://dom.spec.whatwg.org/#interface-range) or [MDN manual](https://developer.mozilla.org/en-US/docs/Web/API/Range). +There also exist methods to compare ranges, but these are rarely used. When you need them, please refer to the [spec](https://dom.spec.whatwg.org/#interface-range) or [MDN manual](mdn:/api/Range). ## Selection -`Range` is a generic object for managing selection ranges. We may create such objects, pass them around -- they do not visually select anything on their own. +`Range` is a generic object for managing selection ranges. Although, creating a `Range` doesn't mean that we see a selection on screen. -The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`. +We may create `Range` objects, pass them around -- they do not visually select anything on their own. -A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac). +The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`. A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac). Here's a screenshot of a selection with 3 ranges, made in Firefox: @@ -276,9 +329,19 @@ Here's a screenshot of a selection with 3 ranges, made in Firefox: Other browsers support at maximum 1 range. As we'll see, some of `Selection` methods imply that there may be many ranges, but again, in all browsers except Firefox, there's at maximum 1. +Here's a small demo that shows the current selection (select something and click) as text: + + + ## Selection properties -Similar to a range, a selection has a start, called "anchor", and the end, called "focus". +As said, a selection may in theory contain multiple ranges. We can get these range objects using the method: + +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. + +Also, there exist properties that often provide better convenience. + +Similar to a range, a selection object has a start, called "anchor", and the end, called "focus". The main selection properties are: @@ -289,36 +352,39 @@ The main selection properties are: - `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist. - `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox. -````smart header="Selection end may be in the document before start" -There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc. +```smart header="Selection end/start vs Range" + +There's an important difference between a selection anchor/focus compared with a `Range` start/end. + +As we know, `Range` objects always have their start before the end. + +For selections, that's not always the case. -Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left". +Selecting something with a mouse can be done in both directions: either "left-to-right" or "right-to-left". -If the start (anchor) of the selection goes in the document before the end (focus), this selection is said to have "forward" direction. +In other words, when the mouse button is pressed, and then it moves forward in the document, then its end (focus) will be after its start (anchor). E.g. if the user starts selecting with mouse and goes from "Example" to "italic": ![](selection-direction-forward.svg) -Otherwise, if they go from the end of "italic" to "Example", the selection is directed "backward", its focus will be before the anchor: +...But the same selection could be done backwards: starting from "italic" to "Example" (backward direction), then its end (focus) will be before the start (anchor): ![](selection-direction-backward.svg) - -That's different from `Range` objects that are always directed forward: the range start can't be after its end. -```` +``` ## Selection events There are events on to keep track of selection: -- `elem.onselectstart` -- when a selection starts on `elem`, e.g. the user starts moving mouse with pressed button. - - Preventing the default action makes the selection not start. -- `document.onselectionchange` -- whenever a selection changes. - - Please note: this handler can be set only on `document`. +- `elem.onselectstart` -- when a selection *starts* specifically on element `elem` (or inside it). For instance, when the user presses the mouse button on it and starts to move the pointer. + - Preventing the default action cancels the selection start. So starting a selection from this element becomes impossible, but the element is still selectable. The visitor just needs to start the selection from elsewhere. +- `document.onselectionchange` -- whenever a selection changes or starts. + - Please note: this handler can be set only on `document`, it tracks all selections in it. ### Selection tracking demo -Here's a small demo that shows selection boundaries dynamically as it changes: +Here's a small demo. It tracks the current selection on the `document` and shows its boundaries: ```html run height=80

Select me: italic and bold

@@ -326,21 +392,25 @@ Here's a small demo that shows selection boundaries dynamically as it changes: From – To ``` -### Selection getting demo +### Selection copying demo + +There are two approaches to copying the selected content: -To get the whole selection: -- As text: just call `document.getSelection().toString()`. -- As DOM nodes: get the underlying ranges and call their `cloneContents()` method (only first range if we don't support Firefox multiselection). +1. We can use `document.getSelection().toString()` to get it as text. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangeAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. -And here's the demo of getting the selection both as text and as DOM nodes: +Here's the demo of copying the selected content both as text and as DOM nodes: ```html run height=100

Select me: italic and bold

@@ -368,15 +438,15 @@ As text: ## Selection methods -Selection methods to add/remove ranges: +We can work with the selection by adding/removing ranges: -- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except firefox, only `0` is used. +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. - `addRange(range)` -- add `range` to selection. All browsers except Firefox ignore the call, if the selection already has an associated range. - `removeRange(range)` -- remove `range` from the selection. - `removeAllRanges()` -- remove all ranges. - `empty()` -- alias to `removeAllRanges`. -Also, there are convenience methods to manipulate the selection range directly, without `Range`: +There are also convenience methods to manipulate the selection range directly, without intermediate `Range` calls: - `collapse(node, offset)` -- replace selected range with a new one that starts and ends at the given `node`, at position `offset`. - `setPosition(node, offset)` -- alias to `collapse`. @@ -388,7 +458,7 @@ Also, there are convenience methods to manipulate the selection range directly, - `deleteFromDocument()` -- remove selected content from the document. - `containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partially if the second argument is `true`) -So, for many tasks we can call `Selection` methods, no need to access the underlying `Range` object. +For most tasks these methods are just fine, there's no need to access the underlying `Range` object. For example, selecting the whole contents of the paragraph `

`: @@ -415,10 +485,10 @@ The same thing using ranges: ``` -```smart header="To select, remove the existing selection first" -If the selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. +```smart header="To select something, remove the existing selection first" +If a document selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. -The exception is some selection methods, that replace the existing selection, like `setBaseAndExtent`. +The exception is some selection methods, that replace the existing selection, such as `setBaseAndExtent`. ``` ## Selection in form controls @@ -494,7 +564,7 @@ Focus on me, the cursor will be at position 10. // zero delay setTimeout to run after browser "focus" action finishes setTimeout(() => { // we can set any selection - // if start=end, the cursor it exactly at that place + // if start=end, the cursor is exactly at that place area.selectionStart = area.selectionEnd = 10; }); }; diff --git a/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg new file mode 100644 index 0000000000..2951607a2d --- /dev/null +++ b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg @@ -0,0 +1 @@ +<p>Hello</p>p.firstChild \ No newline at end of file diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md new file mode 100644 index 0000000000..2911b76cf4 --- /dev/null +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md @@ -0,0 +1,50 @@ +The console output is: 1 7 3 5 2 6 4. + +The task is quite simple, we just need to know how microtask and macrotask queues work. + +Let's see what's going on, step by step. + +```js +console.log(1); +// The first line executes immediately, it outputs `1`. +// Macrotask and microtask queues are empty, as of now. + +setTimeout(() => console.log(2)); +// `setTimeout` appends the callback to the macrotask queue. +// - macrotask queue content: +// `console.log(2)` + +Promise.resolve().then(() => console.log(3)); +// The callback is appended to the microtask queue. +// - microtask queue content: +// `console.log(3)` + +Promise.resolve().then(() => setTimeout(() => console.log(4))); +// The callback with `setTimeout(...4)` is appended to microtasks +// - microtask queue content: +// `console.log(3); setTimeout(...4)` + +Promise.resolve().then(() => console.log(5)); +// The callback is appended to the microtask queue +// - microtask queue content: +// `console.log(3); setTimeout(...4); console.log(5)` + +setTimeout(() => console.log(6)); +// `setTimeout` appends the callback to macrotasks +// - macrotask queue content: +// `console.log(2); console.log(6)` + +console.log(7); +// Outputs 7 immediately. +``` + +To summarize, + +1. Numbers `1` and `7` show up immediately, because simple `console.log` calls don't use any queues. +2. Then, after the main code flow is finished, the microtask queue runs. + - It has commands: `console.log(3); setTimeout(...4); console.log(5)`. + - Numbers `3` and `5` show up, while `setTimeout(() => console.log(4))` adds the `console.log(4)` call to the end of the macrotask queue. + - The macrotask queue is now: `console.log(2); console.log(6); console.log(4)`. +3. After the microtask queue becomes empty, the macrotask queue executes. It outputs `2`, `6`, `4`. + +Finally, we have the output: `1 7 3 5 2 6 4`. \ No newline at end of file diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md new file mode 100644 index 0000000000..ad406b3be2 --- /dev/null +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md @@ -0,0 +1,21 @@ +importance: 5 + +--- + +# What will be the output of this code? + +```js +console.log(1); + +setTimeout(() => console.log(2)); + +Promise.resolve().then(() => console.log(3)); + +Promise.resolve().then(() => setTimeout(() => console.log(4))); + +Promise.resolve().then(() => console.log(5)); + +setTimeout(() => console.log(6)); + +console.log(7); +``` diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index b106862152..8844c0020c 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -9,7 +9,7 @@ ## 이벤트 루프 -*이벤트 루프(event loop)* 정의는 아주 간단합니다. 이벤트 루프는 태스크가 들어오길 기다렸다가 태스크가 들어오면 이를 처리하고, 처리할 태스크가 없는 경우엔 잠드는, 끊임없이 돌아가는 자바스크립트 내 루프입니다(task는 '작업'이라고 번역할 수 있는데, 매크로·마이크로태스크 등의 용어와 일치시키기 위해 '태스크'라고 음차 번역하였습니다 - 옮긴이). +*이벤트 루프(event loop)* 개념은 아주 간단합니다. 이벤트 루프는 태스크가 들어오길 기다렸다가 태스크가 들어오면 이를 실행하고, 처리할 태스크가 없는 경우엔 잠드는 자바스크립트 엔진 내부에서 돌아가는 무한 루프입니다(task는 '작업'이라고 번역할 수 있는데, 매크로·마이크로태스크 등의 용어와 일치시키기 위해 '태스크'라고 음차 번역하였습니다 - 옮긴이). 자바스크립트 엔진이 돌아가는 알고리즘을 일반화하면 다음과 같습니다. @@ -18,7 +18,7 @@ 2. 처리해야 할 태스크가 없는 경우: - 잠들어 있다가 새로운 태스크가 추가되면 다시 1로 돌아감 -바로 이 알고리즘이 우리가 브라우저를 사용해 인터넷을 서핑할 때 돌아가는 알고리즘입니다. 이렇게 자바스크립트 엔진은 대부분의 시간 동안 아무런 일도 하지 않고 쉬고 있다가 스크립트나 핸들러, 이벤트가 활성화될 때만 돌아갑니다. +바로 이 알고리즘이 우리가 브라우저를 사용해 페이지를 둘러볼 때 돌아가는 알고리즘입니다. 이렇게 자바스크립트 엔진은 대부분의 시간 동안 아무런 일도 하지 않고 쉬고 있다가 스크립트나 핸들러, 이벤트가 활성화될 때만 돌아갑니다. 그렇다면 자바스크립트 엔진을 활성화하는 태스크엔 과연 어떤 것들이 있을까요? 대표적인 태스크는 다음과 같습니다. @@ -31,19 +31,19 @@ 새로운 태스크는 엔진이 바쁠 때 추가될 수도 있습니다. 이때 이 태스크는 큐에 추가됩니다. -이렇게 태스크가 추가되는 큐는 V8 용어로 '매크로태스크 큐(macrotask queue)'라고 부릅니다. +이렇게 태스크가 추가되는 큐는 [v8](https://v8.dev/) 용어로 '매크로태스크 큐(macrotask queue)'라고 부릅니다. ![](eventLoop.svg) -좀 더 구체적인 사례를 가지고 매크로태스크 큐에 대해 알아봅시다. 엔진이 `script`를 처리하느라 바쁜데 사용자가 마우스를 움직여 `mousemove` 이벤트를 활성화하고, 바로 이어서 `setTimeout`에서 설정한 시간이 지났다고 가정해 봅시다. 이때 세 태스크는 큐에 하나씩 추가되는데, 위 그림에 이런 상황을 묘사해 보았습니다. +예를 들어 엔진이 `script`를 실행하는 동안 사용자가 마우스를 움직여 `mousemove` 이벤트가 발생하고, 이어서 `setTimeout`에서 설정한 시간이 만료되었다고 가정해 봅시다. 이 경우 각 태스크는 큐에 차례대로 추가되며 위 그림이 바로 이런 상황을 나타냅니다. -큐에 있는 태스크들은 '들어간 순서대로' 처리됩니다. 엔진은 `script`를 먼저 처리하고 `mousemove` 이벤트와 핸들러, `setTimeout` 핸들러를 순차적으로 처리합니다. +큐에 있는 태스크들은 '들어간 순서대로'(first come – first served) 처리됩니다. 엔진은 `script`를 먼저 처리하고 `mousemove` 이벤트와 핸들러, `setTimeout` 핸들러를 차례대로 처리합니다. 지금까진 어려운 것이 없어 보입니다. 그렇죠? 여기서 잠시 두 가지 세부 사항을 짚고 넘어갑시다. -1. 엔진이 특정 태스크를 처리하는 동안엔 렌더링이 절대 일어나지 않습니다. 태스크를 처리하는 데 걸리는 시간이 길지 않으면 이는 전혀 문제가 되지 않습니다. 처리가 끝나는 대로 DOM 변경을 화면에 반영하면 되기 때문입니다. -2. 태스크 처리에 긴 시간이 걸리면, 브라우저는 태스크를 처리하는 동안에 발생한 사용자 이벤트 등의 새로운 태스크들을 처리하지 못합니다. 인터넷 서핑을 하다 보면 '응답 없는 페이지(Page Unresponsive)'라는 얼럿 창을 만나게 되는 경우가 종종 있습니다. 이 얼럿 창은 아주 복잡한 계산이 필요하거나 프로그래밍 에러 때문에 무한 루프에 빠지게 될 때 나타나는데, 브라우저는 얼럿 창을 통해 사용자에게 페이지 전체와 함께 해당 태스크를 취소시킬지 말지를 선택하도록 유도합니다. +1. 엔진이 특정 태스크를 처리하는 동안엔 렌더링이 절대 일어나지 않습니다. 태스크를 처리하는 데 걸리는 시간이 길든 짧든 마찬가지입니다. DOM 변경 사항은 태스크 처리가 끝난 뒤에만 화면에 반영됩니다. +2. 태스크 처리에 긴 시간이 걸리면 브라우저는 태스크를 처리하는 동안에 발생한 사용자 이벤트 등의 새로운 태스크들을 처리하지 못합니다. 인터넷 서핑을 하다 보면 '응답 없는 페이지(Page Unresponsive)'라는 얼럿 창을 만나게 되는 경우가 종종 있습니다. 이 얼럿 창은 아주 복잡한 계산이 필요하거나 프로그래밍 에러 때문에 무한 루프에 빠지게 될 때 나타나는데, 브라우저는 얼럿 창을 통해 사용자에게 페이지 전체와 함께 해당 태스크를 취소시킬지 말지를 선택하도록 유도합니다. 자, 이론을 충분히 살펴봤으니 지금부턴 이 지식을 실무에서 어떻게 활용할 수 있을지 알아보도록 합시다. @@ -55,7 +55,7 @@ CPU 소모가 아주 많은 태스크 하나가 있다고 가정해 봅시다. 코드 강조라는 태스크를 수행하느라 엔진이 바쁠 때엔 사용자 이벤트 처리나 DOM 관련 작업이 완전히 멈추게 됩니다. 그러다 보면 브라우저에 '지연'이 생기거나 심하면 '멈춤' 현상까지 발생하기도 하죠. 절대 있어서는 안 될 일입니다. -이런 불가피한 상황들은 태스크를 여러 조각으로 쪼개 예방할 수 있습니다. 앞부분 100줄만 먼저 강조하고, 지연시간이 0인 `setTimeout`을 사용해 새롭게 스케줄링을 한 다음, 그 다음 100줄을 강조하는 식으로 코드를 변경하면 되죠. +이런 문제는 큰 태스크를 여러 조각으로 나누어 예방할 수 있습니다. 앞의 100줄을 먼저 강조하고, 다음 100줄은 지연 시간이 0인 `setTimeout`으로 예약한 뒤 같은 방식으로 계속 처리하면 됩니다. 실제 코드를 통해 어떻게 하면 태스크를 쪼갤 수 있는지 알아봅시다. 직접 강조기능을 구현하는 대신 `1`부터 `1000000000`까지의 숫자를 세주는 함수를 사용해 간결한 코드로 시연해 보겠습니다. @@ -161,7 +161,7 @@ count(); 태스크를 여러 개로 쪼갤 때의 장점은 진행 상태를 나타내주는 프로그레스 바(progress bar)를 만들 때도 드러납니다. -아시다시피 브라우저는 시간이 오래 걸리든 아니든 상관없이 현재 작업 중인 태스크가 끝나야 DOM 변경분을 화면에 렌더링해줍니다. +앞서 설명했듯이 현재 실행 중인 태스크의 처리 시간이 길든 짧든, 브라우저는 해당 태스크가 끝난 뒤에만 DOM 변경 사항을 화면에 반영합니다. 이런 브라우저 동작 방식은 완성되지 않은 '중간' 상태의 화면이 사용자에게 노출되는 걸 막아주기 때문에 유리합니다. 요소를 여러 개 만들고 이 요소들을 하나씩 화면에 추가한 다음 원하는 요소의 스타일을 변경시키는 일련의 과정이 담긴 함수가 있는데, 이 함수를 실행하는 동안에 변경사항 모두가 사용자에게 노출된다면 사용자는 혼란을 느꼈을 겁니다. @@ -239,7 +239,7 @@ menu.onclick = function() { ## 매크로태스크와 마이크로태스크 -태스크는 이번 챕터에서 설명한 *매크로태스크(macrotask)* 와 챕터에서 다룬 *마이크로태스크(microtask)* 로 나뉩니다. +태스크는 이번 챕터에서 설명한 *매크로태스크(macrotask)*와 챕터에서 다룬 *마이크로태스크(microtask)*로 나뉩니다. 마이크로태스크는 코드를 사용해서만 만들 수 있는데, 주로 프라미스를 사용해 만듭니다. 프라미스와 함께 쓰이는 `.then/catch/finally` 핸들러가 마이크로태스크가 되죠. 여기에 더하여 마이크로태스크는 프라미스를 핸들링하는 또 다른 문법인 `await`를 사용해 만들기도 합니다. @@ -304,7 +304,7 @@ alert("code"); ## 요약 -이벤트 루프 알고리즘을 요약하면 다음과 같습니다(자세한 사항은 [명세서](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)에서 확인할 수 있습니다). +이벤트 루프 알고리즘을 조금 더 자세히 살펴보면 다음과 같습니다(다만 아래 설명도 [명세서](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)에 비해 단순화된 형태입니다.) 1. *매크로태스크* 큐에서 가장 오래된 태스크를 꺼내 실행합니다(예: 스크립트를 실행). 2. 모든 *마이크로태스크*를 실행합니다. @@ -317,7 +317,7 @@ alert("code"); 새로운 *매크로태스크*를 스케줄링하는 방법은 다음과 같습니다. - 지연시간이 0인 `setTimeout(f)` 사용하기 -이 방법을 사용하면 계산이 복잡한 큰 태스크 하나를 여러 개로 쪼갤 수 있습니다. 태스크를 여러 개로 쪼개면 태스크 중간중간 사용자 이벤트에 반응할 수 있고, 작업 진척 상태를 화면에 표시해줄 수도 있습니다. +이 방법을 사용하면 계산량이 많은 큰 태스크 하나를 여러 개로 쪼갤 수 있습니다. 이렇게 하면 브라우저가 태스크 중간중간 사용자 이벤트에 반응할 수 있고, 진행 상태를 화면에 표시할 수도 있습니다. 지연시간이 0인 `setTimeout`은 이벤트가 완전히 처리되고 난 후(버블링이 끝난 후)에 특정 작업을 수행하도록 스케줄링할 때도 사용됩니다. diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index 7540b03045..f2c87d1e01 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -38,26 +38,6 @@ button.onclick = () => { This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally. -What if the popup opens from `onclick`, but after `setTimeout`? That's a bit tricky. - -Try this code: - -```js run -// open after 3 seconds -setTimeout(() => window.open('http://google.com'), 3000); -``` - -The popup opens in Chrome, but gets blocked in Firefox. - -...If we decrease the delay, the popup works in Firefox too: - -```js run -// open after 1 seconds -setTimeout(() => window.open('http://google.com'), 1000); -``` - -The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not. - ## window.open The syntax to open a popup is: `window.open(url, name, params)`: @@ -87,7 +67,7 @@ Settings for `params`: There is also a number of less supported browser-specific features, which are usually not used. Check window.open in MDN for examples. -## Example: a minimalistic window +## Example: a minimalistic window Let's open a window with minimal set of features, just to see which of them browser allows to disable: @@ -120,7 +100,7 @@ Rules for omitted settings: ## Accessing popup from window -The `open` call returns a reference to the new window. It can be used to manipulate it's properties, change location and even more. +The `open` call returns a reference to the new window. It can be used to manipulate its properties, change location and even more. In this example, we generate popup content from JavaScript: @@ -192,7 +172,7 @@ newWindow.onload = function() { ``` -## Scrolling and resizing +## Moving and resizing There are methods to move/resize a window: @@ -239,7 +219,7 @@ There's also `window.onscroll` event. Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. And there are also `focus/blur` events that allow to catch the moment when the visitor focuses on a window and switches elsewhere. -Although, in practice they are severely limited, because in the past evil pages abused them. +Although, in practice they are severely limited, because in the past evil pages abused them. For instance, look at this code: @@ -257,10 +237,10 @@ Still, there are some use cases when such calls do work and can be useful. For instance: -- When we open a popup, it's might be a good idea to run a `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now. +- When we open a popup, it might be a good idea to run `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now. - If we want to track when a visitor actually uses our web-app, we can track `window.onfocus/onblur`. That allows us to suspend/resume in-page activities, animations etc. But please note that the `blur` event means that the visitor switched out from the window, but they still may observe it. The window is in the background, but still may be visible. -## Summary +## Summary Popup windows are used rarely, as there are alternatives: loading and displaying information in-page, or in iframe. diff --git a/3-frames-and-windows/03-cross-window-communication/article.md b/3-frames-and-windows/03-cross-window-communication/article.md index 53f5f55fc4..4d4e320e44 100644 --- a/3-frames-and-windows/03-cross-window-communication/article.md +++ b/3-frames-and-windows/03-cross-window-communication/article.md @@ -116,6 +116,13 @@ document.domain = 'site.com'; That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain. +```warn header="Deprecated, but still working" +The `document.domain` property is in the process of being removed from the [specification](https://html.spec.whatwg.org/multipage/origin.html#relaxing-the-same-origin-restriction). The cross-window messaging (explained soon below) is the suggested replacement. + +That said, as of now all browsers support it. And the support will be kept for the future, not to break old code that relies on `document.domain`. +``` + + ## Iframe: wrong document pitfall When an iframe comes from the same origin, and we may access its `document`, there's a pitfall. It's not related to cross-origin things, but important to know. @@ -263,12 +270,12 @@ The window that wants to send a message calls [postMessage](mdn:api/Window.postM Arguments: `data` -: The data to send. Can be any object, the data is cloned using the "structured cloning algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser. +: The data to send. Can be any object, the data is cloned using the "structured serialization algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser. `targetOrigin` : Specifies the origin for the target window, so that only a window from the given origin will get the message. -The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read it's `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it. +The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read its `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it. Specifying `targetOrigin` ensures that the window only receives the data if it's still at the right site. Important when the data is sensitive. diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 1daa87dd08..34d0a91ae2 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -154,7 +154,7 @@ Depending on your browser, the `iframe` above is either empty or alerting you th ## Showing with disabled functionality -The `X-Frame-Options` header has a side-effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. +The `X-Frame-Options` header has a side effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. So there are other solutions... For instance, we can "cover" the page with a `

` with styles `height: 100%; width: 100%;`, so that it will intercept all clicks. That `
` is to be removed if `window == top` or if we figure out that we don't need the protection. diff --git a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js index 2f51384efb..00c37bb94e 100644 --- a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js +++ b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js @@ -2,9 +2,9 @@ function concat(arrays) { // sum of individual array lengths let totalLength = arrays.reduce((acc, value) => acc + value.length, 0); - if (!arrays.length) return null; - let result = new Uint8Array(totalLength); + + if (!arrays.length) return result; // for each array - copy it over result // next array is copied right after the previous one diff --git a/4-binary/01-arraybuffer-binary-arrays/article.md b/4-binary/01-arraybuffer-binary-arrays/article.md index 6e6ea8022f..2827e277e2 100644 --- a/4-binary/01-arraybuffer-binary-arrays/article.md +++ b/4-binary/01-arraybuffer-binary-arrays/article.md @@ -30,11 +30,11 @@ Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in com **To manipulate an `ArrayBuffer`, we need to use a "view" object.** -A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`. +A view object does not store anything on its own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`. For instance: -- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". +- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". - **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer". - **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer". - **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from 5.0x10-324 to 1.8x10308. @@ -71,13 +71,13 @@ for(let num of view) { ## TypedArray -The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities. +The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properties. Please note, there's no constructor called `TypedArray`, it's just a common "umbrella" term to represent one of views over `ArrayBuffer`: `Int8Array`, `Uint8Array` and so on, the full list will soon follow. When you see something like `new TypedArray`, it means any of `new Int8Array`, `new Uint8Array`, etc. -Typed array behave like regular arrays: have indexes and iterable. +Typed arrays behave like regular arrays: have indexes and are iterable. A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types. @@ -126,9 +126,9 @@ new TypedArray(); We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided). -To access the `ArrayBuffer`, there are properties: -- `arr.buffer` -- references the `ArrayBuffer`. -- `arr.byteLength` -- the length of the `ArrayBuffer`. +To access the underlying `ArrayBuffer`, there are following properties in `TypedArray`: +- `buffer` -- references the `ArrayBuffer`. +- `byteLength` -- the length of the `ArrayBuffer`. So, we can always move from one view to another: ```js @@ -209,7 +209,7 @@ These methods allow us to copy typed arrays, mix them, create new arrays from ex ## DataView -[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format. +[DataView](mdn:/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format. - For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`. - With `DataView` we access the data with methods like `.getUint8(i)` or `.getUint16(i)`. We choose the format at method call time instead of the construction time. @@ -259,7 +259,7 @@ To do almost any operation on `ArrayBuffer`, we need a view. - `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits. - Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`. -In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed. +In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common denominator". We can access it as `.buffer` and make another view if needed. There are also two additional terms, that are used in descriptions of methods that operate on binary data: - `ArrayBufferView` is an umbrella term for all these kinds of views. diff --git a/4-binary/02-text-decoder/article.md b/4-binary/02-text-decoder/article.md index 619eb9d2d3..85f8e826d0 100644 --- a/4-binary/02-text-decoder/article.md +++ b/4-binary/02-text-decoder/article.md @@ -2,7 +2,7 @@ 이진 데이터가 문자열이라면 어떨지 생각해봅시다. 예를 들어 텍스트 데이터가 있는 파일을 받았다고 가정하겠습니다. -내장 객체, [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder)는 주어진 버퍼와 인코딩으로 값을 실제 자바스크립트 문자열로 읽을 수 있게 해줍니다. +내장 객체인 [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder)는 주어진 버퍼와 인코딩으로 값을 실제 자바스크립트 문자열로 읽을 수 있게 해줍니다. 첫 번째로 객체를 생성합니다. ```js @@ -12,7 +12,7 @@ let decoder = new TextDecoder([label], [options]); - **`label`** -- 기본적인 인코딩 방식은 `utf-8`이지만 `big5`, `windows-1251` 및 다른 인코딩 방식도 지원됩니다. - **`options`** -- 선택 항목입니다. - **`fatal`** -- 불린 값. `true`인 경우, 잘못된 글자(디코딩 불가능한 글자)를 대상으로 예외를 던집니다. `false(기본값)`인 경우, 글자를 `\uFFFD`로 대체합니다. - - **`ignoreBOM`** -- 불린 값이 `true`인 경우 사용되지 않는 바이트 순서 표식(Byte Order Mark, BOM)을 무시합니다. + - **`ignoreBOM`** -- 불린 값이 `true`인 경우 선택적인 바이트 순서 유니코드 표식(an optional byte-order Unicode mark, BOM)을 무시합니다. 이 옵션은 거의 필요하지 않습니다. 그런 다음 생성했던 객체를 디코딩합니다. diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index 84cf6f1cca..fc01505775 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -55,7 +55,7 @@ This behavior is similar to JavaScript strings: we can't change a character in a ## Blob as URL -A Blob can be easily used as an URL for ``, `` or other tags, to show its contents. +A Blob can be easily used as a URL for ``, `` or other tags, to show its contents. Thanks to `type`, we can also download/upload `Blob` objects, and the `type` naturally becomes `Content-Type` in network requests. @@ -74,7 +74,7 @@ link.href = URL.createObjectURL(blob); We can also create a link dynamically in JavaScript and simulate a click by `link.click()`, then download starts automatically. -Here's the similar code that causes user to download the dynamicallly created `Blob`, without any HTML: +Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML: ```js run let link = document.createElement('a'); @@ -99,9 +99,9 @@ blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273 For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. -A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects an url. +A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects a URL. -There's a side-effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. +There's a side effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. The mapping is automatically cleared on document unload, so `Blob` objects are freed then. But if an app is long-living, then that doesn't happen soon. @@ -119,7 +119,7 @@ An alternative to `URL.createObjectURL` is to convert a `Blob` into a base64-enc That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important -- we can use this encoding in "data-urls". -A [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls. +A [data url](mdn:/http/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls. For instance, here's a smiley: @@ -151,7 +151,7 @@ reader.onload = function() { }; ``` -Both ways of making an URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. +Both ways of making a URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. ```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url" + We need to revoke them if care about memory. @@ -166,8 +166,8 @@ We can create a `Blob` of an image, an image part, or even make a page screensho Image operations are done via `` element: -1. Draw an image (or its part) on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage). -2. Call canvas method [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done. +1. Draw an image (or its part) on canvas using [canvas.drawImage](mdn:/api/CanvasRenderingContext2D/drawImage). +2. Call canvas method [.toBlob(callback, format, quality)](mdn:/api/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done. In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob: @@ -186,7 +186,7 @@ let context = canvas.getContext('2d'); context.drawImage(img, 0, 0); // we can context.rotate(), and do many other things on canvas -// toBlob is async opereation, callback is called when done +// toBlob is async operation, callback is called when done canvas.toBlob(function(blob) { // blob ready, download it let link = document.createElement('a'); @@ -211,21 +211,44 @@ For screenshotting a page, we can use a library such as /* process the ArrayBuffer */); ``` +## From Blob to stream + +When we read and write to a blob of more than `2 GB`, the use of `arrayBuffer` becomes more memory intensive for us. At this point, we can directly convert the blob to a stream. + +A stream is a special object that allows to read from it (or write into it) portion by portion. It's outside of our scope here, but here's an example, and you can read more at . Streams are convenient for data that is suitable for processing piece-by-piece. + +The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the `Blob`. + +Then we can read from it, like this: + +```js +// get readableStream from blob +const readableStream = blob.stream(); +const stream = readableStream.getReader(); + +while (true) { + // for each iteration: value is the next blob fragment + let { done, value } = await stream.read(); + if (done) { + // no more data in the stream + console.log('all blob processed.'); + break; + } + + // do something with the data portion we've just read from the blob + console.log(value); +} +``` ## Summary @@ -235,7 +258,9 @@ That makes Blobs convenient for upload/download operations, that are so common i Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types. -We can easily convert betweeen `Blob` and low-level binary data types: +We can easily convert between `Blob` and low-level binary data types: + +- We can make a `Blob` from a typed array using `new Blob(...)` constructor. +- We can get back `ArrayBuffer` from a Blob using `blob.arrayBuffer()`, and then create a view over it for low-level binary processing. -- We can make a Blob from a typed array using `new Blob(...)` constructor. -- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing. +Conversion streams are very useful when we need to handle large blob. You can easily create a `ReadableStream` from a blob. The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the blob. diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 3f6f3a9d49..9b5f7565f2 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -61,8 +61,8 @@ if (response.ok) { // HTTP 상태 코드가 200~299일 경우 `response` 에는 프라미스를 기반으로 하는 다양한 메서드가 있습니다. 이 메서드들을 사용하면 다양한 형태의 응답 본문을 처리할 수 있습니다. -- **`response.text()`** -- 응답을 읽고 텍스트를 반환합니다, -- **`response.json()`** -- 응답을 JSON 형태로 파싱합니다, +- **`response.text()`** -- 응답을 읽고 텍스트를 반환합니다. +- **`response.json()`** -- 응답을 JSON 형태로 파싱합니다. - **`response.formData()`** -- 응답을 `FormData` 객체 형태로 반환합니다. `FormData`에 대한 자세한 내용은 [다음 챕터](info:formdata)에서 다루겠습니다. - **`response.blob()`** -- 응답을 [Blob](info:blob)(타입이 있는 바이너리 데이터) 형태로 반환합니다. - **`response.arrayBuffer()`** -- 응답을 [ArrayBuffer](info:arraybuffer-binary-arrays)(바이너리 데이터를 로우 레벨 형식으로 표현한 것) 형태로 반환합니다. @@ -194,9 +194,9 @@ let response = fetch(protectedUrl, { - **`method`** -- HTTP 메서드(예: `POST`) - **`body`** -- 요청 본문으로 다음 항목 중 하나이어야 합니다. - - 문자열(예: JSON 문자열) - - `FormData`객체 -- `form/multipart` 형태로 데이터를 전송하기 위해 쓰입니다. - - `Blob`나 `BufferSource` -- 바이너리 데이터 전송을 위해 쓰입니다. + - 문자열(예: JSON으로 인코딩된 문자열) + - `FormData` 객체 -- `multipart/form-data` 형태로 데이터를 전송하기 위해 쓰입니다. + - `Blob` / `BufferSource` -- 바이너리 데이터 전송을 위해 쓰입니다. - [URLSearchParams](info:url) -- 데이터를 `x-www-form-urlencoded` 형태로 보내기 위해 쓰이는데, 요즘엔 잘 사용하지 않습니다. 대부분은 JSON을 요청 본문에 실어 보내게 됩니다. @@ -303,14 +303,15 @@ fetch(url, options) 응답 본문을 얻으려면 다음과 같은 메서드를 사용하면 됩니다. - **`response.text()`** -- 응답을 텍스트 형태로 반환함 -- **`response.json()`** -- 응답을 파싱해 JSON 객체로 변경함 -- **`response.formData()`** -- 응답을 `FormData` 객체 형태로 반환(form/multipart 인코딩에 대한 내용은 다음 챕터에서 다룸) +- **`response.json()`** -- 응답을 파싱해 JSON 객체로 변환함 +- **`response.formData()`** -- 응답을 `FormData` 객체 형태로 반환(`multipart/form-data` 인코딩에 대한 내용은 다음 챕터에서 다룸) - **`response.blob()`** -- 응답을 [Blob](info:blob)(타입이 있는 바이너리 데이터) 형태로 반환 - **`response.arrayBuffer()`** -- 응답을 [ArrayBuffer](info:arraybuffer-binary-arrays)(바이너리 데이터를 로우 레벨로 표현한 것) 형태로 반환 지금까지 배운 `fetch` 옵션은 다음과 같습니다. - `method` -- HTTP 메서드 - `headers` -- 요청 헤드가 담긴 객체(제약 사항이 있음) -- `body` -- 보내려는 데이터(요청 본문)로 `string`이나 `FormData`, `BufferSource`, `Blob`, `UrlSearchParams` 객체 형태 +- `headers` -- 요청 헤더가 담긴 객체(제약 사항이 있음) +- `body` -- 보내려는 데이터(요청 본문)로 `string`이나 `FormData`, `BufferSource`, `Blob`, `URLSearchParams` 객체 형태 이어지는 챕터에선 이 외의 옵션과 다양한 `fetch` 유스 케이스를 살펴보겠습니다. diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md index 9e07d98408..f9ac977fa7 100644 --- a/5-network/02-formdata/article.md +++ b/5-network/02-formdata/article.md @@ -47,7 +47,7 @@ HTML에 `form` 요소가 있는 경우, 위와 같은 코드를 작성하면 해 ``` -요청을 받아 처리하는 서버 측 코드는 튜토리얼 범위를 넘어서서 추가하진 않았는데, 서버는 POST 요청을 받아 '저장 성공'이라는 응답을 보내준다고 정도만 알고 계시면 됩니다. +이 예시에서는 서버 코드가 튜토리얼 범위를 벗어나므로 제시하지 않습니다. 서버는 POST 요청을 받고 "사용자 정보 저장 성공"이라고 응답합니다. ## FormData 메서드 diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md index 2d003157d5..76b05d5149 100644 --- a/5-network/03-fetch-progress/article.md +++ b/5-network/03-fetch-progress/article.md @@ -5,11 +5,11 @@ The `fetch` method allows to track *download* progress. Please note: there's currently no way for `fetch` to track *upload* progress. For that purpose, please use [XMLHttpRequest](info:xmlhttprequest), we'll cover it later. -To track download progress, we can use `response.body` property. It's `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. +To track download progress, we can use `response.body` property. It's a `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment. -Here's the sketch of code that reads the reponse from `response.body`: +Here's the sketch of code that reads the response from `response.body`: ```js // instead of response.json() and other methods @@ -110,3 +110,5 @@ Let's explain that step-by-step: At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process. Once again, please note, that's not for *upload* progress (no way now with `fetch`), only for *download* progress. + +Also, if the size is unknown, we should check `receivedLength` in the loop and break it once it reaches a certain limit. So that the `chunks` won't overflow the memory. diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index 6548f81d27..0ed70d88e7 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -1,60 +1,60 @@ -# Fetch: Abort +# Fetch: 요청 중단하기 -As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we cancel an ongoing `fetch`? E.g. if the user actions on our site indicate that the `fetch` isn't needed any more. +`fetch`는 프라미스를 반환합니다. 그런데 자바스크립트에는 일반적으로 프라미스를 '중단'한다는 개념이 없습니다. 그렇다면 진행 중인 `fetch`는 어떻게 취소할 수 있을까요? 예를 들어 사이트에서 사용자 행동을 보고 더 이상 `fetch`가 필요 없다고 판단한 경우처럼 말이죠. -There's a special built-in object for such purposes: `AbortController`. It can be used to abort not only `fetch`, but other asynchronous tasks as well. +이런 목적을 위해 만들어진 특별한 내장 객체가 있습니다. 바로 `AbortController`입니다. `AbortController`를 사용하면 `fetch`뿐만 아니라 다른 비동기 작업도 중단할 수 있습니다. -The usage is very straightforward: +사용법은 아주 간단합니다. -## The AbortController object +## AbortController 객체 -Create a controller: +컨트롤러를 하나 만듭니다. ```js let controller = new AbortController(); ``` -A controller is an extremely simple object. +컨트롤러는 아주 단순한 객체입니다. -- It has a single method `abort()`, -- And a single property `signal` that allows to set event liseners on it. +- 메서드는 `abort()` 하나뿐입니다. +- 이벤트 리스너를 설정할 수 있는 프로퍼티도 `signal` 하나뿐입니다. -When `abort()` is called: -- `controller.signal` emits the `"abort"` event. -- `controller.signal.aborted` property becomes `true`. +`abort()`가 호출되면 다음 일이 일어납니다. +- `controller.signal`에서 `"abort"` 이벤트가 발생합니다. +- `controller.signal.aborted` 프로퍼티 값이 `true`가 됩니다. -Generally, we have two parties in the process: -1. The one that performs an cancelable operation, it sets a listener on `controller.signal`. -2. The one one that cancels: it calls `controller.abort()` when needed. +보통 이 과정에는 두 주체가 있습니다. +1. 취소 가능한 작업을 수행하는 쪽은 `controller.signal`에 리스너를 설정합니다. +2. 취소하는 쪽은 필요할 때 `controller.abort()`를 호출합니다. -Here's the full example (without `fetch` yet): +전체 예시를 살펴봅시다. 아직 `fetch`는 사용하지 않습니다. ```js run let controller = new AbortController(); let signal = controller.signal; -// The party that performs a cancelable operation -// gets "signal" object -// and sets the listener to trigger when controller.abort() is called -signal.addEventListener('abort', () => alert("abort!")); +// 취소 가능한 작업을 수행하는 쪽은 +// "signal" 객체를 받고, +// controller.abort()가 호출되었을 때 실행될 리스너를 설정합니다. +signal.addEventListener('abort', () => alert("중단!")); -// The other party, that cancels (at any point later): -controller.abort(); // abort! +// 취소하는 쪽은 나중에 언제든 다음을 호출합니다. +controller.abort(); // 중단! -// The event triggers and signal.aborted becomes true +// 이벤트가 발생하고 signal.aborted가 true가 됩니다. alert(signal.aborted); // true ``` -As we can see, `AbortController` is just a means to pass `abort` events when `abort()` is called on it. +예시에서 볼 수 있듯이 `AbortController`는 `abort()`가 호출되었을 때 `abort` 이벤트를 전달하는 수단입니다. -We could implement same kind of event listening in our code on our own, without `AbortController` object at all. +`AbortController` 객체 없이도 코드에서 이런 이벤트 리스닝 방식을 직접 구현할 수 있습니다. -But what's valuable is that `fetch` knows how to work with `AbortController` object, it's integrated with it. +하지만 중요한 점은 `fetch`가 `AbortController` 객체와 함께 동작하는 방법을 알고 있다는 것입니다. `fetch`와 `AbortController`는 통합되어 있습니다. -## Using with fetch +## fetch와 함께 사용하기 -To become able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: +`fetch`를 취소할 수 있게 하려면 `AbortController`의 `signal` 프로퍼티를 `fetch` 옵션으로 넘기면 됩니다. ```js let controller = new AbortController(); @@ -63,22 +63,22 @@ fetch(url, { }); ``` -The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`. +`fetch` 메서드는 `AbortController`와 함께 동작하는 방법을 알고 있습니다. `fetch`는 `signal`의 `abort` 이벤트를 감지합니다. -Now, to to abort, call `controller.abort()`: +이제 중단하려면 `controller.abort()`를 호출하면 됩니다. ```js controller.abort(); ``` -We're done: `fetch` gets the event from `signal` and aborts the request. +이게 전부입니다. `fetch`가 `signal`로부터 이벤트를 받아 요청을 중단합니다. -When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`. +`fetch`가 중단되면 해당 프라미스는 `AbortError` 에러와 함께 거부됩니다. 따라서 `try..catch` 등으로 이를 처리해야 합니다. -Here's the full example with `fetch` aborted after 1 second: +다음은 1초 후 `fetch`를 중단하는 전체 예시입니다. ```js run async -// abort in 1 second +// 1초 후 중단 let controller = new AbortController(); setTimeout(() => controller.abort(), 1000); @@ -87,62 +87,62 @@ try { signal: controller.signal }); } catch(err) { - if (err.name == 'AbortError') { // handle abort() - alert("Aborted!"); + if (err.name == 'AbortError') { // abort() 처리 + alert("중단되었습니다!"); } else { throw err; } } ``` -## AbortController is scalable +## AbortController는 확장성이 좋습니다 -`AbortController` is scalable, it allows to cancel multiple fetches at once. +`AbortController`는 확장성이 좋습니다. 여러 `fetch`를 한꺼번에 취소할 수 있습니다. -Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all: +다음은 여러 `url`을 병렬로 가져오고, 하나의 컨트롤러로 모든 요청을 중단하는 코드 예시입니다. ```js -let urls = [...]; // a list of urls to fetch in parallel +let urls = [...]; // 병렬로 가져올 url 목록 let controller = new AbortController(); -// an array of fetch promises +// fetch 프라미스 배열 let fetchJobs = urls.map(url => fetch(url, { signal: controller.signal })); let results = await Promise.all(fetchJobs); -// if controller.abort() is called from elsewhere, -// it aborts all fetches +// 어디서든 controller.abort()가 호출되면 +// 모든 fetch가 중단됩니다. ``` -If we have our own asynchronous tasks, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. +`fetch`와는 다른 자체 비동기 작업이 있다면 하나의 `AbortController`를 사용해 해당 작업과 `fetch`를 함께 멈출 수 있습니다. -We just need to listen to its `abort` event in our tasks: +작업 안에서 `abort` 이벤트를 감지하기만 하면 됩니다. ```js let urls = [...]; let controller = new AbortController(); -let ourJob = new Promise((resolve, reject) => { // our task +let ourJob = new Promise((resolve, reject) => { // 자체 작업 ... controller.signal.addEventListener('abort', reject); }); -let fetchJobs = urls.map(url => fetch(url, { // fetches +let fetchJobs = urls.map(url => fetch(url, { // fetch 작업들 signal: controller.signal })); -// Wait for fetches and our task in parallel +// fetch 작업과 자체 작업을 병렬로 기다립니다. let results = await Promise.all([...fetchJobs, ourJob]); -// if controller.abort() is called from elsewhere, -// it aborts all fetches and ourJob +// 어디서든 controller.abort()가 호출되면 +// 모든 fetch와 ourJob이 중단됩니다. ``` -## Summary +## 요약 -- `AbortController` is a simple object that generates `abort` event on it's `signal` property when `abort()` method is called (and also sets `signal.aborted` to `true`). -- `fetch` integrates with it: we pass `signal` property as the option, and then `fetch` listens to it, so it becomes possible to abort the `fetch`. -- We can use `AbortController` in our code. The "call `abort()`" -> "listen to `abort` event" interaction is simple and universal. We can use it even without `fetch`. +- `AbortController`는 `abort()` 메서드가 호출되면 `signal` 프로퍼티에서 `abort` 이벤트를 발생시키는 단순한 객체입니다. 이때 `signal.aborted`도 `true`로 설정됩니다. +- `fetch`는 `AbortController`와 통합되어 있습니다. `signal` 프로퍼티를 옵션으로 넘기면 `fetch`가 이를 감지하므로 `fetch`를 중단할 수 있습니다. +- `AbortController`는 일반 코드에서도 사용할 수 있습니다. `abort()` 호출과 `abort` 이벤트 감지로 이어지는 상호작용은 단순하고 범용적입니다. `fetch` 없이도 사용할 수 있습니다. diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index 8ff155a47d..b54d017a98 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -44,7 +44,7 @@ CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들 */!* - + *!* */!* @@ -123,9 +123,9 @@ CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들 표준이 아닌 헤더가 들어있거나 안전하지 않은 메서드(`DELETE` 등)를 사용한 요청은 안전한 요청이 될 수 없습니다. 아주 오래전에는 자바스크립트를 사용해 이런 요청을 보내는 것이 불가능 했습니다. 따라서 연식이 오래된 서버는 이런 요청을 받으면 '웹 페이지는 이런 요청을 보낼 수 없었기 때문에' 뭔가 특별한 곳에서 요청이 왔을거라 해석했었습니다. -그런데 시간이 지나고 개발자가 자바스크립트를 사용해 안전하지 않은 요청을 보낼 수 있게되자, 브라우저는 안전하지 않은 요청을 서버에 전송하기 전에 'preflight' 요청을 먼저 전송해 '서버가 크로스 오리진 요청을 받을 준비가 되어있는지를 확인'합니다. +시간이 흘러 개발자가 자바스크립트를 사용해 안전하지 않은 요청을 보낼 수 있게 되면서, 브라우저는 안전하지 않은 요청을 서버에 전송하기 전에 특별한 'preflight' 요청을 먼저 보냅니다. 이 요청은 '서버가 이런 크로스 오리진 요청을 허용할 의사'가 있는지 확인하는 역할을 합니다. -이때 서버에서 크로스 오리진 요청은 허용하지 않는다는 정보를 담은 헤더를 브라우저에 응답하면 안전하지 않은 요청은 서버로 전송되지 않습니다. +이때 서버가 헤더를 통해 명시적으로 크로스 오리진 요청에 대한 허용 의사를 밝히지 않으면, 안전하지 않은 요청은 전송되지 않습니다. 이제 개괄적인 설명이 끝났으니 CORS에 대해 좀 더 자세히 알아봅시다. @@ -169,6 +169,7 @@ Access-Control-Allow-Origin: https://javascript.info - `Cache-Control` - `Content-Language` +- `Content-Length` - `Content-Type` - `Expires` - `Last-Modified` @@ -176,13 +177,7 @@ Access-Control-Allow-Origin: https://javascript.info 이 외의 응답 헤더에 접근하면 에러가 발생합니다. -```smart -위 리스트에 `Content-Length` 헤더는 없습니다! - -`Content-Length`는 응답 본문 크기 정보를 담고 있는 헤더입니다. 무언가를 다운로드하는데, 다운로드가 몇 퍼센트나 진행되었는지 확인하려면 이 헤더에 접근할 수 있어야 합니다. 그런데 이 헤더에 접근하려면 특별한 권한이 필요합니다. 자세한 내용은 아래에서 다루겠습니다. -``` - -자바스크립트를 사용해 안전하지 않은 응답 헤더에 접근하려면 서버에서 `Access-Control-Expose-Headers`라는 헤더를 보내줘야만 합니다. `Access-Control-Expose-Headers`엔 자바스크립트 접근을 허용하는 안전하지 않은 헤더 목록이 담겨있습니다. 각 헤더는 콤마로 구분됩니다. +자바스크립트를 사용해 안전하지 않은 응답 헤더에 접근하려면 서버에서 `Access-Control-Expose-Headers`라는 헤더를 보내줘야만 합니다. `Access-Control-Expose-Headers`엔 자바스크립트 접근을 허용하는 안전하지 않은 헤더 목록이 담겨있습니다. 각 헤더는 쉼표로 구분됩니다. 예시: @@ -190,14 +185,15 @@ Access-Control-Allow-Origin: https://javascript.info 200 OK Content-Type:text/html; charset=UTF-8 Content-Length: 12345 +Content-Encoding: gzip API-Key: 2c9de507f2c54aa1 Access-Control-Allow-Origin: https://javascript.info *!* -Access-Control-Expose-Headers: Content-Length,API-Key +Access-Control-Expose-Headers: Content-Encoding,API-Key */!* ``` -이렇게 `Access-Control-Expose-Headers` 헤더가 응답 헤더에 있어야만 자바스크립트로 응답 헤더의 `Content-Length`와 `API-Key`를 읽을 수 있습니다. +이렇게 `Access-Control-Expose-Headers` 헤더가 응답 헤더에 있어야만 자바스크립트로 응답 헤더의 `Content-Encoding`과 `API-Key`를 읽을 수 있습니다. ## 안전하지 않은 요청 @@ -205,19 +201,20 @@ Access-Control-Expose-Headers: Content-Length,API-Key 그런데 과거엔 웹페이지에서 `GET`, `POST` 이외의 HTTP 메서드를 사용해 요청을 보낼 수 있을거란 상상조차 할 수 없었습니다. 아직까지도 이런 메서드를 다룰 수 없는 웹서버도 꽤 있죠. 이런 서버들은 `GET`, `POST` 이외의 메서드를 사용한 요청이 오면 '이건 브라우저가 보낸 요청이 아니야'라고 판단하고 접근 권한을 확인합니다. -이런 혼란스러운 상황을 피하고자 브라우저는 '안전하지 않은' 요청이 이뤄지는 경우, 서버에 바로 요청을 보내지 않고 'preflight' 요청이라는 사전 요청을 서버에 보내 권한이 있는지를 확인합니다. +이런 혼란스러운 상황을 방지하기 위해 브라우저는 '안전하지 않은' 요청이 이뤄지는 경우, 서버에 바로 요청을 보내지 않고 먼저 'preflight' 요청이라는 사전 요청을 서버에 보내 허용 여부를 확인합니다. -preflight 요청은 `OPTIONS` 메서드를 사용하고 두 헤더가 함께 들어가며, 본문은 비어있습니다. +preflight 요청은 `OPTIONS` 메서드를 사용하고 본문은 비어있습니다. 요청에는 다음 세 헤더가 들어갑니다. - `Access-Control-Request-Method` 헤더 -- 안전하지 않은 요청에서 사용하는 메서드 정보가 담겨있습니다. -- `Access-Control-Request-Headers` 헤더 -- 안전하지 않은 요청에서 사용하는 헤더 목록이 담겨있습니다. 각 헤더는 쉼표로 구분됩니다. +- `Access-Control-Request-Headers` 헤더 -- 안전하지 않은 HTTP 헤더 목록을 쉼표로 구분해 전달합니다. +- `Origin` 헤더 -- 요청이 어디에서 왔는지 알려줍니다. (`https://javascript.info` 등) 안전하지 않은 요청을 허용하기로 협의하였다면 서버는 본문이 비어있고 상태 코드가 200인 응답을 다음과 같은 헤더와 함께 브라우저로 보냅니다. -- `Access-Control-Allow-Origin` -- `*`이나 요청을 보낸 오리진 이어야 합니다(예: https://javascript.info) +- `Access-Control-Allow-Origin` -- 허용하려면 `*` 또는 `https://javascript.info` 같은 요청 오리진이어야 합니다. - `Access-Control-Allow-Methods` -- 허용된 메서드 정보가 담겨있습니다. - `Access-Control-Allow-Headers` -- 허용된 헤더 목록이 담겨있습니다. -- `Access-Control-Max-Age` -- 퍼미션 체크 여부를 몇 초간 캐싱해 놓을지를 명시합니다. 이렇게 퍼미션 정보를 캐싱해 놓으면 브라우저는 일정 기간 동안 preflight 요청을 생략하고 안전하지 않은 요청을 보낼 수 있습니다. +- `Access-Control-Max-Age` -- 허용 정보를 몇 초 동안 캐싱할지를 명시합니다. 이렇게 허용 정보를 캐싱해 놓으면 브라우저는 일정 기간 동안 preflight 요청을 생략하고 안전하지 않은 요청을 보낼 수 있습니다. ![](xhr-preflight.svg) @@ -235,8 +232,8 @@ let response = await fetch('https://site.com/service.json', { 참고로 위 요청이 안전하지 않은 요청으로 분류되는 데는 세 가지 이유가 있습니다. - `PATCH` 메서드를 사용하고 있습니다. -- `Content-Type`이 `application/x-www-form-urlencoded`나 `multipart/form-data`, `text/plain`가 아닙니다. -- 비표준 헤더 `API-Key`를 사용합니다. +- `Content-Type`이 `application/x-www-form-urlencoded`나 `multipart/form-data`, `text/plain`이 아닙니다. +- 안전하지 않은 헤더 `API-Key`를 사용합니다. ### 1단계(preflight 요청) @@ -255,19 +252,20 @@ Access-Control-Request-Headers: Content-Type,API-Key - 크로스 오리진 특수 헤더: - `Origin` -- 본 요청의 오리진 - `Access-Control-Request-Method` -- 본 요청에서 사용하는 메서드 - - `Access-Control-Request-Headers` -- 본 요청에서 사용하는 안전하지 않은 헤더 목록(콤마로 구분) + - `Access-Control-Request-Headers` -- 본 요청에서 사용하는 안전하지 않은 헤더 목록(쉼표로 구분) ### 2단계(preflight 응답) -서버는 상태 코드 200과 함께 다음과 같은 헤더를 담은 응답을 보냅니다. +서버는 상태 코드 200과 함께 다음 헤더를 담은 응답을 보내야 합니다. +- `Access-Control-Allow-Origin: https://javascript.info` - `Access-Control-Allow-Methods: PATCH` - `Access-Control-Allow-Headers: Content-Type,API-Key` 이렇게 응답이 와야 비로소 본 요청을 보낼 수 있습니다. 그렇지 않으면 에러가 발생합니다. -미래에 `PATCH` 이외의 메서드와 다양한 헤더를 허용할 수 있도록 세팅하려면 `Access-Control-Allow-Methods`와 `Access-Control-Allow-Headers`에 원하는 메서드와 헤더를 추가해 놓으면 됩니다. +서버가 앞으로 다른 메서드나 헤더를 받을 가능성이 있다면, 미리 목록에 추가해두는 것이 좋습니다. -아래와 같이 말이죠. +아래 응답은 `PUT`, `DELETE`와 추가 헤더도 함께 허용하는 예시입니다. ```http 200 OK @@ -279,11 +277,11 @@ Access-Control-Max-Age: 86400 이렇게 서버에서 preflight 응답이 오면 브라우저는 `Access-Control-Allow-Methods`에 `PATCH`가 있는 것을 확인하고, 이어서 `Access-Control-Allow-Headers`에 `Content-Type`과 `API-Key`가 있는 것을 확인합니다. 둘 다 있는 것을 확인했기 때문에 이제 브라우저는 본 요청을 서버에 보냅니다. -참고로 `Access-Control-Max-Age` 헤더가 응답으로 오면 preflight 허용 여부가 헤더와 함께 캐싱되기 때문에 브라우저는 헤더 값에 명시한 초동안 preflight 요청을 보내지 않습니다. 따라서 위 예시처럼 응답이 온 경우엔 하루동안 preflight 요청을 전송하지 않고 바로 본 요청이 전송됩니다. +참고로 `Access-Control-Max-Age` 헤더가 응답으로 오면 preflight 허용 여부가 헤더와 함께 캐싱되기 때문에 브라우저는 헤더 값에 명시한 초 동안 preflight 요청을 보내지 않습니다. 따라서 위 예시처럼 응답이 온 경우엔 하루 동안 preflight 요청을 전송하지 않고 바로 본 요청을 전송합니다. ### 3단계(실제 요청) -preflight 요청이 성공적으로 이뤄진 후에야 브라우저는 본 요청을 보냅니다. 지금부터의 프로세스는 안전한 요청이 이뤄질때의 절차와 동일합니다. +preflight 요청이 성공적으로 이뤄진 후에야 브라우저는 본 요청을 보냅니다. 지금부터의 절차는 안전한 요청이 이뤄질 때의 절차와 동일합니다. 본 요청은 크로스 오리진 요청이기 때문에 `Origin` 헤더가 붙습니다. @@ -315,7 +313,7 @@ preflight 요청은 '무대 밖에서' 일어나기 때문에 자바스크립트 자바스크립트로 크로스 오리진 요청을 보내는 경우, 기본적으로 쿠키나 HTTP 인증 같은 자격 증명(credential)이 함께 전송되지 않습니다. -HTTP 요청의 경우 대개 쿠키가 함께 전송되는데, 자바스크립트를 사용해 만든 크로스 오리진 요청은 예외이죠. +HTTP 요청에서 보통은 `http://site.com`으로 요청을 보내면 해당 도메인의 쿠키가 함께 전송됩니다. 하지만 자바스크립트를 사용해 만든 cross-origin 요청은 예외적으로 쿠키가 포함되지 않습니다. 따라서 `fetch('http://another.com')`를 사용해 요청을 보내도 `another.com` 관련 쿠키가 함께 전송되지 않습니다. @@ -352,16 +350,16 @@ Access-Control-Allow-Credentials: true 브라우저 관점에선 크로스 오리진 요청은 안전한(safe) 크로스 오리진 요청과 그렇지 않은 크로스 오리진 요청 두 분류로 나뉩니다. 안전한 요청은 다음 조건을 모두 충족하는 요청입니다. -- 메서드: GET이나 POST 혹은 HEAD -- 헤더: +- 메서드: GET, POST, HEAD 중 하나 +- 헤더 -- 다음만 설정 가능 - `Accept` - `Accept-Language` - `Content-Language` - 값이 `application/x-www-form-urlencoded`나 `multipart/form-data`, `text/plain`인 `Content-Type` -안전한 요청은 아주 오래전 부터 ``이나 ` ``` +<<<<<<< HEAD 이제 본격적으로 각 프로퍼티를 살펴봅시다. ## transition-property @@ -89,6 +104,27 @@ growing.onclick = function() { `transition-delay`엔 음수 값도 넣을 수 있습니다. 값이 음수일 땐 애니메이션 효과가 중간부터 나타납니다. `transition-duration`을 `2s`, 지연 시간을 `-1s`로 설정하면 애니메이션 효과는 1초가 지난 후 1초 동안 지속됩니다. CSS `translate` 프로퍼티와 지금까지 배운 내용을 사용해 `0`부터 `9`까지가 화면에 자연스럽게 나타나도록 해봅시다. +======= +Now, let's cover animation properties one by one. + +## transition-property + +In `transition-property`, we write a list of properties to animate, for instance: `left`, `margin-left`, `height`, `color`. Or we could write `all`, which means "animate all properties". + +Do note that, there are properties which can not be animated. However, [most of the generally used properties are animatable](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties). + +## transition-duration + +In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](https://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`. + +## transition-delay + +In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is `2s`, then the animation starts 1 second after the property change and the total duration will be 2 seconds. + +Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second. + +Here the animation shifts numbers from `0` to `9` using CSS `translate` property: +>>>>>>> upstream/master [codetabs src="digits"] @@ -108,13 +144,21 @@ CSS `translate` 프로퍼티와 지금까지 배운 내용을 사용해 `0`부 stripe.classList.add('animate'); ``` +<<<<<<< HEAD 이번엔 `transition-delay`에 음수를 써서 예시를 약간 변형해봅시다. 현재 시각을 기준으로 '초'를 추출하고, 이 값에 마이너스 기호를 붙여서 `transition-delay` 값으로 지정하면 현재 초를 기준으로 숫자가 나타나고, 애니메이션 효과가 적용되는 것을 확인할 수 있습니다. +======= +We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative `transition-delay`. +>>>>>>> upstream/master 직접 숫자를 클릭해보세요. 현재 날짜가 2020년 9월 12일 오후 12시 17분 8초라면, 숫자 8부터 스르륵 이동합니다. [codetabs src="digits-negative-delay"] +<<<<<<< HEAD 사용한 코드는 아래와 같습니다. +======= +JavaScript does it with an extra line: +>>>>>>> upstream/master ```js stripe.onclick = function() { @@ -129,26 +173,49 @@ stripe.onclick = function() { ## transition-timing-function +<<<<<<< HEAD `transition-timing-function` 프로퍼티를 사용해 timing 함수를 만들면 시간에 따라 애니메이션 효과를 어떻게 분배할지를 설정할 수 있습니다. 애니메이션 효과가 초반엔 천천히 나타나다가 나중엔 빠르게 나타나게 할 수 있고, 물론 이 반대도 가능합니다. 처음 이 프로퍼티를 접하면 애니메이션 프로퍼티 중 가장 복잡한 프로퍼티 같다고 느낄 수 있습니다. 그렇지만 시간을 조금 투자해 학습하면 매우 간단한 프로퍼티라는 생각이 들 겁니다. `transition-timing-function` 프로퍼티 값엔 베지어 곡선(bezier curve)이나 단계(step)가 올 수 있습니다. 먼저 사용 빈도가 높은 베지어 곡선부터 알아봅시다. +======= +The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa. + +It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it. + +That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often. +>>>>>>> upstream/master ### 베지어 곡선 +<<<<<<< HEAD `transition-timing-function`엔 조절점이 4개면서 아래 조건을 만족하는 [베지어 곡선](/bezier-curve)을 지정할 수 있습니다. 1. 첫 번째 조절점: `(0,0)` 2. 마지막 조절점: `(1,1)` 3. 중간 조절점들: `x`가 `0`과 `1` 사이에 있어야 함. `y`엔 제약이 없음 +======= +The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfy the conditions: + +1. First control point: `(0,0)`. +2. Last control point: `(1,1)`. +3. For intermediate points, the values of `x` must be in the interval `0..1`, `y` can be anything. +>>>>>>> upstream/master CSS에선 베지어 곡선을 `cubic-bezier(x2, y2, x3, y3)`형태로 정의합니다. 규칙에 따라 첫 번째 조절점은 `(0,0)`, 네 번째 조절점은 `(1,1)`로 고정되므로 두 번째와 세 번째 조절점만 설정하면 됩니다. +<<<<<<< HEAD 조절점을 변경해 만든 베지어 곡선을 사용해 정의한 timing 함수는 시간이 지남에 따라 얼마나 빠르게 애니메이션 효과가 나타나게 할지를 보여줍니다. - `x`축은 시간이 됩니다. `0`은 애니메이션 시작하는 시간, `1`은 끝나는 시간을 나타냅니다. - `y`축은 프로세스 완성 정도를 나타냅니다. `0`은 프로퍼티 시작 값, `1`은 최종값을 나타냅니다. +======= +The timing function describes how fast the animation process goes. + +- The `x` axis is the time: `0` -- the start, `1` -- the end of `transition-duration`. +- The `y` axis specifies the completion of the process: `0` -- the starting value of the property, `1` -- the final value. +>>>>>>> upstream/master 가장 간단한 timing 함수를 적용하면 애니메이션 효과가 일정한 속도로 나타납니다. `cubic-bezier(0, 0, 1, 1)`를 사용해 기울기가 1인 직선형 함수를 만들어봅시다. @@ -168,7 +235,11 @@ CSS에선 베지어 곡선을 `cubic-bezier(x2, y2, x3, y3)`형태로 정의합 .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); +<<<<<<< HEAD /* left 속성값(450px)은 자바스크립트에서 설정함 */ +======= + /* click on a train sets left to 450px, thus triggering the animation */ +>>>>>>> upstream/master } ``` @@ -191,13 +262,21 @@ CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); +<<<<<<< HEAD /* left 속성값(450px)은 자바스크립트에서 설정함 */ +======= + /* click on a train sets left to 450px, thus triggering the animation */ +>>>>>>> upstream/master } ``` 직접 베지어 곡선을 만드는 것 말고, 내장 곡선을 사용할 수도 있습니다. 내장 곡선엔 `linear`, `ease`, `ease-in`, `ease-out`, `ease-in-out` 등이 있습니다. +<<<<<<< HEAD `linear`는 위에서 본 직선 형태의 timing 함수를 쓸 때 사용된 `cubic-bezier(0, 0, 1, 1)`와 동일한 효과를 줍니다. +======= +The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, which we described above. +>>>>>>> upstream/master 각 내장 곡선에 대응하는 베지어 곡선은 다음과 같습니다. @@ -210,27 +289,39 @@ CSS: 이제 `ease-out`을 사용해 기차가 점점 느려지도록 해봅시다. - ```css .train { left: 0; transition: left 5s ease-out; - /* transition: left 5s cubic-bezier(0, .5, .5, 1); */ + /* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */ } ``` 베지어 곡선 `cubic-bezier(0.0, 0.5, 0.5 ,1.0)`을 사용한 것처럼 기차가 점점 느려지긴 하지만 프로세스 정도가 조금 다른 것을 확인할 수 있습니다. +<<<<<<< HEAD 한편, **베지어 곡선을 사용하면 애니메이션이 지정한 범위를 '넘어서' 적용되게 할 수 있습니다.** 베지어 곡선에서 중간 조절점의 `y` 좌표는 제약이 없습니다. 음수 또는 매우 큰 값도 가능하죠. 그런데 조절점의 `y` 좌표가 음수 또는 큰 값일 때 베지어 곡선은 매우 낮거나 높게 그려집니다. 이러면 애니메이션이 정상 범위를 벗어납니다. 아래쪽 기차 예시에 다음과 같은 css를 적용했다고 가정해봅시다. +======= +**A Bezier curve can make the animation exceed its range.** + +The control points on the curve can have any `y` coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range. + +In the example below the animation code is: + +>>>>>>> upstream/master ```css .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); +<<<<<<< HEAD /* left 속성값(400px)은 자바스크립트에서 설정함 */ +======= + /* click on a train sets left to 450px */ +>>>>>>> upstream/master } ``` @@ -244,6 +335,7 @@ CSS: [codetabs src="train-over"] +<<<<<<< HEAD 왜 이렇게 이동하는지는 아래의 베지어 곡선 그래프를 보면 아주 명확히 알 수 있습니다. ![](bezier-train-over.svg) @@ -251,14 +343,39 @@ CSS: 두 번째 조절점의 `y` 좌표가 `0`보다 작아지고, 세 번째 조절점의 좌표가 `1`보다 커지면서 곡선이 '정상' 범위를 벗어난 것을 확인할 수 있습니다. '정상' 범위인 `0..1`을 벗어난 것이죠. 아시다시피, timing 함수의 `y`축은 '애니메이션 프로세스의 완성도'를 나타냅니다. `y`가 `0`일 땐 프로퍼티 시작 값을, `y`가 `1`일 땐 프로세스 종료 값을 의미하죠. 그렇기 때문에 `y`가 `0`보다 작아지면 `left` 프로퍼티가 시작 값인 100px 보다 낮게 설정되고, `y`가 `1`보다 커지면 `left` 프로퍼티가 끝값인 400px보다 커지게 됩니다. +======= +Why it happens is pretty obvious if we look at the graph of the given Bezier curve: + +![](bezier-train-over.svg) + +We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. + +As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property beyond the starting `left` and `y>1` -- past the final `left`. +>>>>>>> upstream/master 이 예시에선 `y` 값이 정상 범위를 크게 벗어나지 않기 때문에 '부드럽게' 변형이 일어납니다. 그런데 `y` 값이 `-99`나 `99`가 되면 기차가 앞·뒤로 아주 크게 움직이겠죠? +<<<<<<< HEAD 이쯤 되면 원하는 애니메이션 효과를 줄 수 있는 베지어 곡선은 어떻게 만들 수 있을지 의문이 들 겁니다. 베지어 곡선을 만들어주는 툴은 다양한데, 에서도 가능합니다. +======= +But how do we make a Bezier curve for a specific task? There are many tools. + +- For instance, we can do it on the site . +- Browser developer tools also have special support for Bezier curves in CSS: + 1. Open the developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). + 2. Select the `Elements` tab, then pay attention to the `Styles` sub-panel at the right side. + 3. CSS properties with a word `cubic-bezier` will have an icon before this word. + 4. Click this icon to edit the curve. + +>>>>>>> upstream/master ### 단계 +<<<<<<< HEAD timing 함수 `steps(number of steps[, start/end])`를 사용하면 애니메이션을 여러 단계(step)로 나눌 수 있습니다. +======= +The timing function `steps(number of steps[, start/end])` allows splitting an transition into multiple steps. +>>>>>>> upstream/master 숫자를 사용해 예시를 만들어봅시다. @@ -266,7 +383,23 @@ timing 함수 `steps(number of steps[, start/end])`를 사용하면 애니메이 [codetabs src="step-list"] +<<<<<<< HEAD 이제 애니메이션을 적용해 빨간 '박스' 밖에 있는 숫자들은 숨기고, 단계별로 숫자 목록을 왼쪽으로 이동 시켜, 사용자 눈엔 숫자가 하나씩 증가하는 것처럼 보이게 합시다. +======= +In the HTML, a stripe of digits is enclosed into a fixed-length `
`: + +```html +
+
0123456789
+
+``` + +The `#digit` div has a fixed width and a border, so it looks like a red window. + +We'll make a timer: the digits will appear one by one, in a discrete way. + +To achieve that, we'll hide the `#stripe` outside of `#digit` using `overflow: hidden`, and then shift the `#stripe` to the left step-by-step. +>>>>>>> upstream/master 9까지 숫자를 증가시켜야 하므로 단계는 총 9개가 됩니다. @@ -277,23 +410,38 @@ timing 함수 `steps(number of steps[, start/end])`를 사용하면 애니메이 } ``` +<<<<<<< HEAD 숫자를 클릭해 원하는 대로 동작하는지 확인해봅시다. [codetabs src="step"] timing 함수 `steps(9, start)`에서 첫 번째 인수는 단계의 수이므로 전체 프로세스가 9단계로 나눠 적용되 10%씩 진행되는 것을 확인할 수 있습니다. 이때 시간 간격도 자동으로 9단계로 나뉘기 때문에 1초 간격으로 숫자가 증가하게 됩니다. +======= +The first argument of `steps(9, start)` is the number of steps. The transform will be split into 9 parts (10% each). The time interval is automatically divided into 9 parts as well, so `transition: 9s` gives us 9 seconds for the whole animation – 1 second per digit. +>>>>>>> upstream/master 두 번째 인수는 `start`나 `end` 중 하나입니다. +<<<<<<< HEAD 두 번째 인수가 `start`인 경우엔 애니메이션이 첫 번째 단계부터 바로 시작됩니다. 숫자를 클릭하면 숫자가 `1`로 바로 바뀌고(첫 번째 단계), 1초 후에 다음 숫자(다음 단계)로 바뀌는 것을 확인할 수 있습니다. +======= +The `start` means that in the beginning of animation we need to make the first step immediately. + +In action: + +[codetabs src="step"] + +A click on the digit changes it to `1` (the first step) immediately, and then changes in the beginning of the next second. +>>>>>>> upstream/master 초별로 프로세스가 몇 %씩 진행되는지를 정리하면 다음과 같죠. - `0s` -- `-10%` (애니메이션이 시작하자마자 바로 첫 번째 단계가 수행됨) - `1s` -- `-20%` - ... +<<<<<<< HEAD - `8s` -- `-80%` - (마지막 초에 최종값이 나타남) @@ -303,32 +451,69 @@ timing 함수 `steps(9, start)`에서 첫 번째 인수는 단계의 수이므 - `0s` -- `0` - `1s` -- `-10%` (1초가 지난 후에 첫 단계가 수행됨) +======= +- `8s` -- `-90%` +- (the last second shows the final value). + +Here, the first change was immediate because of `start` in the `steps`. + +The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second. + +So the process for `steps(9, end)` would go like this: + +- `0s` -- `0` (during the first second nothing changes) +- `1s` -- `-10%` (first change at the end of the 1st second) +>>>>>>> upstream/master - `2s` -- `-20%` - ... - `9s` -- `-90%` +<<<<<<< HEAD 두 번째 인수를 변화 시켜(`steps(9, end)`) 실제 어떻게 애니메이션이 변화하는지 확인해봅시다. [codetabs src="step-end"] 참고로 다음과 같은 내장 값을 사용해 timing 함수를 지정할 수도 있습니다. +======= +Here's `steps(9, end)` in action (note the pause before the first digit change): + +[codetabs src="step-end"] + +There are also some pre-defined shorthands for `steps(...)`: +>>>>>>> upstream/master - `step-start` -- `steps(1, start)`와 같습니다. 따라서 애니메이션의 첫 단계가 바로 시작되고 첫 번째 단계만 실행됩니다. 마치 애니메이션 효과가 없는 것처럼 보입니다. - `step-end` -- `steps(1, end)`와 같습니다. `transition-duration`의 끝에 애니메이션이 한 단계만 진행됩니다. +<<<<<<< HEAD 위 값들을 사용하면 움직이는 효과를 볼 수 없기 때문에 거의 사용되지 않습니다. ## transitionend 이벤트 CSS 애니메이션이 끝나면 `transitionend` 이벤트가 자동으로 트리거됩니다. +======= +These values are rarely used, as they represent not a real animation, but rather a single-step change. We mention them here for completeness. + +## Event: "transitionend" + +When the CSS animation finishes, the `transitionend` event triggers. +>>>>>>> upstream/master `transitionend` 이벤트는 애니메이션이 끝났을 때 무언가를 하고 싶은 경우 많이 사용됩니다. 애니메이션 여러 개를 조합할 때도 자주 쓰이죠. +<<<<<<< HEAD 예를 들어봅시다. 아래 예시에서 배를 클릭하면 배가 오른쪽, 왼쪽으로 움직이는데 한번 왕복할 때마다 오른쪽으로 더 멀리 이동합니다. [iframe src="boat" height=300 edit link] 트랜지션이 종료될 때마다 방향을 뒤집는 함수 `go`가 다시 실행되면서 새로운 애니메이션이 시작되는 것이죠. +======= +For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right: + +[iframe src="boat" height=300 edit link] + +The animation is initiated by the function `go` that re-runs each time the transition finishes, and flips the direction: +>>>>>>> upstream/master ```js boat.onclick = function() { @@ -337,11 +522,19 @@ boat.onclick = function() { function go() { if (times % 2) { +<<<<<<< HEAD // 오른쪽으로 가기 boat.classList.remove('back'); boat.style.marginLeft = 100 * times + 200 + 'px'; } else { // 왼쪽으로 가기 +======= + // sail to the right + boat.classList.remove('back'); + boat.style.marginLeft = 100 * times + 200 + 'px'; + } else { + // sail to the left +>>>>>>> upstream/master boat.classList.add('back'); boat.style.marginLeft = 100 * times - 200 + 'px'; } @@ -357,7 +550,11 @@ boat.onclick = function() { }; ``` +<<<<<<< HEAD 트렌지션과 관련된 이벤트는 몇 가지 특수 프로퍼티를 지원합니다. +======= +The event object for `transitionend` has a few specific properties: +>>>>>>> upstream/master `event.propertyName` : 애니메이션이 완료된 프로퍼티로, 동시에 여러 개의 프로퍼티에 애니메이션 효과를 줄 때 사용할 수 있습니다. @@ -369,7 +566,11 @@ boat.onclick = function() { CSS 문법인 `@keyframes`을 사용하면 간단한 애니메이션 여러 개를 한꺼번에 실행시킬 수 있습니다. +<<<<<<< HEAD `@keyframes`엔 애니메이션 '이름'과 무엇을, 언제, 어디서 움직일지를 설정할 수 있습니다. `@keyframes`에 적절한 값을 넣은 후엔 `animation` 프로퍼티를 사용해 원하는 요소에 커스텀 애니메이션을 적용할 수 있습니다. 물론 추가 매개변수도 지정할 수 있습니다. +======= +It specifies the "name" of the animation and rules - what, when and where to animate. Then using the `animation` property, we can attach the animation to the element and specify additional parameters for it. +>>>>>>> upstream/master 주석에 설명을 달아놓은 예시를 살펴봅시다. @@ -405,17 +606,107 @@ CSS 문법인 `@keyframes`을 사용하면 간단한 애니메이션 여러 개 `@keyframes`을 다루는 글이나 [명세서](https://drafts.csswg.org/css-animations/)를 읽으면 더 많은 정보를 찾을 수 있으니 참고해보시기 바랍니다. +<<<<<<< HEAD 참고로 사이트에 있는 특정 요소를 계속 정적인 형태로 움직이지 않는 한은 `@keyframes`를 쓸 일은 많지 않을 겁니다. +======= +You probably won't need `@keyframes` often, unless everything is in constant motion on your sites. + +## Performance + +Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. + +However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change. + +In more technical details, when there's a style change, the browser goes through 3 steps to render the new look: + +1. **Layout**: re-compute the geometry and position of each element, then +2. **Paint**: re-compute how everything should look like at their places, including background, colors, +3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist. + +During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at . + +The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations. + +Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too. + +The `transform` property is a great choice, because: +- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it). +- CSS transforms never affect neighbour elements. + +...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage. + +In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it. + +Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient. + +Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(…)`, use `transform: scale` for increasing element size, etc. + +The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects. + +Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations. + +For example, here clicking on the `#boat` element adds the class with `transform: translateX(300px)` and `opacity: 0`, thus making it move `300px` to the right and disappear: + +```html run height=260 autorun no-beautify + + + + +``` + +Here's a more complex example, with `@keyframes`: + +```html run height=80 autorun no-beautify +

click me to start / stop

+ +``` +>>>>>>> upstream/master ## 요약 +<<<<<<< HEAD CSS 애니메이션을 사용하면 하나 또는 여러 CSS 프로퍼티를 부드럽게(부드럽지 않게도 가능) 변화시킬 수 있습니다. +======= +CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. +>>>>>>> upstream/master CSS 애니메이션은 전환이 필요한 대다수의 경우에 큰 도움을 줍니다. 자바스크립트를 사용해도 전환 효과를 줄 수 있긴 한데 이에 대해선 다음 챕터에서 다룰 예정입니다. 참고로 CSS 애니메이션은 자바스크립트 애니메이션과 비교해 다음과 같은 장단점이 있습니다. ```compare plus="CSS animations" minus="JavaScript animations" +<<<<<<< HEAD + 간단한 애니메이션을 간단히 수행함 + 빠르고 CPU를 많이 소모하지 않음 - 자바스크립트 애니메이션보다 덜 유연함. 요소의 '폭발' 같은 특수한 애니메이션 로직을 구현할 수 없음 @@ -423,5 +714,16 @@ CSS 애니메이션은 전환이 필요한 대다수의 경우에 큰 도움을 ``` 사실 대부분의 애니메이션은 이번 챕터에 설명한 CSS 프로퍼티를 사용해 구현할 수 있습니다. 여기에 더하여 `transitionend` 이벤트를 사용해 애니메이션이 끝난 후에 실행시킬 자바스크립트 코드를 지정할 수도 있죠. +======= ++ Simple things done simply. ++ Fast and lightweight for CPU. +- JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element. +- Not just property changes. We can create new elements in JavaScript as part of the animation. +``` + +In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance. + +The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code. +>>>>>>> upstream/master 하지만 좀 더 복잡한 케이스를 다루려면 자바스크립트 애니메이션을 알아야 하므로 다음 챕터에선 이를 다뤄보겠습니다. diff --git a/7-animation/3-js-animation/1-animate-ball/solution.md b/7-animation/3-js-animation/1-animate-ball/solution.md index 5d3f08eefb..0dc67b8bd7 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.md +++ b/7-animation/3-js-animation/1-animate-ball/solution.md @@ -2,7 +2,7 @@ To bounce we can use CSS property `top` and `position:absolute` for the ball ins The bottom coordinate of the field is `field.clientHeight`. The CSS `top` property refers to the upper edge of the ball. So it should go from `0` till `field.clientHeight - ball.clientHeight`, that's the final lowest position of the upper edge of the ball. -To to get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. +To get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. Here's the final code for the animation: diff --git a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html index 7e031e8d13..146033cf72 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html +++ b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html index b246f422f0..f587ff607a 100644 --- a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html +++ b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/article.md b/7-animation/3-js-animation/article.md index 0049543401..b85e91e216 100644 --- a/7-animation/3-js-animation/article.md +++ b/7-animation/3-js-animation/article.md @@ -77,9 +77,9 @@ setInterval(animate3, 20); These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load less CPU load and look smoother. -There's one more thing to keep in mind. Sometimes when CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. +There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. -But how do we know about that in JavaScript? There's a specification [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. +But how do we know about that in JavaScript? There's a specification [Animation timing](https://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. The syntax: ```js @@ -96,7 +96,7 @@ The returned value `requestId` can be used to cancel the call: cancelAnimationFrame(requestId); ``` -The `callback` gets one argument -- the time passed from the beginning of the page load in microseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now). +The `callback` gets one argument -- the time passed from the beginning of the page load in milliseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now). Usually `callback` runs very soon, unless the CPU is overloaded or the laptop battery is almost discharged, or there's another reason. @@ -159,7 +159,7 @@ Function `animate` accepts 3 parameters that essentially describes the animation } ``` - It's graph: + Its graph: ![](linear.svg) That's just like `transition-timing-function: linear`. There are more interesting variants shown below. @@ -227,7 +227,7 @@ See in action (click to activate): [iframe height=40 src="quad" link] -...Or the cubic curve or event greater `n`. Increasing the power makes it speed up faster. +...Or the cubic curve or even greater `n`. Increasing the power makes it speed up faster. Here's the graph for `progress` in the power `5`: @@ -283,7 +283,7 @@ The `bounce` function does the same, but in the reverse order: "bouncing" starts ```js function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } @@ -397,7 +397,7 @@ The effect is clearly seen if we compare the graphs of `easeIn`, `easeOut` and ` ![](circ-ease.svg) -- Red is the regular variantof `circ` (`easeIn`). +- Red is the regular variant of `circ` (`easeIn`). - Green -- `easeOut`. - Blue -- `easeInOut`. @@ -405,7 +405,7 @@ As we can see, the graph of the first half of the animation is the scaled down ` ## More interesting "draw" -Instead of moving the element we can do something else. All we need is to write the write the proper `draw`. +Instead of moving the element we can do something else. All we need is to write the proper `draw`. Here's the animated "bouncing" text typing: @@ -452,4 +452,4 @@ Surely we could improve it, add more bells and whistles, but JavaScript animatio JavaScript animations can use any timing function. We covered a lot of examples and transformations to make them even more versatile. Unlike CSS, we are not limited to Bezier curves here. -The same is about `draw`: we can animate anything, not just CSS properties. +The same is true about `draw`: we can animate anything, not just CSS properties. diff --git a/7-animation/3-js-animation/bounce-easeinout.view/index.html b/7-animation/3-js-animation/bounce-easeinout.view/index.html index 837c50db1c..aed3d9d081 100644 --- a/7-animation/3-js-animation/bounce-easeinout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeinout.view/index.html @@ -26,7 +26,7 @@ function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce-easeout.view/index.html b/7-animation/3-js-animation/bounce-easeout.view/index.html index e52eae8deb..69dbb7ce08 100644 --- a/7-animation/3-js-animation/bounce-easeout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeout.view/index.html @@ -22,7 +22,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce.view/index.html b/7-animation/3-js-animation/bounce.view/index.html index 1be2580d98..3575ed8205 100644 --- a/7-animation/3-js-animation/bounce.view/index.html +++ b/7-animation/3-js-animation/bounce.view/index.html @@ -19,7 +19,7 @@ animate({ duration: 3000, timing: function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/text.view/index.html b/7-animation/3-js-animation/text.view/index.html index e404fe5c40..4947e4cd4a 100644 --- a/7-animation/3-js-animation/text.view/index.html +++ b/7-animation/3-js-animation/text.view/index.html @@ -29,14 +29,14 @@ timing: bounce, draw: function(progress) { let result = (to - from) * progress + from; - textArea.value = text.substr(0, Math.ceil(result)) + textArea.value = text.slice(0, Math.ceil(result)) } }); } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/8-web-components/1-webcomponents-intro/article.md b/8-web-components/1-webcomponents-intro/article.md index 37a5d71565..c8080ed965 100644 --- a/8-web-components/1-webcomponents-intro/article.md +++ b/8-web-components/1-webcomponents-intro/article.md @@ -26,9 +26,15 @@ 그리고 우주정거장은 인간이 우주에서 날고, 살아있을 수 있게 해줍니다. +<<<<<<< HEAD 어떻게 이렇게 복잡한 장치가 만들어졌을까요? 개발을 안정적이고 확장 가능한 수준으로 만들기 위해 또는 그에 가깝게 만들기 위해 어떤 원칙을 빌려올 수 있을까요? +======= +How are such complex devices created? + +Which principles could we borrow to make our development same-level reliable and scalable? Or, at least, close to it? +>>>>>>> upstream/master ## 컴포넌트 아키텍처 diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md index 702ff90739..a84ed11923 100644 --- a/8-web-components/2-custom-elements/article.md +++ b/8-web-components/2-custom-elements/article.md @@ -115,7 +115,7 @@ customElements.define("time-formatted", TimeFormatted); // (2) > ``` -1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time. +1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](mdn:/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time. 2. We need to register our new element by `customElements.define(tag, class)`. 3. And then we can use it everywhere. @@ -149,7 +149,7 @@ The `connectedCallback` triggers when the element is added to the document. Not In the current implementation of ``, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, we expect the change to be immediately visible. So let's fix this. -We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for an attribute for performance reasons. +We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for other, unlisted attributes (that's for performance reasons). Here's a new ``, that auto-updates when attributes change: @@ -320,7 +320,7 @@ For example, buttons are instances of `HTMLButtonElement`, let's build upon it. class HelloButton extends HTMLButtonElement { /* custom element methods */ } ``` -2. Provide an third argument to `customElements.define`, that specifies the tag: +2. Provide the third argument to `customElements.define`, that specifies the tag: ```js customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*); ``` @@ -365,7 +365,7 @@ Our new button extends the built-in one. So it keeps the same styles and standar ## References - HTML Living Standard: . -- Compatiblity: . +- Compatiblity: . ## Summary @@ -397,4 +397,4 @@ Custom elements can be of two types: /*