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..8a1e103c28 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -1,6 +1,10 @@ # 자바스크립트란? +<<<<<<< HEAD 자바스크립트(JavaScript)가 언어로서 지닌 특징에 대해 알아보겠습니다. 이어서 자바스크립트로 무엇을 할 수 있을지, 다른 기술들이 자바스크립트를 어떻게 활용하고 있는지도 이야기해 보겠습니다. +======= +Let's see what's so special about JavaScript, what we can achieve with it, and what other technologies play well with it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 정의 @@ -24,26 +28,44 @@ 엔진의 종류는 다양한데, 엔진마다 특유의 코드네임이 있습니다. 아래처럼 말이죠. +<<<<<<< HEAD - [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에 사용됩니다. 위의 코드네임은 개발 관련 글에서 종종 언급되기 때문에 기억해 두는 것이 좋습니다. 본 튜토리얼에서도 해당 코드네임을 사용할 예정입니다. "X라는 기능은 V8에서만 지원합니다."라는 식으로 말이죠. 이런 문장을 만나면 Chrome과 Opera에서만 이 기능을 지원한다고 이해하시면 됩니다. +======= +- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome, Opera and Edge. +- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox. +- ...There are other codenames like "Chakra" for IE, "JavaScriptCore", "Nitro" and "SquirrelFish" for Safari, etc. + +The terms above are good to remember because they are used in developer articles on the internet. We'll use them too. For instance, if "a feature X is supported by V8", then it probably works in Chrome, Opera and Edge. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="엔진은 어떻게 동작하나요?" 엔진이 어떻게 동작하는지 이해하려면 상당한 시간을 쏟아부어야 합니다. 하지만 기본 원리는 다음과 같이 간단합니다. +<<<<<<< HEAD 1. 엔진(브라우저라면 내장 엔진)이 스크립트를 읽습니다(파싱). 2. 읽어 들인 스크립트를 기계어로 전환합니다(컴파일). 3. 기계어로 전환된 코드가 실행됩니다. 기계어로 전환되었기 때문에 실행 속도가 빠릅니다. +======= +1. The engine (embedded if it's a browser) reads ("parses") the script. +2. Then it converts ("compiles") the script to machine code. +3. And then the machine code runs, pretty fast. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 엔진은 프로세스 각 단계마다 최적화를 진행합니다. 심지어 컴파일이 끝나고 실행 중인 코드를 감시하면서, 이 코드로 흘러가는 데이터를 분석하고, 분석 결과를 토대로 기계어로 전환된 코드를 다시 최적화하기도 합니다. 이런 과정을 거치면 스크립트 실행 속도는 더욱 더 빨라집니다. ``` ## 브라우저에서 할 수 있는 일 +<<<<<<< HEAD 모던 자바스크립트는 '안전한' 프로그래밍 언어입니다. 메모리나 CPU 같은 저수준 영역의 조작을 허용하지 않습니다. 애초에 이러한 접근이 필요치 않은 브라우저를 대상으로 만든 언어이기 때문이죠. +======= +Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or the CPU, because it was initially created for browsers which do not require it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트의 능력은 실행 환경에 상당한 영향을 받습니다. [Node.js](https://wikipedia.org/wiki/Node.js) 환경에선 임의의 파일을 읽거나 쓰고, 네트워크 요청을 수행하는 함수를 지원합니다. @@ -59,7 +81,11 @@ ## 브라우저에서 할 수 없는 일 +<<<<<<< HEAD 브라우저는 보안을 위해 자바스크립트의 기능에 제약을 걸어놓았습니다. 이런 제약은 악성 웹페이지가 개인 정보에 접근하거나 사용자의 데이터를 손상하는 것을 막기 위해 만들어졌습니다. +======= +JavaScript's abilities in the browser are limited to protect the user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 몇 가지 제약사항을 소개해 드리겠습니다. @@ -67,6 +93,7 @@ 모던 브라우저를 사용하면 파일을 다룰 순 있습니다. 하지만 접근은 제한되어 있습니다. 사용자가 브라우저 창에 파일을 '끌어다 두거나' `` 태그를 통해 파일을 선택할 때와 같이 특정 상황에서만 파일 접근을 허용합니다. +<<<<<<< HEAD 카메라나 마이크 같은 디바이스와 상호 작용하려면 사용자의 명시적인 허가가 있어야 합니다. 자바스크립트가 활성화된 페이지라도 사용자 몰래 웹 카메라를 작동 시켜 수집한 정보를 [국가안보국(NSA)](https://en.wikipedia.org/wiki/National_Security_Agency)과 같은 곳에 몰래 전송할 수 없습니다. - 브라우저 내 탭과 창은 대개 서로의 정보를 알 수 없습니다. 그런데 자바스크립트를 사용해 한 창에서 다른 창을 열 때는 예외가 적용됩니다. 하지만 이 경우에도 도메인이나 프로토콜, 포트가 다르다면 페이지에 접근할 수 없습니다. @@ -78,21 +105,44 @@ ![](limitations.svg) 브라우저 환경 밖, 예를 들어 서버라면 이러한 제약은 존재하지 않을 것입니다. 다만, 모던 브라우저에선 추가 권한 허가를 요청하는 플러그인이나 익스텐션 설치가 허용됩니다. +======= + There are ways to interact with the camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). +- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other page if they come from different sites (from a different domain, protocol or port). + + This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and must contain special JavaScript code that handles it. We'll cover that in the tutorial. + + This limitation is, again, for the user's safety. A page from `http://anysite.com` which a user has opened must not be able to access another browser tab with the URL `http://gmail.com`, for example, and steal information from there. +- JavaScript can easily communicate over the net to the server where the current page came from. But its ability to receive data from other sites/domains is crippled. Though possible, it requires explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's a safety limitation. + +![](limitations.svg) + +Such limitations do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugins/extensions which may ask for extended permissions. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 자바스크립트만의 강점 자바스크립트엔 다양한 장점이 있지만 여기선 *세 가지*만 언급해 보도록 하겠습니다. ```compare +<<<<<<< HEAD + HTML/CSS와 완전히 통합할 수 있음 + 간단한 일은 간단하게 처리할 수 있게 해줌 + 모든 주요 브라우저에서 지원하고, 기본 언어로 사용됨 +======= ++ Full integration with HTML/CSS. ++ Simple things are done simply. ++ Supported by all major browsers and enabled by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 이 세 가지 모두를 지원하는 브라우저 연관 기술은 자바스크립트뿐입니다. 이런 특징 때문에 자바스크립트는 브라우저 인터페이스를 만들 때 가장 널리 사용되고 있습니다. +<<<<<<< HEAD 이 외에도 자바스크립트를 이용해 서버나 모바일 앱 등을 만드는 것도 가능합니다. +======= +That said, JavaScript can be used to create servers, mobile applications, etc. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 자바스크립트 '너머의' 언어들 @@ -100,21 +150,42 @@ 프로젝트마다 요구사항이 천차만별이기 때문에 이는 당연한 현상입니다. +<<<<<<< HEAD 이로 인해 근래엔 브라우저에서 실행 되기 전에 자바스크립트로 *트랜스파일(transpile, 변환)* 할 수 있는 새로운 언어들이 많이 등장했습니다. +======= +So, recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 최신 툴을 사용하면 트랜스파일을 빠르고 명확하게 수행할 수 있습니다. 최신도구는 자바스크립트 이외의 언어로 작성한 코드를 '보이지 않는 곳에서' 자바스크립트로 자동 변환해줍니다. 자바스크립트로 트랜스파일이 가능한 언어 몇 가지를 소개해 드리겠습니다. +<<<<<<< HEAD - [CoffeeScript](http://coffeescript.org/)는 자바스크립트를 위한 'syntactic sugar'입니다. 짧은 문법을 도입하여 명료하고 이해하기 쉬운 코드를 작성할 수 있습니다. Ruby 개발자들이 좋아합니다. - [TypeScript](http://www.typescriptlang.org/)는 개발을 단순화 하고 복잡한 시스템을 지원하려는 목적으로 '자료형의 명시화(strict data typing)'에 집중해 만든 언어입니다. Microsoft가 개발하였습니다. - [Flow](http://flow.org/) 역시 자료형을 강제하는데, TypeScript와는 다른 방식을 사용합니다. Facebook이 개발하였습니다. - [Dart](https://www.dartlang.org/)는 모바일 앱과 같이 브라우저가 아닌 환경에서 동작하는 고유의 엔진을 가진 독자적 언어입니다. Google이 개발하였습니다. 이 외에도 자바스크립트로 트랜스파일 할 수 있는 언어는 다양합니다. 개발 언어로 이런 언어 중 하나를 택한다고 하더라도 자신이 무엇을 하고 있는지 이해하려면 결국엔 자바스크립트를 알아야 합니다. +======= +- [CoffeeScript](https://coffeescript.org/) is "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. +- [TypeScript](https://www.typescriptlang.org/) is concentrated on adding "strict data typing" to simplify the development and support of complex systems. It is developed by Microsoft. +- [Flow](https://flow.org/) also adds data typing, but in a different way. Developed by Facebook. +- [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps), but also can be transpiled to JavaScript. Developed by Google. +- [Brython](https://brython.info/) is a Python transpiler to JavaScript that enables the writing of applications in pure Python without JavaScript. +- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) is a modern, concise and safe programming language that can target the browser or Node. + +There are more. Of course, even if we use one of these transpiled languages, we should also know JavaScript to really understand what we're doing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 +<<<<<<< HEAD - 자바스크립트는 브라우저에서만 쓸 목적으로 고안된 언어이지만, 지금은 다양한 환경에서 쓰이고 있습니다. - 오늘날 자바스크립트는 브라우저 환경에서 가장 널리 사용되는 언어로 자리매김하였습니다. HTML/CSS와 완전한 통합이 가능합니다. - 자바스크립트로 '트랜스파일'할 수 있는 언어는 많습니다. 각 언어마다 고유한 기능을 제공하죠. 자바스크립트에 숙달한 뒤에 이 언어들을 살펴볼 것을 추천드립니다. +======= +- JavaScript was initially created as a browser-only language, but it is now used in many other environments as well. +- Today, JavaScript has a unique position as the most widely-adopted browser language, fully integrated with HTML/CSS. +- There are many languages that get "transpiled" to JavaScript and provide certain features. It is recommended to take a look at them, at least briefly, after mastering JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..532f2c470b 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -1,7 +1,11 @@ # 매뉴얼과 명세서 +<<<<<<< HEAD 본 *튜토리얼*은 자바스크립트를 기본부터 차근차근 배울 수 있도록 만들어졌습니다. 그런데 어느 정도 자바스크립트가 익숙해지면 튜토리얼 이외의 자료가 필요한 시점이 옵니다. +======= +This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other resources. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 명세서 @@ -9,14 +13,23 @@ ECMA-262 명세서의 고유한 형식 때문에 명세서를 처음 접하는 사람은 그 내용을 이해하기가 쉽지 않습니다. 자바스크립트에 관한 정보를 얻을 수 있는 가장 신뢰할 만한 자료이긴 하지만 일상적인 참고 자료로는 적합하지 않죠. +<<<<<<< HEAD ECMA-262명세서는 새로운 버전이 매년 나옵니다. 공식 버전이 나오기 이전의 최신 초안은 에서 확인할 수 있습니다. +======= +A new specification version is released every year. Between these releases, the latest specification draft is at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 갓 명세서에 등록된 기능이나 '등록되기 바로 직전'에 있는 기능(스테이지(stage)3 상태의 기능), 제안 목록은 에서 확인할 수 있습니다. +<<<<<<< HEAD 본 튜토리얼의 [두 번째 대 단원](info:browser-environment)에서 브라우저와 관련된 명세서를 다룰 예정이므로, 만약 브라우저에서 돌아가는 기능을 구현하는 개발자라면 해당 내용을 확인해 보시기 바랍니다. +======= +Also, if you're developing for the browser, then there are other specifications covered in the [second part](info:browser-environment) of the tutorial. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 매뉴얼 +<<<<<<< HEAD - Mozilla 재단이 운영하는 **MDN JavaScript Reference**엔 다양한 예제와 정보가 있습니다. 특정 함수나 메서드에 대한 깊이 있는 정보를 얻고 싶다면 이 사이트가 제격입니다. 링크는 다음과 같습니다. @@ -28,10 +41,28 @@ ECMA-262명세서는 새로운 버전이 매년 나옵니다. 공식 버전이 자바스크립트는 끊임없이 발전하는 언어입니다. 새로운 기능이 정기적으로 추가되죠. 특정 브라우저나 엔진이 내가 사용하려는 기능을 지원하는지 확인할 땐, 아래 두 사이트가 좋습니다. +======= +- **MDN (Mozilla) JavaScript Reference** is the main manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc. + + You can find it at . + +Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for the `parseInt` function. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 에선 브라우저가 특정 기능을 지원하는지 (표 형태로) 확인할 수 있습니다. 암호화 관련 기능인 cryptography를 특정 브라우저에서 사용할 수 있는지 아닌지를 보려면 를 확인하면 됩니다. - 에선 자바스크립트 기능 목록이 있고, 해당 기능을 특정 엔진이 지원하는지 여부를 거대한 표를 통해 보여줍니다. 실제 개발을 하다 보면 위에 언급 드린 자료가 아주 유용할 겁니다. 메서드나 함수 관련 정보, 브라우저 지원 여부 등은 의사결정을 내릴 때 꼭 필요한 정보이기 때문입니다. -말씀드린 사이트나 이 페이지를 기억해 놓았다가 특정 기능에 대한 상세한 정보가 필요할 때 방문해 보시길 바랍니다. \ No newline at end of file +<<<<<<< HEAD +말씀드린 사이트나 이 페이지를 기억해 놓았다가 특정 기능에 대한 상세한 정보가 필요할 때 방문해 보시길 바랍니다. +======= +To see their support among browser-based and other engines, see: + +- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: . +- - a table with language features and engines that support those or don't support. + +All these resources are useful in real-life development, as they contain valuable information about language details, their support, etc. + +Please remember them (or this page) for the cases when you need in-depth information about a particular feature. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..5bd04123e9 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -12,8 +12,13 @@ IDE를 이용하면 수많은 파일로 구성된 프로젝트를 불러오고, 아직 어떤 IDE를 사용할지 결정하지 못했다면, 아래 두 옵션을 고려해 보시길 바랍니다. +<<<<<<< HEAD - [Visual Studio Code](https://code.visualstudio.com/) (크로스 플랫폼, 무료) - [WebStorm](http://www.jetbrains.com/webstorm/) (크로스 플랫폼, 유료) +======= +- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). +- [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, paid). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Windows 사용자라면 'Visual Studio'라는 IDE를 들어보셨을 겁니다. Visual Studio는 'Visual Studio Code'와는 다릅니다. 'Visual Studio'는 .NET 플랫폼 개발에 쓰이는 유료 에디터로, Windows에서만 사용할 수 있습니다. 자바스크립트도 지원합니다. Visual Studio의 무료 버전인 [Visual Studio Community](https://www.visualstudio.com/vs/community/)도 있으니 참고하시기 바랍니다. @@ -29,6 +34,7 @@ Windows 사용자라면 'Visual Studio'라는 IDE를 들어보셨을 겁니다. 경량 에디터는 다양한 플러그인을 지원합니다. 디렉터리 레벨 문법 분석기나 자동완성기능 등을 플러그인을 설치해 사용할 수 있습니다. 플러그인을 사용하면 경량 에디터에서도 IDE 못지않게 다양한 기능을 사용할 수 있죠. 요즘엔 경량 에디터와 IDE 사이의 엄격한 구분이 사라져가는 추세입니다. +<<<<<<< HEAD 추천하는 에디터는 다음과 같습니다. - [Atom](https://atom.io/) (크로스 플랫폼, 무료) @@ -36,6 +42,13 @@ Windows 사용자라면 'Visual Studio'라는 IDE를 들어보셨을 겁니다. - [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/)도 에디터로 사용법만 잘 숙지하면 충분히 에디터 역할을 잘합니다. +======= +There are many options, for instance: + +- [Sublime Text](https://www.sublimetext.com/) (cross-platform, shareware). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). +- [Vim](https://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 논쟁하지 맙시다 @@ -43,4 +56,13 @@ Windows 사용자라면 'Visual Studio'라는 IDE를 들어보셨을 겁니다. 이 외에도 제가 모르는 훌륭한 에디터가 있을 수 있으니 여러분이 가장 좋아하는 것 하나를 택하시면 됩니다. +<<<<<<< HEAD 여타 툴과 마찬가지로 에디터를 선택하는 것은 프로젝트의 종류, 개발 습관, 개인 성향에 따라 다르므로 이에 관한 논쟁은 지양하도록 합시다. +======= +The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. + +The author's personal opinion: + +- I'd use [Visual Studio Code](https://code.visualstudio.com/) if I develop mostly frontend. +- Otherwise, if it's mostly another language/platform and partially frontend, then consider other editors, such as XCode (Mac), Visual Studio (Windows) or Jetbrains family (Webstorm, PHPStorm, RubyMine etc, depending on the language). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..9d6fb61b0b 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 물론 아래와 같이 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..580c4a125b 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **반면, 세미콜론이 정말로 필요하지만 자바스크립트가 이를 추정하지 '못하는' 상황도 존재합니다.** @@ -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") +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +[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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` 줄 바꿈으로 문을 나눴더라도, 문 사이엔 세미콜론을 넣는 것이 좋습니다. 자바스크립트 커뮤니티에서도 이를 규칙으로 정해 권장하고 있습니다. 다시 한번 정리하도록 하겠습니다. 세미콜론은 *생략할 수 있습니다.* 하지만 세미콜론을 사용하는 것이 더 안전하므로 이를 기억하고 따르도록 합시다. 입문자라면 이를 더 잘 지키도록 합시다. 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..524a309460 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 현재 방문자를 나타내는 변수 이름 짓기 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..523bc3b079 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 상황에서 `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? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +``` diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index b17049faa5..54b7efec04 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -24,7 +24,11 @@ let message; let message; *!* +<<<<<<< HEAD message = 'Hello'; // 문자열을 저장합니다. +======= +message = 'Hello'; // store the string 'Hello' in the variable named message +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -63,7 +67,12 @@ let age = 25; let message = 'Hello'; ``` +<<<<<<< HEAD 어떤 사람들은 이런 방식으로도 변수를 정의합니다. +======= +Some people also define multiple variables in this multiline style: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify let user = 'John', age = 25, @@ -87,22 +96,37 @@ let user = 'John' *!*var*/!* message = 'Hello'; ``` +<<<<<<< HEAD `var`는 `let`과 *거의* 동일하게 동작합니다. `var`도 `let`처럼 변수를 선언하는 데 쓰이죠. 다만 `var`는 '오래된' 방식입니다. `let`과 `var`의 미묘한 차이점에 대해선 추후 에서 자세히 다루도록 하겠습니다. 지금 시점에선 이 차이점이 중요하지 않기 때문에 넘어가도록 합시다. +======= +The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way. + +There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 현실 속의 비유 '상자' 안에 데이터를 저장하는데, 이 상자에는 특별한 이름표가 붙어 있다고 상상해 봅시다. 이렇게 하면 '변수'를 좀 더 쉽게 이해할 수 있습니다. +<<<<<<< HEAD 예를 들어, 변수 `message`는 `message`라는 이름표가 붙어있는 상자에 `"Hello!"`라는 값을 저장한 것이라고 생각할 수 있습니다. +======= +For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](variable.svg) 상자 속엔 어떤 값이든지 넣을 수 있습니다. +<<<<<<< HEAD 원하는 만큼 값을 변경할 수도 있습니다. +======= +We can also change it as many times as we want: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let message; @@ -148,12 +172,21 @@ let message = "That"; // SyntaxError: 'message' has already been declared 따라서 변수는 딱 한 번만 선언하고, 선언한 변수를 참조할 때는 `let` 없이 변수명만 사용해 참조해야 합니다. ```` +<<<<<<< HEAD ```smart header="함수형 언어" [함수형(functional)](https://en.wikipedia.org/wiki/Functional_programming) 프로그래밍 언어는 변숫값 변경을 금지합니다. [스칼라(Scala)](http://www.scala-lang.org/)와 [얼랭(Erlang)](http://www.erlang.org/)은 대표적인 함수형 언어입니다. +======= +```smart header="Functional languages" +It's interesting to note that there exist so-called [pure functional](https://en.wikipedia.org/wiki/Purely_functional_programming) programming languages, such as [Haskell](https://en.wikipedia.org/wiki/Haskell), that forbid changing variable values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이들 언어에서는 '상자 속에' 값이 일단 저장되면, 그 값을 영원히 유지합니다. 다른 값을 저장하고 싶다면 새로운 상자를 만들어야(새 변수를 선언해야)만 합니다. 이전 변수를 재사용할 수 없습니다. +<<<<<<< HEAD 처음 봤을 땐 좀 이상해 보일 수 있지만, 함수형 언어는 중대한 개발에 상당히 적합한 언어입니다. 이런 제약이 장점으로 작용하는 병렬 계산(parallel computation)과 같은 영역도 있죠. 당장은 사용할 계획이 없더라도 이런 언어를 공부하는 것은 시야를 넓히는 데 도움이 되므로, 학습을 권유 드립니다. +======= +Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 변수 명명 규칙 @@ -191,19 +224,32 @@ let 1a; // 변수명은 숫자로 시작해선 안 됩니다. let my-name; // 하이픈 '-'은 변수명에 올 수 없습니다. ``` +<<<<<<< HEAD ```smart header="대·소문자 구별" `apple`와 `AppLE`은 서로 다른 변수입니다. ``` ````smart header="비 라틴계 언어도 변수명에 사용할 수 있지만 권장하진 않습니다." 키릴 문자, 심지어 상형문자도 변수명에 사용할 수 있습니다. 모든 언어를 변수명에 사용할 수 있죠. +======= +```smart header="Case matters" +Variables named `apple` and `APPLE` are two different variables. +``` + +````smart header="Non-Latin letters are allowed, but not recommended" +It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let имя = '...'; let 我 = '...'; ``` +<<<<<<< HEAD 위 코드에는 기술적인 에러가 없습니다. 변수명도 유효합니다. 하지만 영어를 변수명에 사용하는 것이 국제적인 관습이므로, 변수명은 영어를 사용해서 만들길 권유 드립니다. 다른 나라 사람이 스크립트를 볼 경우 등을 대비해 장기적인 안목을 가지고 코드를 작성합시다. +======= +Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ````warn header="예약어" @@ -258,12 +304,20 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // error, can't reassign the constant! ``` +<<<<<<< HEAD 변숫값이 절대 변경되지 않을 것이라 확신하면, 값이 변경되는 것을 방지하면서 다른 개발자들에게 이 변수는 상수라는 것을 알리기 위해 `const`를 사용해 변수를 선언하도록 합시다. +======= +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 대문자 상수 +<<<<<<< HEAD 기억하기 힘든 값을 변수에 할당해 별칭으로 사용하는 것은 널리 사용되는 관습입니다. +======= +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 상수는 대문자와 밑줄로 구성된 이름으로 명명합니다. @@ -288,16 +342,29 @@ alert(color); // #FF7F00 그렇다면 언제 일반적인 방식으로 상수를 명명하고, 언제 대문자를 사용해서 명명해야 하는 걸까요? 명확히 짚고 넘어갑시다. +<<<<<<< HEAD '상수'는 변수의 값이 절대 변하지 않음을 의미합니다. 그중에는 (빨간색을 나타내는 16진수 값처럼) 코드가 실행되기 전에 이미 그 값을 알고 있는 상수도 있고, 런타임 과정에서 *계산되지만* 최초 할당 이후 값이 변하지 않는 상수도 있습니다. 예시: +======= +Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment. + +For instance: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js const pageLoadTime = /* 웹페이지를 로드하는데 걸린 시간 */; ``` +<<<<<<< HEAD `pageLoadTime`의 값은 페이지가 로드되기 전에는 정해지지 않기 때문에 일반적인 방식으로 변수명을 지었습니다. 하지만 이 값은 최초 할당 이후에 변경되지 않으므로 여전히 상수입니다. 정리하자면, 대문자 상수는 '하드 코딩한' 값의 별칭을 만들 때 사용하면 됩니다. +======= +The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment. + +In other words, capital-named constants are only used as aliases for "hard-coded" values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 바람직한 변수명 @@ -305,18 +372,31 @@ const pageLoadTime = /* 웹페이지를 로드하는데 걸린 시간 */; 변수명은 간결하고, 명확해야 합니다. 변수가 담고있는 것이 무엇인지 잘 설명할 수 있어야 하죠. +<<<<<<< HEAD 변수의 이름을 짓는 것은 프로그래밍에서 가장 중요하고 복잡한 기술 중 하나입니다. 변수명만 슬쩍 봐도 초보자가 코드를 작성했는지, 노련한 개발자가 작성했는지 알 수 있습니다. 실제 프로젝트에선 맨 처음부터 완전히 독립적인 코드를 작성하기보다 기존 코드의 틀을 변경하고 확장하는데 대부분의 시간을 보냅니다. 작성했던 코드를 얼마 후에 다시 봤을 때, 정보에 알맞은 이름이 적혀있으면 정보를 더 쉽게 찾을 수 있습니다. 다시 말해, 변수가 올바른 이름을 가졌을 때 말이죠. +======= +Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer. + +In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그러므로 변수를 선언하기 전에 내가 지은 변수의 이름이 괜찮은지 숙고해 주시기 바랍니다. 아래는 변수 명명 시 참고하기 좋은 규칙입니다. +<<<<<<< HEAD - `userName` 이나 `shoppingCart`처럼 사람이 읽을 수 있는 이름을 사용하세요. - 무엇을 하고 있는지 명확히 알고 있지 않을 경우 외에는 줄임말이나 `a`, `b`, `c`와 같은 짧은 이름은 피하세요. - 최대한 서술적이고 간결하게 명명해 주세요. `data`와 `value`는 나쁜 이름의 예시입니다. 이런 이름은 아무것도 설명해주지 않습니다. 코드 문맥상 변수가 가리키는 데이터나 값이 아주 명확할 때에만 이런 이름을 사용합시다. - 자신만의 규칙이나 소속된 팀의 규칙을 따르세요. 만약 사이트 방문객을 'user'라고 부르기로 했다면, 이와 관련된 변수를 `currentVisitor`나 `newManInTown`이 아닌 `currentUser`나 `newUser`라는 이름으로 지어야 합니다. +======= +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing. +- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. +- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 간단해 보이나요? 그렇게 보이긴 합니다. 그러나 실전에서 서술적이고 간결한 변수명을 짓는 것은 간단하지 않습니다. 그럼, 화이팅! diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 46134566a7..525e984aa7 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -46,6 +46,7 @@ n = 12.345; alert( "숫자가 아님" / 2 ); // NaN, 문자열을 숫자로 나누면 오류가 발생합니다. ``` +<<<<<<< HEAD `NaN`은 여간해선 바뀌지 않습니다. `NaN`에 어떤 추가 연산을 해도 결국 `NaN`이 반환됩니다. ```js run @@ -53,6 +54,17 @@ n = 12.345; ``` 연산 과정 어디에선가 `NaN`이 반환되었다면, 이는 모든 결과에 영향을 미칩니다. +======= + `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`: + + ```js run + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "not a number" / 2 - 1 ); // NaN + ``` + + So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result (there's only one exception to that: `NaN ** 0` is `1`). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="수학 연산은 안전합니다." 자바스크립트에서 행해지는 수학 연산은 '안전'하다고 볼 수 있습니다. 0으로 나눈다거나 숫자가 아닌 문자열을 숫자로 취급하는 등의 이례적인 연산이 자바스크립트에선 가능합니다. @@ -64,11 +76,28 @@ n = 12.345; 숫자를 다루는 방법에 대해선 챕터에서 자세히 알아보도록 하겠습니다. -## BigInt +## BigInt [#bigint-type] +<<<<<<< HEAD 내부 표현 방식 때문에 자바스크립트에선 (253-1)(`9007199254740991`) 보다 큰 값 혹은 -(253-1) 보다 작은 정수는 '숫자형'을 사용해 나타낼 수 없습니다. 사실 대부분의 상황에서 이런 제약사항은 문제가 되지 않습니다. 그렇지만 암호 관련 작업같이 아주 큰 숫자가 필요한 상황이거나 아주 높은 정밀도로 작업을 해야 할 때는 이런 큰 숫자가 필요합니다. +======= +In JavaScript, the "number" type cannot safely represent integer values larger than (253-1) (that's `9007199254740991`), or less than -(253-1) for negatives. + +To be really precise, the "number" type can store larger integers (up to 1.7976931348623157 * 10308), but outside of the safe integer range ±(253-1) there'll be a precision error, because not all digits fit into the fixed 64-bit storage. So an "approximate" value may be stored. + +For example, these two numbers (right above the safe range) are the same: + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +So to say, all odd integers greater than (253-1) can't be stored at all in the "number" type. + +For most purposes ±(253-1) range is quite enough, but sometimes we need the entire range of really big integers, e.g. for cryptography or microsecond-precision timestamps. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `BigInt`형은 표준으로 채택된 지 얼마 안 된 자료형으로, 길이에 상관없이 정수를 나타낼 수 있습니다. @@ -81,11 +110,15 @@ const bigInt = 1234567890123456789012345678901234567890n; `BigInt`형 숫자는 자주 쓰이지 않기 때문에 여기서 자세히 다루지 않고 별도의 챕터, 에서 설명드리겠습니다. 아주 큰 숫자를 사용해야 하는 경우라면 해당 챕터를 참고해 주시기 바랍니다. +<<<<<<< HEAD ```smart header="호환성 이슈" 이 글이 작성된 시점엔 Firefox, Chrome, Edge, Safari에서만 `BigInt`를 지원합니다. IE에선 지원하지 않습니다. ``` ## 문자형 +======= +## String +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트에선 문자열(string)을 따옴표로 묶습니다. @@ -127,7 +160,11 @@ alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (큰따옴표는 ```smart header="*글자형*은 없습니다." 일부 언어는 글자 하나를 저장할 때 쓰이는 자료형, '글자(character)'형을 따로 지원합니다. C 언어와 Java의 `char`가 대표적인 예입니다. +<<<<<<< HEAD 자바스크립트는 글자형을 지원하지 않습니다. `문자형`만 있을 뿐입니다. 여기엔 글자가 하나 혹은 여러 개 들어갈 수 있습니다. +======= +In JavaScript, there is no such type. There's only one type: `string`. A string may consist of zero characters (be empty), one character or many of them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 불린형 @@ -208,6 +245,7 @@ alert(age); // "undefined" ## typeof 연산자 [#type-typeof] +<<<<<<< HEAD `typeof` 연산자는 인수의 자료형을 반환합니다. 자료형에 따라 처리 방식을 다르게 하고 싶거나 변수의 자료형을 빠르게 알아내고자 할 때 유용합니다. `typeof` 연산자는 두 가지 형태의 문법을 지원합니다. @@ -218,6 +256,11 @@ alert(age); // "undefined" 괄호가 있든 없든 결과가 동일합니다. `typeof x`를 호출하면 인수의 자료형을 나타내는 문자열을 반환합니다. +======= +The `typeof` operator returns the type of the operand. It's useful when we want to process values of different types differently or just want to do a quick check. + +A call to `typeof x` returns a string with the type name: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js typeof undefined // "undefined" @@ -247,14 +290,33 @@ typeof alert // "function" (3) 마지막 세 줄은 약간의 설명이 필요해 보이네요. +<<<<<<< HEAD 1. `Math`는 수학 연산을 제공하는 내장 객체이므로 `"object"`가 출력됩니다. `Math`에 대해선 챕터에서 학습하도록 하겠습니다. 내장 객체는 객체형이라는 것을 알려주기 위해 이런 예시를 작성해 보았습니다. 2. `typeof null`의 결과는 `"object"`입니다. `null`은 별도의 고유한 자료형을 가지는 특수 값으로 객체가 아니지만, 하위 호환성을 유지하기 위해 이런 오류를 수정하지 않고 남겨둔 상황입니다. 언어 자체의 오류이므로 `null`이 객체가 아님에 유의하시기 바랍니다. 3. `typeof`는 피연산자가 함수면 `"function"`을 반환합니다. 그러므로 `typeof alert`는 `"function"`을 출력해줍니다. 그런데 '함수'형은 따로 없습니다. 함수는 객체형에 속합니다. 이런 동작 방식이 형식적으론 잘못되긴 했지만, 아주 오래전에 만들어진 규칙이었기 때문에 하위 호환성 유지를 위해 남겨진 상태입니다. 한편, 실무에선 이런 특징이 매우 유용하게 사용되기도 합니다. ## 요약 +======= +1. `Math` is a built-in object that provides mathematical operations. We will learn it in the chapter . Here, it serves just as an example of an object. +2. The result of `typeof null` is `"object"`. That's an officially recognized error in `typeof`, coming from very early days of JavaScript and kept for compatibility. Definitely, `null` is not an object. It is a special value with a separate type of its own. The behavior of `typeof` is wrong here. +3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That also comes from the early days of JavaScript. Technically, such behavior isn't correct, but can be convenient in practice. + +```smart header="The `typeof(x)` syntax" +You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`. + +To put it clear: `typeof` is an operator, not a function. The parentheses here aren't a part of `typeof`. It's the kind of parentheses used for mathematical grouping. + +Usually, such parentheses contain a mathematical expression, such as `(2 + 2)`, but here they contain only one argument `(x)`. Syntactically, they allow to avoid a space between the `typeof` operator and its argument, and some people like it. + +Some people prefer `typeof(x)`, although the `typeof x` syntax is much more common. +``` + +## Summary +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트에는 여덟 가지 기본 자료형이 있습니다. +<<<<<<< HEAD - `숫자형` -- 정수, 부동 소수점 숫자 등의 숫자를 나타낼 때 사용합니다. 정수의 한계는 ±253 입니다. - `bigint` -- 길이 제약 없이 정수를 나타낼 수 있습니다. - `문자형` -- 빈 문자열이나 글자들로 이뤄진 문자열을 나타낼 때 사용합니다. 단일 문자를 나타내는 별도의 자료형은 없습니다. @@ -263,11 +325,29 @@ typeof alert // "function" (3) - `undefined` -- `undefined` 값만을 위한 독립 자료형입니다. `undefined`는 할당되지 않은 값을 나타냅니다. - `객체형` -- 복잡한 데이터 구조를 표현할 때 사용합니다. - `심볼형` -- 객체의 고유 식별자를 만들 때 사용합니다. +======= +- Seven primitive data types: + - `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1). + - `bigint` for integer numbers of arbitrary length. + - `string` for strings. A string may have zero or more characters, there's no separate single-character type. + - `boolean` for `true`/`false`. + - `null` for unknown values -- a standalone type that has a single value `null`. + - `undefined` for unassigned values -- a standalone type that has a single value `undefined`. + - `symbol` for unique identifiers. +- And one non-primitive data type: + - `object` for more complex data structures. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `typeof` 연산자는 피연산자의 자료형을 알려줍니다. +<<<<<<< HEAD - `typeof x` 또는 `typeof(x)` 형태로 사용합니다. - 피연산자의 자료형을 문자열 형태로 반환합니다. - `null`의 typeof 연산은 `"object"`인데, 이는 언어상 오류입니다. null은 객체가 아닙니다. +======= +- Usually used as `typeof x`, but `typeof(x)` is also possible. +- Returns a string with the name of the type, like `"string"`. +- For `null` returns `"object"` -- this is an error in the language, it's not actually an object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이어지는 챕터에선 원시 자료형에 대해 학습해 볼 예정입니다. 원시형에 어느 정도 익숙해지면 객체형에 대해 알아보도록 하겠습니다. diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md index e094ea75ee..278379e823 100644 --- a/1-js/02-first-steps/06-alert-prompt-confirm/article.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -30,8 +30,13 @@ result = prompt(title, [default]); `default` : 입력 필드의 초깃값(선택값) +<<<<<<< HEAD ```smart header="인수를 감싸는 대괄호 `[...]`의 의미" `default`를 감싸는 대괄호는 이 매개변수가 필수가 아닌 선택값이라는 것을 의미합니다. +======= +```smart header="The square brackets in syntax `[...]`" +The square brackets around `default` in the syntax above denote that the parameter is optional, not required. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 사용자는 프롬프트 대화상자의 입력 필드에 원하는 값을 입력하고 확인을 누를 수 있습니다. 값을 입력하길 원하지 않는 경우는 취소(Cancel) 버튼을 누르거나 `key:Esc`를 눌러 대화상자를 빠져나가면 됩니다. 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..5eaefbf34c 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,13 @@ 이 외에, 전달받은 값을 의도를 갖고 원하는 타입으로 변환(명시적 변환)해 주는 경우도 형 변환이라고 할 수 있습니다. +<<<<<<< HEAD ```smart header="객체의 형변환은 나중에 다룹니다." 이 챕터에선 객체는 다루지 않겠습니다. 여기선 원시형의 형변환에 대해서만 다룰 예정입니다. +======= +```smart header="Not talking about objects yet" +In this chapter, we won't cover objects. For now, we'll just be talking about primitives. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 객체의 형 변환이 어떻게 이뤄지는지 에 대해선 객체에 대한 학습이 끝난 후 알아보겠습니다. ``` @@ -34,7 +39,11 @@ alert(typeof value); // string ## 숫자형으로 변환 +<<<<<<< HEAD 숫자형으로의 변환은 수학과 관련된 함수와 표현식에서 자동으로 일어납니다. +======= +Numeric conversion in mathematical functions and expressions happens automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 숫자형이 아닌 값에 나누기 `/`를 적용한 경우와 같이 말이죠. @@ -69,8 +78,13 @@ alert(age); // NaN, 형 변환이 실패합니다. |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| +<<<<<<< HEAD |true and false | `1` 과 `0` | | `string` | 문자열의 처음과 끝 공백이 제거됩니다. 공백 제거 후 남아있는 문자열이 없다면 `0`, 그렇지 않다면 문자열에서 숫자를 읽습니다. 변환에 실패하면 `NaN`이 됩니다.| +======= +|true and false | `1` and `0` | +| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -130,7 +144,11 @@ alert( Boolean(" ") ); // 공백이 있는 문자열도 비어있지 않은 문 |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | +<<<<<<< HEAD | `string` | 전달받은 문자열을 "그대로" 읽되, 처음과 끝의 공백을 무시합니다. 문자열이 비어있다면 `0`이 되고, 오류 발생 시 `NaN`이 됩니다. | +======= +| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **`불린형으로 변환`** 은 논리 연산 시 발생합니다. `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..6288cc0d99 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,20 @@ undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` +<<<<<<< HEAD 1. 피 연산자 중 하나가 문자열인 `"" + 1`에서 `1`은 문자형으로 변환됩니다. 따라서 공백과 문자열 1을 더한, `"" + 1 = "1"`과 같은 효과를 발휘하죠. 그다음 연산 `"1" + 0`에도 같은 규칙이 적용됩니다. 2. 뺄셈 연산자 `-`는 기타 수학 연산자처럼 숫자형만을 인수로 받습니다. 빈 문자열 `""`는 숫자 `0`으로 변환되기 때문에 결과는 `-1`이 됩니다. 3. 피 연산자 중 하나가 문자열이므로 숫자 5가 문자열로 변환됩니다. 4. 뺄셈 연산자는 인수를 숫자형으로 변화시키므로 `" -9 "`는 숫자 `-9`로 변합니다. 앞, 뒤 공백은 제거되죠. 5. 숫자형으로 변환 시 `null`은 `0`이 됩니다. 6. `undefined`는 숫자형으로 변환시 `NaN`이 됩니다. -7. 문자열이 숫자형으로 변할 땐 문자열 앞뒤의 공백이 삭제됩니다. 뺄셈 연산자 앞의 피연산자는 공백을 만드는 문자 `\t`와 `\n`, 그 사이의 "일반적인" 공백으로 구성됩니다. 따라서 `" \t \n"`는 숫자형으로 변환 시 길이가 `0`인 문자열로 취급되어 숫자 `0`이 됩니다. \ No newline at end of file +7. 문자열이 숫자형으로 변할 땐 문자열 앞뒤의 공백이 삭제됩니다. 뺄셈 연산자 앞의 피연산자는 공백을 만드는 문자 `\t`와 `\n`, 그 사이의 "일반적인" 공백으로 구성됩니다. 따라서 `" \t \n"`는 숫자형으로 변환 시 길이가 `0`인 문자열로 취급되어 숫자 `0`이 됩니다. +======= +1. The addition with a string `"" + 1` converts `1` to a string: `"" + 1 = "1"`, and then we have `"1" + 0`, the same rule is applied. +2. The subtraction `-` (like most math operations) only works with numbers, it converts an empty string `""` to `0`. +3. The addition with a string appends the number `5` to the string. +4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it). +5. `null` becomes `0` after the numeric conversion. +6. `undefined` becomes `NaN` after the numeric conversion. +7. Space characters are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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/4-fix-prompt/solution.md b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md index defe5642c0..d130e9a1ee 100644 --- a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md @@ -9,7 +9,11 @@ let b = "2"; // prompt("덧셈할 두 번째 숫자를 입력해주세요.", 2); alert(a + b); // 12 ``` +<<<<<<< HEAD 예시가 제대로 동작하게 하려면 덧셈 연산 `+`가 수행되기 전에 문자열을 숫자로 변환해야 합니다. 이때 `Number()`를 사용하거나 변수 앞에 `+`를 붙여줄 수 있습니다. +======= +What we should do is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 코드에선 `prompt` 함수 바로 앞에서 문자열을 숫자로 변환했습니다. diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index 29193f4995..e3e3485687 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -50,23 +50,46 @@ 예시: ```js run +<<<<<<< HEAD alert( 5 % 2 ); // 5를 2로 나눈 후의 나머지인 1을 출력 alert( 8 % 3 ); // 8을 3으로 나눈 후의 나머지인 2를 출력 +======= +alert( 5 % 2 ); // 1, the remainder of 5 divided by 2 +alert( 8 % 3 ); // 2, the remainder of 8 divided by 3 +alert( 8 % 4 ); // 0, the remainder of 8 divided by 4 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 거듭제곱 연산자 ** +<<<<<<< HEAD 거듭제곱 연산자(exponentiation operator)를 사용한 `a ** b`를 평가하면 `a`를 `b`번 곱한 값이 반환됩니다. +======= +The exponentiation operator `a ** b` raises `a` to the power of `b`. + +In school maths, we write that as ab. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: ```js run +<<<<<<< HEAD alert( 2 ** 2 ); // 4 (2 * 2) alert( 2 ** 3 ); // 8 (2 * 2 * 2) alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) ``` 거듭제곱 연산자는 정수가 아닌 숫자에 대해서도 동작합니다. `1/2`을 사용하면 제곱근을 구할 수 있죠. +======= +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 +``` + +Just like in maths, the exponentiation operator is defined for non-integer numbers as well. + +For example, a square root is an exponentiation by ½: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 4 ** (1/2) ); // 2 (1/2 거듭제곱은 제곱근) @@ -76,7 +99,11 @@ alert( 8 ** (1/3) ); // 2 (1/3 거듭제곱은 세제곱근) ## 이항 연산자 '+'와 문자열 연결 +<<<<<<< HEAD 이제 학교에서 배운 기본 산술 연산자를 넘어, 자바스크립트가 제공하는 특별한 연산자 기능에 대해 살펴봅시다. +======= +Let's meet the features of JavaScript operators that are beyond school arithmetics. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 덧셈 연산자 `+`는 대개 숫자를 더한 결과를 반환합니다. @@ -104,7 +131,16 @@ alert( 2 + '1' ); // "21" alert(2 + 2 + '1' ); // '221'이 아니라 '41'이 출력됩니다. ``` +<<<<<<< HEAD 연산은 왼쪽에서 오른쪽으로 순차적으로 진행되기 때문에 이런 결과가 나왔습니다. 두 개의 숫자 뒤에 문자열이 오는 경우, 숫자가 먼저 더해지고, 그 후 더해진 숫자와 문자열과의 병합이 일어납니다. +======= +Here, operators work one after another. The first `+` sums two numbers, so it returns `4`, then the next `+` adds the string `1` to it, so it's like `4 + '1' = '41'`. + +```js run +alert('1' + 2 + 2); // "122" and not "14" +``` +Here, the first operand is a string, the compiler treats the other two operands as strings too. The `2` gets concatenated to `'1'`, so it's like `'1' + 2 = "12"` and `"12" + 2 = "122"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이처럼 이항 덧셈 연산자 `+`는 문자열 연결과 변환이라는 특별한 기능을 제공합니다. 다른 산술 연산자가 오직 숫자형의 피연산자만 다루고, 피연산자가 숫자형이 아닌 경우에 그 형을 숫자형으로 바꾸는 것과는 대조적입니다. @@ -180,6 +216,7 @@ alert( +apples + +oranges ); // 5 자바스크립트는 다양한 연산자를 제공하는데, 이 모든 연산자엔 우선순위가 매겨져 있습니다. 우선순위 숫자가 클수록 먼저 실행됩니다. 순위가 같으면 왼쪽부터 시작해서 오른쪽으로 연산이 수행됩니다. +<<<<<<< HEAD 아래는 [우선순위 테이블(precedence table)](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence)의 일부를 발췌한 표입니다. 순서를 기억할 필요는 없지만, 동일한 기호의 단항 연산자는 이항 연산자보다 우선순위가 더 높다는 것에 주목해 주시기 바랍니다. | 순위 | 연산자 이름 | 기호 | @@ -197,10 +234,33 @@ alert( +apples + +oranges ); // 5 | ... | ... | ... | '단항 덧셈 연산자'는 우선순위 `17`로, '(이항) 덧셈 연산자'의 우선순위 `13`보다 높습니다. 표현식 `"+apples + +oranges"`에서 단항 덧셈 연산자가 덧셈보다 먼저 수행되는 이유가 바로 이 때문입니다. +======= +Here's an extract from the [precedence table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones): + +| Precedence | Name | Sign | +|------------|------|------| +| ... | ... | ... | +| 14 | unary plus | `+` | +| 14 | unary negation | `-` | +| 13 | exponentiation | `**` | +| 12 | multiplication | `*` | +| 12 | division | `/` | +| 11 | addition | `+` | +| 11 | subtraction | `-` | +| ... | ... | ... | +| 2 | assignment | `=` | +| ... | ... | ... | + +As we can see, the "unary plus" has a priority of `14` which is higher than the `11` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 할당 연산자 +<<<<<<< HEAD 무언가를 할당할 때 쓰이는 `=`도 연산자입니다. 이 연산자는 할당(assignment) 연산자라고 불리는데, 우선순위는 `3`으로 아주 낮습니다. +======= +Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `2`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `x = 2 * 2 + 1`과 같은 표현식에서 계산이 먼저 이뤄지고, 그 결과가 `x`에 할당되는 이유가 바로 이 때문입니다. @@ -214,7 +274,11 @@ alert( x ); // 5 `=`는 연산자이기 때문에 흥미로운 함축성을 내포하고 있습니다. +<<<<<<< HEAD 자바스크립트에서 대부분의 연산자들은 값을 반환합니다. `+`와 `-`뿐만 아니라 `=` 역시 값을 반환하죠. +======= +All operators in JavaScript return a value. That's obvious for `+` and `-`, but also true for `=`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `x = value`을 호출하면 `value`가 `x`에 쓰여지고, 이에 더하여 *`value`가 반환됩니다*. @@ -294,9 +358,13 @@ alert( n ); // 14 ```js run let n = 2; -n *= 3 + 5; +n *= 3 + 5; // right part evaluated first, same as n *= 8 +<<<<<<< HEAD alert( n ); // 16 (*=의 우측이 먼저 평가되므로, 위 식은 n *= 8과 동일합니다.) +======= +alert( n ); // 16 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 증가·감소 연산자 @@ -428,7 +496,11 @@ counter++; - 오른쪽 시프트(RIGHT SHIFT) ( `>>` ) - 부호 없는 오른쪽 시프트(ZERO-FILL RIGHT SHIFT) ( `>>>` ) +<<<<<<< HEAD 비트 연산자는 저수준(2진 표현)에서 숫자를 다뤄야 할 때 쓰이므로 흔하게 쓰이진 않습니다. 웹 개발 시엔 이런 일이 자주 일어나지 않기 때문에 비트 연산자를 만날 일은 거의 없죠. 그렇긴 해도 암호를 다뤄야 할 땐 비트 연산자가 유용하기 때문에 때가 되면 MDN의 [비트 연산자](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) 문서를 보시는 걸 추천합니다. +======= +These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) chapter on MDN when a need arises. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 쉼표 연산자 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..57056c3563 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 @@ -12,10 +12,20 @@ null === +"\n0\n" → false 해설: +<<<<<<< HEAD 1. 명백히 true입니다. 2. 문자열의 비교는 사전순서가 기준이므로 false입니다. `"a"`는 `"p"`보다 작습니다. 3. 두 피연산자는 문자열이므로, 사전순으로 비교가 이뤄집니다. 왼쪽 피연산자의 첫 번째 글자 `"2"`는 오른쪽 피연산자의 첫 번째 글자 `"1"`보다 큽니다. 4. `null`과 `undefined`는 같습니다. 5. 일치 연산자는 형도 체크합니다. 형이 다르면 false가 반환됩니다. 6. (4)와 유사한 문제입니다. `null`은 오직 `undefined`와 같습니다. -7. 형이 다르므로 false가 반환됩니다. \ No newline at end of file +7. 형이 다르므로 false가 반환됩니다. +======= +1. Obviously, true. +2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`. +3. Again, dictionary comparison, first char `"2"` is greater than the first char `"1"`. +4. Values `null` and `undefined` equal each other only. +5. Strict equality is strict. Different types from both sides lead to false. +6. Similar to `(4)`, `null` only equals `undefined`. +7. Strict equality of different types. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md index 0619bae6c0..afdef79a9a 100644 --- a/1-js/02-first-steps/09-comparison/article.md +++ b/1-js/02-first-steps/09-comparison/article.md @@ -4,6 +4,7 @@ 자바스크립트에서 기본 수학 연산은 아래와 같은 문법을 사용해 표현할 수 있습니다. +<<<<<<< HEAD - 보다 큼·작음: a > b, a < b - 보다 크거나·작거나 같음: a >= b, a <= b - 같음(동등): `a == b`. 등호 `=`가 두 개 연달아 오는 것에 유의하세요. `a ​​= b`와 같이 등호가 하나일 때는 할당을 의미합니다. @@ -12,6 +13,16 @@ 이번 글에선 비교 시 일어나는 기이한 현상을 포함하여 다양한 자료형을 대상으로 자바스크립트가 어떻게 비교를 하는지에 대해 다룰 예정입니다. 글 말미에는 자바스크립트에서만 일어나는 '기이한' 현상을 어떻게 예방할 수 있는지에 대해서 언급해두었습니다. +======= +- Greater/less than: a > b, a < b. +- Greater/less than or equals: a >= b, a <= b. +- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment. +- Not equals: In maths the notation is , but in JavaScript it's written as a != b. + +In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities. + +At the end you'll find a good recipe to avoid "JavaScript quirks"-related issues. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 불린형 반환 @@ -57,7 +68,13 @@ alert( 'Bee' > 'Be' ); // true 4. 글자 간 비교가 끝날 때까지 이 과정을 반복합니다. 5. 비교가 종료되었고 문자열의 길이도 같다면 두 문자열은 동일하다고 결론 냅니다. 비교가 종료되었지만 두 문자열의 길이가 다르면 길이가 긴 문자열이 더 크다고 결론 냅니다. +<<<<<<< HEAD 예시의 `'Z' > 'A'`는 위 알고리즘의 첫 번째 단계에서 비교 결과가 도출됩니다. 반면, 문자열 `'Glow'`와 `'Glee'`는 복수의 문자로 이루어진 문자열이기 때문에, 아래와 같은 순서로 문자열 비교가 이뤄집니다. +======= +In the first example above, the comparison `'Z' > 'A'` gets to a result at the first step. + +The second comparison `'Glow'` and `'Glee'` needs more steps as strings are compared character-by-character: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. `G`는 `G`와 같습니다. 2. `l`은 `l`과 같습니다. 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..562802995e 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!" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](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..0879f47fdb 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: ```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!'`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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..4d5c47bebf 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 "이상과 이하"는 `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..e0e8b05b6f 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 답안은 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..707a266ad5 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' ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } else { alert( '인증에 실패하였습니다.' ); } +<<<<<<< HEAD } else if (userName == '' || userName == null) { alert( '취소되었습니다.' ); +======= +} else if (userName === '' || userName === null) { + alert( 'Canceled' ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } 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..a62f237f49 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 연산자에 '논리'라는 수식어가 붙긴 하지만 논리 연산자는 피연산자로 불린형뿐만 아니라 모든 타입의 값을 받을 수 있습니다. 연산 결과 역시 모든 타입이 될 수 있습니다. @@ -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] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 지금까진 피연산자가 불린형인 경우만을 다뤘습니다. 전통적인 방식이죠. 이제 자바스크립트에서만 제공하는 논리연산자 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시를 실행하면 두 번째 메시지만 출력됩니다. @@ -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`". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` 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..a4c91ce166 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let firstName = null; let lastName = null; let nickName = "바이올렛"; +<<<<<<< HEAD // null이나 undefined가 아닌 첫 번째 피연산자 +======= +// shows the first defined value: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시를 살펴봅시다. +<<<<<<< HEAD ```js height = height ?? 100; ``` @@ -53,6 +133,8 @@ height = height ?? 100; 이제 `??`와 `||`을 비교해봅시다. +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 연산자 우선순위 +<<<<<<< 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시를 실행하면 문법 에러가 발생합니다. @@ -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 `??`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 제약을 피하려면 괄호를 사용해주세요. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `??`는 변수에 기본값을 할당하는 용도로 사용할 수 있습니다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..0f2c5fa340 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 참고: `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..33ef3079df 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 일반적인 반복문 알고리즘은 다음과 같습니다. @@ -162,11 +186,14 @@ for (i = 0; i < 3; i++) { // 기존에 정의된 변수 사용 alert(i); // 3, 반복문 밖에서 선언한 변수이므로 사용할 수 있음 ``` - ```` +<<<<<<< HEAD ### 구성 요소 생략하기 +======= +### Skipping parts +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ````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: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js labelName: for (...) { ... @@ -342,7 +379,12 @@ labelName: for (...) { // 입력받은 값을 가지고 무언가를 함 } } +<<<<<<< HEAD alert('완료!'); +======= + +alert('Done!'); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 위 예시에서 `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) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 요약 diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md index 251cbba446..dbf775660e 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' ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 자료형의 중요성 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..8ee841c60d 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..51db4ad085 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..4d77a83368 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` @@ -137,12 +145,20 @@ alert( userName ); // 함수는 외부 변수에 접근하지 않습니다. 따 ## 매개변수 +<<<<<<< HEAD 매개변수(parameter)를 이용하면 임의의 데이터를 함수 안에 전달할 수 있습니다. 매개변수는 *인자(parameter)* 라고 불리기도 합니다. +======= +We can pass arbitrary data to functions using parameters. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시에서 함수 showMessage는 매개변수 `from` 과 `text`를 가집니다. ```js run +<<<<<<< HEAD function showMessage(*!*from, text*/!*) { // 인자: from, text +======= +function showMessage(*!*from, text*/!*) { // parameters: from, text +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(from + ': ' + text); } @@ -152,8 +168,11 @@ function showMessage(*!*from, text*/!*) { // 인자: from, text `(*)`, `(**)`로 표시한 줄에서 함수를 호출하면, 함수에 전달된 인자는 지역변수 `from`과 `text`에 복사됩니다. 그 후 함수는 지역변수에 복사된 값을 사용합니다. +<<<<<<< HEAD 예시 하나를 더 살펴봅시다. 전역 변수 `from`이 있고, 이 변수를 함수에 전달하였습니다. 함수가 `from`을 변경하지만, 변경 사항은 외부 변수 `from`에 반영되지 않았습니다. 함수는 언제나 복사된 값을 사용하기 때문입니다. +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 매개변수는 함수 선언 방식 괄호 사이에 있는 변수입니다(선언 시 쓰이는 용어). - 인수는 함수를 호출할 때 매개변수에 전달되는 값입니다(호출 시 쓰이는 용어). @@ -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 `=`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 매개변수에 값을 전달해도 그 값이 `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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function showMessage(text) { // ... *!* +<<<<<<< HEAD if (text === undefined) { // 매개변수가 생략되었다면 text = '빈 문자열'; +======= + if (text === undefined) { // if the parameter is missing + text = 'empty message'; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } */!* @@ -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'; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ... } ``` +<<<<<<< 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 함수 == 주석 @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 함수 이름을 지을 땐 아래와 같은 규칙을 따르는 것이 좋습니다. 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..e1806a4ebb 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 함수는 값이기 때문에 `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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 콜백 함수 @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **함수 `ask`의 인수, `showOk`와 `showCancel`은 *콜백 함수* 또는 *콜백*이라고 불립니다.** 함수를 함수의 인수로 전달하고, 필요하다면 인수로 전달한 그 함수를 "나중에 호출(called back)"하는 것이 콜백 함수의 개념입니다. 위 예시에선 사용자가 "yes"라고 대답한 경우 `showOk`가 콜백이 되고, "no"라고 대답한 경우 `showCancel`가 콜백이 됩니다. +<<<<<<< HEAD 아래와 같이 함수 표현식을 사용하면 코드 길이가 짧아집니다. +======= +We can use Function Expressions to write an equivalent, shorter function: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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" `=`: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // | *!* welcome(); // / (실행) @@ -289,8 +349,13 @@ if (age < 18) { } else { +<<<<<<< HEAD function welcome() { alert("안녕하세요!"); +======= + function welcome() { + alert("Greetings!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } } @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 함수 선언문을 사용하면 가독성도 좋아집니다. 코드에서 `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..848bee57f2 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 함수의 축약 버전이라고 할 수 있죠. ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 인수가 하나밖에 없다면 인수를 감싸는 괄호를 생략할 수 있습니다. 괄호를 생략하면 코드 길이를 더 줄일 수 있습니다. @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래와 같이 말이죠. @@ -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" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* }; @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..a116b17388 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 참고로, 추후에 배우게 될 클래스와 같은 몇몇 모던 기능은 엄격 모드를 자동으로 활성화합니다. @@ -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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 세 함수는 모두 *모달* 창을 띄워주는데, 모달 창이 닫히기 전까지 코드 실행이 중지됩니다. 사용자는 모달 창 외에 페이지에 있는 그 무엇과도 상호작용할 수 없습니다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 조건부 연산자 : 조건부 연산자는 자바스크립트 연산자 중 유일하게 매개변수가 3개인 연산자입니다. `cond ? resultA : resultB`와 같은 형태로 사용하고, `cond`가 truthy면 `resultA`를, 아니면 `resultB`를 반환합니다. @@ -256,7 +276,11 @@ switch (age) { 3. 화살표 함수: ```js +<<<<<<< HEAD // 화살표(=>) 우측엔 표현식이 있음 +======= + // expression on the right side +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자세한 내용은 에서 살펴보시기 바랍니다. 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..cc96fa8b64 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,8 @@ +<<<<<<< HEAD # Chrome으로 디버깅하기 +======= +# Debugging in the browser +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 좀 더 복잡한 코드를 작성하기 전에, 디버깅이란 것에 대해 이야기해봅시다. @@ -38,7 +42,11 @@ Sources 패널은 크게 세 개의 영역으로 구성됩니다. 콘솔 창에 구문(statement)을 입력하고 실행하면 아랫줄에 실행 결과가 출력됩니다. +<<<<<<< HEAD `1+2`를 입력하면 `3`이 출력되고, `hello("debugger")`를 입력하면 `undefined`가 출력되죠. `undefined`가 출력되는 이유는 `hello("debugger")`가 아무것도 반환하지 않기 때문입니다. +======= +For example, here `1+2` results in `3`, while the function call `hello("debugger")` returns nothing, so the result is `undefined`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](chrome-sources-console.svg) @@ -62,13 +70,22 @@ Sources 패널 우측의 디버깅 영역을 보면 중단점 목록을 확인 - 마우스 오른쪽 버튼을 클릭했을 때 나오는 'Remove breakpoint' 옵션을 통해 중단점을 삭제할 수도 있습니다. - 이 외에도 다양한 기능이 있습니다. +<<<<<<< HEAD ```smart header="조건부 중단점" 줄 번호에 커서를 옮긴 후 마우스 오른쪽 버튼을 클릭하면 *조건부 중단점(conditional breakpoint)* 을 설정할 수 있습니다. `Add conditional breakpoint`를 클릭했을 때 뜨는 작은 창에 표현식을 입력하면, 표현식이 참인 경우에만 실행을 중지시킬 수 있습니다. +======= +```smart header="Conditional breakpoints" +*Right click* on the line number allows to create a *conditional* breakpoint. It only triggers when the given expression, that you should provide when you create it, is truthy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 조건부 중단점을 설정하면 변수에 특정 값이 할당될 때나 함수의 매개 변수에 특정 값이 들어올 때만 실행을 중단시킬 수 있어 디버깅 시 유용하게 활용할 수 있습니다. ``` +<<<<<<< HEAD ## debugger 명령어 +======= +## The command "debugger" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시처럼 스크립트 내에 `debugger` 명령어를 적어주면 중단점을 설정한 것과 같은 효과를 봅니다. @@ -84,8 +101,12 @@ function hello(name) { } ``` +<<<<<<< HEAD debugger 명령어를 사용하면 브라우저를 켜 개발자 도구를 열고 소스 코드 영역을 띄워 중단점을 설정하는 수고를 하지 않아도 됩니다. 에디터를 떠나지 않고도 중단점을 설정할 수 있기 때문에 편리하죠. +======= +Such command works only when the development tools are open, otherwise the browser ignores it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 멈추면 보이는 것들 @@ -99,7 +120,11 @@ debugger 명령어를 사용하면 브라우저를 켜 개발자 도구를 열 1. **`Watch` -- 표현식을 평가하고 결과를 보여줍니다.** +<<<<<<< HEAD Add Expression 버튼 `+`를 클릭해 원하는 표현식을 입력한 후 `key:Enter`를 누르면 중단 시점의 값을 보여줍니다. 입력한 표현식은 실행 과정 중에 계속해서 재평가됩니다. +======= + You can click the plus `+` and input an expression. The debugger will show its value, automatically recalculating it in the process of execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 2. **`Call Stack` -- 코드를 해당 중단점으로 안내한 실행 경로를 역순으로 표시합니다.** @@ -137,11 +162,20 @@ debugger 명령어를 사용하면 브라우저를 켜 개발자 도구를 열 -- 'Step over': 다음 명령어를 실행하되, *함수 안으로 들어가진 않음* (단축키 `key:F10`) : 'Step'과 유사하지만, 다음 문이 함수 호출일 때 'Step'과는 다르게 동작합니다(`alert` 같은 내장함수에는 해당하지 않고, 직접 작성한 함수일 때만 동작이 다릅니다). +<<<<<<< HEAD 'Step'은 함수 내부로 들어가 함수 본문 첫 번째 줄에서 실행을 멈춥니다. 반면 'Step over'는 보이지 않는 곳에서 중첩 함수를 실행하긴 하지만 함수 내로 진입하지 않습니다. 실행은 함수 실행이 끝난 후에 즉시 멈춥니다. 'Step over'은 함수 호출 시 내부에서 어떤 일이 일어나는지 궁금하지 않을 때 유용합니다. +======= + -- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`. +: Similar to the previous "Step" command, but behaves differently if the next statement is a function call (not a built-in, like `alert`, but a function of our own). + + If we compare them, the "Step" command goes into a nested function call and pauses the execution at its first line, while "Step over" executes the nested function call invisibly to us, skipping the function internals. + + The execution is then paused immediately after that function call. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -- 'Step into' (단축키 `key:F11`) : 'Step'과 유사한데, 비동기 함수 호출에서 'Step'과는 다르게 동작합니다. 이제 막 자바스크립트를 배우기 시작한 분이라면 비동기 호출에 대해 아직 배우지 않았기 때문에 'Step'과 'Step into'의 차이를 몰라도 괜찮습니다. @@ -157,8 +191,13 @@ debugger 명령어를 사용하면 브라우저를 켜 개발자 도구를 열 -- 예외 발생 시 코드를 자동 중지시켜주는 기능을 활성화/비활성화 : 활성화되어 있고, 개발자 도구가 열려있는 상태에서 스크립트 실행 중에 에러가 발생하면 실행이 자동으로 멈춥니다. 실행이 중단되었기 때문에 변수 등을 조사해 어디서 에러가 발생했는지 찾을 수 있게 됩니다. 개발하다가 에러와 함께 스크립트가 죽었다면 디버거를 열고 이 옵션을 활성화한 후, 페이지를 새로 고침하면 에러가 발생한 곳과 에러 발생 시점의 컨텍스트를 확인할 수 있습니다. +<<<<<<< HEAD ```smart header="Continue to here 옵션" 특정 줄에서 마우스 오른쪽 버튼을 클릭해 컨텍스트 메뉴를 열면 "Continue to here"라는 옵션을 볼 수 있습니다. +======= + -- enable/disable automatic pause in case of an error. +: When enabled, if the developer tools is open, an error during the script execution automatically pauses it. Then we can analyze variables in the debugger to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 중단점을 설정하기는 귀찮은데 해당 줄에서 실행을 재개하고 싶을 때 아주 유용한 옵션입니다. ``` @@ -187,7 +226,11 @@ for (let i = 0; i < 5; i++) { 2. `debugger`문 만났을 때 3. 에러가 발생했을 때(개발자 도구가 열려있고 버튼이 '활성화'되어있는 경우) +<<<<<<< HEAD 스크립트 실행이 중지되면 중단 시점을 기준으로 변수에 어떤 값이 들어가 있는지 확인할 수 있습니다. 또한 단계별로 코드를 실행해 가며, 어디서 문제가 발생했는지 추적할 수도 있습니다. 이런 식으로 디버깅이 진행됩니다. +======= +When paused, we can debug: examine variables and trace the code to see where the execution goes wrong. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 개발자 도구는 여기서 소개한 기능 이외의 다양한 기능을 지원합니다. Google에서 제공하는 개발자 도구 공식 매뉴얼은 에서 확인할 수 있습니다. 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..5d82dff5c8 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -116,7 +116,11 @@ if ( 탭 대신 스페이스를 이용했을 때의 장점 중 하나는 들여쓰기 정도를 좀 더 유연하게 변경할 수 있다는 점입니다. +<<<<<<< HEAD 아래 예시처럼 인수 모두의 위치를 여는 괄호와 맞출 수 있죠. +======= + For instance, we can align the parameters with the opening bracket, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify show(parameters, @@ -301,11 +305,19 @@ Linter라는 도구를 사용하면 내가 작성한 코드가 스타일 가이 유명 linter: +<<<<<<< HEAD - [JSLint](http://www.jslint.com/) -- 역사가 오래된 linter - [JSHint](http://www.jshint.com/) -- JSLint보다 세팅이 좀 더 유연한 linter - [ESLint](http://eslint.org/) -- 가장 최근에 나온 linter 위 linter 모두 훌륭한 기능을 제공합니다. 글쓴이는 [ESLint](http://eslint.org/)를 사용하고 있습니다. +======= +- [JSLint](https://www.jslint.com/) -- one of the first linters. +- [JSHint](https://jshint.com/) -- more settings than JSLint. +- [ESLint](https://eslint.org/) -- probably the newest one. + +All of them can do the job. The author uses [ESLint](https://eslint.org/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 대부분의 linter는 플러그인 형태로 유명 에디터와 통합해 사용할 수 있습니다. 원하는 스타일을 설정하는 것 역시 가능합니다. @@ -328,14 +340,18 @@ ESLint를 사용한다고 가정했을 때 아래 절차를 따르면 에디터 }, "rules": { "no-console": 0, - "indent": ["warning", 2] + "indent": 2 } } ``` 위 예시에서 지시자 `"extends"`는 "eslint:recommended"를 기반으로 이를 확장해 스타일 가이드를 설정하겠다는 걸 의미합니다. 이렇게 세팅한 이후에 자신만의 스타일을 설정하면 됩니다. +<<<<<<< HEAD 스타일 규칙을 모아놓은 세트를 웹에서 다운로드해 이를 기반으로 스타일 가이드를 설정하는 것도 가능합니다. 설치 방법에 대한 자세한 내용은 에서 확인해 보시기 바랍니다. +======= +It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 몇몇 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..5ba87da1a7 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -143,7 +143,11 @@ function pow(x, n) { [WebStorm](https://www.jetbrains.com/webstorm/) 등의 다양한 에디터는 이런 주석을 이용해 자동 완성 기능, 자동 에러 검출 기능 등을 제공합니다. +<<<<<<< HEAD [JSDoc 3](https://github.com/jsdoc3/jsdoc)이나 기타 유사한 툴을 사용하면 주석으로 HTML 문서를 만들 수 있습니다. 자세한 정보는 에서 확인하시기 바랍니다. +======= +Also, there are tools like [JSDoc 3](https://github.com/jsdoc/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 왜 이런 방법으로 문제를 해결했는지를 설명하는 주석 : 무엇이 적혀있는지는 중요합니다. 그런데 무슨 일이 일어나고 있는지 파악하려면 무엇이 *적혀있지 않은 지*가 더 중요할 수 있습니다. '왜 이 문제를 이런 방법으로 해결했나?'라는 질문에 코드는 답을 해 줄 수 없기 때문입니다. 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..8e4210d21b 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -1,9 +1,14 @@ # 닌자 코드 +<<<<<<< HEAD ```quote author="공자" 생각 없이 배우기만 하면 얻는 것이 없고,
생각만 하고 배우지 않으면 오류나 독단에 빠질 위험이 있다. +======= +```quote author="Confucius (Analects)" +Learning without thought is labor lost; thought without learning is perilous. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 닌자라 불리던 전설 속 개발자들은 유지보수 담당 개발자를 혹독하게 훈련하고자 (아래에서 소개해 드릴) 다양한 편법을 사용하곤 했습니다. @@ -106,10 +111,15 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ## 동의어 사용하기 +<<<<<<< HEAD ```quote author="공자" 모든 일 중 가장 어려운 일은
어두운 방에서 검은 고양이를 찾는 일이다.

특히 그 방에 고양이가 없을 때에. +======= +```quote author="Laozi (Tao Te Ching)" +The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` *유사한* 뜻을 가진 단어 여러 개를 *같은*걸 명명하는 데 사용해서 당신의 풍부한 어휘력을 은연중에 드러내 봅시다. 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..ef28b61ab0 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -2,7 +2,11 @@ 테스트 자동화는 앞으로 풀어야 할 과제에서뿐만 아니라 현업에서도 광범위하게 쓰입니다. +<<<<<<< HEAD ## 테스트는 왜 해야 하는가? +======= +## Why do we need tests? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 함수를 하나 만들고 있다고 해 봅시다. 대부분 매개변수-결과 관계를 중심으로 어떻게 코드를 작성할지 구상하실 겁니다. @@ -51,7 +55,11 @@ describe("pow", function() { 스펙은 세 가지 주요 구성 요소로 이루어집니다. `describe("title", function() { ... })` +<<<<<<< HEAD : 구현하고자 하는 기능에 대한 설명이 들어갑니다. 우리 예시에선 함수 `pow`가 어떤 동작을 하는지에 대한 설명이 들어갈 겁니다. `it` 블록을 한데 모아주는 역할도 합니다. +======= +: What functionality we're describing? In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `it("유스 케이스 설명", function() { ... })` : `it`의 첫 번째 인수엔 특정 유스 케이스에 대한 설명이 들어갑니다. 이 설명은 *누구나 읽을 수 있고 이해할 수 있는 자연어*로 적어줍니다. 두 번째 인수엔 유스 케이스 테스트 함수가 들어갑니다. @@ -67,6 +75,7 @@ describe("pow", function() { 실제 개발에 착수하면 아래와 같은 순서로 개발이 진행됩니다. +<<<<<<< HEAD 1. 명세서 초안을 작성합니다. 초안엔 기본적인 테스트도 들어갑니다. 2. 명세서 초안을 보고 코드를 작성합니다. 3. 코드가 작동하는지 확인하기 위해 [Mocha](http://mochajs.org/)라 불리는 테스트 프레임워크를 사용해 명세서를 실행합니다.(Mocha에 대해선 아래에서 다룰 예정입니다.) 이때, 코드가 잘못 작성되었다면 에러가 출력됩니다. 개발자는 테스트를 모두 통과해 에러가 더는 출력되지 않을 때까지 코드를 수정합니다. @@ -74,20 +83,39 @@ describe("pow", function() { 5. 명세서에 지금까진 고려하지 않았던 유스케이스 몇 가지를 추가합니다. 테스트가 실패하기 시작할 겁니다. 6. 세 번째 단계로 돌아가 테스트를 모두 통과할 때까지 코드를 수정합니다. 7. 기능이 완성될 때까지 3~6단계를 반복합니다. +======= +1. An initial spec is written, with tests for the most basic functionality. +2. An initial implementation is created. +3. To check whether it works, we run the testing framework [Mocha](https://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. +4. Now we have a working initial implementation with tests. +5. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail. +6. Go to 3, update the implementation till tests give no errors. +7. Repeat steps 3-6 till the functionality is ready. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 위와 같은 방법은 *반복적인(iterative)* 성격을 지닙니다. 명세서를 작성하고 실행한 후 테스트를 모두 통과할 때까지 코드를 작성하고, 또 다른 테스트를 추가해 앞의 과정을 반복하니까요. 이렇게 하다 보면 종래에는 완전히 동작하는 코드와 테스트 둘 다를 확보하게 됩니다. 이제 실제 사례에 위 개발 프로세스를 적용해 보겠습니다. +<<<<<<< HEAD 함수 `pow`의 스펙 초안은 이미 위에서 작성했으므로, 첫 번째 단계는 이미 끝난 상황입니다. 코드를 본격적으로 작성하기 전에 잠시 자바스크립트 라이브러리 몇 가지를 사용해 테스트를 실행해 보겠습니다. 지금 상태에선 테스트 모두가 실패할 텐데 그런데도 실행해 보는 이유는 테스트가 실제로 돌아가는지 확인하기 위해서입니다. +======= +The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use a few JavaScript libraries to run the tests, just to see that they are working (they will all fail). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 스펙 실행하기 본 튜토리얼에선 총 3개의 라이브러리를 사용해 테스트를 진행해보겠습니다. 각 라이브러리에 대한 설명은 아래와 같습니다. +<<<<<<< HEAD - [Mocha](http://mochajs.org/) -- 핵심 테스트 프레임워크로, `describe`, `it`과 같은 테스팅 함수와 테스트 실행 관련 주요 함수를 제공합니다. - [Chai](http://chaijs.com) -- 다양한 assertion을 제공해 주는 라이브러리입니다. 우리 예시에선 `assert.equal` 정도만 사용해 볼 예정입니다. - [Sinon](http://sinonjs.org/) -- 함수의 정보를 캐내는 데 사용되는 라이브러리로, 내장 함수 등을 모방합니다. 본 챕터에선 사용하지 않고, 다른 챕터에서 실제로 사용해 볼 예정입니다. +======= +- [Mocha](https://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. +- [Chai](https://www.chaijs.com/) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. +- [Sinon](https://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 세 라이브러리 모두, 브라우저나 서버 사이드 환경을 가리지 않고 사용 가능합니다. 여기선 브라우저 환경을 가정하고 사용해 보겠습니다. @@ -338,6 +366,7 @@ describe("pow", function() { ```smart header="다양한 assertion" 위에서 사용한 `assert.isNaN`은 `NaN`인지 아닌지를 확인해줍니다. +<<<<<<< HEAD [Chai](http://chaijs.com)는 이 외에도 다양한 assertion을 지원합니다. - `assert.equal(value1, value2)` -- `value1`과 `value2`의 동등성을 확인합니다(`value1 == value2`). @@ -346,6 +375,16 @@ describe("pow", function() { - `assert.isTrue(value)` -- `value`가 `true`인지 확인합니다(`value === true`). - `assert.isFalse(value)` -- `value`가 `false`인지 확인합니다(`value === false`). - 이 외의 다양한 assertion은 [docs](http://chaijs.com/api/assert/)에서 확인할 수 있습니다. +======= +There are other assertions in [Chai](https://www.chaijs.com/) as well, for instance: + +- `assert.equal(value1, value2)` -- checks the equality `value1 == value2`. +- `assert.strictEqual(value1, value2)` -- checks the strict equality `value1 === value2`. +- `assert.notEqual`, `assert.notStrictEqual` -- inverse checks to the ones above. +- `assert.isTrue(value)` -- checks that `value === true` +- `assert.isFalse(value)` -- checks that `value === false` +- ...the full list is in the [docs](https://www.chaijs.com/api/assert/) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 새롭게 추가한 테스트를 통과할 수 있도록 `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..4db61e7c9e 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,10 +1,17 @@ +<<<<<<< HEAD # 폴리필 자바스크립트는 끊임없이 진화하는 언어입니다. 새로운 제안(proposal)이 정기적으로 등록, 분석되고, 가치가 있다고 판단되는 제안은 에 추가됩니다. 그리고 궁극적으로 [명세서(specification)](http://www.ecma-international.org/publications/standards/Ecma-262.htm)에 등록됩니다. +======= +# Polyfills and transpilers + +The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트 엔진을 만드는 각 조직은 나름대로 우선순위를 매겨 명세서 내 어떤 기능을 먼저 구현할지 결정합니다. 명세서에 등록된 기능보다 초안(draft)에 있는 제안을 먼저 구현하기로 결정하는 경우도 있습니다. 구현 난도가 높아서 이런 결정을 내리는 경우도 있지만, 구미를 당기지 않아 이런 결정을 내리기도 합니다. +<<<<<<< HEAD 엔진이 표준 전체를 지원하지 않고 일부만 지원하는 건 흔한 일이죠. 엔진별로 어떤 기능을 지원하고 있는지는 에서 확인할 수 있습니다. 표가 상당히 큰데, 각 기능에 대해선 차근차근 배울 예정이니 너무 겁먹지 않으셔도 됩니다. @@ -52,3 +59,87 @@ alert('우측 상단 모서리에 있는 "재생" 버튼을 눌러 스크립트 ``` Google Chrome은 모든 브라우저 중 대개 가장 먼저 최신 기능을 지원합니다. 트랜스파일러 없이 최신 기능을 사용할 수 있기 때문에 Chrome은 데모용으로 사용하기 좋습니다. 그런데 다른 브라우저들도 많이 뒤처지는 편은 아니니 안심하고 사용하셔도 될 것 같습니다. +======= +So it's quite common for an engine to implement only part of the standard. + +A good page to see the current state of support for language features is (it's big, we have a lot to study yet). + +As programmers, we'd like to use most recent features. The more good stuff - the better! + +On the other hand, how to make our modern code work on older engines that don't understand recent features yet? + +There are two tools for that: + +1. Transpilers. +2. Polyfills. + +Here, in this chapter, our purpose is to get the gist of how they work, and their place in web development. + +## Transpilers + +A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that translates source code to another source code. It can parse ("read and understand") modern code and rewrite it using older syntax constructs, so that it'll also work in outdated engines. + +E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" `??`. So, if a visitor uses an outdated browser, it may fail to understand the code like `height = height ?? 100`. + +A transpiler would analyze our code and rewrite `height ?? 100` into `(height !== undefined && height !== null) ? height : 100`. + +```js +// before running the transpiler +height = height ?? 100; + +// after running the transpiler +height = (height !== undefined && height !== null) ? height : 100; +``` + +Now the rewritten code is suitable for older JavaScript engines. + +Usually, a developer runs the transpiler on their own computer, and then deploys the transpiled code to the server. + +Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there. + +Modern project build systems, such as [webpack](https://webpack.js.org/), provide a means to run a transpiler automatically on every code change, so it's very easy to integrate into the development process. + +## Polyfills + +New language features may include not only syntax constructs and operators, but also built-in functions. + +For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23)` returns `1`. + +In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail. + +As we're talking about new functions, not syntax changes, there's no need to transpile anything here. We just need to declare the missing function. + +A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. + +For this particular case, the polyfill for `Math.trunc` is a script that implements it, like this: + +```js +if (!Math.trunc) { // if no such function + // implement it + Math.trunc = function(number) { + // Math.ceil and Math.floor exist even in ancient JavaScript engines + // they are covered later in the tutorial + return number < 0 ? Math.ceil(number) : Math.floor(number); + }; +} +``` + +JavaScript is a highly dynamic language. Scripts may add/modify any function, even built-in ones. + +One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need. + +## Summary + +In this chapter we'd like to motivate you to study modern and even "bleeding-edge" language features, even if they aren't yet well-supported by JavaScript engines. + +Just don't forget to use a transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). They'll ensure that the code works. + +For example, later when you're familiar with JavaScript, you can setup a code build system based on [webpack](https://webpack.js.org/) with the [babel-loader](https://github.com/babel/babel-loader) plugin. + +Good resources that show the current state of support for various features: +- - for pure JavaScript. +- - for browser-related functions. + +P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md index 5117e2ebf9..2466d9fbd9 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md @@ -2,9 +2,15 @@ importance: 3 --- +<<<<<<< HEAD # 프로퍼티 값 두 배로 부풀리기 객체 `obj`의 프로퍼티 값이 숫자인 경우 그 값을 `두 배` 해주는 함수 `multiplyNumeric(obj)`을 만들어보세요. +======= +# Multiply numeric property values by 2 + +Create a function `multiplyNumeric(obj)` that multiplies all numeric property values of `obj` by `2`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 63412c01fa..b0920a5dd9 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -44,7 +44,11 @@ let user = { // 객체 ![user object](object-user.svg) +<<<<<<< HEAD 서랍장에 파일을 추가하고 뺄 수 있듯이 개발자는 프로퍼티를 추가, 삭제할 수 있습니다. +======= +We can add, remove and read files from it at any time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 점 표기법(dot notation)을 이용하면 프로퍼티 값을 읽는 것도 가능합니다. @@ -62,7 +66,11 @@ user.isAdmin = true; ![user object 2](object-user-isadmin.svg) +<<<<<<< HEAD `delete` 연산자를 사용하면 프로퍼티를 삭제할 수 있습니다. +======= +To remove a property, we can use the `delete` operator: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js delete user.age; @@ -92,6 +100,7 @@ let user = { ``` 이런 쉼표를 'trailing(길게 늘어지는)' 혹은 'hanging(매달리는)' 쉼표라고 부릅니다. 이렇게 끝에 쉼표를 붙이면 모든 프로퍼티가 유사한 형태를 보이기 때문에 프로퍼티를 추가, 삭제, 이동하는 게 쉬워집니다. +<<<<<<< HEAD ````smart header="상수 객체는 수정될 수 있습니다." 주의하세요. `const`로 선언된 객체는 수정될 수 있습니다. @@ -117,6 +126,9 @@ alert(user.name); // Pete ```` ## 대괄호 표기법 +======= +## Square brackets +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 여러 단어를 조합해 프로퍼티 키를 만든 경우엔, 점 표기법을 사용해 프로퍼티 값을 읽을 수 없습니다. @@ -225,13 +237,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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 이유로 프로퍼티 이름이 확정된 상황이고, 단순한 이름이라면 처음엔 점 표기법을 사용하다가 뭔가 복잡한 상황이 발생했을 때 대괄호 표기법으로 바꾸는 경우가 많습니다. ## 단축 프로퍼티 +<<<<<<< HEAD 실무에선 프로퍼티 값을 기존 변수에서 받아와 사용하는 경우가 종종 있습니다. +======= +In real code, we often use existing variables as values for property names. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -276,7 +296,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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그런데 객체 프로퍼티엔 이런 제약이 없습니다. @@ -349,7 +373,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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { age: 30 }; @@ -379,7 +407,11 @@ alert( "test" in obj ); // `in`을 사용하면 프로퍼티 유무를 제대로 `undefined`는 변수는 정의되어 있으나 값이 할당되지 않은 경우에 쓰기 때문에 프로퍼티 값이 `undefined`인 경우는 흔치 않습니다. 값을 '알 수 없거나(unknown)' 값이 '비어 있다는(empty)' 것을 나타낼 때는 주로 `null`을 사용합니다. 위 예시에서 `in` 연산자는 자리에 어울리지 않는 초대손님처럼 보이네요. +<<<<<<< HEAD ## 'for..in' 반복문 +======= +## The "for..in" loop [#forin] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `for..in` 반복문을 사용하면 객체의 모든 키를 순회할 수 있습니다. `for..in`은 앞서 학습했던 `for(;;)` 반복문과는 완전히 다릅니다. @@ -436,7 +468,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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그런데 코드를 실행해 보면 예상과는 전혀 다른 결과가 출력됩니다. @@ -448,6 +484,7 @@ for (let code in codes) { ````smart header="정수 프로퍼티? 그게 뭔가요?" '정수 프로퍼티'라는 용어는 변형 없이 정수에서 왔다 갔다 할 수 있는 문자열을 의미합니다. +<<<<<<< HEAD 문자열 "49"는 정수로 변환하거나 변환한 정수를 다시 문자열로 바꿔도 변형이 없기 때문에 정수 프로퍼티입니다. 하지만 '+49'와 '1.2'는 정수 프로퍼티가 아닙니다. ```js run @@ -455,6 +492,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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` @@ -503,9 +550,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]`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 객체엔 다음과 같은 추가 연산자를 사용할 수 있습니다. - 프로퍼티를 삭제하고 싶을 때: `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..d3d6369f8b 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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 참조에 의한 비교 +<<<<<<< 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 객체가 할당된 변수를 복사하면 동일한 객체에 대한 참조 값이 하나 더 만들어진다는 걸 배웠습니다. +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래와 같이 말이죠. @@ -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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문법과 동작 방식은 다음과 같습니다. ```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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 중첩 객체 복사 +<<<<<<< HEAD 지금까진 `user`의 모든 프로퍼티가 원시값인 경우만 가정했습니다. 그런데 프로퍼티는 다른 객체에 대한 참조 값일 수도 있습니다. 이 경우는 어떻게 해야 할까요? +======= +Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래와 같이 말이죠. ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그리고 복사된 참조를 이용한 모든 작업(프로퍼티 추가·삭제 등)은 동일한 객체를 대상으로 이뤄집니다. +<<<<<<< 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..80e09a1706 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) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 값은 *루트(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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트 엔진 내에선 [가비지 컬렉터(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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 연결된 객체 @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 최적화 기법: +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이 외에도 다양한 최적화 기법과 가비지 컬렉션 알고리즘이 있습니다. 다양한 기법과 알고리즘을 소개해 드리고 싶지만, 엔진마다 세부 사항이나 기법이 다르기 때문에 여기서 멈추도록 하겠습니다. 엔진이 발전하면 기법도 달라지기 때문에 학습해야 할 이유가 진짜 없다면 '심화' 학습은 그리 가치 있지 않다고 생각합니다. 순수한 호기심 때문이라면 물론 괜찮습니다. 이런 분들을 위해 아래에 링크를 몇 개를 소개해놓았습니다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 모던 자바스크립트 엔진은 좀 더 발전된 가비지 컬렉션 알고리즘을 사용합니다. 어떤 알고리즘을 사용하는지 궁금하다면 '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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..abcb56a08a 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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..ef3c9999c7 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..2a54a3a2b8 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*. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 위 예시에선 `user`에 할당된 `sayHi`가 메서드이죠. @@ -50,8 +58,13 @@ let user = { *!* // 함수 선언 function sayHi() { +<<<<<<< HEAD alert("안녕하세요!"); }; +======= + alert("Hello!"); +} +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // 선언된 함수를 메서드로 등록 user.sayHi = sayHi; @@ -81,7 +94,11 @@ user = { // 단축 구문을 사용하니 더 깔끔해 보이네요. user = { *!* +<<<<<<< HEAD sayHi() { // "sayHi: function()"과 동일합니다. +======= + sayHi() { // same as "sayHi: function(){...}" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 메서드와 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 +*/!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래와 같이 코드를 작성해도 문법 에러가 발생하지 않습니다. 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..101c143530 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()`? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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..c102574810 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: 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..4d6068b67c 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `'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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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..f6052ef7e3 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이제 막 자바스크립트를 배우기 시작했다면 옵셔널 체이닝이 등장하게 된 배경 상황을 직접 겪어보지 않았을 겁니다. 몇 가지 사례를 재현하면서 왜 옵셔널 체이닝이 등장했는지 알아봅시다. +<<<<<<< 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 옵셔널 체이닝의 등장 +<<<<<<< HEAD `?.`은 `?.`'앞'의 평가 대상이 `undefined`나 `null`이면 평가를 멈추고 `undefined`를 반환합니다. +======= +The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **설명이 장황해지지 않도록 지금부턴 평가후 결과가 `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 `?.`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 `?.`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ````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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 단락 평가 `?.`는 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈춥니다. 참고로 이런 평가 방법을 단락 평가(short-circuit)라고 부릅니다. +<<<<<<< HEAD 그렇기 때문에 함수 호출을 비롯한 `?.` 오른쪽에 있는 부가 동작은 `?.`의 평가가 멈췄을 때 더는 일어나지 않습니다. +======= +So, if there are any further function calls or operations to the right of `?.`, they won't be made. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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++ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(x); // 0, x는 증가하지 않습니다. ``` +<<<<<<< HEAD ## ?.()와 ?.[] +======= +## Other variants: ?.(), ?.[] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `?.`은 연산자가 아닙니다. `?.`은 함수나 대괄호와 함께 동작하는 특별한 문법 구조체(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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `.`대신 대괄호 `[]`를 사용해 객체 프로퍼티에 접근하는 경우엔 `?.[]`를 사용할 수도 있습니다. 위 예시와 마찬가지로 `?.[]`를 사용하면 객체 존재 여부가 확실치 않은 경우에도 안전하게 프로퍼티를 읽을 수 있습니다. ```js run +let key = "firstName"; + let user1 = { firstName: "Violet" }; +<<<<<<< HEAD let user2 = null; // user2는 권한이 없는 사용자라고 가정해봅시다. let key = "firstName"; +======= +let user2 = null; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 여러 예시를 통해 살펴보았듯이 옵셔널 체이닝 문법은 꽤 직관적이고 사용하기도 쉽습니다. `?.` 왼쪽 평가 대상이 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md index c0fd2f3ff4..5aaab9eed8 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 심볼 @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 설명이 같은 심볼 두 개를 만들고 이를 비교해보겠습니다. 동일 연산자(`==`)로 비교 시 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시에서 `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: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 서드파티 코드에서 가지고 온 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 제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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그런데 [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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### 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)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 특징을 이용하면 원하는 것을 객체 안에 '은밀하게' 숨길 수 있습니다. 외부 스크립트에선 우리가 숨긴 것을 절대 볼 수 없습니다. 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..aa46e14eb4 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 챕터에선 객체의 형 변환은 다루지 않았습니다. 원시형 자료가 어떻게 문자, 숫자, 논리형으로 변환되는지만 알아보았죠. 이젠 메서드와 심볼에 대한 지식을 갖추었으니 본격적으로 이 공백을 메꿔봅시다. +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -## 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): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `"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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 동등 연산자 `==`를 사용해 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교할 때도, 객체를 어떤 자료형으로 바꿔야 할지 확신이 안 서므로 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **자바스크립트는 형 변환이 필요할 때, 아래와 같은 알고리즘에 따라 원하는 메서드를 찾고 호출합니다.** +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 일반 객체는 기본적으로 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 `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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 위에서 소개해드린 세 개의 메서드는 '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"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 확신할 수 있는 단 한 가지는 객체가 아닌 원시값을 반환해 준다는 것뿐입니다. ```smart header="과거의 잔재" `toString`이나 `valueOf`가 객체를 반환해도 에러가 발생하지 않습니다. 다만 이때는 반환 값이 무시되고, 메서드 자체가 존재하지 않았던 것처럼 동작합니다. 이렇게 동작하는 이유는 과거 자바스크립트엔 '에러'라는 개념이 잘 정립되어있지 않았기 때문입니다. +<<<<<<< HEAD 반면에 `Symbol.toPrimitive`는 *무조건* 원시자료를 반환해야 합니다. 그렇지 않으면 에러가 발생합니다. +======= +In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 추가 형 변환 @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 요약 원시값을 기대하는 내장 함수나 연산자를 사용할 때 객체-원시형으로의 형 변환이 자동으로 일어납니다. +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 객체-원시형 변환엔 다음 알고리즘이 적용됩니다. +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..c964834fab 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? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..f46c9a6a69 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 조금 어색해 보이지만, 자바스크립트 창안자는 아래와 같은 방법을 사용해 해결책을 모색하였습니다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 인수로 받은 문자열의 모든 글자를 대문자로 바꿔주는 메서드 [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: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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..5dac6ad39b 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 3b04f080a0..42ea55c840 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 . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자, 그럼 일반적인 숫자에 대해서 자세히 알아봅시다. @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +```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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 `'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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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)`. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 어림수 구하기 @@ -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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 부정확한 계산 +<<<<<<< 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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 1e500 ); // Infinity @@ -190,7 +286,11 @@ alert( 1e500 ); // Infinity 원인을 이해하려면 집중이 필요하긴 하지만, 꽤 자주 발생하는 현상인 정밀도 손실(loss of precision)도 있습니다. +<<<<<<< HEAD 예시를 살펴봅시다. +======= +Consider this (falsy!) equality test: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 왜 이런 일이 발생하는 걸까요? 숫자는 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)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 문제를 해결하는 방법은 없을까요? 물론 있습니다. 가장 신뢰할만한 방법은 [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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 구현을 하다 보면 무한 소수가 나오는 경우를 완전히 차단해야 하는 경우가 생기곤 합니다. 달러가 아닌 센트 단위로 물품 가격을 저장하는 쇼핑몰을 담당하고 있는데, 행사 때문에 가격을 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이 두 에지 케이스를 제외하곤, `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)). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 내장 함수 `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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..7bab69a45b 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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..b0e449a475 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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..9918d18588 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 특수 기호 @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str1 = "Hello\nWorld"; // '줄 바꿈 기호'를 사용해 두 줄짜리 문자열을 만듦 @@ -74,12 +87,17 @@ World`; alert(str1 == str2); // true ``` +<<<<<<< HEAD 자바스크립트엔 줄 바꿈 문자를 비롯한 다양한 '특수' 문자들이 있습니다. 특수 문자 목록: +======= +There are other, less common special characters: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 | 특수 문자 | 설명 | |-----------|-------------| +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 문자열의 길이 @@ -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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 특정 글자에 접근하기 +<<<<<<< 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = `Hello`; +<<<<<<< HEAD alert( str[1000] ); // undefined alert( str.charAt(1000) ); // '' (빈 문자열) +======= +alert( str[-2] ); // undefined +alert( str.at(-2) ); // l +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 부분 문자열 `"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` 메서드(아래에서 배움)를 사용해 부분 문자열 포함 여부를 검사합니다. 이런 기법은 오래된 자바스크립트에서만 볼 수 있습니다. +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 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" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 부분 문자열 추출하기 @@ -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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 | 메서드 | 추출할 부분 문자열 | 음수 허용 여부(인수)| |--------|-----------|-----------| +<<<<<<< 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` | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 문자열 비교하기 @@ -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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 이제 이 배경지식을 가지고 코드 `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] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 언어마다 문자 체계가 다르기 때문에 문자열을 '제대로' 비교하는 알고리즘을 만드는 건 생각보다 간단하지 않습니다. 문자열을 비교하려면 일단 페이지에서 어떤 언어를 사용하고 있는지 브라우저가 알아야 합니다. +<<<<<<< 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/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이외에도 문자열에 쓸 수 있는 유용한 메서드 몇 가지가 있습니다. @@ -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 . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..597f6087f3 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 # 빠른 해답 @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..e9b42edd93 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 단계를 하나씩 거칠 때마다 배열 모습은 아래와 같이 변해야 합니다. 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..cafb4c4b55 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]`. + +Although, 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **아래는 배열 끝에 무언가를 해주는 메서드입니다.** @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시를 하나 살펴봅시다. 배열은 객체와 마찬가지로 참조를 통해 복사됩니다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그런데 개발자가 배열을 '순서가 있는 자료의 컬렉션'처럼 다루지 않고 일반 객체처럼 다루면 이런 기법들이 제대로 동작하지 않습니다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 숫자형 인수 하나를 넣어서 `new Array`를 호출하면 배열이 만들어지는데, 이 배열엔 *요소가 없는 반면 길이는 인수와 같아*집니다. +<<<<<<< HEAD 예시를 통해 `new Array()`의 이런 특징이 어떻게 실수를 유발할 수 있는지 알아봅시다. +======= +Let's see how one can shoot themselves in the foot: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 다차원 배열 @@ -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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 배열은 특수한 형태의 객체로, 순서가 있는 자료를 저장하고 관리하는 용도에 최적화된 자료구조입니다. +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `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 . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..0444ec8b0a 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 새로 작성하는 함수는 기존 배열 `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..70bf82a5b9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래와 같이 괄호 없이 코드를 작성할 수 없습니다. ```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..1eae3d4960 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,10 @@ # 배열과 메서드 +<<<<<<< HEAD 배열은 다양한 메서드를 제공합니다. 학습 편의를 위해 본 챕터에선 배열 메서드를 몇 개의 그룹으로 나눠 소개하도록 하겠습니다. +======= +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요소 추가·제거 메서드 @@ -32,19 +36,31 @@ alert( arr.length ); // 3 원하는 대로 요소를 지웠지만 배열의 요소는 여전히 세 개이네요. `arr.length == 3`을 통해 이를 확인할 수 있습니다. +<<<<<<< HEAD 이는 자연스러운 결과입니다. `delete obj.key`는 `key`를 이용해 해당 키에 상응하는 값을 지우기 때문이죠. `delete` 메서드는 제 역할을 다 한 것입니다. 그런데 우리는 삭제된 요소가 만든 빈 공간을 나머지 요소들이 자동으로 채울 것이라 기대하며 이 메서드를 썼습니다. 배열의 길이가 더 짧아지길 기대하며 말이죠. +======= +That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 기대를 충족하려면 특별한 메서드를 사용해야 합니다. +<<<<<<< HEAD [arr.splice(start)](mdn:js/Array/splice)는 만능 스위스 맥가이버 칼 같은 메서드입니다. 요소를 자유자재로 다룰 수 있게 해주죠. 이 메서드를 사용하면 요소 추가, 삭제, 교체가 모두 가능합니다. +======= +The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문법은 다음과 같습니다. ```js -arr.splice(index[, deleteCount, elem1, ..., elemN]) +arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` +<<<<<<< HEAD 첫 번째 매개변수는 조작을 가할 첫 번째 요소를 가리키는 `인덱스(index)`입니다. 두 번째 매개변수는 `deleteCount`로, 제거하고자 하는 요소의 개수를 나타냅니다. `elem1, ..., elemN`은 배열에 추가할 요소를 나타냅니다. +======= +It modifies `arr` starting from the index `start`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 splice 메서드를 사용해 작성된 예시 몇 가지를 보여드리겠습니다. @@ -62,7 +78,11 @@ alert( arr ); // ["I", "JavaScript"] 쉽죠? 인덱스 `1`이 가리키는 요소부터 시작해 요소 한 개(`1`)를 지웠습니다. +<<<<<<< HEAD 다음 예시에선 요소 세 개(3)를 지우고, 그 자리를 다른 요소 두 개로 교체해 보도록 하겠습니다. +======= +In the next example, we remove 3 elements and replace them with the other two: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; @@ -84,7 +104,11 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- 삭제된 요소로 구성된 배열 ``` +<<<<<<< HEAD `splice` 메서드의 `deleteCount`를 `0`으로 설정하면 요소를 제거하지 않으면서 새로운 요소를 추가할 수 있습니다. +======= +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = ["I", "study", "JavaScript"]; @@ -114,7 +138,11 @@ alert( arr ); // 1,2,3,4,5 ### slice +<<<<<<< HEAD [arr.slice](mdn:js/Array/slice)는 `arr.splice`와 유사해 보이지만 훨씬 간단합니다. +======= +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문법: @@ -124,7 +152,11 @@ arr.slice([start], [end]) 이 메서드는 `"start"` 인덱스부터 (`"end"`를 제외한) `"end"`인덱스까지의 요소를 복사한 새로운 배열을 반환합니다. `start`와 `end`는 둘 다 음수일 수 있는데 이땐, 배열 끝에서부터의 요소 개수를 의미합니다. +<<<<<<< HEAD `arr.slice`는 문자열 메서드인 `str.slice`와 유사하게 동작하는데 `arr.slice`는 서브 문자열(substring) 대신 서브 배열(subarray)을 반환한다는 점이 다릅니다. +======= +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -206,7 +238,11 @@ alert( arr.concat(arrayLike) ); // 1,2,something,else 문법: ```js arr.forEach(function(item, index, array) { +<<<<<<< HEAD // 요소에 무언가를 할 수 있습니다. +======= + // ... do something with an item +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); ``` @@ -234,6 +270,7 @@ arr.forEach(function(item, index, array) { ### indexOf, lastIndexOf와 includes +<<<<<<< HEAD [arr.indexOf](mdn:js/Array/indexOf)와 [arr.lastIndexOf](mdn:js/Array/lastIndexOf), [arr.includes](mdn:js/Array/includes)는 같은 이름을 가진 문자열 메서드와 문법이 동일합니다. 물론 하는 일도 같습니다. 연산 대상이 문자열이 아닌 배열의 요소라는 점만 다릅니다. - `arr.indexOf(item, from)`는 인덱스 `from`부터 시작해 `item(요소)`을 찾습니다. 요소를 발견하면 해당 요소의 인덱스를 반환하고, 발견하지 못했으면 `-1`을 반환합니다. @@ -241,6 +278,16 @@ arr.forEach(function(item, index, array) { - `arr.includes(item, from)`는 인덱스 `from`부터 시작해 `item`이 있는지를 검색하는데, 해당하는 요소를 발견하면 `true`를 반환합니다. 예시: +======= +The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/includes) have the similar syntax and do essentially the same as their string counterparts, but operate on items instead of characters: + +- `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. +- `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. + +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = [1, 0, false]; @@ -252,6 +299,7 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` +<<<<<<< HEAD 위 메서드들은 요소를 찾을 때 완전 항등 연산자 `===` 을 사용한다는 점에 유의하시기 바랍니다. 보시는 바와 같이 `false`를 검색하면 정확히 `false`만을 검색하지, 0을 검색하진 않습니다. 요소의 위치를 정확히 알고 싶은게 아니고 요소가 배열 내 존재하는지 여부만 확인하고 싶다면 `arr.includes`를 사용하는 게 좋습니다. @@ -262,11 +310,41 @@ alert( arr.includes(1) ); // true const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (완전 항등 비교 === 는 NaN엔 동작하지 않으므로 0이 출력되지 않습니다.) alert( arr.includes(NaN) );// true (NaN의 여부를 확인하였습니다.) +======= +Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. + +If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. + +The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. + +```js run +let fruits = ['Apple', 'Orange', 'Apple'] + +alert( fruits.indexOf('Apple') ); // 0 (first Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) ``` +````smart header="The `includes` method handles `NaN` correctly" +A minor, but noteworthy feature of `includes` is that it correctly handles `NaN`, unlike `indexOf`: + +```js run +const arr = [NaN]; +alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0) +alert( arr.includes(NaN) );// true (correct) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +``` +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. +```` + +<<<<<<< HEAD ### find와 findIndex 객체로 이루어진 배열이 있다고 가정해 봅시다. 특정 조건에 부합하는 객체를 배열 내에서 어떻게 찾을 수 있을까요? +======= +### find and findIndex/findLastIndex + +Imagine we have an array of objects. How do we find an object with a specific condition? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이럴 때 [arr.find(fn)](mdn:js/Array/find)을 사용할 수 있습니다. @@ -284,7 +362,11 @@ let result = arr.find(function(item, index, array) { - `index` -- 요소의 인덱스 - `array` -- 배열 자기 자신 +<<<<<<< HEAD 함수가 참을 반환하면 탐색은 중단되고 해당 `요소`가 반환됩니다. 원하는 요소를 찾지 못했으면 `undefined`가 반환됩니다. +======= +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `id`와 `name` 프로퍼티를 가진 사용자 객체로 구성된 배열을 예로 들어보겠습니다. 배열 내에서 `id == 1` 조건을 충족하는 사용자 객체를 찾아봅시다. @@ -300,11 +382,38 @@ let user = users.find(item => item.id == 1); alert(user.name); // John ``` +<<<<<<< HEAD 실무에서 객체로 구성된 배열을 다뤄야 할 일이 잦기 때문에 `find` 메서드 활용법을 알아두면 좋습니다. +======= +In real life, arrays of objects are a common thing, so the `find` method is very useful. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그런데 위 예시에서 `find` 안의 함수가 인자를 하나만 가지고 있다는 점에 주목해주시기 바랍니다(`item => item.id == 1`). 이런 패턴이 가장 많이 사용되는 편입니다. 다른 인자들(`index`, `array`)은 잘 사용되지 않습니다. +<<<<<<< HEAD [arr.findIndex](mdn:js/Array/findIndex)는 `find`와 동일한 일을 하나, 조건에 맞는 요소를 반환하는 대신 해당 요소의 인덱스를 반환한다는 점이 다릅니다. 조건에 맞는 요소가 없으면 `-1`이 반환됩니다. +======= +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. + +The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`. + +Here's an example: + +```js run +let users = [ + {id: 1, name: "John"}, + {id: 2, name: "Pete"}, + {id: 3, name: "Mary"}, + {id: 4, name: "John"} +]; + +// Find the index of the first John +alert(users.findIndex(user => user.name == 'John')); // 0 + +// Find the index of the last John +alert(users.findLastIndex(user => user.name == 'John')); // 3 +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### filter @@ -389,7 +498,12 @@ alert( arr ); // *!*1, 15, 2*/!* 기본 정렬 기준 대신 새로운 정렬 기준을 만들려면 `arr.sort()`에 새로운 함수를 넘겨줘야 합니다. +<<<<<<< HEAD 인수로 넘겨주는 함수는 반드시 값 두 개를 비교해야 하고 반환 값도 있어야 합니다. +======= +The function should compare two arbitrary values and return: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function compare(a, b) { if (a > b) return 1; // 첫 번째 값이 두 번째 값보다 큰 경우 @@ -418,11 +532,19 @@ alert(arr); // *!*1, 2, 15*/!* 이제 기대했던 대로 요소가 정렬되었습니다. +<<<<<<< HEAD 여기서 잠시 멈춰 위 예시에서 어떤 일이 일어났는지 생각해 봅시다. 사실 `arr`엔 숫자, 문자열, 객체 등이 들어갈 수 있습니다. 알 수 없는 *무언가*로 구성된 집합이 되는 거죠. 이제 이 비 동질적인 집합을 정렬해야 한다고 가정해봅시다. 무언가를 정렬하려면 기준이 필요하겠죠? 이때 *정렬 기준을 정의해주는 함수(ordering function, 정렬 함수)* 가 필요합니다. `sort`에 정렬 함수를 인수로 넘겨주지 않으면 이 메서드는 사전편집 순으로 요소를 정렬합니다. `arr.sort(fn)`는 포괄적인 정렬 알고리즘을 이용해 구현되어있습니다. 대개 최적화된 [퀵 소트(quicksort)](https://en.wikipedia.org/wiki/Quicksort)를 사용하는데, `arr.sort(fn)`는 주어진 함수를 사용해 정렬 기준을 만들고 이 기준에 따라 요소들을 재배열하므로 개발자는 내부 정렬 동작 원리를 알 필요가 없습니다. 우리가 해야 할 일은 정렬 함수 `fn`을 만들고 이를 인수로 넘겨주는 것뿐입니다. 정렬 과정에서 어떤 요소끼리 비교가 일어났는지 확인하고 싶다면 아래 코드를 활용하시면 됩니다. +======= +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. + +The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. + +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -494,7 +616,11 @@ alert( arr ); // 5,4,3,2,1 [str.split(delim)](mdn:js/String/split)을 이용하면 우리가 원하는 것을 정확히 할 수 있습니다. 이 메서드는 구분자(delimiter) `delim`을 기준으로 문자열을 쪼개줍니다. +<<<<<<< HEAD 아래 예시에선 쉼표와 공백을 합친 문자열이 구분자로 사용되고 있습니다. +======= +In the example below, we split by a comma followed by a space: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -561,9 +687,15 @@ let value = arr.reduce(function(accumulator, item, index, array) { - `index` -- 요소의 위치 - `array` -- 배열 +<<<<<<< HEAD 이전 함수 호출 결과는 다음 함수를 호출할 때 첫 번째 인수(`previousValue`)로 사용됩니다. 첫 번째 인수는 앞서 호출했던 함수들의 결과가 누적되어 저장되는 '누산기(accumulator)'라고 생각하면 됩니다. 마지막 함수까지 호출되면 이 값은 `reduce`의 반환 값이 됩니다. +======= +As the function is applied, the result of the previous function call is passed to the next one as the first argument. + +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 복잡해 보이긴 하지만 예제를 통해 메서드를 이해해 봅시다. @@ -630,8 +762,12 @@ arr.reduce((sum, current) => sum + current); 이런 예외상황 때문에 항상 초깃값을 명시해 줄 것을 권장합니다. +<<<<<<< HEAD [arr.reduceRight](mdn:js/Array/reduceRight)는 `reduce`와 동일한 기능을 하지만 배열의 오른쪽부터 연산을 수행한다는 점이 다른 메서드입니다. +======= +The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Array.isArray로 배열 여부 알아내기 @@ -641,7 +777,11 @@ arr.reduce((sum, current) => sum + current); ```js run alert(typeof {}); // object +<<<<<<< HEAD alert(typeof []); // object +======= +alert(typeof []); // object (same) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 그런데 배열은 자주 사용되는 자료구조이기 때문에 배열인지 아닌지를 감별해내는 특별한 메서드가 있다면 아주 유용할 겁니다. [Array.isArray(value)](mdn:js/Array/isArray)는 이럴 때 사용할 수 있는 유용한 메서드입니다. `value`가 배열이라면 `true`를, 배열이 아니라면 `false`를 반환해주죠. @@ -656,7 +796,11 @@ alert(Array.isArray([])); // true 함수를 호출하는 대부분의 배열 메서드(`find`, `filter`, `map` 등. `sort`는 제외)는 `thisArg`라는 매개변수를 옵션으로 받을 수 있습니다. +<<<<<<< HEAD 자주 사용되는 인수가 아니어서 지금까진 이 매개변수에 대해 언급하지 않았는데, 튜토리얼의 완성도를 위해 `thisArg`에 대해 잠시 언급하고 넘어가도록 하겠습니다. +======= +That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `thisArg`는 아래와 같이 활용할 수 있습니다. @@ -700,12 +844,17 @@ alert(soldiers[1].age); // 23 `thisArgs`에 `army`를 지정하지 않고 단순히 `users.filter(army.canJoin)`를 사용했다면 `army.canJoin`은 단독 함수처럼 취급되고, 함수 본문 내 `this`는 `undefined`가 되어 에러가 발생했을 겁니다. +<<<<<<< HEAD `users.filter(user => army.canJoin(user))`를 사용하면 `users.filter(army.canJoin, army)`를 대체할 수 있긴 한데 `thisArg`를 사용하는 방식이 좀 더 이해하기 쉬우므로 더 자주 사용됩니다. +======= +A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The latter is used more often, as it's a bit easier to understand for most people. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 지금까지 살펴본 배열 메서드를 요약해보도록 합시다. +<<<<<<< HEAD - 요소를 더하거나 지우기 - `push(...items)` -- 맨 끝에 요소 추가하기 - `pop()` -- 맨 끝 요소 추출하기 @@ -720,10 +869,27 @@ alert(soldiers[1].age); // 23 - `includes(value)` -- 배열에 `value`가 있으면 `true`를, 그렇지 않으면 `false`를 반환함 - `find/filter(func)` -- `func`의 반환 값을 `true`로 만드는 첫 번째/전체 요소를 반환함 - `findIndex`는 `find`와 유사함. 다만 요소 대신 인덱스를 반환함 +======= +- To add/remove elements: + - `push(...items)` -- adds items to the end, + - `pop()` -- extracts an item from the end, + - `shift()` -- extracts an item from the beginning, + - `unshift(...items)` -- adds items to the beginning. + - `splice(pos, deleteCount, ...items)` -- at index `pos` deletes `deleteCount` elements and inserts `items`. + - `slice(start, end)` -- creates a new array, copies elements from index `start` till `end` (not inclusive) into it. + - `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken. + +- To search among elements: + - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. + - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. + - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. + - `findIndex` is like `find`, but returns the index instead of a value. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 배열 전체 순회하기 - `forEach(func)` -- 모든 요소에 `func`을 호출함. 결과는 반환되지 않음 +<<<<<<< HEAD - 배열 변형하기 - `map(func)` -- 모든 요소에 `func`을 호출하고, 반환된 결과를 가지고 새로운 배열을 만듦 - `sort(func)` -- 배열을 정렬하고 정렬된 배열을 반환함 @@ -733,22 +899,61 @@ alert(soldiers[1].age); // 23 - 기타 - `Array.isArray(arr)` -- `arr`이 배열인지 여부를 판단함 +======= +- To transform the array: + - `map(func)` -- creates a new array from results of calling `func` for every element. + - `sort(func)` -- sorts the array in-place, then returns it. + - `reverse()` -- reverses the array in-place, then returns it. + - `split/join` -- convert a string to array and back. + - `reduce/reduceRight(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. + +- Additionally: + - `Array.isArray(value)` checks `value` for being an array, if so returns `true`, otherwise `false`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `sort`, `reverse`, `splice`는 기존 배열을 변형시킨다는 점에 주의하시기 바랍니다. 지금까지 배운 메서드만으로 배열과 관련된 작업 99%를 해결할 수 있습니다. 이 외의 배열 메서드도 있긴 한데 잠시 언급하고 넘어가겠습니다. +<<<<<<< HEAD - [arr.some(fn)](mdn:js/Array/some)과 [arr.every(fn)](mdn:js/Array/every)는 배열을 확인합니다. +======= +- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) check the array. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 두 메서드는 `map`과 유사하게 모든 요소를 대상으로 함수를 호출합니다. `some`은 함수의 반환 값을 `true`로 만드는 요소가 하나라도 있는지 여부를 확인하고 `every`는 모든 요소가 함수의 반환 값을 `true`로 만드는지 여부를 확인합니다. 두 메서드 모두 조건을 충족하면 `true`를, 그렇지 않으면 `false`를 반환합니다. +<<<<<<< HEAD - [arr.fill(value, start, end)](mdn:js/Array/fill)은 `start`부터 `end`까지 `value`를 채워 넣습니다. +======= + These methods behave sort of like `||` and `&&` operators: if `fn` returns a truthy value, `arr.some()` immediately returns `true` and stops iterating over the rest of items; if `fn` returns a falsy value, `arr.every()` immediately returns `false` and stops iterating over the rest of items as well. + + We can use `every` to compare arrays: + + ```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) -- fills the array with repeating `value` from index `start` to `end`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin)은 `start`부터 `end`까지 요소를 복사하고, 복사한 요소를 `target`에 붙여넣습니다. 기존 요소가 있다면 덮어씁니다. +<<<<<<< HEAD 배열에 관한 모든 메서드는 [manual](mdn:js/Array)에서 찾아볼 수 있습니다. 배워야 할 메서드 종류가 너무 많아서 이걸 다 외워야 하나라는 생각이 들 수 있는데, 생각보다 쉬우니 너무 걱정하지 않으셨으면 좋겠습니다. +======= +- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) create a new flat array from a multidimensional array. + +For the full list, see the [manual](mdn:js/Array). + +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 일단은 요약본을 참고해 자주 사용하는 메서드가 무엇인지 정도만 알아두어도 괜찮습니다. 아래 과제를 풀면서 충분히 연습하다 보면 배열 메서드에 대한 경험치가 쌓일 겁니다. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index ab37e2aa2a..71b2ab9408 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -2,7 +2,11 @@ *반복 가능한(iterable, 이터러블)* 객체는 배열을 일반화한 객체입니다. 이터러블 이라는 개념을 사용하면 어떤 객체에든 `for..of` 반복문을 적용할 수 있습니다. +<<<<<<< HEAD 배열은 대표적인 이터러블입니다. 배열 외에도 다수의 내장 객체가 반복 가능합니다. 문자열 역시 이터러블의 예입니다. +======= +*Iterable* objects are a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 배열이 아닌 객체가 있는데, 이 객체가 어떤 것들의 컬렉션(목록, 집합 등)을 나타내고 있는 경우, `for..of` 문법을 적용할 수만 있다면 컬렉션을 순회하는데 유용할 겁니다. 이게 가능하도록 해봅시다. @@ -25,12 +29,21 @@ let range = { // for(let num of range) ... num=1,2,3,4,5 ``` +<<<<<<< HEAD `range`를 이터러블로 만들려면(`for..of`가 동작하도록 하려면) 객체에 `Symbol.iterator`(특수 내장 심볼)라는 메서드를 추가해 아래와 같은 일이 벌어지도록 해야 합니다. 1. `for..of`가 시작되자마자 `for..of`는 `Symbol.iterator`를 호출합니다(`Symbol.iterator`가 없으면 에러가 발생합니다). `Symbol.iterator`는 반드시 *이터레이터(iterator, 메서드 `next`가 있는 객체)* 를 반환해야 합니다. 2. 이후 `for..of`는 *반환된 객체(이터레이터)만*을 대상으로 동작합니다. 3. `for..of`에 다음 값이 필요하면, `for..of`는 이터레이터의 `next()`메서드를 호출합니다. 4. `next()`의 반환 값은 `{done: Boolean, value: any}`와 같은 형태이어야 합니다. `done=true`는 반복이 종료되었음을 의미합니다. `done=false`일땐 `value`에 다음 값이 저장됩니다. +======= +To make the `range` object iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). + +1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`. +2. Onward, `for..of` works *only with that returned object*. +3. When `for..of` wants the next value, it calls `next()` on that object. +4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the loop is finished, otherwise `value` is the next value. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `range`를 반복 가능한 객체로 만들어주는 코드는 다음과 같습니다. @@ -43,11 +56,16 @@ let range = { // 1. for..of 최초 호출 시, Symbol.iterator가 호출됩니다. range[Symbol.iterator] = function() { +<<<<<<< HEAD // Symbol.iterator는 이터레이터 객체를 반환합니다. // 2. 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, 이때 다음 값도 정해집니다. +======= + // ...it returns the iterator object: + // 2. Onward, for..of works only with the iterator object below, asking it for next values +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 return { current: this.from, - last: this.to, + last: this.to, // 3. for..of 반복문에 의해 반복마다 next()가 호출됩니다. next() { @@ -139,7 +157,11 @@ for (let char of str) { ## 이터레이터를 명시적으로 호출하기 +<<<<<<< HEAD 이터레이터를 어떻게 명시적으로 사용할 수 있는지 살펴보면서 좀 더 깊게 이해해봅시다. +======= +For deeper understanding, let's see how to use an iterator explicitly. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `for..of`를 사용했을 때와 동일한 방법으로 문자열을 순회할 건데, 이번엔 직접 호출을 통해서 순회해보겠습니다. 다음 코드는 문자열 이터레이터를 만들고, 여기서 값을 '수동으로' 가져옵니다. @@ -164,16 +186,28 @@ while (true) { ## 이터러블과 유사 배열 [#array-like] +<<<<<<< HEAD 비슷해 보이지만 아주 다른 용어 두 가지가 있습니다. 헷갈리지 않으려면 두 용어를 잘 이해하고 있어야 합니다. +======= +Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - *이터러블(iterable)* 은 위에서 설명한 바와 같이 메서드 `Symbol.iterator`가 구현된 객체입니다. - *유사 배열(array-like)* 은 인덱스와 `length` 프로퍼티가 있어서 배열처럼 보이는 객체입니다. +<<<<<<< HEAD 브라우저 등의 호스트 환경에서 자바스크립트를 사용해 문제를 해결할 때 이터러블 객체나 유사 배열 객체 혹은 둘 다인 객체를 만날 수 있습니다. +======= +When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이터러블 객체(`for..of` 를 사용할 수 있음)이면서 유사배열 객체(숫자 인덱스와 `length` 프로퍼티가 있음)인 문자열이 대표적인 예입니다. +<<<<<<< HEAD 이터러블 객체라고 해서 유사 배열 객체는 아닙니다. 유사 배열 객체라고 해서 이터러블 객체인 것도 아닙니다. +======= +But an iterable may not be array-like. And vice versa an array-like may not be iterable. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 위 예시의 `range`는 이터러블 객체이긴 하지만 인덱스도 없고 `length` 프로퍼티도 없으므로 유사 배열 객체가 아닙니다. @@ -217,8 +251,13 @@ alert(arr.pop()); // World (메서드가 제대로 동작합니다.) 이터러블을 사용한 예시는 다음과 같습니다. +<<<<<<< HEAD ```js // range는 챕터 위쪽 예시에서 그대로 가져왔다고 가정합시다. +======= +```js run +// assuming that range is taken from the example above +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (배열-문자열 형 변환이 제대로 동작합니다.) ``` @@ -232,8 +271,13 @@ Array.from(obj[, mapFn, thisArg]) 예시: +<<<<<<< HEAD ```js // range는 챕터 위쪽 예시에서 그대로 가져왔다고 가정합시다. +======= +```js run +// assuming that range is taken from the example above +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // 각 숫자를 제곱 let arr = Array.from(range, num => num * num); @@ -269,7 +313,11 @@ for (let char of str) { alert(chars); ``` +<<<<<<< HEAD 어쨌든 `Array.from`을 사용한 예시가 더 짧습니다. +======= +...But it is shorter. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `Array.from`을 사용하면 서로게이트 쌍을 처리할 수 있는 `slice`를 직접 구현할 수도 있습니다. @@ -291,16 +339,29 @@ alert( str.slice(1, 3) ); // 쓰레깃값 출력 (영역이 다른 특수 값) `for..of`을 사용할 수 있는 객체를 *이터러블*이라고 부릅니다. +<<<<<<< HEAD - 이터러블엔 메서드 `Symbol.iterator`가 반드시 구현되어 있어야 합니다. - `obj[Symbol.iterator]`의 결과는 *이터레이터*라고 부릅니다. 이터레이터는 이어지는 반복 과정을 처리합니다. - 이터레이터엔 객체 `{done: Boolean, value: any}`을 반환하는 메서드 `next()`가 반드시 구현되어 있어야 합니다. 여기서 `done:true`은 반복이 끝났음을 의미하고 그렇지 않은 경우엔 `value`가 다음 값이 됩니다. - 메서드 `Symbol.iterator`는 `for..of`에 의해 자동으로 호출되는데, 개발자가 명시적으로 호출하는 것도 가능합니다. - 문자열이나 배열 같은 내장 이터러블에도 `Symbol.iterator`가 구현되어 있습니다. - 문자열 이터레이터는 서로게이트 쌍을 지원합니다. +======= +- Technically, iterables must implement the method named `Symbol.iterator`. + - The result of `obj[Symbol.iterator]()` is called an *iterator*. It handles further iteration process. + - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. +- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. +- Built-in iterables like strings or arrays, also implement `Symbol.iterator`. +- String iterator knows about surrogate pairs. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 인덱스와 `length` 프로퍼티가 있는 객체는 *유사 배열*이라 불립니다. 유사 배열 객체엔 다양한 프로퍼티와 메서드가 있을 수 있는데 배열 내장 메서드는 없습니다. 명세서를 보면 대부분의 메서드는 '진짜' 배열이 아닌 이터러블이나 유사 배열을 대상으로 동작한다고 쓰여 있는걸 볼 수 있습니다. 이 방법이 더 추상적이기 때문입니다. +<<<<<<< HEAD `Array.from(obj[, mapFn, thisArg])`을 사용하면 이터러블이나 유사 배열인 `obj`를 진짜 `Array`로 만들 수 있습니다. 이렇게 하면 `obj`에도 배열 메서드를 사용할 수 있죠. 선택 인수 `mapFn`와 `thisArg`는 각 요소에 함수를 적용할 수 있게 해줍니다. +======= +`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md index 251dee8bd9..c88ba3e5ba 100644 --- a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md @@ -4,7 +4,11 @@ importance: 5 # 반복 가능한 객체의 키 +<<<<<<< HEAD `map.keys()`를 사용해 배열을 반환받고, 이 배열을 변수에 저장해 `.push`와 같은 배열 메서드를 적용하고 싶다고 해봅시다. +======= +We'd like to get an array of `map.keys()` in a variable and then apply array-specific methods to it, e.g. `.push`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 작동하지 않네요. 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..2395f91f51 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,19 +1,31 @@ # 맵과 셋 +<<<<<<< HEAD 지금까진 아래와 같은 복잡한 자료구조를 학습해 보았습니다. - 객체 -- 키가 있는 컬렉션을 저장함 - 배열 -- 순서가 있는 컬렉션을 저장함 +======= +Till now, we've learned about the following complex data structures: + +- Objects are used for storing keyed collections. +- Arrays are used for storing ordered collections. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 하지만 현실 세계를 반영하기엔 이 두 자료구조 만으론 부족해서 `맵(Map)`과 `셋(Set)`이 등장하게 되었습니다. ## 맵 +<<<<<<< HEAD [맵(Map)](mdn:js/Map)은 키가 있는 데이터를 저장한다는 점에서 `객체`와 유사합니다. 다만, `맵`은 키에 다양한 자료형을 허용한다는 점에서 차이가 있습니다. +======= +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 맵에는 다음과 같은 주요 메서드와 프로퍼티가 있습니다. +<<<<<<< HEAD - `new Map()` -- 맵을 만듭니다. - `map.set(key, value)` -- `key`를 이용해 `value`를 저장합니다. - `map.get(key)` -- `key`에 해당하는 값을 반환합니다. `key`가 존재하지 않으면 `undefined`를 반환합니다. @@ -21,6 +33,15 @@ - `map.delete(key)` -- `key`에 해당하는 값을 삭제합니다. - `map.clear()` -- 맵 안의 모든 요소를 제거합니다. - `map.size` -- 요소의 개수를 반환합니다. +======= +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element (the key/value pair) by the key. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -41,8 +62,13 @@ alert( map.size ); // 3 맵은 객체와 달리 키를 문자형으로 변환하지 않습니다. 키엔 자료형 제약이 없습니다. +<<<<<<< HEAD ```smart header="`map[key]`는 `Map`을 쓰는 바른 방법이 아닙니다." `map[key] = 2`로 값을 설정하는 것 같이 `map[key]`를 사용할 수 있긴 합니다. 하지만 이 방법은 `map`을 일반 객체처럼 취급하게 됩니다. 따라서 여러 제약이 생기게 되죠. +======= +```smart header="`map[key]` isn't the right way to use a `Map`" +Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (only string/symbol keys and so on). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `map`을 사용할 땐 `map`전용 메서드 `set`, `get` 등을 사용해야만 합니다. ``` @@ -63,15 +89,21 @@ visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` +<<<<<<< HEAD 객체를 키로 사용할 수 있다는 점은 `맵`의 가장 중요한 기능 중 하나입니다. `객체`에는 문자열 키를 사용할 수 있습니다. 하지만 객체 키는 사용할 수 없습니다. +======= +Using objects as keys is one of the most notable and important `Map` features. The same does not count for `Object`. String as a key in `Object` is fine, but we can't use another `Object` as a key in `Object`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 객체형 키를 `객체`에 써봅시다. ```js run let john = { name: "John" }; +let ben = { name: "Ben" }; let visitsCountObj = {}; // 객체를 하나 만듭니다. +<<<<<<< HEAD visitsCountObj[john] = 123; // 객체(john)를 키로 해서 객체에 값(123)을 저장해봅시다. *!* @@ -81,6 +113,18 @@ alert( visitsCountObj["[object Object]"] ); // 123 ``` `visitsCountObj`는 객체이기 때문에 모든 키를 문자형으로 변환시킵니다. 이 과정에서 `john`은 문자형으로 변환되어 `"[object Object]"`가 됩니다. +======= +visitsCountObj[ben] = 234; // try to use ben object as the key +visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced + +*!* +// That's what got written! +alert( visitsCountObj["[object Object]"] ); // 123 +*/!* +``` + +As `visitsCountObj` is an object, it converts all `Object` keys, such as `john` and `ben` above, to same string `"[object Object]"`. Definitely not what we want. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="`맵`이 키를 비교하는 방식" `맵`은 [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero)라 불리는 알고리즘을 사용해 값의 등가 여부를 확인합니다. 이 알고리즘은 일치 연산자 `===`와 거의 유사하지만, `NaN`과 `NaN`을 같다고 취급하는 것에서 일치 연산자와 차이가 있습니다. 따라서 맵에선 `NaN`도 키로 쓸 수 있습니다. @@ -98,14 +142,24 @@ map.set('1', 'str1') ``` ```` +<<<<<<< HEAD ## 맵의 요소에 반복 작업하기 +======= +## Iteration over Map +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 다음 세 가지 메서드를 사용해 `맵`의 각 요소에 반복 작업을 할 수 있습니다. +<<<<<<< HEAD - `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) -- returns an iterable for keys, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- returns an iterable for values, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -161,7 +215,11 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` +<<<<<<< HEAD 평범한 객체를 가지고 `맵`을 만들고 싶다면 내장 메서드 [Object.entries(obj)](mdn:js/Object/entries)를 활용해야 합니다. 이 메서드는 객체의 키-값 쌍을 요소(`[key, value]`)로 가지는 배열을 반환합니다. +======= +If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -232,16 +290,29 @@ let obj = Object.fromEntries(map); // .entries()를 생략함 ## 셋 +<<<<<<< HEAD `셋(Set)`은 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션입니다. 셋에 키가 없는 값이 저장됩니다. +======= +A [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a special type collection - "set of values" (without keys), where each value may occur only once. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 주요 메서드는 다음과 같습니다. +<<<<<<< HEAD - `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) -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value, returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 셋 내에 동일한 값(value)이 있다면 `set.add(value)`을 아무리 많이 호출하더라도 아무런 반응이 없을 겁니다. 셋 내의 값에 중복이 없는 이유가 바로 이 때문이죠. @@ -271,7 +342,11 @@ for (let user of set) { } ``` +<<<<<<< HEAD `셋` 대신 배열을 사용하여 방문자 정보를 저장한 후, 중복 값 여부는 배열 메서드인 [arr.find](mdn:js/Array/find)를 이용해 확인할 수도 있습니다. 하지만 `arr.find`는 배열 내 요소 전체를 뒤져 중복 값을 찾기 때문에, 셋보다 성능 면에서 떨어집니다. 반면, `셋`은 값의 유일무이함을 확인하는데 최적화되어있습니다. +======= +The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 셋의 값에 반복 작업하기 @@ -290,20 +365,35 @@ set.forEach((value, valueAgain, set) => { 흥미로운 점이 눈에 띄네요. `forEach`에 쓰인 콜백 함수는 세 개의 인수를 받는데, 첫 번째는 `값`, 두 번째도 *같은 값*인 `valueAgain`을 받고 있습니다. 세 번째는 목표하는 객체(셋)이고요. 동일한 값이 인수에 두 번 등장하고 있습니다. +<<<<<<< HEAD 이렇게 구현된 이유는 `맵`과의 호환성 때문입니다. `맵`의 `forEach`에 쓰인 콜백이 세 개의 인수를 받을 때를 위해서죠. 이상해 보이겠지만 이렇게 구현해 놓았기 때문에 `맵`을 `셋`으로 혹은 `셋`을 `맵`으로 교체하기가 쉽습니다. +======= +That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But this may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `셋`에도 `맵`과 마찬가지로 반복 작업을 위한 메서드가 있습니다. +<<<<<<< HEAD - `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) -- returns an iterable object for values, +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- same as `set.keys()`, for compatibility with `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 +<<<<<<< HEAD `맵`은 키가 있는 값이 저장된 컬렉션입니다. +======= +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- is a collection of keyed values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 주요 메서드와 프로퍼티: +<<<<<<< HEAD - `new Map([iterable])` -- 맵을 만듭니다. `[key,value]`쌍이 있는 `iterable`(예: 배열)을 선택적으로 넘길 수 있는데, 이때 넘긴 이터러블 객체는 맵 초기화에 사용됩니다. - `map.set(key, value)` -- 키를 이용해 값을 저장합니다. - `map.get(key)` -- 키에 해당하는 값을 반환합니다. `key`가 존재하지 않으면 `undefined`를 반환합니다. @@ -311,21 +401,43 @@ set.forEach((value, valueAgain, set) => { - `map.delete(key)` -- 키에 해당하는 값을 삭제합니다. - `map.clear()` -- 맵 안의 모든 요소를 제거합니다. - `map.size` -- 요소의 개수를 반환합니다. +======= +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key, returns the map itself. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 일반적인 `객체`와의 차이점: - 키의 타입에 제약이 없습니다. 객체도 키가 될 수 있습니다. - `size` 프로퍼티 등의 유용한 메서드나 프로퍼티가 있습니다. +<<<<<<< HEAD `셋`은 중복이 없는 값을 저장할 때 쓰이는 컬렉션입니다. +======= +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 주요 메서드와 프로퍼티: +<<<<<<< HEAD - `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) -- creates the set, with optional `iterable` (e.g. array) of values for initialization. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value (does nothing if `value` exists), returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `맵`과 `셋`에 반복 작업을 할 땐, 해당 컬렉션에 요소나 값을 추가한 순서대로 반복 작업이 수행됩니다. 따라서 이 두 컬렉션은 정렬이 되어있지 않다고 할 수 없습니다. 그렇지만 컬렉션 내 요소나 값을 재 정렬하거나 (배열에서 인덱스를 이용해 요소를 가져오는 것처럼) 숫자를 이용해 특정 요소나 값을 가지고 오는 것은 불가능합니다. diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md index 8a428c77a4..b43a83c575 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -25,7 +25,11 @@ messages.shift(); // 이제 readMessages에는 요소가 하나만 남게 되었습니다(실제 메모리에서 사라지는 건 나중이 되겠지만 말이죠). ``` +<<<<<<< HEAD `위크셋`을 사용하면 메시지를 저장하고 위크셋 내에 메시지가 있는지 여부를 쉽게 확인할 수 있습니다. +======= +The `WeakSet` allows to store a set of messages and easily check for the existence of a message in it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 위크셋은 자동으로 자신을 청소한다는 장점이 있습니다. 다만 이 장점 때문에 반복 작업이 불가능해서 읽음 상태의 메시지를 '한꺼번에' 가지고 오지 못한다는 단점도 생깁니다. 배열에 저장된 모든 메시지를 대상으로 반복 작업을 수행해 해당 메시지가 위크셋에 저장되어 있는지 확인하면 읽음 상태의 메시지를 '한 번에' 얻어올 수 있습니다. 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..28ac3d7eed 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,8 +1,18 @@ +<<<<<<< HEAD # 위크맵과 위크셋 에서 배웠듯이 자바스크립트 엔진은 도달 가능한 (그리고 추후 사용될 가능성이 있는) 값을 메모리에 유지합니다. 예시: +======= + +# WeakMap and WeakSet + +As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. + +For instance: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let john = { name: "John" }; @@ -30,8 +40,14 @@ let array = [ john ]; john = null; // 참조를 null로 덮어씀 *!* +<<<<<<< HEAD // john을 나타내는 객체는 배열의 요소이기 때문에 가비지 컬렉터의 대상이 되지 않습니다. // array[0]을 이용하면 해당 객체를 얻는 것도 가능합니다. +======= +// the object previously referenced by john is stored inside the array +// therefore it won't be garbage-collected +// we can get it as array[0] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert(JSON.stringify(array[0])); ``` @@ -59,13 +75,21 @@ for(let obj of map.keys()){ alert(map.size); ``` +<<<<<<< HEAD 이런 관점에서 `위크맵(WeakMap)`은 일반 `맵`과 전혀 다른 양상을 보입니다. 위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 됩니다. +======= +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시를 이용해 이에 대해 자세히 알아보도록 합시다. ## 위크맵 +<<<<<<< HEAD `맵`과 `위크맵`의 첫 번째 차이는 `위크맵`의 키가 반드시 객체여야 한다는 점입니다. 원시값은 위크맵의 키가 될 수 없습니다. +======= +The first difference between [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is that keys must be objects, not primitive values: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let weakMap = new WeakMap(); @@ -99,16 +123,22 @@ 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`을 나타내는 객체처럼, 객체는 모든 참조를 잃게 되면 자동으로 가비지 컬렉션의 대상이 됩니다. 그런데 가비지 컬렉션의 *동작 시점*은 정확히 알 수 없습니다. +<<<<<<< HEAD 가비지 컬렉션이 일어나는 시점은 자바스크립트 엔진이 결정합니다. 객체는 모든 참조를 잃었을 때, 그 즉시 메모리에서 삭제될 수도 있고, 다른 삭제 작업이 있을 때까지 대기하다가 함께 삭제될 수도 있습니다. 현재 `위크맵`에 요소가 몇 개 있는지 정확히 파악하는 것 자체가 불가능한 것이죠. 가비지 컬렉터가 한 번에 메모리를 청소할 수도 있고, 부분 부분 메모리를 청소할 수도 있으므로 위크맵의 요소(키/값) 전체를 대상으로 무언가를 하는 메서드는 동작 자체가 불가능합니다. 그럼 위크맵을 어떤 경우에 사용할 수 있을까요? +======= +The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically, the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported. + +Now, where do we need such a data structure? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 유스 케이스: 추가 데이터 @@ -152,7 +182,11 @@ countUser(john); // John의 방문 횟수를 증가시킵니다. john = null; ``` +<<<<<<< HEAD 이제 `john`을 나타내는 객체는 가비지 컬렉션의 대상이 되어야 하는데, `visitsCountMap`의 키로 사용되고 있어서 메모리에서 삭제되지 않습니다. +======= +Now, `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 특정 사용자를 나타내는 객체가 메모리에서 사라지면 해당 객체에 대한 정보(방문 횟수)도 우리가 손수 지워줘야 하는 상황입니다. 이렇게 하지 않으면 `visitsCountMap`가 차지하는 메모리 공간이 한없이 커질 겁니다. 애플리케이션 구조가 복잡할 땐, 이렇게 쓸모 없는 데이터를 수동으로 비워주는 게 꽤 골치 아픕니다. @@ -169,13 +203,23 @@ function countUser(user) { } ``` +<<<<<<< HEAD `위크맵`을 사용해 사용자 방문 횟수를 저장하면 `visitsCountMap`을 수동으로 청소해줄 필요가 없습니다. `john`을 나타내는 객체가 도달 가능하지 않은 상태가 되면 자동으로 메모리에서 삭제되기 때문입니다. `위크맵`의 키(`john`)에 대응하는 값(john의 방문 횟수)도 자동으로 가비지 컬렉션의 대상이 됩니다. +======= +Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable, by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 유스 케이스: 캐싱 +<<<<<<< HEAD 위크맵은 캐싱(caching)이 필요할 때 유용합니다. 캐싱은 시간이 오래 걸리는 작업의 결과를 저장해서 연산 시간과 비용을 절약해주는 기법입니다. 동일한 함수를 여러 번 호출해야 할 때, 최초 호출 시 반환된 값을 어딘가에 저장해 놓았다가 그다음엔 함수를 호출하는 대신 저장된 값을 사용하는 게 캐싱의 실례입니다. 아래 예시는 함수 연산 결과를 `맵`에 저장하고 있습니다. +======= +Another common example is caching. We can store ("cache") results from a function, so that future calls on the same object can reuse it. + +To achieve that, we can use `Map` (not optimal scenario): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // 📁 cache.js @@ -187,6 +231,7 @@ function process(obj) { let result = /* 연산 수행 */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -212,7 +257,11 @@ alert(cache.size); // 1 (엇! 그런데 객체가 여전히 cache에 남아있 `process(obj)`를 여러 번 호출하면 최초 호출할 때만 연산이 수행되고, 그 이후엔 연산 결과를 `cache`에서 가져옵니다. 그런데 `맵`을 사용하고 있어서 객체가 필요 없어져도 `cache`를 수동으로 청소해 줘야 합니다. +<<<<<<< HEAD `맵`을 `위크맵`으로 교체하면 이런 문제를 예방할 수 있습니다. 객체가 메모리에서 삭제되면, 캐시에 저장된 결과(함수 연산 결과) 역시 메모리에서 자동으로 삭제되기 때문입니다. +======= +If we replace `Map` with `WeakMap`, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // 📁 cache.js @@ -226,6 +275,7 @@ function process(obj) { let result = /* 연산 수행 */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -247,6 +297,7 @@ obj = null; ## 위크셋 +<<<<<<< HEAD 이제 `위크셋(WeakSet)`에 대해 알아봅시다. - `위크셋`은 `셋`과 유사한데, 객체만 저장할 수 있다는 점이 다릅니다. 원시값은 저장할 수 없습니다. @@ -254,6 +305,15 @@ obj = null; - `셋`과 마찬가지로 `위크셋`이 지원하는 메서드는 단출합니다. `add`, `has`, `delete`를 사용할 수 있고, `size`, `keys()`나 반복 작업 관련 메서드는 사용할 수 없습니다. '위크'맵과 유사하게 '위크'셋도 부차적인 데이터를 저장할 때 사용할 수 있습니다. 다만, 위크셋엔 위크맵처럼 복잡한 데이터를 저장하지 않습니다. 대신 "예"나 "아니오" 같은 간단한 답변을 얻는 용도로 사용됩니다. 물론 `위크셋`에 저장되는 값들은 객체이겠죠. +======= +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) behaves similarly: + +- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). +- An object exists in the set while it is reachable from somewhere else. +- Like `Set`, it supports [`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) and [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), but not `size`, `keys()` and no iterations. + +Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시와 함께 위크셋의 용도를 알아봅시다. 아래 코드에선 사용자의 사이트 방문 여부를 추적하는 용도로 `위크셋`을 사용하고 있습니다. @@ -281,10 +341,15 @@ john = null; // visitedSet에서 john을 나타내는 객체가 자동으로 삭제됩니다. ``` +<<<<<<< HEAD `위크맵`과 `위크셋`의 가장 큰 단점은 반복 작업이 불가능하다는 점입니다. 위크맵이나 위크셋에 저장된 자료를 한 번에 얻는 게 불가능하죠. 이런 단점은 불편함을 초래하는 것 같아 보이지만, `위크맵`과 `위크셋`을 이용해 할 수 있는 주요 작업을 방해하진 않습니다. `위크맵`과 `위크셋`은 객체와 함께 '추가' 데이터를 저장하는 용도로 쓸 수 있습니다. +======= +The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and the inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 +<<<<<<< HEAD `위크맵`은 `맵`과 유사한 컬렉션입니다. `위크맵`을 구성하는 요소의 키는 오직 객체만 가능합니다. 키로 사용된 객체가 메모리에서 삭제되면 이에 대응하는 값 역시 삭제됩니다. `위크셋`은 `셋`과 유사한 컬렉션입니다. 위크셋엔 객체만 저장할 수 있습니다. 위크셋에 저장된 객체가 도달 불가능한 상태가 되면 해당 객체는 메모리에서 삭제됩니다. @@ -292,3 +357,14 @@ john = null; 두 자료구조 모두 구성 요소 전체를 대상으로 하는 메서드를 지원하지 않습니다. 구성 요소 하나를 대상으로 하는 메서드만 지원합니다. 객체엔 '주요' 자료를, `위크맵`과 `위크셋`엔 '부수적인' 자료를 저장하는 형태로 위크맵과 위크셋을 활용할 수 있습니다. 객체가 메모리에서 삭제되면, (그리고 오로지 `위크맵`과 `위크셋`의 키만 해당 객체를 참조하고 있다면) 위크맵이나 위크셋에 저장된 연관 자료들 역시 메모리에서 자동으로 삭제됩니다. +======= +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. + +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. + +Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector. + +That comes at the cost of not having support for `clear`, `size`, `keys`, `values`... + +`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "primary" object storage. Once the object is removed from the primary storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..2381c68e97 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 @@ -76,9 +76,15 @@ for (let value of Object.values(user)) { 하지만 `Object.entries`와 `Object.fromEntries`를 순차적으로 적용하면 객체에도 배열 전용 메서드 사용할 수 있습니다. 적용 방법은 다음과 같습니다. +<<<<<<< HEAD 1. `Object.entries(obj)`를 사용해 객체의 키-값 쌍이 요소인 배열을 얻습니다. 2. 1.에서 만든 배열에 `map` 등의 배열 전용 메서드를 적용합니다. 3. 2.에서 반환된 배열에 `Object.fromEntries(array)`를 적용해 배열을 다시 객체로 되돌립니다. +======= +1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`. +2. Use array methods on that array, e.g. `map`, to transform these key/value pairs. +3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이 방법을 사용해 가격 정보가 저장된 객체 prices의 프로퍼티 값을 두 배로 늘려보도록 합시다. @@ -91,12 +97,22 @@ let prices = { *!* let doublePrices = Object.fromEntries( +<<<<<<< HEAD // 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌립니다. Object.entries(prices).map(([key, value]) => [key, value * 2]) +======= + // convert prices to array, map each key/value pair into another pair + // and then fromEntries gives back the object + Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ); */!* alert(doublePrices.meat); // 8 -``` +``` +<<<<<<< HEAD 지금 당장은 어렵게 느껴지겠지만 한두 번 위 방법을 적용해 보면 객체에 배열 전용 메서드를 적용하는게 쉬워질 겁니다. +======= +It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..4fe82e46e1 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -2,19 +2,38 @@ `객체`와 `배열`은 자바스크립트에서 가장 많이 쓰이는 자료 구조입니다. +<<<<<<< HEAD 키를 가진 데이터 여러 개를 하나의 엔티티에 저장할 땐 객체를, 컬렉션에 데이터를 순서대로 저장할 땐 배열을 사용하죠. 개발을 하다 보면 함수에 객체나 배열을 전달해야 하는 경우가 생기곤 합니다. 가끔은 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우가 생기기도 하죠. 이럴 때 객체나 배열을 변수로 '분해'할 수 있게 해주는 특별한 문법인 *구조 분해 할당(destructuring assignment)* 을 사용할 수 있습니다. 이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해(destructuring)는 그 진가를 발휘합니다. +======= +- Objects allow us to create a single entity that stores data items by key. +- Arrays allow us to gather data items into an ordered list. + +However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. + +*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. + +Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 배열 분해하기 +<<<<<<< HEAD 배열이 어떻게 변수로 분해되는지 예제를 통해 살펴봅시다. ```js // 이름과 성을 요소로 가진 배열 let arr = ["Bora", "Lee"] +======= +Here's an example of how an array is destructured into variables: + +```js +// we have an array with a name and surname +let arr = ["John", "Smith"] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* // 구조 분해 할당을 이용해 @@ -23,20 +42,38 @@ let arr = ["Bora", "Lee"] let [firstName, surname] = arr; */!* +<<<<<<< HEAD alert(firstName); // Bora alert(surname); // Lee +======= +alert(firstName); // John +alert(surname); // Smith +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 이제 인덱스를 이용해 배열에 접근하지 않고도 변수로 이름과 성을 사용할 수 있게 되었습니다. 아래 예시처럼 `split` 같은 반환 값이 배열인 메서드를 함께 활용해도 좋습니다. +<<<<<<< HEAD ```js let [firstName, surname] = "Bora Lee".split(' '); ``` ````smart header="'분해(destructuring)'는 '파괴(destructive)'를 의미하지 않습니다." 구조 분해 할당이란 명칭은 어떤 것을 복사한 이후에 변수로 '분해(destructurize)'해준다는 의미 때문에 붙여졌습니다. 이 과정에서 분해 대상은 수정 또는 파괴되지 않습니다. +======= +```js run +let [firstName, surname] = "John Smith".split(' '); +alert(firstName); // John +alert(surname); // Smith +``` + +As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. + +````smart header="\"Destructuring\" does not mean \"destructive\"." +It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 배열의 요소를 직접 변수에 할당하는 것보다 코드 양이 줄어든다는 점만 다릅니다. ```js @@ -58,7 +95,11 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic alert( title ); // Consul ``` +<<<<<<< HEAD 두 번째 요소는 생략되었지만, 세 번째 요소는 `title`이라는 변수에 할당된 것을 확인할 수 있습니다. 할당할 변수가 없기 때문에 네 번째 요소 역시 생략되었습니다. +======= +In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ````smart header="할당 연산자 우측엔 모든 이터러블이 올 수 있습니다." @@ -69,29 +110,48 @@ 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. ```` +<<<<<<< HEAD ````smart header="할당 연산자 좌측엔 뭐든지 올 수 있습니다." 할당 연산자 좌측엔 '할당할 수 있는(assignables)' 것이라면 어떤 것이든 올 수 있습니다. +======= +````smart header="Assign to anything at the left-side" +We can use any "assignables" on the left side. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래와 같이 객체 프로퍼티도 가능합니다. ```js run let user = {}; +<<<<<<< HEAD [user.name, user.surname] = "Bora Lee".split(' '); alert(user.name); // Bora +======= +[user.name, user.surname] = "John Smith".split(' '); + +alert(user.name); // John +alert(user.surname); // Smith +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` +<<<<<<< HEAD ````smart header=".entries()로 반복하기" [Object.entries(obj)](mdn:js/Object/entries)는 이전 챕터에서 학습한 바 있습니다. 이 메서드와 구조 분해를 조합하면 객체의 키와 값을 순회해 변수로 분해 할당할 수 있습니다. +======= +````smart header="Looping with .entries()" +In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. + +We can use it with destructuring to loop over the keys-and-values of an object: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { @@ -99,7 +159,11 @@ let user = { age: 30 }; +<<<<<<< HEAD // 객체의 키와 값 순회하기 +======= +// loop over the keys-and-values +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* for (let [key, value] of Object.entries(user)) { */!* @@ -107,7 +171,11 @@ for (let [key, value] of Object.entries(user)) { } ``` +<<<<<<< HEAD 맵에도 물론 이 메서드를 활용할 수 있습니다. +======= +The similar code for a `Map` is simpler, as it's iterable: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = new Map(); @@ -115,6 +183,7 @@ user.set("name", "John"); user.set("age", "30"); *!* +// Map iterates as [key, value] pairs, very convenient for destructuring for (let [key, value] of user) { */!* alert(`${key}:${value}`); // name:John, then age:30 @@ -122,47 +191,95 @@ for (let [key, value] of user) { ``` ```` +<<<<<<< HEAD ```smart header="변수 교환 트릭" 두 변수에 저장된 값을 교환할 때 구조 분해 할당을 사용할 수 있습니다. +======= +````smart header="Swap variables trick" +There's a well-known trick for swapping values of two variables using a destructuring assignment: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let guest = "Jane"; let admin = "Pete"; +<<<<<<< HEAD // 변수 guest엔 Pete, 변수 admin엔 Jane이 저장되도록 값을 교환함 +======= +// Let's swap the values: make guest=Pete, admin=Jane +*!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [guest, admin] = [admin, guest]; +*/!* alert(`${guest} ${admin}`); // Pete Jane(값 교환이 성공적으로 이뤄졌습니다!) ``` 예시에선 임시 배열을 만들어 두 변수를 담고, 요소 순서를 교체해 배열을 분해하는 방식을 사용했습니다. +<<<<<<< HEAD 이 방식을 사용하면 두 개뿐만 아니라 그 이상의 변수에 담긴 값도 교환할 수 있습니다. +======= +We can swap more than two variables this way. +```` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### '...'로 나머지 요소 가져오기 +<<<<<<< HEAD 배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 이어지는 나머지 값들은 한데 모아서 저장하고 싶을 때가 있습니다. 이럴 때는 점 세 개 `...`를 붙인 매개변수 하나를 추가하면 '나머지(rest)' 요소를 가져올 수 있습니다. +======= +Usually, if the array is longer than the list at the left, the "extra" items are omitted. + +For example, here only two items are taken, and the rest is just ignored: ```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 +// Further items aren't assigned anywhere +``` + +If we'd like also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +```js run +let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* +<<<<<<< HEAD // `rest`는 배열입니다. +======= +// rest is an array of items, starting from the 3rd one +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 */!* ``` +<<<<<<< HEAD `rest`는 나머지 배열 요소들이 저장된 새로운 배열이 됩니다. `rest` 대신에 다른 이름을 사용해도 되는데, 변수 앞의 점 세 개(`...`)와 변수가 가장 마지막에 위치해야 한다는 점은 지켜주시기 바랍니다. +======= +The value of `rest` is the array of the remaining array elements. + +We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment. + +```js run +let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +// now titles = ["Consul", "of the Roman Republic"] +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 기본값 +<<<<<<< HEAD 할당하고자 하는 변수의 개수가 분해하고자 하는 배열의 길이보다 크더라도 에러가 발생하지 않습니다. 할당할 값이 없으면 undefined로 취급되기 때문입니다. +======= +If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run *!* @@ -187,7 +304,11 @@ alert(surname); // Anonymous (기본값) 복잡한 표현식이나 함수 호출도 기본값이 될 수 있습니다. 이렇게 기본식으로 표현식이나 함수를 설정하면 할당할 값이 없을 때 표현식이 평가되거나 함수가 호출됩니다. +<<<<<<< HEAD 기본값으로 두 개의 `prompt` 함수를 할당한 예시를 살펴봅시다. 값이 제공되지 않았을 때만 함수가 호출되므로, `prompt`는 한 번만 호출됩니다. +======= +For instance, here we use the `prompt` function for two defaults: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // name의 prompt만 실행됨 @@ -197,7 +318,7 @@ alert(surname); // 김 (배열에서 받아온 값) alert(name); // prompt에서 받아온 값 ``` - +Please note: the `prompt` will run only for the missing value (`surname`). ## 객체 분해하기 @@ -209,7 +330,11 @@ alert(name); // prompt에서 받아온 값 let {var1, var2} = {var1:…, var2:…} ``` +<<<<<<< HEAD 할당 연산자 우측엔 분해하고자 하는 객체를, 좌측엔 상응하는 객체 프로퍼티의 '패턴'을 넣습니다. 분해하려는 객체 프로퍼티의 키 목록을 패턴으로 사용하는 예시를 살펴봅시다. +======= +We should have an existing object on the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in `{...}`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -229,7 +354,13 @@ alert(width); // 100 alert(height); // 200 ``` +<<<<<<< HEAD 프로퍼티 `options.title`과 `options.width`, `options.height`에 저장된 값이 상응하는 변수에 할당된 것을 확인할 수 있습니다. 참고로 순서는 중요하지 않습니다. 아래와 같이 작성해도 위 예시와 동일하게 동작합니다. +======= +Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. + +The order does not matter. This works too: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // let {...} 안의 순서가 바뀌어도 동일하게 동작함 @@ -238,7 +369,11 @@ let {height, width, title} = { title: "Menu", height: 200, width: 100 } 할당 연산자 좌측엔 좀 더 복잡한 패턴이 올 수도 있습니다. 분해하려는 객체의 프로퍼티와 변수의 연결을 원하는 대로 조정할 수도 있습니다. +<<<<<<< HEAD 객체 프로퍼티를 프로퍼티 키와 다른 이름을 가진 변수에 저장해봅시다. `options.width`를 `w`라는 변수에 저장하는 식으로 말이죠. 좌측 패턴에 콜론(:)을 사용하면 원하는 목표를 달성할 수 있습니다. +======= +If we want to assign a property to a variable with another name, for instance, make `options.width` go into the variable named `w`, then we can set the variable name using a colon: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let options = { @@ -391,9 +526,15 @@ alert( title ); // Menu ## 중첩 구조 분해 +<<<<<<< HEAD 객체나 배열이 다른 객체나 배열을 포함하는 경우, 좀 더 복잡한 패턴을 사용하면 중첩 배열이나 객체의 정보를 추출할 수 있습니다. 이를 중첩 구조 분해(nested destructuring)라고 부릅니다. 아래 예시에서 객체 `options`의 `size` 프로퍼티 값은 또 다른 객체입니다. `items` 프로퍼티는 배열을 값으로 가지고 있습니다. 대입 연산자 좌측의 패턴은 정보를 추출하려는 객체 `options`와 같은 구조를 갖추고 있습니다. +======= +If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. + +In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern on the left side of the assignment has the same structure to extract values from them: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let options = { @@ -422,7 +563,11 @@ alert(item1); // Cake alert(item2); // Donut ``` +<<<<<<< HEAD `extra`(할당 연산자 좌측의 패턴에는 없음)를 제외한 `options` 객체의 모든 프로퍼티가 상응하는 변수에 할당되었습니다. +======= +All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](destructuring-complex.svg) @@ -432,9 +577,15 @@ alert(item2); // Donut ## 똑똑한 함수 매개변수 +<<<<<<< HEAD 함수에 매개변수가 많은데 이중 상당수는 선택적으로 쓰이는 경우가 종종 있습니다. 사용자 인터페이스와 연관된 함수에서 이런 상황을 자주 볼 수 있죠. 메뉴 생성에 관여하는 함수가 있다고 해 봅시다. 메뉴엔 너비, 높이, 제목, 항목 리스트 등이 필요하기 때문에 이 정보는 매개변수로 받습니다. 먼저 리팩토링 전의 메뉴 생성 함수를 살펴보겠습니다. +======= +There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on. + +Here's a bad way to write such a function: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -442,7 +593,11 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` +<<<<<<< HEAD 이렇게 함수를 작성하면 넘겨주는 인수의 순서가 틀려 문제가 발생할 수 있습니다. 문서화가 잘 되어있다면 IDE가 순서를 틀리지 않게 도움을 주긴 하겠지만 말이죠. 이 외에도 대부분의 매개변수에 기본값이 설정되어 있어 굳이 인수를 넘겨주지 않아도 되는 경우에 문제가 발생합니다. +======= +In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 코드를 살펴보시죠. 어떤 느낌이 드시나요? @@ -507,7 +662,11 @@ function({ }) ``` +<<<<<<< HEAD 매개변수로 전달된 객체의 프로퍼티 `incomingProperty`는 `varName`에 할당되겠죠. 만약 값이 없다면 `defaultValue`가 기본값으로 사용될 겁니다. +======= +Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 참고로 이렇게 함수 매개변수를 구조 분해할 땐, 반드시 인수가 전달된다고 가정되고 사용된다는 점에 유의하시기 바랍니다. 모든 인수에 기본값을 할당해 주려면 빈 객체를 명시적으로 전달해야 합니다. @@ -534,7 +693,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,9 +703,13 @@ showMenu(); // Menu 100 200 - 배열 분해하기: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = defaultValue, item2, ...rest] = array ``` +<<<<<<< HEAD array의 첫 번째 요소는 `item1`에, 두 번째 요소는 변수 `item2`에 할당되고, 이어지는 나머지 요소들은 배열 `rest` 저장됩니다. +======= + The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 할당 연산자 좌측의 패턴과 우측의 구조가 같으면 중첩 배열이나 객체가 있는 복잡한 구조에서도 원하는 데이터를 뽑아낼 수 있습니다. 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..0f56f13a86 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"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..860f84a47b 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 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..8f4da2c713 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트가 제공하는 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 커스텀 "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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..a8c6a990a8 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..97b4ac90d3 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)!`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 따라서 `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..0175e4fcc3 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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..50dfac512c 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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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(…)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 따라서 좀 더 정확히는 실행이 '서브 호출 바로 직후'에 시작된다고 이야기 할 수 있습니다. ``` @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 재귀를 사용하면 코드가 짧아지고 코드 이해도가 높아지며 유지보수에도 이점이 있습니다. 모든 곳에서 메모리 최적화를 신경 써서 코드를 작성해야 하는 것은 아닙니다. 우리가 필요한 것은 좋은 코드입니다. 이런 이유 때문에 재귀를 사용합니다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시에서 구현한 `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..c73086e370 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 여분의 매개변수는 그 값들을 담을 배열 이름을 마침표 세 개 `...`뒤에 붙여주면 함수 선언부에 포함시킬 수 있습니다. 이때 마침표 세 개 `...`는 "남아있는 매개변수들을 한데 모아 배열에 집어넣어라."는 것을 의미합니다. @@ -225,7 +229,11 @@ alert( Array.from(str) ); // H,e,l,l,o 이런 이유때문에 무언가를 배열로 바꿀 때는 전개 구문보다 `Array.from`이 보편적으로 사용됩니다. +<<<<<<< HEAD ## 배열과 객체의 복사본 만들기 +======= +## Copy an array/object +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [참조에 의한 객체 복사](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 +*/!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // 배열 복사본의 요소가 기존 배열 요소와 진짜 같을까요? 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 +*/!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // 객체 복사본의 프로퍼티들이 기존 객체의 프로퍼티들과 진짜 같을까요? 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 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..645b56f5e4 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; - 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..d819c35c87 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시의 실행 결과를 예측해보세요. 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..e1cb3bd367 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* 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..a3304cd82b 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? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이젠 이런 간단한 시나리오부터 시작해 좀 더 복잡한 시나리오를 다룰 수 있도록 지식을 확장해 봅시다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 렉시컬 환경 @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 함수 호출은 끝났지만 렉시컬 환경이 메모리에 유지되는 이유는 바로 이 때문입니다. @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index eeeffc5359..b2efed8c65 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` [변수](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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 즉시 실행 함수 표현식을 요즘에는 자주 쓰지 않습니다. 하지만 오래된 스크립트에서 만날 수 있기 때문에 즉시 실행 함수 표현식이 무엇인지 알아 둘 필요가 있습니다. @@ -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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + + 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"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }()*!*)*/!*; *!*!*/!*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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이 외에도 전역 객체와 관련된 특성 하나가 더 있는데, 이에 대해선 다음 챕터에서 다루도록 하겠습니다. 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..18817b6df2 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 본 튜토리얼은 브라우저 환경에서 구동되기 때문에 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..122182c754 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..d9390d3fbe 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 can call itself reliably. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 객체로서의 함수 특징을 이용해 커스텀 프로퍼티를 만들면 함수는 자기 자신을 이용해 원하는 일을 수행할 수 있고, 함수 자기 자신에 딸린 프로퍼티의 기능도 사용할 수 있다는 장점이 있습니다. 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..869f73a355 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,11 @@ getFunc()(); // getFunc의 렉시컬 환경에 있는 값 *!*"test"*/!*가 출 그런데 스크립트가 프로덕션 서버에 반영되기 전, *압축기(minifier)* 에 의해 압축될 때 문제가 발생합니다. 압축기는 스크립트에서 주석이나 여분의 공백 등을 없애 코드 크기를 줄여주는 특수한 프로그램인데 압축기가 지역 변수 이름을 짧게 바꾸면서 문제가 발생하죠. +<<<<<<< HEAD 구체적으로 어떤 부분이 문제가 되는지 예시를 통해 알아봅시다. 함수 내부에 `let userName`라는 변수가 있으면 이 지역변수는 압축기에 의해 `let a` 등(짧은 이름)으로 대체되는데, 이때 `userName` 모두가 `a`로 교체됩니다. `userName`은 지역변수이고, 함수 외부에선 함수 내부에 있는 변수에 접근할 수 없기 때문에 이렇게 해도 전혀 문제가 없죠. 압축기는 단순히 변수를 찾아서 바꾸지 않고 코드 구조를 분석해 기존에 작성한 코드의 기능을 망가뜨리지 않으면서 영리하게 제 역할을 수행합니다. +======= +For instance, if a function has `let userName`, minifier replaces it with `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 동작 방식 때문에 `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..b3024d7885 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,11 @@ let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) : 실행 전 대기 시간으로, 단위는 밀리초(millisecond, 1000밀리초 = 1초)이며 기본값은 0입니다. `arg1`, `arg2`... +<<<<<<< HEAD : 함수에 전달할 인수들로, IE9 이하에선 지원하지 않습니다. +======= +: Arguments for the function +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시를 통해 `setTimeout`을 어떻게 쓸 수 있는지 알아봅시다. 아래 코드를 실행하면 1초 후에 `sayHi()`가 호출됩니다. @@ -102,7 +106,11 @@ alert(timerId); // 위 타이머 식별자와 동일함 (취소 후에도 식별 다시 한번 말씀드리자면, 스케줄링에 관한 명세는 따로 존재하지 않습니다. 명세가 없기 때문에 호스트 환경마다 약간의 차이가 있을 수밖에 없습니다. +<<<<<<< HEAD 참고로 브라우저는 HTML5의 [timers section](https://www.w3.org/TR/html5/webappapis.html#timers)을 준수하고 있습니다. +======= +For browsers, timers are described in the [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) of HTML Living Standard. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## setInterval @@ -129,7 +137,11 @@ setTimeout(() => { clearInterval(timerId); alert('정지'); }, 5000); ```smart header="`alert` 창이 떠 있더라도 타이머는 멈추지 않습니다." Chrome과 Firefox를 포함한 대부분의 브라우저는 `alert/confirm/prompt` 창이 떠 있는 동안에도 내부 타이머를 멈추지 않습니다. +<<<<<<< HEAD 위 예시를 실행하고 첫 번째 `alert` 창이 떴을 때 몇 초간 기다렸다가 창을 닫으면, 두 번째 `alert` 창이 바로 나타나는 것을 보고 이를 확인할 수 있습니다. 이런 이유로 얼럿 창은 명시한 지연 시간인 2초보다 더 짧은 간격으로 뜨게 됩니다. +======= +So if you run the code above and don't dismiss the `alert` window for some time, then the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 중첩 setTimeout @@ -232,7 +244,11 @@ setTimeout(function() {...}, 100); `setInterval`의 경우는, `clearInterval`이 호출되기 전까지 함수에 대한 참조가 메모리에 유지됩니다. +<<<<<<< HEAD 그런데 이런 동작 방식에는 부작용이 하나 있습니다. 외부 렉시컬 환경을 참조하는 함수가 있다고 가정해 봅시다. 이 함수가 메모리에 남아있는 동안엔 외부 변수 역시 메모리에 남아있기 마련입니다. 그런데 이렇게 되면 실제 함수가 차지했어야 하는 공간보다 더 많은 메모리 공간이 사용됩니다. 이런 부작용을 방지하고 싶다면 스케줄링할 필요가 없어진 함수는 아무리 작더라도 취소하도록 합시다. +======= +There's a side effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 대기 시간이 0인 setTimeout @@ -255,8 +271,13 @@ alert("Hello"); 대기 시간이 0인 setTimeout을 활용한 브라우저 환경에서의 유스 케이스는 에서 자세히 다루도록 하겠습니다. +<<<<<<< HEAD ````smart header="브라우저 환경에서 실제 대기 시간은 0이 아닙니다." 브라우저는 [HTML5 표준](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers)에서 정한 중첩 타이머 실행 간격 관련 제약을 준수합니다. 해당 표준엔 "다섯 번째 중첩 타이머 이후엔 대기 시간을 최소 4밀리초 이상으로 강제해야 한다."라는 제약이 명시되어있습니다. +======= +````smart header="Zero delay is in fact not zero (in a browser)" +In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시를 보며 이 제약 사항을 이해해봅시다. 예시 내 `setTimeout`은 지연 없이 함수 run을 다시 호출할 수 있게 스케줄링 되어 있습니다. 배열 `times`에는 실제 지연 간격에 대한 정보가 기록되도록 해놓았는데, 배열 times에 어떤 값이 저장되는지 알아봅시다. @@ -281,22 +302,41 @@ setTimeout(function run() { 이는 오래전부터 있던 제약인데, 구식 스크립트 중 일부는 아직 이 제약에 의존하는 경우가 있어서 명세서를 변경하지 못하고 있는 상황입니다. +<<<<<<< HEAD 한편, 서버 측엔 이런 제약이 없습니다. Node.js의 [process.nextTick](https://nodejs.org/api/process.html)과 [setImmediate](https://nodejs.org/api/timers.html)를 이용하면 비동기 작업을 지연 없이 실행할 수 있습니다. 위에서 언급된 제약은 브라우저에 한정됩니다. +======= +For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) for Node.js. So this note is browser-specific. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 요약 +<<<<<<< HEAD - `setInterval(func, delay, ...args)`과 `setTimeout(func, delay, ...args)`은 `delay`밀리초 후에 `func`을 규칙적으로, 또는 한번 실행하도록 해줍니다. - `setInterval·setTimeout`을 호출하고 반환받은 값을 `clearInterval·clearTimeout`에 넘겨주면 스케줄링을 취소할 수 있습니다. - 중첩 `setTimeout`을 사용하면 `setInterval`을 사용한 것보다 유연하게 코드를 작성할 수 있습니다. 여기에 더하여 *지연 간격* 보장이라는 장점도 있습니다. - 대기 시간이 0인 setTimeout(`setTimeout(func, 0)` 혹은 `setTimeout(func)`)을 사용하면 '현재 스크립트의 실행이 완료된 후 가능한 한 빠르게' 원하는 함수를 호출할 수 있습니다. - 지연 없이 중첩 `setTimeout`을 5회 이상 호출하거나 지연 없는 `setInterval`에서 호출이 5회 이상 이뤄지면, 4밀리초 이상의 지연 간격이 강제로 더해집니다. 이는 브라우저에만 적용되는 사항이며, 하위 호환성을 위해 유지되고 있습니다. +======= +- Methods `setTimeout(func, delay, ...args)` and `setInterval(func, delay, ...args)` allow us to run the `func` once/regularly after `delay` milliseconds. +- To cancel the execution, we should call `clearTimeout/clearInterval` with the value returned by `setTimeout/setInterval`. +- Nested `setTimeout` calls are a more flexible alternative to `setInterval`, allowing us to set the time *between* executions more precisely. +- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current script is complete". +- The browser limits the minimal delay for five or more nested calls of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 스케줄링 메서드를 사용할 땐 명시한 지연 간격이 *보장*되지 않을 수도 있다는 점에 유의해야 합니다. +<<<<<<< HEAD 아래와 같은 상황에서 브라우저 내 타이머가 느려지면 지연 간격이 보장되지 않습니다. - CPU가 과부하 상태인 경우 - 브라우저 탭이 백그라운드 모드인 경우 - 노트북이 배터리에 의존해서 구동 중인 경우 +======= +For example, the in-browser timer may slow down for a lot of reasons: +- The CPU is overloaded. +- The browser tab is in the background mode. +- The laptop is on battery saving mode. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 상황에서 타이머의 최소 지연 시간은 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..d0bdba0691 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,19 @@ function cachingDecorator(func) { slow = cachingDecorator(slow); +<<<<<<< HEAD alert( slow(1) ); // slow(1)이 저장되었습니다. alert( "다시 호출: " + slow(1) ); // 동일한 결과 alert( slow(2) ); // slow(2)가 저장되었습니다. alert( "다시 호출: " + slow(2) ); // 윗줄과 동일한 결과 +======= +alert( slow(1) ); // slow(1) is cached and the result returned +alert( "Again: " + slow(1) ); // slow(1) result returned from cache + +alert( slow(2) ); // slow(2) is cached and the result returned +alert( "Again: " + slow(2) ); // slow(2) result returned from cache +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` `cachingDecorator`같이 인수로 받은 함수의 행동을 변경시켜주는 함수를 *데코레이터(decorator)* 라고 부릅니다. @@ -301,18 +309,32 @@ func.apply(context, args) 따라서 아래 코드 두 줄은 거의 같은 역할을 합니다. ```js +<<<<<<< HEAD func.call(context, ...args); // 전개 구문을 사용해 인수가 담긴 배열을 전달하는 것과 func.apply(context, args); // call을 사용하는 것은 동일합니다. ``` 그런데 약간의 차이가 있긴 합니다. +======= +func.call(context, ...args); +func.apply(context, args); +``` + +They perform the same call of `func` with given context and arguments. + +There's only a subtle difference regarding `args`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 전개 구문 `...`은 *이터러블* `args`을 분해 해 `call`에 전달할 수 있도록 해줍니다. - `apply`는 오직 *유사 배열* 형태의 `args`만 받습니다. +<<<<<<< HEAD 이 차이만 빼면 두 메서드는 완전히 동일하게 동작합니다. 인수가 이터러블 형태라면 `call`을, 유사 배열 형태라면 `apply`를 사용하면 됩니다. 배열같이 이터러블이면서 유사 배열인 객체엔 둘 다를 사용할 수 있는데, 대부분의 자바스크립트 엔진은 내부에서 `apply`를 최적화 하기 때문에 `apply`를 사용하는 게 좀 더 빠르긴 합니다. +======= +...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 컨텍스트와 함께 인수 전체를 다른 함수에 전달하는 것을 *콜 포워딩(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..630e872170 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,9 @@ +<<<<<<< HEAD 에러는 `ask`가 함수 `loginOk`, `loginFail`을 객체 없이 가지고 오기 때문에 발생합니다. +======= +The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..30fd75beaf 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -185,8 +185,13 @@ let user = { let say = user.say.bind(user); +<<<<<<< HEAD say("Hello"); // Hello, John (인수 "Hello"가 say로 전달되었습니다.) say("Bye"); // Bye, John ("Bye"가 say로 전달되었습니다.) +======= +say("Hello"); // Hello, John! ("Hello" argument is passed to say) +say("Bye"); // Bye, John! ("Bye" is passed to say) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ````smart header="`bindAll`로 메서드 전체 바인딩하기" @@ -200,7 +205,11 @@ for (let key in user) { } ``` +<<<<<<< HEAD 자바스크립트 라이브러리를 사용해도 대규모 바인딩을 할 수 있습니다. lodash 라이브러리의 [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll)이 그 예입니다. +======= +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) in lodash. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 부분 적용 @@ -245,7 +254,11 @@ alert( double(5) ); // = mul(2, 5) = 10 이런 방식을 [부분 적용(partial application)](https://en.wikipedia.org/wiki/Partial_application)이라고 부릅니다. 부분 적용을 사용하면 기존 함수의 매개변수를 고정하여 새로운 함수를 만들 수 있습니다. +<<<<<<< HEAD 위 예시에선 `this`를 사용하지 않았다는 점에 주목하시기 바랍니다. `bind`엔 컨텍스트를 항상 넘겨줘야 하므로 `null`을 사용했습니다. +======= +Please note that we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 부분 적용을 사용해 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..a83776bccc 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,13 @@ let group = { showList() { *!* this.students.forEach(function(student) { +<<<<<<< HEAD // TypeError: Cannot read property 'title' of undefined alert(this.title + ': ' + student) +======= + // Error: Cannot read property 'title' of undefined + alert(this.title + ': ' + student); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); */!* } @@ -87,7 +92,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..3285694025 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,11 @@ 자 이제 본격적으로 프로퍼티 플래그에 대해 다뤄봅시다. 먼저 플래그를 얻는 방법을 알아보겠습니다. +<<<<<<< HEAD [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor)메서드를 사용하면 특정 프로퍼티에 대한 정보를 *모두* 얻을 수 있습니다. +======= +The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문법: ```js @@ -54,7 +58,11 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` +<<<<<<< HEAD 메서드 [Object.defineProperty](mdn:js/Object/defineProperty)를 사용하면 플래그를 변경할 수 있습니다. +======= +To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문법: @@ -120,8 +128,13 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' 이제 `defineProperty`를 사용해 `writable` 플래그를 `true`로 변경하지 않는 한 그 누구도 객체의 이름을 변경할 수 없게 되었습니다. +<<<<<<< HEAD ```smart header="에러는 엄격 모드에서만 발생합니다." 비 엄격 모드에선 읽기 전용 프로퍼티에 값을 쓰려고 해도 에러가 발생하지 않습니다. 다만 이때 값을 변경하는 것은 불가능합니다. 비 엄격 모드에선 이와 같이 플래그에서 정한 규칙을 위반하는 행위는 에러 없이 그냥 무시됩니다. +======= +```smart header="Errors appear only in strict mode" +In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 아래 예시는 위 예시와 동일하게 동작합니다. 다만 아래 예시에선 `defineProperty` 메서드를 사용해 프로퍼티를 밑바닥부터 만들어 보았습니다. @@ -192,7 +205,11 @@ alert(Object.keys(user)); // name 구성 가능하지 않음을 나타내는 플래그(non-configurable flag)인 `configurable:false`는 몇몇 내장 객체나 프로퍼티에 기본으로 설정되어있습니다. +<<<<<<< HEAD 어떤 프로퍼티의 `configurable` 플래그가 `false`로 설정되어 있다면 해당 프로퍼티는 객체에서 지울 수 없습니다. +======= +A non-configurable property can't be deleted, its attributes can't be modified. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 내장 객체 `Math`의 `PI` 프로퍼티가 대표적인 예입니다. 이 프로퍼티는 쓰기와 열거, 구성이 불가능합니다. @@ -212,11 +229,12 @@ alert( JSON.stringify(descriptor, null, 2 ) ); 개발자가 코드를 사용해 `Math.PI` 값을 변경하거나 덮어쓰는 것도 불가능합니다. ```js run -Math.PI = 3; // Error +Math.PI = 3; // Error, because it has writable: false // 수정도 불가능하지만 지우는 것 역시 불가능합니다. ``` +<<<<<<< HEAD `configurable` 플래그를 `false`로 설정하면 돌이킬 방법이 없습니다. `defineProperty`를 써도 값을 `true`로 되돌릴 수 없죠. `configurable:false`가 만들어내는 구체적인 제약사항은 아래와 같습니다. @@ -226,16 +244,49 @@ Math.PI = 3; // Error 4. 접근자 프로퍼티 `get/set`을 변경할 수 없음(새롭게 만드는 것은 가능함). 이런 특징을 이용하면 아래와 같이 "영원히 변경할 수 없는" 프로퍼티(`user.name`)를 만들 수 있습니다. +======= +We also can't change `Math.PI` to be `writable` again: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run -let user = { }; +// Error, because of configurable: false +Object.defineProperty(Math, "PI", { writable: true }); +``` + +There's absolutely nothing we can do with `Math.PI`. + +Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`. + +**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.** + +Here `user.name` is non-configurable, but we can still change it (as it's writable): + +```js run +let user = { + name: "John" +}; + +Object.defineProperty(user, "name", { + configurable: false +}); + +user.name = "Pete"; // works fine +delete user.name; // Error +``` + +And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`: + +```js run +let user = { + name: "John" +}; Object.defineProperty(user, "name", { - value: "John", writable: false, configurable: false }); +<<<<<<< HEAD *!* // user.name 프로퍼티의 값이나 플래그를 변경할 수 없습니다. // 아래와 같이 변경하려고 하면 에러가 발생합니다. @@ -250,11 +301,28 @@ Object.defineProperty(user, "name", {writable: true}); // Error `configurable` 플래그가 `false`이더라도 `writable` 플래그가 `true`이면 프로퍼티 값을 변경할 수 있습니다. `configurable: false`는 플래그 값 변경이나 프로퍼티 삭제를 막기 위해 만들어졌지, 프로퍼티 값 변경을 막기 위해 만들어진 게 아닙니다. +======= +// won't be able to change user.name or its flags +// all this won't work: +user.name = "Pete"; +delete user.name; +Object.defineProperty(user, "name", { value: "Pete" }); +``` + +```smart header="The only attribute change possible: writable true -> false" +There's a minor exception about changing flags. + +We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Object.defineProperties +<<<<<<< HEAD [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) 메서드를 사용하면 프로퍼티 여러 개를 한 번에 정의할 수 있습니다. +======= +There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문법: @@ -280,7 +348,11 @@ Object.defineProperties(user, { ## Object.getOwnPropertyDescriptors +<<<<<<< HEAD [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors) 메서드를 사용하면 프로퍼티 설명자를 전부 한꺼번에 가져올 수 있습니다. +======= +To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이 메서드를 `Object.defineProperties`와 함께 사용하면 객체 복사 시 플래그도 함께 복사할 수 있습니다. @@ -298,7 +370,11 @@ for (let key in user) { 그런데 이 방법은 플래그는 복사하지 않습니다. 플래그 정보도 복사하려면 `Object.defineProperties`를 사용하시기 바랍니다. +<<<<<<< HEAD 위 샘플 코드처럼 `for..in`을 사용해 객체를 복사하면 심볼형 프로퍼티도 놓치게 됩니다. 하지만 `Object.getOwnPropertyDescriptors`는 심볼형 프로퍼티를 포함한 프로퍼티 설명자 *전체*를 반환합니다. +======= +Another difference is that `for..in` ignores symbolic and non-enumerable properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic and non-enumerable ones. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 객체 수정을 막아주는 다양한 메서드 @@ -306,6 +382,7 @@ for (let key in user) { 아래 메서드를 사용하면 한 객체 내 프로퍼티 *전체*를 대상으로 하는 제약사항을 만들 수 있습니다. +<<<<<<< HEAD [Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) : 객체에 새로운 프로퍼티를 추가할 수 없게 합니다. @@ -314,9 +391,20 @@ for (let key in user) { [Object.freeze(obj)](mdn:js/Object/freeze) : 새로운 프로퍼티 추가나 기존 프로퍼티 삭제, 수정을 막아줍니다. 프로퍼티 전체에 `configurable: false, writable: false`를 설정하는 것과 동일한 효과입니다. +======= +[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) +: Forbids the addition of new properties to the object. + +[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) +: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties. + +[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) +: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 메서드는 위 세 가지 메서드를 사용해서 설정한 제약사항을 확인할 때 사용할 수 있습니다. +<<<<<<< HEAD [Object.isExtensible(obj)](mdn:js/Object/isExtensible) : 새로운 프로퍼티를 추가하는 게 불가능한 경우 `false`를, 그렇지 않은 경우 `true`를 반환합니다. @@ -325,5 +413,15 @@ for (let key in user) { [Object.isFrozen(obj)](mdn:js/Object/isFrozen) : 프로퍼티 추가, 삭제, 변경이 불가능하고 모든 프로퍼티가 `configurable: false, writable: false`이면 `true`를 반환합니다. +======= +[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) +: Returns `false` if adding properties is forbidden, otherwise `true`. + +[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed) +: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`. + +[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen) +: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 위 메서드들은 실무에선 잘 사용되지 않습니다. diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md index a150831247..4fd2ede8c3 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -5,7 +5,11 @@ 첫 번째 종류는 *데이터 프로퍼티(data property)* 입니다. 지금까지 사용한 모든 프로퍼티는 데이터 프로퍼티입니다. 데이터 프로퍼티 조작 방법에 대해선 모두 알고 계실 것이라 생각합니다. +<<<<<<< HEAD 두 번째는 *접근자 프로퍼티(accessor property)* 라 불리는 새로운 종류의 프로퍼티입니다. 접근자 프로퍼티의 본질은 함수인데, 이 함수는 값을 획득(get)하고 설정(set)하는 역할을 담당합니다. 그런데 외부 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 보입니다. +======= +The second type of property is something new. It's an *accessor property*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## getter와 setter diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index eada70304b..6b91597b34 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `[[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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `(**)`로 표시한 줄에서 `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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 프로토타입은 프로퍼티를 읽을 때만 사용합니다. @@ -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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 프로토타입 `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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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..374a879510 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index d655f06df7..a74a7d54a1 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 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..7735f40e6d 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index 42fdd142ee..0d960f0156 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 역사적인 이유가 있습니다. +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 개발자가 위 예시와 같은 코드를 작성할 땐 이런 결과를 의도하면서 구현하진 않았을 겁니다. 키가 무엇이 되었든, 키-값 쌍을 저장하려고 하는데 키가 `__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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 개발자들은 대개 프로토타입이 중간에 바뀌는 시나리오는 배제한 채 개발을 진행합니다. 이런 고정관념 때문에 프로토타입이 중간에 바뀌면서 발생한 버그는 그 원인을 쉽게 찾지 못합니다. 서버 사이드에서 자바스크립트를 사용 할 땐 이런 버그가 취약점이 되기도 합니다. +<<<<<<< HEAD `toString`을 비롯한 내장 메서드에 할당을 할 때도 같은 이유 때문에 예상치 못한 일이 일어날 수 있습니다. +======= +Unexpected things also may happen when assigning to `obj.toString`, as it's a built-in object method. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그럼 우리는 이런 문제를 어떻게 예방할 수 있을까요? +<<<<<<< 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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + + - [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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..3f14c8fd79 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 참고: 콘솔창을 열어 시계가 동작하는 모습을 확인해보세요. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 0e1def2be6..0dc236b769 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 과정을 거친 후에 `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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // 현재 프로토타입에는 메서드가 두 개입니다. 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트는 다양한 경우에 `[[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..4fec1c044a 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()`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 키워드 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 키워드 `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! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` `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: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js class Rabbit extends Animal { stop() { @@ -176,8 +194,12 @@ setTimeout(function() { super.stop() }, 1000); ``` ```` +<<<<<<< HEAD ## 생성자 오버라이딩 +======= +## Overriding constructor +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 생성자 오버라이딩은 좀 더 까다롭습니다. @@ -280,9 +302,13 @@ alert(rabbit.earLength); // 10 */!* ``` +<<<<<<< HEAD ### 클래스 필드 오버라이딩: 까다로운 내용 +======= +### Overriding class fields: a tricky note +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 상속을 받고 필드 값을 오버라이딩했는데 새로운 값 대신 부모 클래스 안에 있는 기존 필드 값을 사용하다니 이상하지 않나요? @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 위 예시에서 `Rabbit`은 하위 클래스이고 `constructor()`가 정의되어 있지 않습니다. 이런 경우 앞서 설명한 바와 같이 생성자는 비어있는데 그 안에 `super(...args)`만 있다고 보면 됩니다. 따라서 `new Rabbit()`을 실행하면 `super()`가 호출되고 그 결과 부모 생성자가 실행됩니다. 그런데 이때 하위 클래스 필드 초기화 순서에 따라 하위 클래스 `Rabbit`의 필드는 `super()` 실행 후에 초기화됩니다. 부모 생성자가 실행되는 시점에 `Rabbit`의 필드는 아직 존재하지 않죠. 이런 이유로 필드를 오버라이딩 했을 때 `Animal`에 있는 필드가 사용된 것입니다. +<<<<<<< HEAD 이렇게 자바스크립트는 오버라이딩시 필드와 메서드의 동작 방식이 미묘하게 다릅니다. +======= +This subtle difference between fields and methods is specific to JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 다행히도 이런 문제는 오버라이딩한 필드를 부모 생성자에서 사용할 때만 발생합니다. 이런 차이가 왜 발생하는지 모르면 결과를 해석할 수 없는 상황이 발생하기 때문에 별도의 공간을 만들어 필드 오버라이딩시 내부에서 벌어지는 일에 대해 자세히 알아보았습니다. 개발하다가 필드 오버라이딩이 문제가 되는 상황이 발생하면 필드 대신 메서드를 사용하거나 getter나 setter를 사용해 해결하면 됩니다. +<<<<<<< HEAD ## super 키워드와 [[HomeObject]] +======= +## Super: internals, [[HomeObject]] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```warn header="어렵습니다." 튜토리얼을 처음 읽는 분이라면 이번 절은 넘어가서도 좋습니다. @@ -545,7 +595,11 @@ longEar.eat(); // 귀가 긴 토끼 이/가 먹이를 먹습니다. ```js run let animal = { sayHi() { +<<<<<<< HEAD console.log(`나는 동물입니다.`); +======= + alert(`I'm an animal`); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } }; @@ -559,7 +613,11 @@ let rabbit = { let plant = { sayHi() { +<<<<<<< HEAD console.log("나는 식물입니다."); +======= + alert("I'm a plant"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } }; 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..7f7094aabf 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아시다시피 'extends' 문법은 두 개의 프로토타입을 설정합니다. 1. 생성자 함수의 `"prototype"` 사이(일반 메서드용) 2. 생성자 함수 자체 사이(정적 메서드용) +<<<<<<< HEAD 예시의 `class Rabbit extends Object`는 다음과 같은 관계를 만들죠. +======= +In the case of `class Rabbit extends Object` it means: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이해를 돕기 위한 그림: 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..b9d5b39597 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. 매개변수(`title`, `date` 등)를 이용해 관련 정보가 담긴 article 생성 2. 오늘 날짜를 기반으로 비어있는 article 생성 @@ -73,7 +95,11 @@ alert( articles[0].title ); // CSS 첫 번째 방법은 생성자를 사용해 구현할 수 있습니다. 두 번째 방법은 클래스에 정적 메서드를 만들어 구현할 수 있습니다. +<<<<<<< HEAD 아래 `Article.createTodays()`같이 말이죠. +======= +Such as `Article.createTodays()` here: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [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] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 정적 프로퍼티와 메서드는 상속됩니다. 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..743436953f 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; + } +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 읽기 전용 프로퍼티 @@ -159,7 +173,11 @@ class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { +<<<<<<< HEAD if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다."); +======= + if (value < 0) value = 0; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } */!* + 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..92760e8c8a 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## instanceof 연산자 [#ref-instanceof] @@ -93,7 +97,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..ec699463e4 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) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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]]`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 이벤트 믹스인 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..c2b7f2ada6 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 @@ -1,8 +1,14 @@ 차이점은 함수 내부의 코드를 보면 분명해집니다. +<<<<<<< HEAD `try..catch`에 '빠져나오게 하는' 코드가 있다면 함수의 행동이 달라집니다. 아래 예시와 같이 `try..catch` 내부에 `return`이 있을 때가 대표적인 예입니다. `finally` 절은 `return`문을 통해 `try..catch`를 빠져나가는 경우를 포함하여 `try..catch`가 종료되는 *모든* 상황에서 실행됩니다. `try..catch`가 종료되었지만, 함수 호출 코드가 제어권을 갖기 직전에 실행되죠. +======= +The behavior is different if there's a "jump out" of `try...catch`. + +For instance, when there's a `return` inside `try...catch`. The `finally` clause works in case of *any* exit from `try...catch`, even via the `return` statement: right after `try...catch` is done, but before the calling code gets the control. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function f() { @@ -11,7 +17,7 @@ function f() { *!* return "결과"; */!* - } catch (e) { + } catch (err) { /// ... } finally { alert('초기화!'); @@ -26,13 +32,19 @@ f(); // cleanup! ```js run function f() { try { +<<<<<<< HEAD alert('시작'); throw new Error("에러 발생!"); } catch (e) { +======= + alert('start'); + throw new Error("an error"); + } catch (err) { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // ... if("에러를 핸들링 할 수 없다면") { *!* - throw e; + throw err; */!* } diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md index b9e5324615..c077252c24 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md @@ -6,6 +6,7 @@ importance: 5 두 코드 조각을 비교해보세요. +<<<<<<< HEAD 1. 첫 번째 코드 조각은 `try..catch` 이후에 코드를 실행하기 위해 `finally`를 사용하였습니다. ```js @@ -13,12 +14,22 @@ importance: 5 작업 } catch (e) { 에러 핸들링 +======= +1. The first one uses `finally` to execute the code after `try...catch`: + + ```js + try { + work work + } catch (err) { + handle errors +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } finally { *!* 작업 내역 삭제 */!* } ``` +<<<<<<< HEAD 2. 두 번째 코드 조각에선 `try..catch` 바로 아래에 작업 내역을 삭제하는 코드를 놓았습니다. ```js @@ -26,6 +37,15 @@ importance: 5 작업 } catch (e) { 에러 핸들링 +======= +2. The second fragment puts the cleaning right after `try...catch`: + + ```js + try { + work work + } catch (err) { + handle errors +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } *!* 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..45672c72ae 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,14 +1,26 @@ +<<<<<<< HEAD # 'try..catch'와 에러 핸들링 +======= +# Error handling, "try...catch" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아무리 프로그래밍에 능한 사람이더라도 에러가 있는 스크립트를 작성할 수 있습니다. 원인은 아마도 실수, 예상치 못한 사용자 입력, 잘못된 서버 응답 등의 수천만 가지 이유 때문일 겁니다. 에러가 발생하면 스크립트는 '죽고'(즉시 중단되고), 콘솔에 에러가 출력됩니다. +<<<<<<< HEAD 그러나 `try..catch` 문법을 사용하면 스크립트가 죽는 걸 방지하고, 에러를 '잡아서(catch)' 더 합당한 무언가를 할 수 있게 됩니다. ## 'try..catch' 문법 'try..catch' 문법은 'try'와 'catch'라는 두 개의 주요 블록으로 구성됩니다. +======= +But there's a syntax construct `try...catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. + +## The "try...catch" syntax + +The `try...catch` construct has two main blocks: `try`, and then `catch`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js try { @@ -24,6 +36,7 @@ try { try..catch 동작 알고리즘은 다음과 같습니다. +<<<<<<< HEAD 1. 먼저, `try {...}` 안의 코드가 실행됩니다. 2. 에러가 없다면, `try` 안의 마지막 줄까지 실행되고, `catch` 블록은 건너뜁니다. 3. 에러가 있다면, `try` 안 코드의 실행이 중단되고, `catch(err)` 블록으로 제어 흐름이 넘어갑니다. 변수 `err`(아무 이름이나 사용 가능)는 무슨 일이 일어났는지에 대한 설명이 담긴 에러 객체를 포함합니다. @@ -31,6 +44,15 @@ try..catch 동작 알고리즘은 다음과 같습니다. ![](try-catch-flow.svg) 이렇게 `try {…}` 블록 안에서 에러가 발생해도 `catch`에서 에러를 처리하기 때문에 스크립트는 죽지 않습니다. +======= +1. First, the code in `try {...}` is executed. +2. If there were no errors, then `catch (err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. +3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch (err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. + +![](try-catch-flow.svg) + +So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시를 살펴봅시다. @@ -45,7 +67,7 @@ try..catch 동작 알고리즘은 다음과 같습니다. alert('try 블록 끝'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('에러가 없으므로, catch는 무시됩니다.'); // (3) @@ -64,7 +86,7 @@ try..catch 동작 알고리즘은 다음과 같습니다. alert('try 블록 끝(절대 도달하지 않음)'); // (2) - } catch(err) { + } catch (err) { alert(`에러가 발생했습니다!`); // *!*(3) <--*/!* @@ -72,33 +94,53 @@ try..catch 동작 알고리즘은 다음과 같습니다. ``` +<<<<<<< HEAD ````warn header="`try..catch`는 오직 런타임 에러에만 동작합니다." `try..catch`는 실행 가능한(runnable) 코드에만 동작합니다. 실행 가능한 코드는 유효한 자바스크립트 코드를 의미합니다. +======= +````warn header="`try...catch` only works for runtime errors" +For `try...catch` to work, the code must be runnable. In other words, it should be valid JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 중괄호 짝이 안 맞는 것처럼 코드가 문법적으로 잘못된 경우엔 `try..catch`가 동작하지 않습니다. ```js run try { {{{{{{{{{{{{ +<<<<<<< HEAD } catch(e) { alert("유효하지 않은 코드이기 때문에, 자바스크립트 엔진은 이 코드를 이해할 수 없습니다."); +======= +} catch (err) { + alert("The engine can't understand this code, it's invalid"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` 자바스크립트 엔진은 코드를 읽고 난 후 코드를 실행합니다. 코드를 읽는 중에 발생하는 에러는 'parse-time 에러'라고 부르는데, 엔진은 이 코드를 이해할 수 없기 때문에 parse-time 에러는 코드 안에서 복구가 불가능합니다. +<<<<<<< HEAD `try..catch`는 유효한 코드에서 발생하는 에러만 처리할 수 있습니다. 이런 에러를 '런타임 에러(runtime error)' 혹은 '예외(exception)'라고 부릅니다. ```` ````warn header="`try..catch`는 동기적으로 동작합니다." setTimeout처럼 '스케줄 된(scheduled)' 코드에서 발생한 예외는 `try..catch`에서 잡아낼 수 없습니다. +======= +So, `try...catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +```` + + +````warn header="`try...catch` works synchronously" +If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run try { setTimeout(function() { noSuchVariable; // 스크립트는 여기서 죽습니다. }, 1000); +<<<<<<< HEAD } catch (e) { alert( "작동 멈춤" ); } @@ -111,6 +153,20 @@ try { setTimeout(function() { try { noSuchVariable; // 이제 try..catch에서 에러를 핸들링 할 수 있습니다! +======= +} catch (err) { + alert( "won't work" ); +} +``` + +That's because the function itself is executed later, when the engine has already left the `try...catch` construct. + +To catch an exception inside a scheduled function, `try...catch` must be inside that function: +```js run +setTimeout(function() { + try { + noSuchVariable; // try...catch handles the error! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } catch { alert( "에러를 잡았습니다!" ); } @@ -125,7 +181,11 @@ setTimeout(function() { ```js try { // ... +<<<<<<< HEAD } catch(err) { // <-- '에러 객체', err 대신 다른 이름으로도 쓸 수 있음 +======= +} catch (err) { // <-- the "error object", could use another word instead of err +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // ... } ``` @@ -150,7 +210,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 ... (호출 스택) @@ -175,9 +235,15 @@ try { } ``` +<<<<<<< HEAD ## 'try..catch' 사용하기 `try..catch`가 실무에서 어떻게 사용되는지 알아봅시다. +======= +## Using "try...catch" + +Let's explore a real-life use case of `try...catch`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 앞서 JSON으로 인코딩된 값을 읽을 수 있도록 해주는 [JSON.parse(str)](mdn:js/JSON/parse) 메서드에 대해 배운 바 있습니다. @@ -205,7 +271,11 @@ JSON에 관한 자세한 정보는 챕터에서 읽어보시기 바 서버에서 전달받은 데이터가 잘못되어 스크립트가 죽는 경우, 사용자는 개발자 콘솔을 열지 않는 이상 절대 원인을 알 수 없습니다. 그런데 사람들은 메시지 등을 통해 에러의 원인을 알지 못한 채 무언가가 '그냥 죽는 것'을 정말 싫어합니다. +<<<<<<< HEAD `try..catch`를 사용해 이를 처리해 봅시다. +======= +Let's use `try...catch` to handle the error: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let json = "{ bad json }"; @@ -217,12 +287,19 @@ try { */!* alert( user.name ); // 이 코드는 동작하지 않습니다. -} catch (e) { +} catch (err) { *!* +<<<<<<< HEAD // 에러가 발생하면 제어 흐름이 catch 문으로 넘어옵니다. alert( "데이터에 에러가 있어 재요청을 시도합니다." ); alert( e.name ); alert( e.message ); +======= + // ...the execution jumps here + alert( "Our apologies, the data has errors, we'll try to request it one more time." ); + alert( err.name ); + alert( err.message ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* } ``` @@ -245,8 +322,13 @@ try { alert( user.name ); // 이름이 없습니다! */!* +<<<<<<< HEAD } catch (e) { alert( "실행되지 않습니다." ); +======= +} catch (err) { + alert( "doesn't execute" ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` @@ -293,12 +375,17 @@ alert(error.message); // 이상한 일이 발생했습니다. o_O ```js run try { +<<<<<<< HEAD JSON.parse("{ 잘못된 형식의 json o_O }"); } catch(e) { +======= + JSON.parse("{ bad json o_O }"); +} catch (err) { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* - 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 +410,13 @@ try { alert( user.name ); +<<<<<<< HEAD } catch(e) { alert( "JSON Error: " + e.message ); // JSON Error: 불완전한 데이터: 이름 없음 +======= +} catch (err) { + alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` @@ -334,7 +426,11 @@ try { ## 에러 다시 던지기 +<<<<<<< HEAD 위 예시에선 불완전한 데이터를 `try..catch`로 처리하였습니다. 그런데 *또 다른 예기치 않은 에러*가 `try {...}` 블록 안에서 발생 할 수도 있습니다. 정의되지 않은 변수 사용 등의 프로그래밍 에러가 발생할 가능성은 항상 있습니다. +======= +In the example above we use `try...catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -345,7 +441,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가 아닙니다.) } @@ -353,7 +449,11 @@ try { 에러는 어떤 상황에서도 발생할 수 있습니다! 몇십 년간 몇백만 명이 사용한 오픈소스 유틸리티에서도 끔찍한 해킹으로 이어질 수 있는 엄청난 버그가 발견되죠. +<<<<<<< HEAD 위에선 '불완전한 데이터'를 다루려는 목적으로 `try..catch`를 썼습니다. 그런데 `catch`는 원래 `try` 블록에서 발생한 *모든* 에러를 잡으려는 목적으로 만들어졌습니다. 그런데 위 예시에서 `catch`는 예상치 못한 에러를 잡아내 주긴 했지만, 에러 종류와 관계없이 `"JSON Error"` 메시지를 보여줍니다. 이렇게 에러 종류와 관계없이 동일한 방식으로 에러를 처리하는 것은 디버깅을 어렵게 만들기 때문에 좋지 않습니다. +======= +In our case, `try...catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 문제를 피하고자 '다시 던지기(rethrowing)' 기술을 사용합니다. 규칙은 간단합니다. @@ -361,16 +461,22 @@ try { '다시 던지기' 기술을 더 자세히 설명하겠습니다. +<<<<<<< HEAD 1. catch가 모든 에러를 받습니다. 2. `catch(err) {...}` 블록 안에서 에러 객체 `err`를 분석합니다. 3. 에러 처리 방법을 알지 못하면 `throw err`를 합니다. +======= +1. Catch gets all errors. +2. In the `catch (err) {...}` block we analyze the error object `err`. +3. If we don't know how to handle it, we do `throw err`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 보통 에러 타입을 `instanceof` 명령어로 체크합니다. ```js run try { user = { /*...*/ }; -} catch(err) { +} catch (err) { *!* if (err instanceof ReferenceError) { */!* @@ -399,24 +505,36 @@ 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 { +<<<<<<< HEAD throw e; // 에러 다시 던지기 (*) +======= + throw err; // rethrow (*) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } */!* } ``` +<<<<<<< HEAD `catch` 블록 안의 `(*)`로 표시한 줄에서 다시 던져진(rethrow) 에러는 `try..catch` '밖으로 던져집니다'. 이때 바깥에 `try..catch`가 있다면 여기서 에러를 잡습니다. 아니라면 스크립트는 죽을 겁니다. +======= +The error throwing on line `(*)` from inside `catch` block "falls out" of `try...catch` and can be either caught by an outer `try...catch` construct (if it exists), or it kills the script. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 하면 `catch` 블록에선 어떻게 다룰지 알고 있는 에러만 처리하고, 알 수 없는 에러는 '건너뛸 수' 있습니다. +<<<<<<< HEAD 이제 `try..catch`를 하나 더 만들어, 다시 던져진 예상치 못한 에러를 처리해 보겠습니다. +======= +The example below demonstrates how such errors can be caught by one more level of `try...catch`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function readData() { @@ -427,11 +545,15 @@ function readData() { *!* blabla(); // 에러! */!* - } catch (e) { + } catch (err) { // ... - if (!(e instanceof SyntaxError)) { + if (!(err instanceof SyntaxError)) { *!* +<<<<<<< HEAD throw e; // 알 수 없는 에러 다시 던지기 +======= + throw err; // rethrow (don't know how to deal with it) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* } } @@ -439,20 +561,32 @@ function readData() { try { readData(); -} catch (e) { +} catch (err) { *!* +<<<<<<< HEAD alert( "External catch got: " + e ); // 에러를 잡음 +======= + alert( "External catch got: " + err ); // caught it! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* } ``` +<<<<<<< HEAD `readData`는 `SyntaxError`만 처리할 수 있지만, 함수 바깥의 `try..catch`에서는 예상치 못한 에러도 처리할 수 있게 되었습니다. +======= +Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -## try..catch..finally +## try...catch...finally 잠깐! 에러 핸들링은 여기서 끝이 아닙니다. +<<<<<<< HEAD `try..catch`는 `finally`라는 코드 절을 하나 더 가질 수 있습니다. +======= +The `try...catch` construct may have one more code clause: `finally`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `finally`안의 코드는 다음과 같은 상황에서 실행됩니다. @@ -463,9 +597,15 @@ try { ```js *!*try*/!* { +<<<<<<< HEAD ... 코드를 실행 ... } *!*catch*/!*(e) { ... 에러 핸들링 ... +======= + ... try to execute the code ... +} *!*catch*/!* (err) { + ... handle errors ... +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } *!*finally*/!* { ... 항상 실행 ... } @@ -475,9 +615,15 @@ try { ```js run try { +<<<<<<< HEAD alert( 'try 블록 시작' ); if (confirm('에러를 만드시겠습니까?')) 이상한_코드(); } catch (e) { +======= + alert( 'try' ); + if (confirm('Make an error?')) BAD_CODE(); +} catch (err) { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( 'catch' ); } finally { alert( 'finally' ); @@ -513,7 +659,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -531,14 +677,24 @@ alert( `연산 시간: ${diff}ms` ); 함수는 `return` 이나 `throw`를 만나면 종료되는데, 이렇게 `finally` 절을 사용하면 두 경우 모두를 처리할 수 있습니다. +<<<<<<< HEAD ```smart header="`try..catch..finally` 안의 변수는 지역 변수입니다." 위 예시에서 변수 `diff`와 `result`는 `try..catch` *전* 에 선언되었다는 점에 주의해 주세요. +======= +```smart header="Variables are local inside `try...catch...finally`" +Please note that `result` and `diff` variables in the code above are declared *before* `try...catch`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `try` 블록 안에서 선언한 변수는 블록 안에서만 유효한 지역 변수가 됩니다. ``` +<<<<<<< HEAD ````smart header="`finally` 와 `return`" `finally` 절은 `try..catch` 절을 빠져나가는 *어떤* 경우에도 실행됩니다. `return`을 사용해 명시적으로 빠져나가려는 경우도 마찬가지입니다. +======= +````smart header="`finally` and `return`" +The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시의 `try` 블록 안엔 `return`이 있습니다. 이 경우엔 값이 바깥 코드로 반환되기 전에 `finally`가 실행됩니다. @@ -550,7 +706,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -563,9 +719,13 @@ alert( func() ); // finally 안의 alert가 실행되고 난 후, 실행됨 ``` ```` -````smart header="`try..finally`" +````smart header="`try...finally`" +<<<<<<< HEAD `catch` 절이 없는 `try..finally` 구문도 상황에 따라 유용하게 쓸 수 있습니다. `try..finally` 안에선 에러를 처리하고 싶지 않지만, 시작한 프로세스가 마무리되었는지 확실히 하고 싶은 경우에 사용합니다. +======= +The `try...finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function func() { @@ -586,7 +746,11 @@ function func() { 이 절은 코어 자바스크립트가 아닙니다. ``` +<<<<<<< HEAD `try..catch` 바깥에서 치명적인 에러가 발생해 스크립트가 죽었다고 상상해봅시다. +======= +Let's imagine we've got a fatal error outside of `try...catch`, and the script died. Like a programming error or some other terrible thing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 대처 방법은 무엇이 있을까요? 어딘가에 에러 내역을 기록해 놓거나 사용자에게 에러가 발생했음을 알려주는 행위를 할 수 있을 겁니다. @@ -632,7 +796,11 @@ window.onerror = function(message, url, line, col, error) { 그런데 전역 핸들러 `window.onerror`는 죽어버린 스크립트를 복구하려는 목적으로는 잘 사용하지 않습니다. 프로그래밍 에러가 발생한 경우 `window.onerror`만으로 스크립트를 복구하는 건 사실상 불가능하죠. `window.onerror`는 개발자에게 에러 메시지를 보내는 용도로 사용합니다. +<<<<<<< HEAD `window.onerror`말고 같은 에러 로깅 관련 상용 서비스가 여러 가지 있습니다. +======= +There are also web-services that provide error-logging for such cases, like or . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 서비스들은 다음과 같은 프로세스로 동작합니다. @@ -643,22 +811,37 @@ window.onerror = function(message, url, line, col, error) { ## 요약 +<<<<<<< HEAD `try..catch`를 이용하면 런타임 에러를 처리할 수 있습니다. `try`에선 코드를 실행하고, 에러가 발생하면 `catch`에서 잡아냅니다. +======= +The `try...catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문법은 다음과 같습니다. ```js try { +<<<<<<< HEAD // 이곳의 코드를 실행 } catch(err) { // 에러가 발생하면, 여기부터 실행됨 // err는 에러 객체 +======= + // run this code +} catch (err) { + // if an error happened, then jump here + // err is the error object +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } finally { // 에러 발생 여부와 상관없이 try/catch 이후에 실행됨 } ``` +<<<<<<< HEAD `try..catch`, `try..catch..finally`이외에도 `try..finally`를 사용할 수 있습니다. +======= +There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 에러 객체엔 다음과 같은 프로퍼티가 있습니다. @@ -666,10 +849,18 @@ try { - `name` -- 에러 이름을 담은 문자열 (에러 생성자 이름) - `stack` -- 표준이 아니지만 대부분의 호스트 환경이 지원하는 프로퍼티로 에러가 발생한 순간의 스택을 나타냄 +<<<<<<< HEAD 에러 객체가 필요 없으면 `catch(err) {` 대신 `catch {`를 쓸 수 있습니다. +======= +If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `throw` 연산자를 사용하면 에러를 직접 만들 수 있습니다. 이론상으론, `throw` 인수에 모든 것을 넘길 수 있지만, 대개 내장 `Error` 클래스를 상속받은 에러 객체를 인수에 넘깁니다. 에러 상속에 대해선 다음 챕터에서 다룰 예정입니다. *다시 던지기*는 에러 처리 시 사용되는 중요한 패턴입니다. `catch` 블록에선 대개 예상하였거나 어떻게 다룰지 알고 있는 에러를 다루고, 예상치 못한 에러는 다시 던지기 합니다. +<<<<<<< HEAD `try..catch`가 없어도 대부분의 호스트 환경이 '전역' 에러 핸들러를 지원하기 때문에 '떨어져 나온' 에러를 잡을 수 있습니다. `window.onerror`는 브라우저 환경의 전역 에러 핸들러입니다. +======= +Even if we don't have `try...catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..db151d2996 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -21,9 +21,15 @@ let json = `{ "name": "John", "age": 30 }`; 따라서 `readUser(json)`은 JSON 형식의 데이터를 읽을 수 있을 뿐만 아니라, 데이터를 '검증'할 수 있어야 합니다. 필수 프로퍼티가 없거나, 위 형식에 맞지 않으면 에러를 발생시킬 수 있어야 하죠. 그런데 이때 발생하는 에러는 `SyntaxError`가 아닙니다. JSON 형식은 맞지만, 자체 기준에 맞지 않기 때문에 발생한 에러이므로 전혀 다른 종류의 에러이죠. 지금부턴 이 에러를 `ValidationError`라고 부르겠습니다. 자 이제 `ValidationError`를 위한 클래스를 만들어봅시다. +<<<<<<< HEAD `ValidationError` 클래스엔 문제가 되는 필드 정보가 저장되어야 합니다. 내장 클래스 `Error`를 상속받아 `ValidationError` 클래스를 만들어봅시다. 그 전에 먼저 잠시 슈도 코드로 `Error` 클래스가 어떻게 생겼는지 살펴보겠습니다. +======= +Our `ValidationError` class should inherit from the `Error` class. + +The `Error` class is built-in, but here's its approximate code so we can understand what we're extending: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // 자바스크립트 자체 내장 에러 클래스 Error의 '슈도 코드' @@ -38,7 +44,7 @@ class Error { 이제 `ValidationError`에서 `Error`를 상속받아보겠습니다. -```js run untrusted +```js run *!* class ValidationError extends Error { */!* @@ -117,15 +123,23 @@ try { // (err instanceof SyntaxError) 대신 사용 가능 } else if (err.name == "SyntaxError") { // (*) // ... -``` +``` 그런데 에러 유형 확인은 `err.name`보다는 `instanceof`를 사용하는 게 훨씬 좋습니다. 나중에 `ValidationError`를 확장하여 `PropertyRequiredError` 같은 새로운 확장 에러를 만들게 될 텐데, `instanceof`는 새로운 상속 클래스에서도 동작하기 때문입니다. +<<<<<<< HEAD `catch`에 알려지지 않은 에러가 있을 때 이 에러는 재 던지기 된다는 점(`(**)`) 또한 주목해서 봐주시기 바랍니다. `catch` 블록에선 유효성 검사와 문법 오류만 처리하고, 다른 종류의 에러는 밖으로 던져야 합니다. +======= +Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (caused by a typo in the code or other unknown reasons) should fall through. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 더 깊게 상속하기 +<<<<<<< HEAD 앞서 만든 `ValidationError` 클래스는 너무 포괄적이어서 뭔가 잘못될 확률이 있습니다. 꼭 필요한 프로퍼티가 누락되거나 `age`에 문자열 값이 들어가는 것처럼 형식이 잘못된 경우를 처리할 수 없죠. 필수 프로퍼티가 없는 경우에 대응할 수 있도록 좀 더 구체적인 클래스 `PropertyRequiredError`를 만들어 봅시다. `PropertyRequiredError`엔 누락된 프로퍼티에 대한 추가 정보가 담겨야 합니다. +======= +The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run class ValidationError extends Error { diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index 8751bf4fbc..270a78e221 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -28,7 +28,11 @@ function loadScript(src) { } ``` +<<<<<<< HEAD 함수 `loadScript(src)`는 ` ``` +<<<<<<< HEAD 참고로 브라우저 환경에서 부득이하게 window 레벨 전역 변수를 만들어야 한다면 `window` 객체에 변수를 명시적으로 할당하고 `window.user`와 같이 접근하는 방식을 취하시면 됩니다. 그런데 이 방법은 정말 필요한 경우에만 사용하길 권유합니다. +======= +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 단 한 번만 평가됨 +<<<<<<< HEAD 동일한 모듈이 여러 곳에서 사용되더라도 모듈은 최초 호출 시 단 한 번만 실행됩니다. 실행 후 결과는 이 모듈을 가져가려는 모든 모듈에 내보내 집니다. 이런 작동 방식은 중요한 결과를 초래합니다. 예시를 통해 이에 대해 알아봅시다. +======= +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. + +The one-time evaluation has important consequences, that we should be aware of. + +Let's see a couple of examples. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert 함수가 있는 모듈(`alert.js`)을 여러 모듈에서 가져오기로 해봅시다. 얼럿 창은 단 한 번만 나타납니다. @@ -133,9 +187,17 @@ import `./alert.js`; // 얼럿창에 '모듈이 평가되었습니다!'가 출 import `./alert.js`; // 아무 일도 발생하지 않습니다. ``` +<<<<<<< HEAD 실무에선 최상위 레벨 모듈을 대개 초기화나 내부에서 쓰이는 데이터 구조를 만들고 이를 내보내 재사용하고 싶을 때 사용합니다. 이제 좀 더 어려운 예시를 살펴보겠습니다. +======= +The second import shows nothing, because the module has already been evaluated. + +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. + +Now, let's consider a deeper example. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 객체를 내보내는 모듈을 만들어봅시다. @@ -160,6 +222,7 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* +<<<<<<< HEAD // 1.js와 2.js 모두 같은 객체를 가져오므로 // 1.js에서 객체에 가한 조작을 2.js에서도 확인할 수 있습니다. */!* @@ -170,12 +233,34 @@ alert(admin.name); // Pete 이런 특징을 이용하면 모듈 *설정(configuration)*을 쉽게 할 수 있습니다. 최초로 실행되는 모듈의 객체 프로퍼티를 원하는 대로 설정하면 다른 모듈에서 이 설정을 그대로 사용할 수 있기 때문이죠. 예시를 통해 이에 대해 자세히 알아봅시다. 아래 `admin.js` 모듈은 어떤 특정한 기능을 제공해주는데, 이 기능을 사용하려면 외부에서 `admin` 객체와 관련된 인증 정보를 받아와야 한다고 가정해봅시다. +======= +// Both 1.js and 2.js reference the same admin object +// Changes made in 1.js are visible in 2.js +*/!* +``` + +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. + +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other importers will see that. + +**Such behavior is actually very convenient, because it allows us to *configure* modules.** + +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { +<<<<<<< HEAD alert(`${admin.name}님, 안녕하세요!`); } ``` @@ -195,19 +280,52 @@ admin.name = "보라"; import {admin, sayHi} from './admin.js'; alert(admin.name); // *!*보라*/!* +======= + alert(`Ready to serve, ${config.user}!`); +} +``` + +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: + +```js +// 📁 init.js +import {config} from './admin.js'; +config.user = "Pete"; +``` + +...Now the module `admin.js` is configured. + +Further importers can call it, and it correctly shows the current user: + +```js +// 📁 another.js +import {sayHi} from './admin.js'; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 sayHi(); // *!*보라*/!*님, 안녕하세요! ``` + ### import.meta `import.meta` 객체는 현재 모듈에 대한 정보를 제공해줍니다. +<<<<<<< HEAD 호스트 환경에 따라 제공하는 정보의 내용은 다른데, 브라우저 환경에선 스크립트의 URL 정보를 얻을 수 있습니다. HTML 안에 있는 모듈이라면, 현재 실행 중인 웹페이지의 URL 정보를 얻을 수 있습니다. ```html run height=0 ``` @@ -233,7 +351,11 @@ sayHi(); // *!*보라*/!*님, 안녕하세요! 브라우저 환경에서 `type="module"`이 붙은 스크립트가 일반 스크립트와 어떤 점이 다른지 알아봅시다. +<<<<<<< HEAD 자바스크립트 초심자나 브라우저 환경에서 자바스크립트를 사용하지 않고 있다면 이 내용은 넘어가셔도 됩니다. +======= +You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 지연 실행 @@ -244,7 +366,11 @@ sayHi(); // *!*보라*/!*님, 안녕하세요! - 모듈 스크립트는 HTML 문서가 완전히 준비될 때까지 대기 상태에 있다가 HTML 문서가 완전히 만들어진 이후에 실행됩니다. 모듈의 크기가 아주 작아서 HTML보다 빨리 불러온 경우에도 말이죠. - 스크립트의 상대적 순서가 유지됩니다. 문서상 위쪽의 스크립트부터 차례로 실행됩니다. +<<<<<<< HEAD 이런 특징 때문에 모듈 스크립트는 항상 완전한 HTML 페이지를 '볼 수' 있고 문서 내 요소에도 접근할 수 있습니다. +======= +As a side effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 예시: @@ -260,7 +386,11 @@ sayHi(); // *!*보라*/!*님, 안녕하세요! diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 7c765f5d18..8ced6d3c69 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -46,7 +46,7 @@ export function sayHi(user) { 아래 예시에선 함수를 먼저 선언한 후, 마지막 줄에서 내보냅니다. -```js +```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); @@ -93,6 +93,7 @@ say.sayBye('John'); 이렇게 하는 데는 몇 가지 이유가 있습니다. +<<<<<<< HEAD 1. [웹팩(webpack)](http://webpack.github.io)과 같은 모던 빌드 툴은 로딩 속도를 높이기 위해 모듈들을 한데 모으는 번들링과 최적화를 수행합니다. 이 과정에서 사용하지 않는 리소스가 삭제되기도 합니다. 아래와 같이 프로젝트에 서드파티 라이브러리인 `say.js`를 도입하였다 가정합시다. 이 라이브러리엔 수 많은 함수가 있습니다. @@ -112,6 +113,16 @@ say.sayBye('John'); 2. 어떤 걸 가지고 올지 명시하면 이름을 간결하게 써줄 수 있습니다. `say.sayHi()`보다 `sayHi()`가 더 간결하네요. 3. 어디서 어떤 게 쓰이는지 명확하기 때문에 코드 구조를 파악하기가 쉬워 리팩토링이나 유지보수에 도움이 됩니다. +======= +1. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. +2. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. + +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports. + +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle. +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## import 'as' @@ -224,7 +235,7 @@ export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; export class { // 에러! (default export가 아닌 경우엔 이름이 꼭 필요합니다.) constructor() {} } -``` +``` ### 'default' name @@ -321,12 +332,16 @@ export {default as User} from './user.js'; // default export를 다시 내보내 다시 내보내기가 왜 필요한건지 의문이 드실 겁니다. 유스 케이스를 통해 다시 내보내기가 실무에서 언제 사용되는지 알아봅시다. +<<<<<<< HEAD NPM을 통해 외부에 공개할 '패키지(package)'를 만들고 있다고 가정합시다. 이 패키지는 수많은 모듈로 구성되어있는데, 몇몇 모듈은 외부에 공개할 기능을, 몇몇 모듈은 이러한 모듈을 도와주는 '헬퍼' 역할을 담당하고 있다고 합시다. +======= +Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 패키지 구조는 아래와 같습니다. ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -337,13 +352,27 @@ auth/ ... ``` +<<<<<<< HEAD 진입점 역할을 하는 '주요 파일'인 `auth/index.js`을 통해 기능을 외부에 노출시키면 이 패키지를 사용하는 개발자들은 아래와 같은 코드로 해당 기능을 사용할 겁니다. +======= +We'd like to expose the package functionality via a single entry point. + +In other words, a person who would like to use our package, should import only from the "main file" `auth/index.js`. + +Like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js import {login, logout} from 'auth/index.js' ``` +<<<<<<< HEAD 이때 우리가 만든 패키지를 사용하는 외부 개발자가 패키지 안의 파일들을 뒤져 내부 구조를 건드리게 하면 안 됩니다. 그러려면 공개할 것만 `auth/index.js`에 넣어 내보내기 하고 나머지는 숨기는 게 좋겠죠. +======= +The "main file", `auth/index.js` exports all the functionality that we'd like to provide in our package. + +The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이때 내보낼 기능을 패키지 전반에 분산하여 구현한 후, `auth/index.js`에서 이 기능들을 가져오고 이를 다시 내보내면 원하는 바를 어느 정도 달성할 수 있습니다. @@ -366,19 +395,36 @@ export {User}; ```js // 📁 auth/index.js +<<<<<<< HEAD // login과 logout을 가지고 온 후 바로 내보냅니다. export {login, logout} from './helpers.js'; // User 가져온 후 바로 내보냅니다. +======= +// re-export login/logout +export {login, logout} from './helpers.js'; + +// re-export the default export as User +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 export {default as User} from './user.js'; ... ``` +<<<<<<< HEAD ### default export 다시 내보내기 +======= +The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. + +### Re-exporting the default export +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 기본 내보내기를 다시 내보낼 때는 주의해야 할 점들이 있습니다. +<<<<<<< HEAD `user.js` 내의 클래스 `User`를 다시 내보내기 한다고 가정해 봅시다. +======= +Let's say we have `user.js` with the `export default class User` and would like to re-export it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // 📁 user.js @@ -387,19 +433,35 @@ export default class User { } ``` +<<<<<<< HEAD 1. `User`를 `export User from './user.js'`로 다시 내보내기 할 때 문법 에러가 발생합니다. 어디가 잘못된 걸까요? default export를 다시 내보내려면 위 예시처럼 `export {default as User}`를 사용해야 합니다. +======= +We can come across two problems with it: + +1. `export User from './user.js'` won't work. That would lead to a syntax error. + + To re-export the default export, we have to write `export {default as User}`, as in the example above. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 2. `export * from './user.js'`를 사용해 모든 걸 한 번에 다시 내보내면 default export는 무시되고, named export만 다시 내보내집니다. +<<<<<<< HEAD 두 가지를 동시에 다시 내보내고 싶다면 두 문을 동시에 사용해야 합니다. +======= + If we'd like to re-export both named and default exports, then two statements are needed: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js export * from './user.js'; // named export를 다시 내보내기 export {default} from './user.js'; // default export를 다시 내보내기 ``` +<<<<<<< HEAD default export를 다시 내보낼 땐 이런 특이한 상황도 인지하고 있다가 처리해줘야 하므로 몇몇 개발자들은 default export를 다시 내보내는것을 선호하지 않습니다. +======= +Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 @@ -418,6 +480,7 @@ default export를 다시 내보낼 땐 이런 특이한 상황도 인지하고 가져오기 타입 역시 정리해 봅시다. +<<<<<<< HEAD - named export 가져오기: - `import {x [as y], ...} from "mod"` - default export 가져오기: @@ -427,6 +490,17 @@ default export를 다시 내보낼 땐 이런 특이한 상황도 인지하고 - `import * as obj from "mod"` - 모듈을 가져오긴 하지만(코드는 실행됨), 변수에 할당하지 않기: - `import "mod"` +======= +- Importing named exports: + - `import {x [as y], ...} from "module"` +- Importing the default export: + - `import x from "module"` + - `import {default as x} from "module"` +- Import all: + - `import * as obj from "module"` +- Import the module (its code runs), but do not assign any of its exports to variables: + - `import "module"` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `import/export` 문은 스크립트의 맨 위나 맨 아래에 올 수 있는데 이 둘엔 차이가 없습니다. diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md index 357a573134..9db69cb2fa 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md @@ -19,5 +19,5 @@ function wrap(target) { user = wrap(user); alert(user.name); // John -alert(user.age); // ReferenceError: Property doesn't exist "age" +alert(user.age); // ReferenceError: Property doesn't exist: "age" ``` diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md index 67e853f537..29a13bbe9f 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md @@ -27,6 +27,6 @@ user = wrap(user); alert(user.name); // John *!* -alert(user.age); // ReferenceError: Property doesn't exist "age" +alert(user.age); // ReferenceError: Property doesn't exist: "age" */!* ``` diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index 4c8d328819..d0eef6b906 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -39,7 +39,7 @@ for(let key in proxy) alert(key); // test, 반복도 잘 동작합니다. -- (3) 그림에서 볼 수 있듯이 트랩이 없으면 `proxy`는 `target`을 둘러싸는 투명한 래퍼가 됩니다. -![](proxy.svg) +![](proxy.svg) `Proxy`는 일반 객체와는 다른 행동 양상을 보이는 '특수 객체(exotic object)'입니다. 프로퍼티가 없죠. `handler`가 비어있으면 `Proxy`에 가해지는 작업은 `target`에 곧바로 전달됩니다. @@ -55,6 +55,7 @@ for(let key in proxy) alert(key); // test, 반복도 잘 동작합니다. -- (3) | 내부 메서드 | 핸들러 메서드 | 작동 시점 | |-----------------|----------------|-------------| +<<<<<<< HEAD | `[[Get]]` | `get` | 프로퍼티를 읽을 때 | | `[[Set]]` | `set` | 프로퍼티에 쓸 때 | | `[[HasProperty]]` | `has` | `in` 연산자가 동작할 때 | @@ -68,6 +69,21 @@ for(let key in proxy) alert(key); // test, 반복도 잘 동작합니다. -- (3) | `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) | | `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | | `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object/keys/values/entries` | +======= +| `[[Get]]` | `get` | reading a property | +| `[[Set]]` | `set` | writing to a property | +| `[[HasProperty]]` | `has` | `in` operator | +| `[[Delete]]` | `deleteProperty` | `delete` operator | +| `[[Call]]` | `apply` | function call | +| `[[Construct]]` | `construct` | `new` operator | +| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | +| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | +| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](mdn:/JavaScript/Reference/Global_Objects/Object/isExtensible) | +| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) | +| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) | +| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | +| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```warn header="규칙" 내부 메서드나 트랩을 쓸 땐 자바스크립트에서 정한 몇 가지 규칙(invariant)을 반드시 따라야 합니다. @@ -335,7 +351,11 @@ let user = { _password: "비밀" }; +<<<<<<< HEAD alert(user._password); // 비밀 +======= +alert(user._password); // secret +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 프락시를 사용해 `_`로 시작하는 프로퍼티에 접근하지 못하도록 막아봅시다. @@ -375,8 +395,13 @@ user = new Proxy(user, { } }, *!* +<<<<<<< HEAD deleteProperty(target, prop) { // 프로퍼티 삭제를 가로챕니다. */!* +======= + deleteProperty(target, prop) { // to intercept property deletion +*/!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 if (prop.startsWith('_')) { throw new Error("접근이 제한되어있습니다."); } else { @@ -437,7 +462,11 @@ user = { ``` +<<<<<<< HEAD `user.checkPassword()`를 호출하면 점 앞의 객체가 `this`가 되므로 프락시로 감싼 `user`에 접근하게 되는데, `this._password`는 `get` 트랩(프로퍼티를 읽으려고 하면 동작함)을 활성화하므로 에러가 던져집니다. +======= +A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `(*)`로 표시한 줄에선 객체 메서드의 컨텍스트를 원본 객체인 `target`에 바인딩시켜준 이유가 바로 여기에 있습니다. `checkPassword()`를 호출할 땐 언제든 트랩 없이 `target`이 `this`가 되게 하기 위해서이죠. @@ -963,9 +992,13 @@ revoke(); alert(proxy.data); // Error ``` -A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. The target object can be garbage-collected after that. +A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. + +Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around while leaving `revoke` in the current scope. + +We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`. -We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object: +Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy: ```js run *!* @@ -980,21 +1013,25 @@ let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); -// ..later in our code.. +// ..somewhere else in our code.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Error (revoked) ``` +<<<<<<< HEAD The benefit of such an approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needed. We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes 'unreachable' (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more. +======= +We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## References - Specification: [Proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). -- MDN: [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). +- MDN: [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). ## Summary @@ -1016,13 +1053,13 @@ We can trap: - Reading (`get`), writing (`set`), deleting (`deleteProperty`) a property (even a non-existing one). - Calling a function (`apply` trap). - The `new` operator (`construct` trap). -- Many other operations (the full list is at the beginning of the article and in the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)). +- Many other operations (the full list is at the beginning of the article and in the [docs](mdn:/JavaScript/Reference/Global_Objects/Proxy)). That allows us to create "virtual" properties and methods, implement default values, observable objects, function decorators and so much more. We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality. -The [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects. +The [Reflect](mdn:/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects. Proxies have some limitations: diff --git a/1-js/99-js-misc/03-currying-partials/article.md b/1-js/99-js-misc/03-currying-partials/article.md index e9601348e2..bc2f9756eb 100644 --- a/1-js/99-js-misc/03-currying-partials/article.md +++ b/1-js/99-js-misc/03-currying-partials/article.md @@ -155,7 +155,7 @@ function curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { - return function pass(...args2) { // (2) + return function(...args2) { // (2) return curried.apply(this, args.concat(args2)); } } @@ -164,6 +164,7 @@ function curried(...args) { 위의 예시를 실행시키면, 두 개의 `if` 분기점이 있습니다. +<<<<<<< HEAD (1)에 해당하는 경우(함수가 호출되었을때): `args` 를 카운트한 갯수가 전달된 원래 함수 func (`func.length`)와 같거나 길다면, 그대로 `func` 호출에 전달함. (2)에 해당하는 경우(partial이 적용될때): 아직 `func`이 호출되지 않습니다. `pass`라는 래퍼가 대신 반환되고, `pass` 래퍼함수가 `curried`를 이전함수와 새로운 인수와 함께 다시 적용합니다. 그 다음 새로운 `curried` 호출에, 다시 새로운 partial (만약에 인수가 충분하지 않으면)을 반환하거나 최종적으로 `func` 결과를 반환합니다. @@ -176,6 +177,12 @@ function curried(...args) { 3. `pass` 래퍼가 다시 `(3)`과 함께 호출됩니다. 다음 호출인 `pass(3)`가 이전의 인수들인 (`1`, `2`)를 가져오고 `3`을 추가하고 `curried(1, 2, 3)` 호출을 합니다 -- 여기에 `3`인수는 마지막으로, 원래의 함수에 전달됩니다. 아직 확실하게 이해되지 않았다면, 호출 순서를 마음속이나 종이에 그려보세요. +======= +1. If passed `args` count is the same or more than the original function has in its definition (`func.length`) , then just pass the call to it using `func.apply`. +2. Otherwise, get a partial: we don't call `func` just yet. Instead, another wrapper is returned, that will re-apply `curried` providing previous arguments together with the new ones. + +Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="오직 고정된 길이의 함수들만 사용 가능합니다" 커링은 해당 함수가 고정된 개수의 인수를 가지도록 요구합니다. diff --git a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md index 9748f9c2ae..93ff0e3974 100644 --- a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md +++ b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md @@ -5,7 +5,11 @@ 2. 역시 일반적인 호출 방법에 속합니다. 괄호가 추가되었긴 하지만 연산 우선순위를 바꾸진 않으므로 점 연산자가 먼저 실행됩니다. +<<<<<<< HEAD 3. 좀 더 복잡한 패턴의 호출(`(expression).method()`)이 등장했네요. 세 번째 호출은 아래와 같은 코드로 쪼갤 수 있습니다. +======= +3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify f = obj.go; // 표현식 계산하기 @@ -14,7 +18,11 @@ 위 코드에서 `f()`는 (메서드가 아닌) 함수로써 호출되었습니다. `this`에 대한 정보가 전혀 없는 상태에서 말이죠. +<<<<<<< HEAD 4. `(3)`과 동일한 패턴의 호출입니다. `expression`이 `obj.go || obj.stop`라는 차이점만 있습니다. +======= +4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `(3)`과 `(4)`에서 어떤 일이 일어나는지 알려면 참조 타입을 다시 상기해야 합니다. 점이나 대괄호를 통해 프로퍼티에 접근하려는 경우 참조 타입 값(`(base, name, strict)`)이 반환됩니다. diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index 2228ea9399..a89fa6d04b 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -4,7 +4,11 @@ ```warn header="심화 학습" 이번 절에선 특정 에지 케이스(edge case)를 설명하기 위한 심화 내용을 다룹니다. +<<<<<<< HEAD 숙련된 상당수의 개발자가 이 절에서 다룰 내용을 모른 채로 일하고 있지만 문제가 없고, 중요한 내용은 아니기 때문에 자바스크립트 내부에서 어떤 일이 일어나는지 알고 싶지 않다면 이번 글은 넘어가거나 미뤄도 괜찮습니다. +======= +It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 복잡한 상황에서 메서드를 호출하면 `this` 값을 잃어버리는 경우가 생깁니다. @@ -59,7 +63,7 @@ user.hi(); let user = { name: "John", hi() { alert(this.name); } -} +}; *!* // 메서드 접근과 호출을 별도의 줄에서 실행함 @@ -87,13 +91,21 @@ hi(); // this가 undefined이기 때문에 에러가 발생합니다. (user, "hi", true) ``` +<<<<<<< HEAD 참조형 값에 괄호 `()`를 붙여 호출하면 객체, 객체의 메서드와 연관된 모든 정보를 받습니다. 이 정보를 기반으로 `this`(`=user`)가 결정됩니다. +======= +When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`user` in this case). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 참조 타입은 내부에서 점 `.`연산에서 알아낸 정보를 괄호 `()`로 전달해주는 '중개인' 역할을 합니다. 그런데 점 연산 이외의 연산(할당 연산 등)은 참조 타입을 통째로 버리고 `user.hi` 값(함수)만 받아 전달합니다. 이 때문에 점 이외의 연산에선 `this` 정보가 사라집니다. +<<<<<<< HEAD `obj.method()` 같이 점을 사용하거나, `obj[method]()` 같이 대괄호를 사용해 함수를 호출했을 때만 `this` 값이 의도한 대로 전달됩니다. 이런 문제는 [func.bind()](/bind#solution-2-bind) 등을 이용하면 해결 할 수 있는데, 이에 대해선 추후에 알아보도록 하겠습니다. +======= +So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 diff --git a/1-js/99-js-misc/05-bigint/article.md b/1-js/99-js-misc/05-bigint/article.md index 7f59ecb55e..ff99ded971 100644 --- a/1-js/99-js-misc/05-bigint/article.md +++ b/1-js/99-js-misc/05-bigint/article.md @@ -126,5 +126,5 @@ We can use such JSBI code "as is" for engines that don't support bigints and for ## 참고 자료 -- [MDN docs on BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). +- [MDN docs on BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt). - [Specification](https://tc39.es/ecma262/#sec-bigint-objects). diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md new file mode 100644 index 0000000000..4f144f8241 --- /dev/null +++ b/1-js/99-js-misc/06-unicode/article.md @@ -0,0 +1,172 @@ + +# Unicode, String internals + +```warn header="Advanced knowledge" +The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters, or other rare symbols. +``` + +As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes. + +JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations: + +- `\xXX` + + `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`. + + Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters. + + These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, the copyright symbol + ``` + +- `\uXXXX` + `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`. + + Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter). + + ```js run + alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation + alert( "\u044F" ); // я, the Cyrillic alphabet letter + alert( "\u2191" ); // ↑, the arrow up symbol + ``` + +- `\u{X…XXXXXX}` + + `X…XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters. + + ```js run + alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) + alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) + ``` + +## Surrogate pairs + +All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation. + +Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode. + +So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair". + +As a side effect, the length of such symbols is `2`: + +```js run +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, a rare Chinese character +``` + +That's because surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! + +We actually have a single symbol in each of the strings above, but the `length` property shows a length of `2`. + +Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters. + +For example, here we can see two odd characters in the output: + +```js run +alert( '𝒳'[0] ); // shows strange symbols... +alert( '𝒳'[1] ); // ...pieces of the surrogate pair +``` + +Pieces of a surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. + +Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. + +So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs. + +They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly. + +One can see the difference here: + +```js run +// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt is surrogate-pair aware +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair +``` + +That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// meaningless 2nd half of the pair +``` + +You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. + +````warn header="Takeaway: splitting strings at an arbitrary point is dangerous" +We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Here we can see a garbage character (first half of the smile surrogate pair) in the output. + +Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens. +```` + +## Diacritical marks and normalization + +In many languages, there are symbols that are composed of the base character with a mark above/under it. + +For instance, the letter `a` can be the base character for these characters: `àáâäãåā`. + +Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations. + +To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. + +For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +For example: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. + +For instance: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below +let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false though the characters look identical (?!) +``` + +To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. + +It is implemented by [str.normalize()](mdn:js/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In reality, this is not always the case. The reason is that the symbol `Ṩ` is "common enough", so Unicode creators included it in the main table and gave it the code. + +If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md new file mode 100644 index 0000000000..777bf703ce --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -0,0 +1,483 @@ + +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +*!* +let admin = user; +*/!* + +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +```` + +## WeakRef + + +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +*!* +let admin = new WeakRef(user); +*/!* + +``` + +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: + + +```js +let ref = admin.deref(); + +if (ref) { + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +} + +const registry = new FinalizationRegistry(cleanupCallback); +``` + +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: + +```js +let user = { name: "John" }; + +const registry = new FinalizationRegistry((heldValue) => { + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: + +```js +registry.register(user, user.name); +``` + +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { + const imgCache = new Map(); + + *!* + const registry = new FinalizationRegistry((imgName) => { // (1) + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); + }); + */!* + + return (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref()) { + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); + imgCache.set(imgName, new WeakRef(newImg)); + *!* + registry.register(newImg, imgName); // (2) + */!* + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: + +![](weakref-finalizationregistry-demo-01.png) + +
+On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

+ +To increase page loading speed, it would be reasonable to download and display photo thumbnails in *compressed* quality. +But, to create a collage from selected photos, download and use them in *full-size* quality. + +Below, we can see, that the intrinsic size of the thumbnails is 240x240 pixels. +The size was chosen on purpose to increase loading speed. +Moreover, we do not need full-size photos in preview mode. + +![](weakref-finalizationregistry-demo-02.png) + +
+Let's assume, that we need to create a collage of 4 photos: we select them, and then click the "Create collage" button. +At this stage, the already known to us weakRefCache function checks whether the required image is in the cache. +If not, it downloads it from the cloud and puts it in the cache for further use. +This happens for each selected image: +

+ +![](weakref-finalizationregistry-demo-03.gif) + +
+ +Paying attention to the output in the console, you can see, which of the photos were downloaded from the cloud - this is indicated by FETCHED_IMAGE. +Since this is the first attempt to create a collage, this means, that at this stage the "weak cache" was still empty, and all the photos were downloaded from the cloud and put in it. + +But, along with the process of downloading images, there is also a process of memory cleanup by the garbage collector. +This means, that the object stored in the cache, which we refer to, using a weak reference, is deleted by the garbage collector. +And our finalizer executes successfully, thereby deleting the key, by which the image was stored in the cache. +CLEANED_IMAGE notifies us about it: + +![](weakref-finalizationregistry-demo-04.jpg) + +
+Next, we realize that we do not like the resulting collage, and decide to change one of the images and create a new one. +To do this, just deselect the unnecessary image, select another one, and click the "Create collage" button again: +

+ +![](weakref-finalizationregistry-demo-05.gif) + +
+But this time not all images were downloaded from the network, and one of them was taken from the weak cache: the CACHED_IMAGE message tells us about it. +This means that at the time of collage creation, the garbage collector had not yet deleted our image, and we boldly took it from the cache, +thereby reducing the number of network requests and speeding up the overall time of the collage creation process: +

+ +![](weakref-finalizationregistry-demo-06.jpg) + +
+Let's "play around" a little more, by replacing one of the images again and creating a new collage: +

+ +![](weakref-finalizationregistry-demo-07.gif) + +
+This time the result is even more impressive. Of the 4 images selected, 3 of them were taken from the weak cache, and only one had to be downloaded from the network. +The reduction in network load was about 75%. Impressive, isn't it? +

+ +![](weakref-finalizationregistry-demo-08.jpg) + +
+ +Of course, it is important to remember, that such behavior is not guaranteed, and depends on the specific implementation and operation of the garbage collector. + +Based on this, a completely logical question immediately arises: why do not we use an ordinary cache, where we can manage its entities ourselves, instead of relying on the garbage collector? +That's right, in the vast majority of cases there is no need to use `WeakRef` and `FinalizationRegistry`. + +Here, we simply demonstrated an alternative implementation of similar functionality, using a non-trivial approach with interesting language features. +Still, we cannot rely on this example, if we need a constant and predictable result. + +You can [open this example in the sandbox](sandbox:weakref-finalizationregistry). + +## Summary + +`WeakRef` - designed to create weak references to objects, allowing them to be deleted from memory by the garbage collector if there are no longer strong references to them. +This is beneficial for addressing excessive memory usage and optimizing the utilization of system resources in applications. + +`FinalizationRegistry` - is a tool for registering callbacks, that are executed when objects that are no longer strongly referenced, are destroyed. +This allows releasing resources associated with the object or performing other necessary operations before deleting the object from memory. \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png new file mode 100644 index 0000000000..0216373425 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css new file mode 100644 index 0000000000..f6df812d07 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css @@ -0,0 +1,49 @@ +.app { + display: flex; + flex-direction: column; + gap: 16px; +} + +.start-messages { + width: fit-content; +} + +.window { + width: 100%; + border: 2px solid #464154; + overflow: hidden; +} + +.window__header { + position: sticky; + padding: 8px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: #736e7e; +} + +.window__title { + margin: 0; + font-size: 24px; + font-weight: 700; + color: white; + letter-spacing: 1px; +} + +.window__button { + padding: 4px; + background: #4f495c; + outline: none; + border: 2px solid #464154; + color: white; + font-size: 16px; + cursor: pointer; +} + +.window__body { + height: 250px; + padding: 16px; + overflow: scroll; + background-color: #736e7e33; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html new file mode 100644 index 0000000000..7f93af4c7b --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html @@ -0,0 +1,28 @@ + + + + + + + WeakRef DOM Logger + + + + +
+ +
+
+

Messages:

+ +
+
+ No messages. +
+
+
+ + + + + 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/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index f9fcd3e89e..7555d4c9d9 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,10 +1,18 @@ # 브라우저 환경과 다양한 명세서 +<<<<<<< HEAD 자바스크립트는 본래 웹 브라우저에서 사용하려고 만든 언어입니다. 이후 진화를 거쳐 다양한 사용처와 플랫폼을 지원하는 언어로 변모하였습니다. 자바스크립트가 돌아가는 플랫폼은 *호스트(host)* 라고 불립니다. 호스트는 브라우저, 웹서버, 심지어는 커피 머신이 될 수도 있습니다. 각 플랫폼은 해당 플랫폼에 특정되는 기능을 제공하는데, 자바스크립트 명세서에선 이를 *호스트 환경(host environment)* 이라고 부릅니다. 호스트 환경은 랭귀지 코어(ECMAScript - 옮긴이)에 더하여 플랫폼에 특정되는 객체와 함수를 제공합니다. 웹브라우저는 웹페이지를 제어하기 위한 수단을 제공하고, Node.js는 서버 사이드 기능을 제공해주죠. +======= +The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms. + +A platform may be a browser, or a web-server or another *host*, or even a "smart" coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a *host environment*. + +A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 그림은 호스트 환경이 웹 브라우저일 때 사용할 수 있는 기능을 개괄적으로 보여줍니다. @@ -15,7 +23,11 @@ 1. 챕터에서 설명한 바와 같이, 자바스크립트 코드의 전역 객체입니다. 2. '브라우저 창(browser window)'을 대변하고, 이를 제어할 수 있는 메서드를 제공합니다. +<<<<<<< HEAD 아래 예시에선 `window` 객체를 전역 객체로 사용하고 있습니다. +======= +For instance, we can use it as a global object: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run global function sayHi() { @@ -26,17 +38,29 @@ function sayHi() { window.sayHi(); ``` +<<<<<<< HEAD 아래 예시에선 `window` 객체가 브라우저 창을 대변하고 있으며, 이를 이용해 창의 높이를 출력합니다. +======= +And we can use it as a browser window, to show the window height: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert(window.innerHeight); // 창 내부(inner window) 높이 ``` +<<<<<<< HEAD `window` 객체엔 다양한 메서드와 프로퍼티가 있는데, 추후 자세히 살펴보도록 하겠습니다. +======= +There are more window-specific methods and properties, which we'll cover later. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 문서 객체 모델(DOM) +<<<<<<< HEAD 문서 객체 모델(Document Object Model, DOM)은 웹 페이지 내의 모든 콘텐츠를 객체로 나타내줍니다. 이 객체는 수정 가능합니다. +======= +The Document Object Model, or DOM for short, represents all page content as objects that can be modified. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `document` 객체는 페이지의 기본 '진입점' 역할을 합니다. `document` 객체를 이용해 페이지 내 그 무엇이든 변경할 수 있고, 원하는 것을 만들 수도 있습니다. @@ -49,18 +73,30 @@ document.body.style.background = "red"; setTimeout(() => document.body.style.background = "", 1000); ``` +<<<<<<< HEAD 문서 객체 모델은 예시에서 소개한 `document.body.style` 외에도 수많은 기능을 제공합니다. 관련 프로퍼티와 메서드에 대한 정보는 WHATWG의 [DOM Living Standard](https://dom.spec.whatwg.org)에서 찾을 수 있습니다. +======= +Here, we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="DOM은 브라우저만을 위한 모델이 아닙니다." DOM 명세서엔 문서의 구조와 이를 조작할 수 있는 객체에 대한 설명이 담겨있습니다. 그런데 브라우저가 아닌 곳에서도 DOM을 사용하는 경우가 있습니다. +<<<<<<< HEAD HTML 페이지를 다운로드하고 이를 가공해주는 서버 사이드 스크립트에서도 DOM을 사용합니다. 이런 스크립트에선 명세서 일부만을 지원하겠지만요. +======= +For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```smart header="스타일링을 위한 CSSOM" CSS 규칙과 스타일시트(stylesheet)는 HTML과는 다른 구조를 띱니다. 따라서 CSS 규칙과 스타일시트를 객체로 나타내고 이 객체를 어떻게 읽고 쓸 수 있을지에 대한 설명을 담은 별도의 명세서, [CSS 객체 모델(CSS Object Model, CSSOM)](https://www.w3.org/TR/cssom-1/)이 존재합니다. +<<<<<<< HEAD CSSOM은 문서에 쓰이는 스타일 규칙을 수정할 때 DOM과 함께 쓰입니다. 그런데 CSS 규칙은 대부분 정적이기 때문에 CSSOM을 실무에서 자주 접하지는 않을 겁니다. 자바스크립트를 이용해 CSS 규칙을 추가 혹은 제거해야 하는 경우는 극히 드물긴 하지만, 이때 CSSOM이 사용됩니다. +======= +The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 브라우저 객체 모델(BOM) @@ -69,8 +105,13 @@ CSSOM은 문서에 쓰이는 스타일 규칙을 수정할 때 DOM과 함께 쓰 예시: +<<<<<<< HEAD - [navigator](mdn:api/Window/navigator) 객체는 브라우저와 운영체제에 대한 정보를 제공합니다. 객체엔 다양한 프로퍼티가 있는데, 가장 잘 알려진 프로퍼티는 현재 사용 중인 브라우저 정보를 알려주는 `navigator.userAgent`와 브라우저가 실행 중인 운영체제(Windows, Linux, Mac 등) 정보를 알려주는 `navigator.platform`입니다. - [location](mdn:api/Window/location) 객체는 현재 URL을 읽을 수 있게 해주고 새로운 URL로 변경(redirect)할 수 있게 해줍니다. +======= +- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differentiate between Windows/Linux/Mac etc). +- The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 아래 예시는 `location` 객체를 어떻게 활용할 수 있을지 보여줍니다. @@ -81,33 +122,60 @@ if (confirm("위키피디아 페이지로 가시겠습니까?")) { } ``` +<<<<<<< HEAD `alert/confirm/prompt` 역시 BOM의 일부입니다. 문서와 직접 연결되어 있지 않지만, 사용자와 브라우저 사이의 커뮤니케이션을 도와주는 순수 브라우저 메서드이죠. ```smart header="다양한 명세" BOM은 [HTML 명세서](https://html.spec.whatwg.org)의 일부입니다. BOM에 관련된 명세가 따로 있지는 않습니다. 에서 볼 수 있는 HTML 명세서는 태그, HTML 속성(attribute) 같은 'HTML' 뿐만 아니라 다양한 객체와 메서드, 브라우저에서만 사용되는 DOM 확장을 다룹니다. 이 모든 것이 HTML 기술에 속하기 때문입니다. HTML 명세서엔 이 외에도 에 있는 내용도 들어갑니다. +======= +The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user. + +```smart header="Specifications" +The BOM is a part of the general [HTML specification](https://html.spec.whatwg.org). + +Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods, and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 요약 표준에 대하여 이야기하면서 다음 명세서들을 알아보았습니다. +<<<<<<< HEAD DOM 명세서 : 문서 구조, 조작, 이벤트에 관한 설명이 담겨있고, 에서 볼 수 있습니다. CSSOM 명세서 : 스타일시트와 스타일 규칙, 이 둘을 어떻게 조작할 수 있는지, 이 둘과 문서 사이의 관계를 어떻게 조작할 수 있는지에 대한 설명이 담겨있고, 에서 볼 수 있습니다. +======= +DOM specification +: Describes the document structure, manipulations, and events, see . + +CSSOM specification +: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 HTML 명세서 : 태그 등의 HTML 언어, `setTimeout`, `alert`, `location` 등의 다양한 브라우저 기능을 정의한 BOM에 대한 설명이 담겨있고, 에서 볼 수 있습니다. DOM 명세서에 다양한 프로퍼티와 메서드를 추가해 확장한 명세서입니다. 몇몇 클래스에 대한 설명은 에서 확인할 수 있습니다. +<<<<<<< HEAD 배울 게 많지만, 모든 걸 한꺼번에 다루고 기억하기엔 그 양이 너무 많기 때문에 지금까지 소개해 드린 링크를 잘 기록해 놓으시기 바랍니다. 프로퍼티나 메서드에 대한 설명을 읽고 싶을 때 Mozilla 재단의 매뉴얼 을 찾아보는 것도 좋긴 하지만, 명세서에서 관련 설명을 찾는 게 더 나을 때도 있기 때문입니다. 명세서에 있는 설명은 복잡하고 내용도 더 많긴 하지만 명세서를 읽는 습관을 들이다 보면 기본 지식을 탄탄하게 쌓을 수 있습니다. +======= +Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all. + +When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 검색창에 'WHATWG [용어]' 혹은 'MDN [용어]'로 검색하면 명세서나 MDN문서에서 원하는 내용을 쉽게 찾을 수 있습니다. , 처럼 말이죠. +<<<<<<< HEAD 자 이제 UI에서 핵심적인 역할을 하는 DOM에 대해 본격적으로 살펴보도록 합시다. +======= +Now, we'll get down to learning the DOM, because the document plays the central role in the UI. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 7128c0d135..7e0c4debc5 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -51,7 +51,11 @@ DOM은 HTML을 아래와 같이 태그 트리 구조로 표현합니다.
@@ -142,8 +146,13 @@ let node4 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1, drawHtmlTree(node4, 'div.domtree', 690, 360); +<<<<<<< HEAD ````warn header="테이블엔 언제나 ``가 있습니다." 테이블은 조금 흥미롭습니다. DOM 명세서에선 테이블에 반드시 ``가 있어야 한다고 못 박아 놓았지만, HTML에선 ``를 생략하곤 합니다. 이때, 브라우저는 자동으로 DOM에 ``를 만들어줍니다. +======= +````warn header="Tables always have ``" +An interesting "special case" is tables. By DOM specification they must have `` tag, but HTML text may omit it. Then the browser creates `` in the DOM automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 HTML: @@ -160,7 +169,11 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType": drawHtmlTree(node5, 'div.domtree', 600, 200); +<<<<<<< HEAD 보이시죠? ``가 어디선가 나타났습니다. 테이블을 다룰 땐 위 내용을 상기해 갑자기 나타난 ``때문에 놀라지 않도록 합시다. +======= +You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 기타 노드 타입 @@ -188,7 +201,11 @@ drawHtmlTree(node5, 'div.domtree', 600, 200);
@@ -199,7 +216,11 @@ drawHtmlTree(node6, 'div.domtree', 690, 500); **HTML 안의 모든 것은 (심지어 그것이 주석이더라도) DOM을 구성합니다.** +<<<<<<< HEAD HTML 문서 최상단에 위치하는 `` 지시자 또한 DOM 노드가 됩니다. 이 노드는 DOM 트리의 `` 바로 위에 위치합니다. 본 튜토리얼에선 이 노드를 다루지 않을 예정이라 다이어그램에도 표시는 하지 않을 것입니다. 하지만 존재하는 노드입니다. +======= +Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문서 전체를 나타내는 `document` 객체 또한 DOM 노드입니다. @@ -212,7 +233,11 @@ HTML 문서 최상단에 위치하는 `` 지시자 또한 DOM 노 ## DOM 구조 직접 보기 +<<<<<<< HEAD [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/)에 들어가면 실시간으로 DOM 구조를 볼 수 있습니다. 사이트에 들어가 DOM 구조를 보고 싶은 HTML 문서를 입력하면 HTML이 바로 DOM으로 바뀌어 출력됩니다. +======= +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Live DOM Viewer를 이용하는 것 말고도 브라우저 개발자 도구를 사용해 DOM을 탐색할 수 있습니다. 실제론 대부분의 개발자가 개발자 도구를 사용합니다. diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index 8b759337f3..044278bb56 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -214,7 +214,11 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## 요소 간 이동 +<<<<<<< HEAD 지금까지 언급한 탐색 관련 프로퍼티는 *모든* 종류의 노드를 참조합니다. `childNodes`를 이용하면 텍스트 노드, 요소 노드, 심지어 주석 노드까지 참조할 수 있죠. +======= +Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 하지만 실무에서 텍스트 노드나 주석 노드는 잘 다루지 않습니다. 웹 페이지를 구성하는 태그의 분신인 요소 노드를 조작하는 작업이 대다수이죠. diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index 9db17890b3..98b3153e60 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -54,8 +54,13 @@ ``` +<<<<<<< HEAD ```warn header="id를 따서 만들어진 전역변수를 요소 접근 시 사용하지 마세요." `id`에 대응하는 전역변수는 [명세서](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem)의 내용을 구현해 만들어진 것으로 표준이긴 하지만 하위 호환성을 위해 남겨둔 동작입니다. +======= +```warn header="Please don't use id-named global variables to access elements" +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 브라우저는 스크립트의 네임스페이스와 DOM의 네임스페이스를 함께 사용할 수 있도록 해서 개발자의 편의를 도모합니다. 그런데 이런 방식은 스크립트가 간단할 땐 괜찮지만, 이름이 충돌할 가능성이 있기 때문에 추천하는 방식은 아닙니다. HTML을 보지 않은 상황에서 코드만 보고 변수의 출처를 알기 힘들다는 단점도 있습니다. @@ -70,8 +75,13 @@ 같은 `id`를 가진 요소가 여러 개 있으면 `document.getElementById`같이 `id`를 이용해 요소를 검색하는 메서드의 동작이 예측 불가능해집니다. 검색된 여러 요소 중 어떤 요소를 반환할지 판단하지 못해 임의의 요소가 반환되죠. 문서 내 동일 `id`가 없도록 해 이런 일을 방지하도록 합시다. ``` +<<<<<<< HEAD ```warn header="`anyNode.getElementById`가 아닌 `document.getElementById`" `getElementById`는 `document` 객체를 대상으로 해당 `id`를 가진 요소 노드를 찾아 줍니다. 문서 노드가 아닌 다른 노드엔 호출할 수 없습니다. +======= +```warn header="Only `document.getElementById`, not `anyElem.getElementById`" +The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## querySelectorAll [#querySelectorAll] @@ -116,7 +126,11 @@ querySelectorAll에는 `:hover`나 `:active` 같은 CSS 선택자의 가상 클 지금까지 소개한 모든 메서드는 DOM 검색에 쓰입니다. +<<<<<<< HEAD [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches)는 DOM을 검색하는 일이 아닌 조금 다른 일을 합니다. 이 메서드는 요소 `elem`이 주어진 CSS 선택자와 일치하는지 여부를 판단해줍니다. 일치한다면 `true`, 아니라면 `false`를 반환하죠. +======= +The [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 요소가 담겨있는 배열 등을 순회해 원하는 요소만 걸러내고자 할 때 유용합니다. @@ -142,7 +156,11 @@ querySelectorAll에는 `:hover`나 `:active` 같은 CSS 선택자의 가상 클 부모 요소, 부모 요소의 부모 요소 등 DOM 트리에서 특정 요소의 상위에 있는 요소들은 *조상(ancestor)* 요소라고 합니다. +<<<<<<< HEAD 메서드 `elem.closest(css)`는 `elem` 자기 자신을 포함하여 CSS 선택자와 일치하는 가장 가까운 조상 요소를 찾을 수 있게 도와줍니다. +======= +The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `closest`메서드는 해당 요소부터 시작해 DOM 트리를 한 단계씩 거슬러 올라가면서 원하는 요소를 찾습니다. CSS 선택자와 일치하는 요소를 찾으면, 검색을 중단하고 해당 요소를 반환합니다. @@ -153,8 +171,13 @@ querySelectorAll에는 `:hover`나 `:active` 같은 CSS 선택자의 가상 클
    +<<<<<<< HEAD
  • 1장
  • 2장
  • +======= +
  • Chapter 1
  • +
  • Chapter 2
  • +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9
@@ -363,7 +386,11 @@ DOM에서 원하는 노드를 검색하게 해주는 주요 메서드 6가지는 +<<<<<<< HEAD 아마 실무에선 `querySelector`나 `querySelectorAll`을 가장 많이 사용하실 겁니다. `getElementBy`로 시작하는 메서드는 대개 오래된 스크립트에서 만날 수 있는데, 일부 이 메서드가 꼭 필요한 상황에서 쓰이는 경우도 있습니다. +======= +By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이 외에 알아두면 좋을 만한 메서드는 아래와 같습니다. 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..b22f82cb34 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 @@ -10,7 +10,11 @@ DOM 노드는 종류에 따라 각각 다른 프로퍼티를 지원합니다. DOM 노드는 종류에 따라 대응하는 내장 클래스가 다릅니다. +<<<<<<< HEAD 계층 구조 꼭대기엔 [EventTarget](https://dom.spec.whatwg.org/#eventtarget)이 있는데, [Node](http://dom.spec.whatwg.org/#interface-node)는 EventTarget을, 다른 DOM 노드들은 Node 클래스를 상속받습니다. +======= +The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 관계를 그림으로 나타내면 다음과 같습니다. @@ -18,6 +22,7 @@ DOM 노드는 종류에 따라 대응하는 내장 클래스가 다릅니다. 각 클래스는 다음과 같은 특징을 지닙니다. +<<<<<<< HEAD - [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` 클래스의 베이스 역할을 합니다. @@ -28,6 +33,41 @@ DOM 노드는 종류에 따라 대응하는 내장 클래스가 다릅니다. - 이외에도 다른 클래스가 많은데, 각 태그에 해당하는 클래스는 고유한 프로퍼티와 메서드를 지원합니다. 이렇게 특정 노드에서 사용할 수 있는 프로퍼티와 메서드는 상속을 기반으로 결정됩니다. +======= +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything. + + Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. + +- [Node](https://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. + + It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are other classes that inherit from it (and so inherit the `Node` functionality). + +- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. + + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

Hello

`. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- the class for comments. They are not shown, but each comment becomes a member of DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- is the base class for DOM elements. + + It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. + + A browser supports not only HTML, but also XML and SVG. So the `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` (we don't need them here) and `HTMLElement`. + +- Finally, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is the basic class for all HTML elements. We'll work with it most of the time. + + It is inherited by concrete HTML elements: + - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `` elements, + - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements, + - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `` elements, + - ...and so on. + +There are many other tags with their own classes that may have specific properties and methods, while some elements, such as ``, `
`, `
` do not have any specific properties, so they are instances of `HTMLElement` class. + +So, the full set of properties and methods of a given node comes as the result of the chain of inheritance. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `` 요소에 대응하는 DOM 객체를 예로 들어봅시다. 이 객체는 [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) 클래스를 기반으로 만들어집니다. @@ -128,14 +168,22 @@ interface HTMLInputElement: HTMLElement { ```html run - ``` +<<<<<<< HEAD 1. 첫 번째 `
`엔 이름이 'HTML 형태'로 저장됩니다. 입력한 태그는 태그로 해석되어 굵은 글씨가 출력되네요. 2. 두 번째 `
`엔 이름이 '텍스트 형태'로 저장됩니다. 따라서 입력한 값 그대로 `이보라`가 출력되는 것을 확인할 수 있습니다. +======= +1. The first `
` gets the name "as HTML": all tags become tags, so we see the bold name. +2. The second `
` gets the name "as text", so we literally see `Winnie-the-Pooh!`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 개발을 하다보면 사용자의 입력값을 받아 처리해야 하는 경우가 많습니다. 이때 사용자가 입력한 값은 텍스트로 처리되어야 합니다. 예상치 못한 HTML이 사이트에 침투하는 것을 막으려면 `textContent`를 사용합시다. @@ -413,7 +474,11 @@ elem.innerHTML = elem.innerHTML + "..." hidden 속성과 hidden 프로퍼티는 요소를 보여줄지 말지 지정할 때 사용할 수 있습니다. +<<<<<<< HEAD `hidden`은 HTML 안에서 쓸 수도 있고 자바스크립트에서도 쓸 수 있습니다. +======= +We can use it in HTML or assign it using JavaScript, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run height="80"
아래 두 div를 숨겨봅시다.
diff --git a/2-ui/1-document/06-dom-attributes-and-properties/article.md b/2-ui/1-document/06-dom-attributes-and-properties/article.md index c683859cf0..ca646c1a0c 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/article.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/article.md @@ -162,7 +162,11 @@ HTML 속성을 어떻게 다루는지에 대한 예시를 살펴봅시다. ``` +<<<<<<< HEAD 그런데 아래 예시의 `input.value`처럼 동기화가 속성에서 프로퍼티 방향으로만 일어나는 예외상황도 존재합니다. +======= +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run @@ -298,7 +302,11 @@ HTML을 작성할 때 우리는 대부분 표준 속성을 사용합니다. 하
``` +<<<<<<< HEAD 이렇게 커스텀 속성을 사용하는 게 `.order-state-new`, `.order-state-pending`, `order-state-canceled`같은 클래스를 사용하는 것보다 왜 선호될까요? +======= +Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이유는 속성은 클래스보다 다루기 편리하다는 점 때문입니다. 속성의 상태는 아래와 같이 쉽게 변경할 수 있습니다. diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md index eab23709c4..483e6758b7 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md @@ -6,7 +6,11 @@ importance: 5 빈 DOM 요소 `elem`과 `text`라는 문자열이 있습니다. +<<<<<<< HEAD 셋 중에서 같은 동작을 수행하는 명령어는 무엇일까요? +======= +Which of these 3 commands will do exactly the same? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. `elem.append(document.createTextNode(text))` 2. `elem.innerHTML = text` diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md index 8ae95c4b28..24f3ff1446 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md @@ -39,15 +39,23 @@ function update() { ```js let timerId; -function clockStart() { // run the clock - timerId = setInterval(update, 1000); +function clockStart() { // run the clock + if (!timerId) { // only set a new interval if the clock is not running + timerId = setInterval(update, 1000); + } update(); // (*) } function clockStop() { clearInterval(timerId); - timerId = null; + timerId = null; // (**) } ``` -`update()`는 `clockStart()` 에서뿐만 아니라 `(*)`로 표시한 줄에서도 호출됩니다. 양쪽 모두에서 `update()`를 호출하지 않으면 `setInterval`이 실행되기 전까지 사용자는 아무런 내용이 없는 시계를 봐야 하기 때문입니다. \ No newline at end of file +<<<<<<< HEAD +`update()`는 `clockStart()` 에서뿐만 아니라 `(*)`로 표시한 줄에서도 호출됩니다. 양쪽 모두에서 `update()`를 호출하지 않으면 `setInterval`이 실행되기 전까지 사용자는 아무런 내용이 없는 시계를 봐야 하기 때문입니다. +======= +Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then. + +Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html index e017bb3783..95bcb96e0f 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html @@ -43,15 +43,24 @@ } function clockStart() { +<<<<<<< HEAD timerId = setInterval(update, 1000); update(); // <-- 첫 번째 setInterval이 실행되기 전까지 기다릴 필요 없이 시작합니다. +======= + // set a new interval only if the clock is stopped + // otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again + if (!timerId) { + timerId = setInterval(update, 1000); + } + update(); // <-- start right now, don't wait 1 second till the first setInterval works +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } function clockStop() { clearInterval(timerId); + timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart() } - clockStart(); diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6455ac18be..1ba12c23b4 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,17 @@ 이 이상한 동작의 이유는 바로 주어진 HTML이 잘못되었기 때문입니다. +<<<<<<< HEAD 브라우저는 이를 자동으로 고쳐야 합니다. 그러나 명세에 따르면 `` 안에는 표와 관련된 특정 태그만이 존재할 수 있기 때문에 텍스트가 있어서는 안 됩니다. 따라서 브라우저는 `'aaa'`를 `
` *앞에* 추가합니다. +======= +The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이제 표를 삭제해도 텍스트가 남아있는 이유가 분명해졌습니다. +<<<<<<< HEAD 이 문제는 브라우저 도구를 사용해 DOM을 탐색해보면 쉽게 답을 찾을 수 있습니다. 브라우저 도구에서는 `
` 앞에 `'aaa'` 가 있는 것으로 표시됩니다. +======= +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 HTML 표준에는 잘못된 HTML을 수정하는 방법이 구체적으로 정해져 있으므로, 이러한 브라우저의 동작은 올바른 동작입니다. \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md index 07f13070f9..57d452483e 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md @@ -22,6 +22,10 @@ importance: 1 alert(table); // table 은 삭제할 표의 id 입니다. table.remove(); +<<<<<<< HEAD // 왜 문서 안에 aaa가 남아 있을까요? +======= + // why there's still "aaa" in the document? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md index f66b63584a..0e0335d886 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/task.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md @@ -8,9 +8,15 @@ importance: 4 리스트의 모든 요소는 아래 방법으로 생성합니다. +<<<<<<< HEAD 1. `prompt`를 사용해 사용자로부터 리스트의 내용을 입력받습니다. 2. 1번에서 입력받은 내용을 갖는 `
  • ` 를 생성한 후 `
      ` 에 추가합니다. 3. 사용자가 입력을 취소할 때까지 계속합니다 (`ESC` 키나 프롬프트 창의 취소 버튼을 누를 때까지). +======= +1. Ask a user about its content using `prompt`. +2. Create the `
    • ` with it and add it to `
        `. +3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 모든 요소는 동적으로 생성되어야 합니다. 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..ce72334bde 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -128,8 +128,21 @@ setTimeout(() => document.body.style.display = "", 1000); // 1초 후 다시 원 이렇게 `style.display`에 빈 문자열을 할당하면 브라우저는 마치 처음부터 `style.display` 프로퍼티가 없었던 것처럼 CSS 클래스와 브라우저 내장 스타일을 페이지에 적용합니다. +<<<<<<< HEAD ````smart header="`style.cssText`로 완전히 다시 쓰기" 개별 스타일 프로퍼티를 적용할 때는 보통 `style.*`를 사용합니다. 그런데 `div.style` 은 객체이고 읽기 전용이기 때문에 `div.style="color: red; width: 100px"`같은 방식으론 전체 스타일을 설정할 수 없습니다. +======= +Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this: + +```js run +document.body.style.background = 'red'; //set background to red + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second +``` + +````smart header="Full rewrite with `style.cssText`" +Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 문자열을 사용해 전체 스타일을 설정하려면 프로퍼티 `style.cssText`를 사용해야 합니다. @@ -260,6 +273,7 @@ pseudo ````warn header="`getComputedStyle`엔 프로퍼티 전체 이름이 필요합니다." `getComputedStyle`을 사용할 때는 `paddingLeft`, `marginTop`, `borderTopWidth`같이 프로퍼티 이름 전체를 정확히 알고 있어야 합니다. 그렇지 않으면 원하는 값을 얻을 수 없는 경우가 생깁니다. +<<<<<<< HEAD `paddingLeft`나 `paddingTop`엔 값이 지정되어있는데 `getComputedStyle(elem).padding`을 사용해 값을 얻으려 하는 경우를 생각해 봅시다. 어떤 값을 얻을 수 있을까요? 아무것도 얻지 못할까요 아니면 값이 설정되어 있는 `paddingLeft`나 `paddingTop`에서 값을 가져올까요? 이런 상황에 적용할만한 표준은 아직 제정되어있지 않습니다. 따라서 브라우저마다 동작 방식이 다릅니다. Chrome 같은 몇몇 브라우저는 아래 예시와 같이 `10px`을 출력해주는데 Firefox에서 빈 문자열이 출력됩니다. @@ -275,6 +289,9 @@ pseudo alert(style.margin); // Firefox에선 빈 문자열이 출력됩니다. ``` +======= +For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ```smart header="`:visited` 링크 관련 스타일은 숨겨져 있습니다." 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..69930e503f 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; } ``` @@ -106,7 +106,11 @@ CSS `position` 프로퍼티가 설정되어있는 조상 요소가 없는 경우 따라서 요소(혹은 이 요소의 조상 요소 중 어떤 것이든)의 CSS `display` 프로퍼티가 `none`이거나 문서 내에 해당 요소가 없으면 모든 기하 프로퍼티 값이 0이 됩니다(`offsetParent` 프로퍼티의 값은 `null`). +<<<<<<< HEAD 요소를 만들긴 했지만 아직 문서에 삽입하기 전이라던가, 새롭게 만든 요소의 `display` 프로퍼티가 `none`이면 기하 프로퍼티 값은 0, `offsetParent` 프로퍼티의 값은 `null`이 되는 것이죠. +======= +For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or its ancestor) has `display:none`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 특징을 이용하면 요소의 숨김 상태 여부를 아래 같은 방법으로 확인할 수 있습니다. @@ -116,7 +120,11 @@ function isHidden(elem) { } ``` +<<<<<<< HEAD 참고로 `isHidden`은 요소가 화면에 있긴 하지만 사이즈가 0일 때(비어있는 `
        ` 등)도 `true`를 반환하기 때문에 주의해서 사용해야 합니다. +======= +Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## clientTop과 clientLeft 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..52ece78a68 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 @@ -2,11 +2,19 @@ 브라우저 창이 차지하는 너비와 높이를 어떻게 구할 수 있을까요? 스크롤 때문에 보이지 않는 영역을 포함하여 문서 전체가 차지하는 너비와 높이는 어떻게 구할 수 있을까요? 자바스크립트를 사용해서 페이지를 스크롤 할 수 있을까요? +<<<<<<< HEAD 이번 챕터에선 위와 같은 물음에 답을 주는 루트 문서 요소인 `document.documentElement`를 살펴볼 예정입니다. `document.documentElement`는 `` 태그와 상응하는 요소로 다양한 메서드를 지원합니다. 유용한 메서드이긴 하지만 몇 가지 주의할 점이 있어 같이 살펴봅시다. +======= +For this type of information, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities to consider. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 브라우저 창의 너비와 높이 +<<<<<<< HEAD 창이 차지하는 너비와 높이를 알려면 `document.documentElement`의 `clientWidth`와 `clientHeight`를 사용하면 됩니다. +======= +To get window width and height, we can use the `clientWidth/clientHeight` of `document.documentElement`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](document-client-width-height.svg) @@ -16,12 +24,21 @@ ``` +<<<<<<< HEAD ````warn header="`window` 객체가 아닌 `document.documentElement`를 쓰는 이유" 브라우저의 `window` 객체 역시 `innerWidth`와 `innerHeight` 프로퍼티를 지원합니다. 이 프로퍼티를 써도 원하는 대로 창 크기를 구할 수 있을 것 같은데 왜 `document.documentElement`의 `clientWidth`나 `clientHeight`를 쓰는 걸까요? 스크롤바가 생기면 스크롤바 역시 공간을 차지하는데, `clientWidth`나 `clientHeight`는 스크롤바가 차지하는 공간을 제외해서 너비나 높이 값을 계산합니다. 눈에 보이는 문서에서 콘텐츠가 실제로 들어가게 될 영역의 너비와 높이 값을 반환하는 것이죠. 그런데 `window.innerWidth/innerHeight`는 스크롤바가 차지하는 영역을 포함해 값을 계산합니다. +======= +````warn header="Not `window.innerWidth/innerHeight`" +Browsers also support properties like `window.innerWidth/innerHeight`. They look like what we want, so why not to use them instead? + +If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content. + +`window.innerWidth/innerHeight` includes the scrollbar. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 스크롤바가 있는 경우 스크롤 바 역시 공간을 차지하는데, 이럴 때 `window`객체와 `document.documentElement`의 해당 프로퍼티들은 다른 값을 반환합니다. ```js run @@ -29,7 +46,11 @@ alert( window.innerWidth ); // 전체 창 너비 alert( document.documentElement.clientWidth ); // 스크롤바가 차지하는 영역을 제외한 창 너비 ``` +<<<<<<< HEAD 창 사이즈가 필요한 경우는 스크롤 바 안쪽에 무언가를 그리거나 위치시킬 때가 대다수입니다. 따라서 `documentElement`의 `clientHeight/clientWidth`를 써야 합니다. +======= +In most cases, we need the *available* window width in order to draw or position something within scrollbars (if there are any), so we should use `documentElement.clientHeight/clientWidth`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ```warn header="`DOCTYPE`을 꼭 써주세요." @@ -40,9 +61,15 @@ alert( document.documentElement.clientWidth ); // 스크롤바가 차지하는 ## 문서의 너비와 높이 +<<<<<<< HEAD 이론상 `document.documentElement`는 문서의 루트 요소에 상응하고, 루트 요소엔 콘텐츠 전부가 들어가기 때문에 우리는 문서의 전체 크기를 `document.documentElement`의 `scrollWidth`와 `scrollHeight`를 사용해 재면 되지 않냐고 생각합니다. 그런데 전체 페이지를 대상으로 했을 때, `document.documentElement`의 프로피터들은 우리가 예상한 대로 동작하지 않습니다. Chrome이나 Safari, Opera에서 스크롤이 없는 경우 `documentElement.scrollHeight`는 `documentElement.clientHeight`보다 작을 때가 있죠. 예상하기엔 같은 값이어야 하는데도 말입니다. +======= +Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure the document's full size as `document.documentElement.scrollWidth/scrollHeight`. + +But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera, if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Weird, right? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 정확한 문서 전체 높이 값을 얻으려면 아래 여섯 프로퍼티가 반환하는 값 중 최댓값을 골라야 합니다. @@ -60,11 +87,19 @@ alert('스크롤에 의해 가려진 분을 포함한 전체 문서 높이: ' + ## 스크롤 정보 얻기 [#page-scroll] +<<<<<<< HEAD DOM 요소의 현재 스크롤 상태(스크롤에 의해 가려진 영역에 대한 정보)는 `scrollLeft`와 `scrollTop` 프로퍼티를 통해 구할 수 있습니다. 대부분의 브라우저에서 문서의 스크롤 상태는 `document.documentElement`의 `scrollLeft`나 `scrollTop`을 이용해 구할 수 있습니다. 다만 구버전 WebKit을 기반으로 하는 브라우저에선 버그([5991](https://bugs.webkit.org/show_bug.cgi?id=5991)) 때문에 `document.documentElement`가 아닌 `document.body`를 사용해야 원하는 값을 구할 수 있습니다. 이쯤 되면 스크롤 포지션 정보를 구하기 위해 브라우저별 예외처리까지 다 해야 하나 라는 생각이 들 수 있을 겁니다. 다행히도 `window`객체의 `pageXOffset`과 `pageYOffset`을 사용하면 브라우저 상관없이 스크롤 정보를 구할 수 있어서 이런 예외 상황을 외워두지 않아도 됩니다. +======= +DOM elements have their current scroll state in their `scrollLeft/scrollTop` properties. + +For document scroll, `document.documentElement.scrollLeft/scrollTop` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. + +Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, `window.pageXOffset/pageYOffset`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert('세로 스크롤에 의해 가려진 위쪽 영역 높이: ' + window.pageYOffset); @@ -73,19 +108,40 @@ alert('가로 스크롤에 의해 가려진 왼쪽 영역 너비: ' + window.pag 참고로 이 두 프로퍼티는 읽기만 가능합니다. +<<<<<<< HEAD ## scrollTo, scrollBy로 스크롤 상태 변경하기 [#window-scroll] ```warn 자바스크립트를 사용해 스크롤을 움직이려면 DOM이 완전히 만들어진 상태이어야 합니다. ``에 있는 스크립트에서 페이지 전체의 스크롤을 움직이려 하면 잘 동작하지 않을 수 있습니다. +======= +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + +## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] + +```warn +To scroll the page with JavaScript, its DOM must be fully built. + +For instance, if we try to scroll the page with a script in ``, it won't work. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 일반 요소의 스크롤 상태는 `scrollTop`이나 `scrollLeft`로 쉽게 변경할 수 있습니다. +<<<<<<< HEAD 페이지 전체의 스크롤 상태 역시 `document.documentElement`의 `scrollTop/scrollLeft`를 사용해 변경 가능하죠(다만, Safari는 `document.body`의 `scrollTop/scrollLeft`를 써야 합니다). 그런데 이보다 더 편하고 브라우저 상관없이 쓸 수 있는 대안이 있긴합니다. 바로 [window.scrollBy(x,y)](mdn:api/Window/scrollBy)와 [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo)입니다. +======= +We can do the same for the page using `document.documentElement.scrollTop/scrollLeft` (except Safari, where `document.body.scrollTop/Left` should be used instead). + +Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `scrollBy(x,y)`메서드를 사용하면 페이지의 스크롤 상태를 현재 포지션을 기준으로 상대적으로 조정합니다. `scrollBy(0,10)`는 문서의 스크롤 상태를 현재를 기준으로 스크롤을 `10px`아래로 내린것 처럼 움직여주죠. @@ -106,10 +162,15 @@ alert('가로 스크롤에 의해 가려진 왼쪽 영역 너비: ' + window.pag ## scrollIntoView +<<<<<<< HEAD 추가 메서드 [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView)를 머릿속에 추가해 스크롤 상태를 완벽히 마스터 해봅시다. +======= +For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `elem.scrollIntoView(top)`를 호출하면 전체 페이지 스크롤이 움직여 `elem`이 눈에 보이는 상태로 변경됩니다. `elem.scrollIntoView`는 인수를 하나 받는데, 인수에 따라 다음과 같이 동작합니다. +<<<<<<< HEAD - `top`이 `true`(디폴트)인 경우, `elem`이 창 제일 위에 보이도록 스크롤 상태가 변경됩니다. `elem`의 위쪽 모서리가 창의 위쪽 모서리와 일치하게 되죠. - `top`이 `false`인 경우, `elem`이 창 가장 아래에 보이도록 스크롤 상태가 변경됩니다. `elem`의 아래쪽 모서리가 창의 아래쪽 모서리와 일치하게 변합니다. @@ -119,15 +180,32 @@ alert('가로 스크롤에 의해 가려진 왼쪽 영역 너비: ' + window.pag 두 번째 버튼을 누르면 버튼의 아래 모서리가 창 밑으로 붙는 것을 확인할 수 있습니다. +======= +- If `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element will be aligned with the window top. +- If `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element will be aligned with the window bottom. + +```online +The button below scrolls the page to position itself at the window top: + + + +And this button scrolls the page to position itself at the bottom: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 스크롤 막기 +<<<<<<< HEAD 때에 따라 문서 스크롤바를 '고정' 해야 하는 경우가 생기곤 합니다. 사용자에게 반드시 전달해야 하는 중요한 메시지가 있어서 이 메시지를 화면에 크게 띄우고, 사용자가 스크롤을 움직여 다른 콘텐츠를 보지 못하게 한 상태에서 메시지를 읽게 하려는 경우가 대표적인 예가 될 수 있습니다. 이럴 때 `document.body.style.overflow = "hidden"`를 사용할 수 있습니다. 해당 스크립트가 동작하면 페이지의 스크롤바 위치가 '고정' 됩니다. +======= +Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. + +To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will "freeze" at its current scroll position. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```online 직접 실습해봅시다. @@ -136,6 +214,7 @@ alert('가로 스크롤에 의해 가려진 왼쪽 영역 너비: ' + window.pag +<<<<<<< HEAD 위쪽 버튼을 누르면 스크롤바가 고정되었다가, 아래 버튼을 누르면 고정이 해제되는 것을 확인할 수 있습니다. ``` @@ -144,13 +223,28 @@ alert('가로 스크롤에 의해 가려진 왼쪽 영역 너비: ' + window.pag 그런데 이 방법은 스크롤바가 사라진다는 단점이 있습니다. 스크롤바는 일정 공간을 차지하는데, 스크롤바가 사라지면 해당 공간을 채우기 위해 콘텐츠가 갑자기 '움직이는' 현상이 발생합니다. 이렇게 페이지 전체의 스크롤 상태가 갑자기 변경되면 사용자 입장에선 이상해 보일 수 있기 때문에 개발자는 스크롤바를 고정시키기 전과 후의 `clientWidth`값을 비교해서 해당 증상을 보정해야 합니다. 스크롤바가 사라질 땐 `clientWidth`값이 커지는데 이때 스크롤바가 차지했던 영역만큼 `document.body`에 `padding`을 줘서 콘텐츠 전체의 너비를 스크롤바가 사라지기 전과 같은 값으로 유지할 수 있습니다. +======= +The first button freezes the scroll, while the second one releases it. +``` + +We can use the same technique to freeze the scroll for other elements, not just for `document.body`. + +The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free and the content "jumps" to fill it. + +That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze. If it increased (the scrollbar disappeared), then add `padding` to `document.body` in place of the scrollbar to keep the content width the same. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 기하 프로퍼티: +<<<<<<< HEAD - 사용자 눈에 보이는 문서(콘텐츠가 실제 보여지는 영역)의 너비와 높이: `document.documentElement.clientWidth/clientHeight` - 스크롤에 의해 가려진 영역을 포함한 문서 전체의 너비와 높이: +======= +- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/clientHeight` +- Width/height of the whole document, with the scrolled out part: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let scrollHeight = Math.max( diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index cd3d1c8cb4..4eac24c65f 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,11 @@ ```online 아래 버튼을 눌러 창 기준 버튼 좌표를 확인해봅시다. +<<<<<<< HEAD

        +======= +

        +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` +<<<<<<< HEAD 이제 원하는 대로 `dispatchEvent`가 `mouse.onclick`을 포함한 현재 코드 실행이 종료된 이후에 실행됩니다. 이벤트 핸들러들이 완전히 독립적으로 되었네요. +======= +Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `menu.onclick`, so event handlers are totally separate. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 출력 순서는 '1', '2', '중첩 이벤트'입니다. @@ -283,9 +302,15 @@ UI 이벤트별 표준 프로퍼티 목록은 명세서에서 확인할 수 있 이벤트를 직접 만드는 경우라면 `CustomEvent` 생성자를 써야 합니다. `CustomEvent` 생성자엔 `detail`이라는 추가 프로퍼티를 명시할 수 있는데, 여기에 이벤트 관련 정보를 저장해야 합니다. 이렇게 하면 모든 핸들러에서 `event.detail`을 통해 커스텀 이벤트의 정보를 알 수 있습니다. +<<<<<<< HEAD 커스텀 이벤트의 이름을 `click`나 `keydown` 같이 브라우저 내장 이벤트처럼 지을 수 있긴 한데, 이런 경우엔 아주 조심해야 합니다. 되도록이면 내장 이벤트와 같은 이름을 가진 브라우저 이벤트를 만들지 말도록 합시다. 대부분의 경우 설계 관점에서 아주 좋지 않은 영항을 끼치기 때문입니다. +======= +Despite the technical possibility of generating browser events like `click` or `keydown`, we should use them with great care. + +We shouldn't generate browser events as it's a hacky way to run handlers. That's bad architecture most of the time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그렇지만 이런 경우에는 브라우저 이벤트를 만드는게 불가피 하니, 사용해도 괜찮습니다. diff --git a/2-ui/3-event-details/1-mouse-events-basics/article.md b/2-ui/3-event-details/1-mouse-events-basics/article.md index fe88929d1a..638649eed8 100644 --- a/2-ui/3-event-details/1-mouse-events-basics/article.md +++ b/2-ui/3-event-details/1-mouse-events-basics/article.md @@ -1,4 +1,9 @@ +<<<<<<< HEAD # 마우스 이벤트 +======= + +# Mouse events +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이번 챕터에선 마우스 이벤트와 마우스 이벤트 객체의 프로퍼티에 대해 자세히 다루겠습니다. @@ -39,9 +44,15 @@ ```online 아래 버튼을 클릭 또는 더블클릭해 실제 마우스 버튼을 클릭했을 때 어떤 일이 발생하는지 알아봅시다. +<<<<<<< HEAD 모든 마우스 이벤트가 버튼 아래 창에 기록되는데, 이벤트 발생 간격이 1초 이상일 때는 이벤트 사이에 가로 선이 추가되도록 해놓았습니다. 이벤트 이름 옆엔 어떤 마우스 버튼이 이벤트를 발생시켰는지를 알려주는 `button` 프로퍼티도 보이는데, `button` 프로퍼티에 대한 내용은 바로 아래에서 설명하도록 하겠습니다. +======= +On the teststand below, all mouse events are logged, and if there is more than a 1 second delay between them, they are separated by a horizontal rule. + +Also, we can see the `button` property that allows us to detect the mouse button; it's explained below. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9
        ``` @@ -52,21 +63,37 @@ `click` 이벤트는 마우스 왼쪽 버튼을, `contextmenu` 이벤트는 마우스 오른쪽 버튼을 눌렀을 때 발생하기 때문에 `click`과 `contextmenu` 이벤트를 다룰 땐 보통 `button` 프로퍼티를 사용하지 않습니다. +<<<<<<< HEAD 반면 `mousedown`이벤트나 `mouseup` 이벤트를 다룰 땐 해당 이벤트의 핸들러에 `event.button`을 명시해 줘야 할 수 있습니다. 이 이벤트들은 마우스 버튼 어디에서나 발생할 수 있는데 `button` 프로퍼티를 사용해야 정확히 어떤 버튼에서 이벤트가 발생했는지 알 수 있기 때문입니다. +======= +On the other hand, `mousedown` and `mouseup` handlers may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 주요 `event.button` 프로퍼티 값을 정리하면 다음과 같습니다. | 버튼 | `event.button` | |--------------|----------------| +<<<<<<< HEAD | 왼쪽(주요 버튼) | 0 | | 가운데(보조 버튼) | 1 | | 오른쪽 (두 번째 버튼) | 2 | | X1(뒤로 가기 버튼) | 3 | | X2(앞으로 가기 버튼) | 4 | +======= +| Left button (primary) | 0 | +| Middle button (auxiliary) | 1 | +| Right button (secondary) | 2 | +| X1 button (back) | 3 | +| X2 button (forward) | 4 | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 상당수의 마우스는 왼쪽, 오른쪽 버튼만 가지고 있기 때문에 이 마우스들이 만들어내는 `event.button` 값은 `0`이나 `2`가 됩니다. 터치를 지원하는 기기들도 사람이 해당 기기를 터치했을 때 유사한 이벤트를 만듭니다. +<<<<<<< HEAD 참고로 `buttons`라는 프로퍼티도 있는데, 이 프로퍼티는 여러 개의 버튼을 한꺼번에 눌렀을 때 해당 버튼들에 대한 정보를 정수 형태로 저장해 줍니다. 실무에서 `buttons` 프로퍼티를 만날 일은 극히 드물긴 하지만 혹시라도 필요하다면[MDN](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons)에서 확인해보시길 바랍니다. +======= +Also there's `event.buttons` property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at [MDN](mdn:/api/MouseEvent/buttons) if you ever need it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```warn header="역사의 뒤안길로 사라진 `event.which`" 오래된 코드를 보다 보면 `event.which`라는 프로퍼티를 발견할 수 있습니다. `event.which` 프로퍼티는 어떤 버튼을 클릭했는지 알려주는 비표준 프로퍼티로 다음과 같은 값을 가집니다. @@ -154,9 +181,15 @@ Windows와 Linux는 `key:Alt`, `key:Shift`, `key:Ctrl` 키를 지원합니다. ## mousedown 이벤트와 선택 막기 +<<<<<<< HEAD 글자 위에서 마우스를 더블클릭하면 글자가 선택되는데, 이런 기본 동작이 사용자 경험을 해칠 때가 있습니다. `dblclick` 이벤트가 발생하면 얼럿 창을 띄우고 싶다고 가정해 봅시다. 제대로 코드를 작성했음에도 불구하고 핸들러가 실행되는 동시에 글자가 선택되는 불필요한 부수효과가 발생하였습니다. +======= +Double mouse click has a side effect that may be disturbing in some interfaces: it selects text. + +For instance, double-clicking on the text below selects it in addition to our handler: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html autorun height=50 이곳을 더블클릭해주세요. diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html index e998165fd2..84d52b18c8 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html @@ -54,7 +54,7 @@

        Once upon a time there was a mother pig who had three little pigs.

        -

        The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

        The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

        The three little pigs set off. "We will take care that the wolf does not catch us," they said.

        diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html index 2dc4394e74..774e24a21e 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html @@ -54,7 +54,7 @@

        Once upon a time there was a mother pig who had three little pigs.

        -

        The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

        The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

        The three little pigs set off. "We will take care that the wolf does not catch us," they said.

        diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js index 4e6e2a3e95..7503ca9c2f 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js @@ -88,7 +88,7 @@ class HoverIntent { if (speed < this.sensitivity) { clearInterval(this.checkSpeedInterval); this.isHover = true; - this.over.call(this.elem, event); + this.over.call(this.elem); } else { // speed fast, remember new coordinates as the previous ones this.prevX = this.lastX; diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md index c7ac0d4db8..d409c3f127 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md @@ -80,7 +80,7 @@ An important feature of `mouseout` -- it triggers, when the pointer moves from a
        ``` -If we're on `#parent` and then move the pointer deeper into `#child`, but we get `mouseout` on `#parent`! +If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`! ![](mouseover-to-child.svg) diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js index 6d87199c26..5752e83ae9 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js @@ -3,7 +3,7 @@ parent.onmouseover = parent.onmouseout = parent.onmousemove = handler; function handler(event) { let type = event.type; - while (type < 11) type += ' '; + while (type.length < 11) type += ' '; log(type + " target=" + event.target.id) return false; diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 664069e943..18e013395f 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -18,11 +18,16 @@ 2. 이후 `mousemove`에서 `position:absolute`의 `left∙top`을 변경합니다. 3. `mouseup`에서는 드래그 앤 드롭 완료와 관련된 모든 작업을 수행합니다. +<<<<<<< HEAD 여기까지가 기본 알고리즘입니다. 이후에는 이동 중인 요소 아래에 있는 다른 요소를 강조하는 기능을 알아보겠습니다. +======= +These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 공을 드래그하는 구현 방법은 다음과 같습니다. ```js +<<<<<<< HEAD ball.onmousedown = function(event) { // (1) absolute 속성과 zIndex 프로퍼티를 수정해 공이 제일 위에서 움직이기 위한 준비를 합니다. ball.style.position = 'absolute'; @@ -31,6 +36,16 @@ ball.onmousedown = function(event) { // 현재 위치한 부모에서 body로 직접 이동하여 // body를 기준으로 위치를 지정합니다. document.body.append(ball); +======= +ball.onmousedown = function(event) { + // (1) prepare to moving: make absolute and on top by z-index + ball.style.position = 'absolute'; + ball.style.zIndex = 1000; + + // move it out of any current parents directly into body + // to make it positioned relative to the body + document.body.append(ball); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // 공을 pageX, pageY 좌표 중앙에 위치하게 합니다. function moveAt(pageX, pageY) { @@ -93,14 +108,22 @@ document의 중간이나 윈도우 어딘가로 점프 되는 현상을 잡기 ## 올바른 위치 지정 +<<<<<<< HEAD 위 예제 코드에서 공은 항상 포인터 아래로 이동합니다. +======= +In the examples above the ball is always moved so that its center is under the pointer: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ``` +<<<<<<< HEAD 나쁘진 않습니다. 다만, 몇 가지 부작용이 있습니다. 드래그 앤 드롭을 시작하기 위해 공 위 어디에서든 `mousedown`을 할 수 있습니다. 공의 가장자리에서 `mousedown`을 하게 되면, 마우스 포인터 아래로 공이 갑자기 점프 되는 부작용이 발생합니다. +======= +Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 포인터를 기준으로 요소의 초기 이동을 유지하는 방법이 포인터 중앙으로 요소를 이동시키는 방법보다 더 좋습니다. @@ -124,7 +147,11 @@ ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ```js // onmousemove +<<<<<<< HEAD // 공은 고정된 포지션을 갖습니다. +======= + // ball has position:absolute +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` @@ -219,7 +246,11 @@ ball.ondragstart = function() { 그러면 무엇을 해야 할까요? +<<<<<<< HEAD `document.elementFromPoint(clientX, clientY)`라는 메서드가 있습니다. 주어진 윈도우 기준 좌표에서 가장 많이 중첩된 요소를 반환합니다. (윈도우 밖의 좌표는 null) +======= +There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 다음과 같이 마우스 이벤트 핸들러에서 포인터 아래에 드롭 가능성을 감지할 수 있습니다. @@ -276,7 +307,11 @@ function onMouseMove(event) { } ``` +<<<<<<< HEAD 아래 예시에서 공을 축구 골대 위로 드래그하면 골대가 강조 표시됩니다. +======= +In the example below when the ball is dragged over the soccer goal, the goal is highlighted. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [codetabs height=250 src="ball4"] @@ -300,4 +335,8 @@ function onMouseMove(event) { - `mousedown/up`에 이벤트 위임을 사용할 수 있습니다. `event.target`을 확인하는 넓은 영역의 이벤트 핸들러는 수백 개의 요소에 대한 드래그 앤 드롭을 관리할 수 있습니다. - 등등 +<<<<<<< HEAD `DragZone`, `Droppable`, `Draggable` 및 기타 클래스 등 아키텍처를 구축하는 프레임워크가 있습니다. 대부분은 앞서 드래그와 드롭에 대한 설명과 유사한 작업을 하므로 이해하기 쉽습니다. 때로는 제3의 솔루션 적용보다 쉽게 수행할 수 있습니다. +======= +There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/3-event-details/6-pointer-events/article.md b/2-ui/3-event-details/6-pointer-events/article.md index 3e751a4af7..71129ea61b 100644 --- a/2-ui/3-event-details/6-pointer-events/article.md +++ b/2-ui/3-event-details/6-pointer-events/article.md @@ -9,16 +9,24 @@ Let's make a small overview, so that you understand the general picture and the - Long ago, in the past, there were only mouse events. Then touch devices became widespread, phones and tablets in particular. For the existing scripts to work, they generated (and still generate) mouse events. For instance, tapping a touchscreen generates `mousedown`. So touch devices worked well with web pages. +<<<<<<< HEAD +======= + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 But touch devices have more capabilities than a mouse. For example, it's possible to touch multiple points at once ("multi-touch"). Although, mouse events don't have necessary properties to handle such multi-touches. - So touch events were introduced, such as `touchstart`, `touchend`, `touchmove`, that have touch-specific properties (we don't cover them in detail here, because pointer events are even better). - Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome. + Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome. - To solve these issues, the new standard Pointer Events was introduced. It provides a single set of events for all kinds of pointing devices. +<<<<<<< HEAD As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the newer [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works and is mostly compartible with Pointer Events level 2. +======= +As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the newer [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works and is mostly compatible with Pointer Events level 2. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Unless you develop for old browsers, such as Internet Explorer 10, or for Safari 12 or below, there's no point in using mouse or touch events any more -- we can switch to pointer events. @@ -43,12 +51,16 @@ Pointer events are named similarly to mouse events: | `gotpointercapture` | - | | `lostpointercapture` | - | -As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon. +As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon. ```smart header="Replacing `mouse` with `pointer` in our code" We can replace `mouse` events with `pointer` in our code and expect things to continue working fine with mouse. +<<<<<<< HEAD The support for touch devices will also "magically" improve. Although, we may need to add `touch-action: none` in some places in CSS. We'll cover it below in the section about `pointercancel`. +======= +The support for touch devices will also "magically" improve. Although, we may need to add `touch-action: none` in some places in CSS. We'll cover it below in the section about `pointercancel`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Pointer event properties @@ -56,20 +68,30 @@ The support for touch devices will also "magically" improve. Although, we may ne Pointer events have the same properties as mouse events, such as `clientX/Y`, `target`, etc., plus some others: - `pointerId` - the unique identifier of the pointer causing the event. +<<<<<<< HEAD Browser-generated. Allows us to handle multiple pointers, such as a touchscreen with stylus and multi-touch (examples will follow). - `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch". +======= + + Browser-generated. Allows us to handle multiple pointers, such as a touchscreen with stylus and multi-touch (examples will follow). +- `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 We can use this property to react differently on various pointer types. - `isPrimary` - is `true` for the primary pointer (the first finger in multi-touch). Some pointer devices measure contact area and pressure, e.g. for a finger on the touchscreen, there are additional properties for that: +<<<<<<< HEAD - `width` - the width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it's always `1`. +======= +- `width` - the width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it's always `1`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `height` - the height of the area where the pointer touches the device. Where unsupported, it's always `1`. - `pressure` - the pressure of the pointer tip, in range from 0 to 1. For devices that don't support pressure must be either `0.5` (pressed) or `0`. - `tangentialPressure` - the normalized tangential pressure. -- `tiltX`, `tiltY`, `twist` - pen-specific properties that describe how the pen is positioned relative the surface. +- `tiltX`, `tiltY`, `twist` - pen-specific properties that describe how the pen is positioned relative to the surface. These properties aren't supported by most devices, so they are rarely used. You can find the details about them in the [specification](https://w3c.github.io/pointerevents/#pointerevent-interface) if needed. @@ -102,16 +124,24 @@ Please note: you must be using a touchscreen device, such as a phone or a tablet ## Event: pointercancel +<<<<<<< HEAD The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated. Such causes are: - The pointer device hardware was physically disabled. - The device orientation changed (tablet rotated). +======= +The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated. + +Such causes are: +- The pointer device hardware was physically disabled. +- The device orientation changed (tablet rotated). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - The browser decided to handle the interaction on its own, considering it a mouse gesture or zoom-and-pan action or something else. We'll demonstrate `pointercancel` on a practical example to see how it affects us. -Let's say we're impelementing drag'n'drop for a ball, just as in the beginning of the article . +Let's say we're implementing drag'n'drop for a ball, just as in the beginning of the article . Here is the flow of user actions and the corresponding events: @@ -126,7 +156,11 @@ Here is the flow of user actions and the corresponding events: So the issue is that the browser "hijacks" the interaction: `pointercancel` fires in the beginning of the "drag-and-drop" process, and no more `pointermove` events are generated. ```online +<<<<<<< HEAD Here's the drag'n'drop demo with loggin of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: +======= +Here's the drag'n'drop demo with logging of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [iframe src="ball" height=240 edit] ``` @@ -141,7 +175,11 @@ We need to do two things: - We can do this by setting `ball.ondragstart = () => false`, just as described in the article . - That works well for mouse events. 2. For touch devices, there are other touch-related browser actions (besides drag'n'drop). To avoid problems with them too: +<<<<<<< HEAD - Prevent them by setting `#ball { touch-action: none }` in CSS. +======= + - Prevent them by setting `#ball { touch-action: none }` in CSS. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - Then our code will start working on touch devices. After we do that, the events will work as intended, the browser won't hijack the process and doesn't emit `pointercancel`. @@ -163,7 +201,11 @@ Pointer capturing is a special feature of pointer events. The idea is very simple, but may seem quite odd at first, as nothing like that exists for any other event type. The main method is: +<<<<<<< HEAD - `elem.setPointerCapture(pointerId)` - binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened. +======= +- `elem.setPointerCapture(pointerId)` -- binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 In other words, `elem.setPointerCapture(pointerId)` retargets all subsequent events with the given `pointerId` to `elem`. @@ -172,6 +214,7 @@ The binding is removed: - automatically when `elem` is removed from the document, - when `elem.releasePointerCapture(pointerId)` is called. +<<<<<<< HEAD **Pointer capturing can be used to simplify drag'n'drop kind of interactions.** As an example, let's recall how one can implement a custom slider, described in the . @@ -195,6 +238,45 @@ Pointer capturing provides a means to bind `pointermove` to `thumb` and avoid an - When `pointerup` happens (dragging complete), the binding is removed automatically, we don't need to care about it. So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Besides, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`. +======= +Now what is it good for? It's time to see a real-life example. + +**Pointer capturing can be used to simplify drag'n'drop kind of interactions.** + +Let's recall how one can implement a custom slider, described in the . + +We can make a `slider` element to represent the strip and the "runner" (`thumb`) inside it: + +```html +
        +
        +
        +``` + +With styles, it looks like this: + +[iframe src="slider-html" height=40 edit] + +

        + +And here's the working logic, as it was described, after replacing mouse events with similar pointer events: + +1. The user presses on the slider `thumb` -- `pointerdown` triggers. +2. Then they move the pointer -- `pointermove` triggers, and our code moves the `thumb` element along. + - ...As the pointer moves, it may leave the slider `thumb` element, go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer. + +In the mouse event based solution, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `mousemove` event handler on the whole `document`. + +That's not a cleanest solution, though. One of the problems is that when a user moves the pointer around the document, it may trigger event handlers (such as `mouseover`) on some other elements, invoke totally unrelated UI functionality, and we don't want that. + +This is the place where `setPointerCapture` comes into play. + +- We can call `thumb.setPointerCapture(event.pointerId)` in `pointerdown` handler, +- Then future pointer events until `pointerup/cancel` will be retargeted to `thumb`. +- When `pointerup` happens (dragging complete), the binding is removed automatically, we don't need to care about it. + +So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Nevertheless, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Here's the essential code: @@ -202,8 +284,23 @@ Here's the essential code: thumb.onpointerdown = function(event) { // retarget all pointer events (until pointerup) to thumb thumb.setPointerCapture(event.pointerId); + + // start tracking pointer moves + thumb.onpointermove = function(event) { + // moving the slider: listen on the thumb, as all pointer events are retargeted to it + let newLeft = event.clientX - slider.getBoundingClientRect().left; + thumb.style.left = newLeft + 'px'; + }; + + // on pointer up finish tracking pointer moves + thumb.onpointerup = function(event) { + thumb.onpointermove = null; + thumb.onpointerup = null; + // ...also process the "drag end" if needed + }; }; +<<<<<<< HEAD thumb.onpointermove = function(event) { // moving the slider: listen on the thumb, as all pointer events are retargeted to it let newLeft = event.clientX - slider.getBoundingClientRect().left; @@ -211,6 +308,9 @@ thumb.onpointermove = function(event) { }; // note: no need to call thumb.releasePointerCapture, +======= +// note: no need to call thumb.releasePointerCapture, +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // it happens on pointerup automatically ``` @@ -218,15 +318,35 @@ thumb.onpointermove = function(event) { The full demo: [iframe src="slider" height=100 edit] + +

        + +In the demo, there's also an additional element with `onmouseover` handler showing the current date. + +Please note: while you're dragging the thumb, you may hover over this element, and its handler *does not* trigger. + +So the dragging is now free of side effects, thanks to `setPointerCapture`. ``` +<<<<<<< HEAD At the end, pointer capturing gives us two benefits: 1. The code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. The binding is released automatically. 2. If there are any `pointermove` handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider. ### Pointer capturing events +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + + +At the end, pointer capturing gives us two benefits: +1. The code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. The binding is released automatically. +2. If there are other pointer event handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider. + +### Pointer capturing events + +There's one more thing to mention here, for the sake of completeness. -There are two associated pointer events: +There are two events associated with pointer capturing: - `gotpointercapture` fires when an element uses `setPointerCapture` to enable capturing. - `lostpointercapture` fires when the capture is released: either explicitly with `releasePointerCapture` call, or automatically on `pointerup`/`pointercancel`. @@ -237,7 +357,11 @@ Pointer events allow handling mouse, touch and pen events simultaneously, with a Pointer events extend mouse events. We can replace `mouse` with `pointer` in event names and expect our code to continue working for mouse, with better support for other device types. +<<<<<<< HEAD For drag'n'drops and complex touch interactions that the browser may decide to hijack and handle on its own - remember to cancel the default action on events and set `touch-events: none` in CSS for elements that we engage. +======= +For drag'n'drops and complex touch interactions that the browser may decide to hijack and handle on its own - remember to cancel the default action on events and set `touch-action: none` in CSS for elements that we engage. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Additional abilities of pointer events are: diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html new file mode 100644 index 0000000000..781016f52a --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html @@ -0,0 +1,6 @@ + + + +
        +
        +
        diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css new file mode 100644 index 0000000000..9b3d3b82d4 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css @@ -0,0 +1,19 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/index.html b/2-ui/3-event-details/6-pointer-events/slider.view/index.html index 2c2a69ec77..b29e646a16 100644 --- a/2-ui/3-event-details/6-pointer-events/slider.view/index.html +++ b/2-ui/3-event-details/6-pointer-events/slider.view/index.html @@ -5,22 +5,33 @@
        +

        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..a37fd651b6 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,19 @@ 폼은 특수한 컬렉션인 `document.forms`의 구성원입니다. +<<<<<<< HEAD `document.forms`는 이름과 순서가 있는 '기명 컬렉션(named collection)'입니다. 개발자는 이 이름이나 순서를 사용해 문서 내의 폼에 접근할 수 있습니다. ```js no-beautify document.forms.my - 이름이 'my'인 폼 document.forms[0] - 문서 내의 첫 번째 폼 +======= +That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form. + +```js no-beautify +document.forms.my; // the form with name="my" +document.forms[0]; // the first form in the document +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 이름이나 순서를 사용해 원하는 폼을 가져온 다음에는 기명 컬렉션 `form.elements`를 사용해 폼의 요소를 얻을 수 있습니다. @@ -36,9 +44,15 @@ document.forms[0] - 문서 내의 첫 번째 폼 ``` +<<<<<<< HEAD 그런데 개발을 하다 보면 이름이 같은 요소 여러 개를 다뤄야 하는 경우가 생기기도 합니다. 라디오 버튼을 다룰 때 이런 상황이 자주 발생하죠. 이때 `form.elements[name]`은 컬렉션이 된다는 사실을 이용할 수 있습니다. 예시를 살펴봅시다. +======= +There may be multiple elements with the same name. This is typical with radio buttons and checkboxes. + +In that case, `form.elements[name]` is a *collection*. For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run height=40
        @@ -119,7 +133,11 @@ alert(ageElems[0]); // [object HTMLInputElement] ``` +<<<<<<< HEAD 그런데 폼 요소의 이름을 변경하는 일은 드물기 때문에 보통은 이런 특징이 문제가 되지 않습니다. +======= +That's usually not a problem, however, because we rarely change names of form elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` @@ -155,7 +173,11 @@ alert(ageElems[0]); // [object HTMLInputElement] ### input과 textarea +<<<<<<< HEAD input과 textarea 요소의 값은 `input.value` (string) 또는 `input.checked`(boolean)을 사용해 얻을 수 있습니다. +======= +We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes and radio buttons. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이렇게 말이죠. @@ -176,12 +198,19 @@ input.checked = true; // 체크박스나 라디오 버튼에서 쓸 수 있습 ``의 값을 설정할 수 있습니다. +<<<<<<< HEAD 1. 조건에 맞는 `
  • `가 '수정 중'일 때는 다른 셀을 눌러도 클릭 이벤트가 무시되어야 합니다. - 테이블엔 더 많은 셀이 추가될 수 있으므로 이벤트 위임을 사용하세요. +======= +- On click -- the cell should become "editable" (textarea appears inside), we can change HTML. There should be no resize, all geometry should remain the same. +- Buttons OK and CANCEL appear below the cell to finish/cancel the editing. +- Only one cell may be editable at a moment. While a `` is in "edit mode", clicks on other cells are ignored. +- The table may have many cells. Use event delegation. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 데모: 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..76461eb4d8 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 @@ -8,6 +8,12 @@ importance: 4 [demo src="solution"] +<<<<<<< HEAD 참고1: `#mouse` 요소 이외의 어느 곳에도 이벤트 핸들러를 달지 마세요. -참고2: HTML과 CSS를 수정하지 마세요. 작성할 자바스크립트는 어떤 요소에서도 동작할 수 있는 범용성이 있어야 합니다. \ No newline at end of file +참고2: HTML과 CSS를 수정하지 마세요. 작성할 자바스크립트는 어떤 요소에서도 동작할 수 있는 범용성이 있어야 합니다. +======= +P.S. Don't put event handlers anywhere except the `#mouse` element. + +P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..accefbbd39 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -1,6 +1,10 @@ # focus와 blur +<<<<<<< HEAD 사용자가 폼 요소를 클릭하거나 `key:Tab` 키를 눌러 요소로 이동하면 해당 요소가 포커스(focus)됩니다. `autofocus`라는 HTML 속성을 사용해도 요소를 포커스 할 수 있는데 이 속성이 있는 요소는 페이지가 로드된 후 자동으로 포커싱 됩니다. 이 외에도 요소를 포커싱(focusing)할 수 있는 방법은 다양합니다. +======= +An element receives the focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus onto an element by default when a page loads and other means of getting the focus. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 요소를 포커싱한다는 것은 일반적으로 '여기에 데이터를 입력할 준비를 하라'는 것을 의미하기 때문에 요소 포커싱이 이뤄지는 순간엔 요구사항을 충족시키는 초기화 코드를 실행할 수 있습니다. @@ -18,8 +22,13 @@ 예시에서 각 핸들러는 다음과 같은 역할을 합니다. +<<<<<<< HEAD - `blur` 핸들러에선 필드에 이메일이 잘 입력되었는지 확인하고 잘 입력되지 않은 경우엔 에러를 보여줍니다. - `focus` 핸들러에선 에러 메시지를 숨깁니다(이메일 재확인은 `blur` 핸들러에서 합니다). +======= +- The `blur` handler checks if the field has an email entered, and if not -- shows an error. +- The `focus` handler hides the error message (on `blur` it will be checked again): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run autorun height=60 <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..23c07c4846 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,11 @@ ## 이벤트 루프 +<<<<<<< HEAD *이벤트 루프(event loop)* 정의는 아주 간단합니다. 이벤트 루프는 태스크가 들어오길 기다렸다가 태스크가 들어오면 이를 처리하고, 처리할 태스크가 없는 경우엔 잠드는, 끊임없이 돌아가는 자바스크립트 내 루프입니다(task는 '작업'이라고 번역할 수 있는데, 매크로·마이크로태스크 등의 용어와 일치시키기 위해 '태스크'라고 음차 번역하였습니다 - 옮긴이). +======= +The *event loop* concept is very simple. There's an endless loop, where the JavaScript engine waits for tasks, executes them and then sleeps, waiting for more tasks. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 자바스크립트 엔진이 돌아가는 알고리즘을 일반화하면 다음과 같습니다. @@ -18,7 +22,11 @@ 2. 처리해야 할 태스크가 없는 경우: - 잠들어 있다가 새로운 태스크가 추가되면 다시 1로 돌아감 +<<<<<<< HEAD 바로 이 알고리즘이 우리가 브라우저를 사용해 인터넷을 서핑할 때 돌아가는 알고리즘입니다. 이렇게 자바스크립트 엔진은 대부분의 시간 동안 아무런 일도 하지 않고 쉬고 있다가 스크립트나 핸들러, 이벤트가 활성화될 때만 돌아갑니다. +======= +That's a formalization of what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그렇다면 자바스크립트 엔진을 활성화하는 태스크엔 과연 어떤 것들이 있을까요? 대표적인 태스크는 다음과 같습니다. @@ -31,6 +39,7 @@ 새로운 태스크는 엔진이 바쁠 때 추가될 수도 있습니다. 이때 이 태스크는 큐에 추가됩니다. +<<<<<<< HEAD 이렇게 태스크가 추가되는 큐는 V8 용어로 '매크로태스크 큐(macrotask queue)'라고 부릅니다. ![](eventLoop.svg) @@ -38,14 +47,31 @@ 좀 더 구체적인 사례를 가지고 매크로태스크 큐에 대해 알아봅시다. 엔진이 `script`를 처리하느라 바쁜데 사용자가 마우스를 움직여 `mousemove` 이벤트를 활성화하고, 바로 이어서 `setTimeout`에서 설정한 시간이 지났다고 가정해 봅시다. 이때 세 태스크는 큐에 하나씩 추가되는데, 위 그림에 이런 상황을 묘사해 보았습니다. 큐에 있는 태스크들은 '들어간 순서대로' 처리됩니다. 엔진은 `script`를 먼저 처리하고 `mousemove` 이벤트와 핸들러, `setTimeout` 핸들러를 순차적으로 처리합니다. +======= +The tasks form a queue, the so-called "macrotask queue" ([v8](https://v8.dev/) term): + +![](eventLoop.svg) + +For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated in the picture above. + +Tasks from the queue are processed on a "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 지금까진 어려운 것이 없어 보입니다. 그렇죠? +<<<<<<< HEAD 여기서 잠시 두 가지 세부 사항을 짚고 넘어갑시다. 1. 엔진이 특정 태스크를 처리하는 동안엔 렌더링이 절대 일어나지 않습니다. 태스크를 처리하는 데 걸리는 시간이 길지 않으면 이는 전혀 문제가 되지 않습니다. 처리가 끝나는 대로 DOM 변경을 화면에 반영하면 되기 때문입니다. 2. 태스크 처리에 긴 시간이 걸리면, 브라우저는 태스크를 처리하는 동안에 발생한 사용자 이벤트 등의 새로운 태스크들을 처리하지 못합니다. 인터넷 서핑을 하다 보면 '응답 없는 페이지(Page Unresponsive)'라는 얼럿 창을 만나게 되는 경우가 종종 있습니다. 이 얼럿 창은 아주 복잡한 계산이 필요하거나 프로그래밍 에러 때문에 무한 루프에 빠지게 될 때 나타나는데, 브라우저는 얼럿 창을 통해 사용자에게 페이지 전체와 함께 해당 태스크를 취소시킬지 말지를 선택하도록 유도합니다. 자, 이론을 충분히 살펴봤으니 지금부턴 이 지식을 실무에서 어떻게 활용할 수 있을지 알아보도록 합시다. +======= +Two more details: +1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. +2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after some time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. + +That was the theory. Now let's see how we can apply that knowledge. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 유스 케이스 1: CPU 소모가 많은 태스크 쪼개기 @@ -55,7 +81,11 @@ CPU 소모가 아주 많은 태스크 하나가 있다고 가정해 봅시다. 코드 강조라는 태스크를 수행하느라 엔진이 바쁠 때엔 사용자 이벤트 처리나 DOM 관련 작업이 완전히 멈추게 됩니다. 그러다 보면 브라우저에 '지연'이 생기거나 심하면 '멈춤' 현상까지 발생하기도 하죠. 절대 있어서는 안 될 일입니다. +<<<<<<< HEAD 이런 불가피한 상황들은 태스크를 여러 조각으로 쪼개 예방할 수 있습니다. 앞부분 100줄만 먼저 강조하고, 지연시간이 0인 `setTimeout`을 사용해 새롭게 스케줄링을 한 다음, 그 다음 100줄을 강조하는 식으로 코드를 변경하면 되죠. +======= +We can avoid problems by splitting the big task into pieces. Highlight the first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 실제 코드를 통해 어떻게 하면 태스크를 쪼갤 수 있는지 알아봅시다. 직접 강조기능을 구현하는 대신 `1`부터 `1000000000`까지의 숫자를 세주는 함수를 사용해 간결한 코드로 시연해 보겠습니다. @@ -161,7 +191,11 @@ count(); 태스크를 여러 개로 쪼갤 때의 장점은 진행 상태를 나타내주는 프로그레스 바(progress bar)를 만들 때도 드러납니다. +<<<<<<< HEAD 아시다시피 브라우저는 시간이 오래 걸리든 아니든 상관없이 현재 작업 중인 태스크가 끝나야 DOM 변경분을 화면에 렌더링해줍니다. +======= +As mentioned earlier, changes to DOM are painted only after the currently running task is completed, irrespective of how long it takes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이런 브라우저 동작 방식은 완성되지 않은 '중간' 상태의 화면이 사용자에게 노출되는 걸 막아주기 때문에 유리합니다. 요소를 여러 개 만들고 이 요소들을 하나씩 화면에 추가한 다음 원하는 요소의 스타일을 변경시키는 일련의 과정이 담긴 함수가 있는데, 이 함수를 실행하는 동안에 변경사항 모두가 사용자에게 노출된다면 사용자는 혼란을 느꼈을 겁니다. @@ -239,7 +273,11 @@ menu.onclick = function() { ## 매크로태스크와 마이크로태스크 +<<<<<<< HEAD 태스크는 이번 챕터에서 설명한 *매크로태스크(macrotask)* 와 챕터에서 다룬 *마이크로태스크(microtask)* 로 나뉩니다. +======= +Along with *macrotasks*, described in this chapter, there are *microtasks*, mentioned in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 마이크로태스크는 코드를 사용해서만 만들 수 있는데, 주로 프라미스를 사용해 만듭니다. 프라미스와 함께 쓰이는 `.then/catch/finally` 핸들러가 마이크로태스크가 되죠. 여기에 더하여 마이크로태스크는 프라미스를 핸들링하는 또 다른 문법인 `await`를 사용해 만들기도 합니다. @@ -304,7 +342,11 @@ alert("code"); ## 요약 +<<<<<<< HEAD 이벤트 루프 알고리즘을 요약하면 다음과 같습니다(자세한 사항은 [명세서](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)에서 확인할 수 있습니다). +======= +A more detailed event loop algorithm (though still simplified compared to the [specification](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. *매크로태스크* 큐에서 가장 오래된 태스크를 꺼내 실행합니다(예: 스크립트를 실행). 2. 모든 *마이크로태스크*를 실행합니다. @@ -317,7 +359,11 @@ alert("code"); 새로운 *매크로태스크*를 스케줄링하는 방법은 다음과 같습니다. - 지연시간이 0인 `setTimeout(f)` 사용하기 +<<<<<<< HEAD 이 방법을 사용하면 계산이 복잡한 큰 태스크 하나를 여러 개로 쪼갤 수 있습니다. 태스크를 여러 개로 쪼개면 태스크 중간중간 사용자 이벤트에 반응할 수 있고, 작업 진척 상태를 화면에 표시해줄 수도 있습니다. +======= +That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react to user events and show progress between them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 지연시간이 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..f8bc9a8f52 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,11 @@ 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. +<<<<<<< HEAD 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 For instance, look at this code: @@ -257,10 +241,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..fdbc376f5a 100644 --- a/4-binary/02-text-decoder/article.md +++ b/4-binary/02-text-decoder/article.md @@ -2,17 +2,28 @@ 이진 데이터가 문자열이라면 어떨지 생각해봅시다. 예를 들어 텍스트 데이터가 있는 파일을 받았다고 가정하겠습니다. +<<<<<<< HEAD 내장 객체, [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder)는 주어진 버퍼와 인코딩으로 값을 실제 자바스크립트 문자열로 읽을 수 있게 해줍니다. +======= +The built-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows one to read the value into an actual JavaScript string, given the buffer and the encoding. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 첫 번째로 객체를 생성합니다. ```js let decoder = new TextDecoder([label], [options]); ``` +<<<<<<< HEAD - **`label`** -- 기본적인 인코딩 방식은 `utf-8`이지만 `big5`, `windows-1251` 및 다른 인코딩 방식도 지원됩니다. - **`options`** -- 선택 항목입니다. - **`fatal`** -- 불린 값. `true`인 경우, 잘못된 글자(디코딩 불가능한 글자)를 대상으로 예외를 던집니다. `false(기본값)`인 경우, 글자를 `\uFFFD`로 대체합니다. - **`ignoreBOM`** -- 불린 값이 `true`인 경우 사용되지 않는 바이트 순서 표식(Byte Order Mark, BOM)을 무시합니다. +======= +- **`label`** -- the encoding, `utf-8` by default, but `big5`, `windows-1251` and many other are also supported. +- **`options`** -- optional object: + - **`fatal`** -- boolean, if `true` then throw an exception for invalid (non-decodable) characters, otherwise (default) replace them with character `\uFFFD`. + - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order Unicode mark), rarely needed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그런 다음 생성했던 객체를 디코딩합니다. 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..f6c6c7b946 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -27,7 +27,11 @@ let promise = fetch(url, [options]) - **`url`** -- 접근하고자 하는 URL - **`options`** -- 선택 매개변수, method나 header 등을 지정할 수 있음 +<<<<<<< HEAD `options`에 아무것도 넘기지 않으면 요청은 `GET` 메서드로 진행되어 `url`로부터 콘텐츠가 다운로드 됩니다. +======= +Without `options`, this is a simple GET request, downloading the contents of the `url`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `fetch()`를 호출하면 브라우저는 네트워크 요청을 보내고 프라미스가 반환됩니다. 반환되는 프라미스는 `fetch()`를 호출하는 코드에서 사용됩니다. @@ -61,12 +65,21 @@ if (response.ok) { // HTTP 상태 코드가 200~299일 경우 `response` 에는 프라미스를 기반으로 하는 다양한 메서드가 있습니다. 이 메서드들을 사용하면 다양한 형태의 응답 본문을 처리할 수 있습니다. +<<<<<<< HEAD - **`response.text()`** -- 응답을 읽고 텍스트를 반환합니다, - **`response.json()`** -- 응답을 JSON 형태로 파싱합니다, - **`response.formData()`** -- 응답을 `FormData` 객체 형태로 반환합니다. `FormData`에 대한 자세한 내용은 [다음 챕터](info:formdata)에서 다루겠습니다. - **`response.blob()`** -- 응답을 [Blob](info:blob)(타입이 있는 바이너리 데이터) 형태로 반환합니다. - **`response.arrayBuffer()`** -- 응답을 [ArrayBuffer](info:arraybuffer-binary-arrays)(바이너리 데이터를 로우 레벨 형식으로 표현한 것) 형태로 반환합니다. - 이 외에도 `response.body`가 있는데, [ReadableStream](https://streams.spec.whatwg.org/#rs-class) 객체인 `response.body`를 사용하면 응답 본문을 청크 단위로 읽을 수 있습니다. 자세한 용례는 곧 살펴보겠습니다. +======= +- **`response.text()`** -- read the response and return as text, +- **`response.json()`** -- parse the response as JSON, +- **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)), +- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), +- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representation of binary data), +- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows you to read the body chunk-by-chunk, we'll see an example later. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 지금까지 배운 내용을 토대로 GitHub에서 마지막 커밋을 JSON 객체 형태로 받아봅시다. @@ -192,12 +205,21 @@ let response = fetch(protectedUrl, { `GET` 이외의 요청을 보내려면 추가 옵션을 사용해야 합니다. +<<<<<<< HEAD - **`method`** -- HTTP 메서드(예: `POST`) - **`body`** -- 요청 본문으로 다음 항목 중 하나이어야 합니다. - 문자열(예: JSON 문자열) - `FormData`객체 -- `form/multipart` 형태로 데이터를 전송하기 위해 쓰입니다. - `Blob`나 `BufferSource` -- 바이너리 데이터 전송을 위해 쓰입니다. - [URLSearchParams](info:url) -- 데이터를 `x-www-form-urlencoded` 형태로 보내기 위해 쓰이는데, 요즘엔 잘 사용하지 않습니다. +======= +- **`method`** -- HTTP-method, e.g. `POST`, +- **`body`** -- the request body, one of: + - a string (e.g. JSON-encoded), + - `FormData` object, to submit the data as `multipart/form-data`, + - `Blob`/`BufferSource` to send binary data, + - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 대부분은 JSON을 요청 본문에 실어 보내게 됩니다. @@ -296,6 +318,7 @@ fetch(url, options) .then(result => /* 결과 처리 */) ``` +<<<<<<< HEAD 응답 객체의 프로퍼티는 다음과 같습니다. - `response.status` -- 응답의 HTTP 코드 - `response.ok` -- 응답 상태가 200과 299 사이에 있는 경우 `true` @@ -307,6 +330,19 @@ fetch(url, options) - **`response.formData()`** -- 응답을 `FormData` 객체 형태로 반환(form/multipart 인코딩에 대한 내용은 다음 챕터에서 다룸) - **`response.blob()`** -- 응답을 [Blob](info:blob)(타입이 있는 바이너리 데이터) 형태로 반환 - **`response.arrayBuffer()`** -- 응답을 [ArrayBuffer](info:arraybuffer-binary-arrays)(바이너리 데이터를 로우 레벨로 표현한 것) 형태로 반환 +======= +Response properties: +- `response.status` -- HTTP code of the response, +- `response.ok` -- `true` if the status is 200-299. +- `response.headers` -- Map-like object with HTTP headers. + +Methods to get response body: +- **`response.text()`** -- return the response as text, +- **`response.json()`** -- parse the response as JSON object, +- **`response.formData()`** -- return the response as `FormData` object (`multipart/form-data` encoding, see the next chapter), +- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), +- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level binary data), +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 지금까지 배운 `fetch` 옵션은 다음과 같습니다. - `method` -- HTTP 메서드 diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md index 9e07d98408..e3d2610860 100644 --- a/5-network/02-formdata/article.md +++ b/5-network/02-formdata/article.md @@ -47,7 +47,11 @@ HTML에 `form` 요소가 있는 경우, 위와 같은 코드를 작성하면 해 ``` +<<<<<<< HEAD 요청을 받아 처리하는 서버 측 코드는 튜토리얼 범위를 넘어서서 추가하진 않았는데, 서버는 POST 요청을 받아 '저장 성공'이라는 응답을 보내준다고 정도만 알고 계시면 됩니다. +======= +In this example, the server code is not presented, as it's beyond our scope. The server accepts the POST request and replies "User saved". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## FormData 메서드 @@ -168,7 +172,11 @@ formData.append("image", imageBlob, "image.png"); [FormData](https://xhr.spec.whatwg.org/#interface-formdata) 객체는 `fetch` 등의 네트워크 메서드를 통해 HTML 폼을 보내는데 사용됩니다. +<<<<<<< HEAD `FormData` 객체는 HTML 폼(`form`)을 직접 넘겨 `new FormData(form)`으로 만들 수도 있고, HTML 폼 없이 다음과 같은 메서드로 필드를 추가해 만들 수도 있습니다. +======= +We can either create `new FormData(form)` from an HTML form, or create an object without a form at all, and then append fields with methods: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `formData.append(name, value)` - `formData.append(name, blob, fileName)` 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..524f033f36 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -18,15 +18,25 @@ let controller = new AbortController(); A controller is an extremely simple object. - It has a single method `abort()`, +<<<<<<< HEAD - And a single property `signal` that allows to set event liseners on it. +======= +- And a single property `signal` that allows to set event listeners on it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 When `abort()` is called: - `controller.signal` emits the `"abort"` event. - `controller.signal.aborted` property becomes `true`. +<<<<<<< HEAD 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. +======= +Generally, we have two parties in the process: +1. The one that performs a cancelable operation, it sets a listener on `controller.signal`. +2. The one that cancels: it calls `controller.abort()` when needed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Here's the full example (without `fetch` yet): @@ -34,8 +44,13 @@ Here's the full example (without `fetch` yet): let controller = new AbortController(); let signal = controller.signal; +<<<<<<< HEAD // The party that performs a cancelable operation // gets "signal" object +======= +// The party that performs a cancelable operation +// gets the "signal" object +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // and sets the listener to trigger when controller.abort() is called signal.addEventListener('abort', () => alert("abort!")); @@ -46,6 +61,7 @@ controller.abort(); // abort! alert(signal.aborted); // true ``` +<<<<<<< HEAD As we can see, `AbortController` is just a means to pass `abort` events when `abort()` is called on it. We could implement same kind of event listening in our code on our own, without `AbortController` object at all. @@ -55,6 +71,17 @@ But what's valuable is that `fetch` knows how to work with `AbortController` obj ## Using with fetch To become able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: +======= +As we can see, `AbortController` is just a mean to pass `abort` events when `abort()` is called on it. + +We could implement the same kind of event listening in our code on our own, without the `AbortController` object. + +But what's valuable is that `fetch` knows how to work with the `AbortController` object. It's integrated in it. + +## Using with fetch + +To be able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let controller = new AbortController(); @@ -65,7 +92,11 @@ fetch(url, { The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`. +<<<<<<< HEAD Now, to to abort, call `controller.abort()`: +======= +Now, to abort, call `controller.abort()`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js controller.abort(); @@ -97,7 +128,11 @@ try { ## AbortController is scalable +<<<<<<< HEAD `AbortController` is scalable, it allows to cancel multiple fetches at once. +======= +`AbortController` is scalable. It allows to cancel multiple fetches at once. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all: @@ -113,7 +148,7 @@ let fetchJobs = urls.map(url => fetch(url, { let results = await Promise.all(fetchJobs); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches ``` @@ -137,12 +172,17 @@ let fetchJobs = urls.map(url => fetch(url, { // fetches // Wait for fetches and our task in parallel let results = await Promise.all([...fetchJobs, ourJob]); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches and ourJob ``` ## Summary +<<<<<<< HEAD - `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`. +======= +- `AbortController` is a simple object that generates an `abort` event on its `signal` property when the `abort()` method is called (and also sets `signal.aborted` to `true`). +- `fetch` integrates with it: we pass the `signal` property as the option, and then `fetch` listens to it, so it's possible to abort the `fetch`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 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`. diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index 8ff155a47d..ea30340b74 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -28,7 +28,11 @@ CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들 **과거 수 년 동안, 한 사이트의 스크립트에서 다른 사이트에 있는 콘텐츠에 접근할 수 없다는 제약이 있었습니다.** +<<<<<<< HEAD 이런 간단하지만 강력한 규칙은 인터넷 보안을 위한 근간이었습니다. 보안 규칙 덕분에 해커가 만든 웹 사이트 `hacker.com`에서 `gmail.com`에 있는 메일 박스에 접근할 수 없던 것이죠. 사람들은 이런 제약 덕분에 안전하게 인터넷을 사용할 수 있었습니다. +======= +That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access the user's mailbox at website `gmail.com`. People felt safe. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 그런데 이 당시의 자바스크립트는 네트워크 요청을 보낼 수 있을 만한 메서드를 지원하지 않았습니다. 자바스크립트는 웹 페이지를 꾸미기 위한 토이 랭귀지 수준이었죠. @@ -44,7 +48,11 @@ CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들 */!* +<<<<<<< HEAD +======= + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* */!* @@ -97,10 +105,15 @@ CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들 처음 네트워크 요청 메서드가 등장했을 때엔 크로스 오리진 요청이 불가능했습니다. 하지만 긴 논의 끝에 크로스 오리진 요청을 허용하기로 결정합니다. 대신 크로스 오리진 요청은 서버에서 명시적으로 크로스 오리진 요청을 '허가' 했다는 것을 알려주는 특별한 헤더를 전송받았을 때만 가능하도록 제약을 걸게 됩니다. +<<<<<<< HEAD ## 안전한 요청 +======= +## Safe requests +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 크로스 오리진 요청은 크게 두 가지 종류로 구분됩니다. +<<<<<<< HEAD 1. 안전한 요청(safe request) 2. 그 외의 요청(안전한 요청이 아닌 요청) @@ -120,20 +133,55 @@ CORS는 악의를 가진 해커로부터 인터넷을 보호하기 위해 만들 안전한 요청과 그렇지 않은 요청의 근본적인 차이는 **특별한 방법을 사용하지 않고도 ``이나 ` ``` +<<<<<<< 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 직접 숫자를 클릭해보세요. 현재 날짜가 2020년 9월 12일 오후 12시 17분 8초라면, 숫자 8부터 스르륵 이동합니다. [codetabs src="digits-negative-delay"] +<<<<<<< HEAD 사용한 코드는 아래와 같습니다. +======= +JavaScript does it with an extra line: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 베지어 곡선 +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 가장 간단한 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 */ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` @@ -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 */ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` 직접 베지어 곡선을 만드는 것 말고, 내장 곡선을 사용할 수도 있습니다. 내장 곡선엔 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 각 내장 곡선에 대응하는 베지어 곡선은 다음과 같습니다. @@ -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: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```css .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); +<<<<<<< HEAD /* left 속성값(400px)은 자바스크립트에서 설정함 */ +======= + /* click on a train sets left to 450px */ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` @@ -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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 이 예시에선 `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. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### 단계 +<<<<<<< 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 숫자를 사용해 예시를 만들어봅시다. @@ -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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 두 번째 인수는 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 초별로 프로세스가 몇 %씩 진행되는지를 정리하면 다음과 같죠. - `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) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `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(...)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 주석에 설명을 달아놓은 예시를 살펴봅시다. @@ -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

    + +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 요약 +<<<<<<< HEAD CSS 애니메이션을 사용하면 하나 또는 여러 CSS 프로퍼티를 부드럽게(부드럽지 않게도 가능) 변화시킬 수 있습니다. +======= +CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 하지만 좀 더 복잡한 케이스를 다루려면 자바스크립트 애니메이션을 알아야 하므로 다음 챕터에선 이를 다뤄보겠습니다. 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..b930a8595d 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? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 컴포넌트 아키텍처 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: /*