diff --git a/.gitattributes b/.gitattributes index 6313b56c57..d3877a5382 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ * text=auto eol=lf +*.svg binary diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index a83b6b68f4..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ -**目标章节**:例如 1-js/01-getting-started/1-intro - -**当前上游最新 commit**:此处填写本项目英文版 https://github.com/javascript-tutorial/en.javascript.info 的最新 commit,例如 https://github.com/javascript-tutorial/zh.javascript.info/commit/b03ca00a992a73aaf213970e71f74ac1c04def33 - -**本 PR 所做更改如下:** - -文件名 | 参考上游 commit | 更改(理由) --|-|- -article.md | a23882d | 修改部分错误 - -> 注意,参考上游 commit 是指你所修改的文件,在英文仓库中同名文件的对应 commit,即你此次提交的修改的依据。如果本 PR 你只是提交一个文字或者语句优化,并非根据上游英文仓库的修改而提交的更新,则请填无。 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 6f9db68d48..536fac47ae 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -1,120 +1,122 @@ -# JavaScript 简介 +# An Introduction to JavaScript -我们一起来聊一下 JavaScript,用它能做什么,它有哪些特性,以及一些跟它配合使用的技术。 +Let's see what's so special about JavaScript, what we can achieve with it, and what other technologies play well with it. -## 什么是 JavaScript? +## What is JavaScript? -**JavaScript** 最初的目的是为了“**赋予网页生命**”。 +*JavaScript* was initially created to "make web pages alive". -这种编程语言我们称之为**脚本**。它们可以写在 HTML 中,在页面加载的时候会自动执行。 +The programs in this language are called *scripts*. They can be written right in a web page's HTML and run automatically as the page loads. -脚本作为纯文本存在和执行。它们不需要特殊的准备或编译即可运行。 +Scripts are provided and executed as plain text. They don't need special preparation or compilation to run. -这方面,JavaScript 和 [Java](http://en.wikipedia.org/wiki/Java) 有很大的区别。 +In this aspect, JavaScript is very different from another language called [Java](https://en.wikipedia.org/wiki/Java_(programming_language)). -```smart header="为什么叫 JavaScript?" -JavaScript 在刚诞生的时候,它的名字叫 “LiveScript”。但是因为当时 Java 很流行,所以决定将一种新语言定位为 Java 的“弟弟”会有助于它的流行。 +```smart header="Why is it called JavaScript?" +When JavaScript was created, it initially had another name: "LiveScript". But Java was very popular at that time, so it was decided that positioning a new language as a "younger brother" of Java would help. -随着 JavaScript 的发展,它已经变成了一门独立的语言,同时也有了自己的语言规范 [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript)。现在,它和 Java 之间没有任何关系。 +But as it evolved, JavaScript became a fully independent language with its own specification called [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), and now it has no relation to Java at all. ``` -现在,JavaScript 不仅仅是在浏览器内执行,也可以在服务端执行,甚至还能在任意搭载了 [JavaScript 引擎](https://en.wikipedia.org/wiki/JavaScript_engine) 的设备中都可以执行。 +Today, JavaScript can execute not only in the browser, but also on the server, or actually on any device that has a special program called [the JavaScript engine](https://en.wikipedia.org/wiki/JavaScript_engine). -浏览器中嵌入了 JavaScript 引擎,有时也称作 JavaScript 虚拟机。 +The browser has an embedded engine sometimes called a "JavaScript virtual machine". -不同的引擎有不同的“代号”,例如: +Different engines have different "codenames". For example: -- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) —— Chrome 和 Opera 中的 JavaScript 引擎。 -- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) —— Firefox 中的 JavaScript 引擎。 -- ……还有其他一些代号,像“Trident”,“Chakra”用于不同版本的 IE,“ChakraCore”用于 Microsoft Edge,“Nitro”和“SquirrelFish”用于 Safari,等等。 +- [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. -上面这些名称很容易记忆,因为经常出现在网上开发者的文章中。我们也会用到这些名称。例如:某个新的功能,如果“JavaScript 引擎 V8 是支持的”,那么我们可以认为这个功能大概能在 Chrome 和 Opera 中正常运行。 +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. -```smart header="引擎是如何工作的?" +```smart header="How do engines work?" -引擎很复杂,但是基本原理很简单。 +Engines are complicated. But the basics are easy. -1. 引擎(通常嵌入在浏览器中)读取(“解析”)脚本。 -2. 然后将脚本转化(“编译”)为机器语言。 -3. 然后这机器语言代码快速地运行。 +1. The engine (embedded if it's a browser) reads ("parses") the script. +2. Then it converts ("compiles") the script to the machine language. +3. And then the machine code runs, pretty fast. -引擎会对流程中的每个阶段都进行优化。它甚至可以在运行时监视编译的脚本,分析数据流并根据这些进一步优化机器代码。 +The engine applies optimizations at each step of the process. It even watches the compiled script as it runs, analyzes the data that flows through it, and further optimizes the machine code based on that knowledge. ``` -## 浏览器中的 JavaScript 能做什么? +## What can in-browser JavaScript do? -现代的 JavaScript 是一种“安全”语言。它不提供对内存或 CPU 的底层访问,因为它最初是为浏览器创建的,不需要这些功能。 +Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or CPU, because it was initially created for browsers which do not require it. -JavaScript 的能力很大程度上依赖于它执行的环境。例如:[Node.js](https://wikipedia.org/wiki/Node.js) 允许 JavaScript 读写任意文件、执行网络请求等。 +JavaScript's capabilities greatly depend on the environment it's running in. For instance, [Node.js](https://wikipedia.org/wiki/Node.js) supports functions that allow JavaScript to read/write arbitrary files, perform network requests, etc. -浏览器中的 JavaScript 可以做与网页操作、用户交互和 Web 服务器相关的所有事情。 +In-browser JavaScript can do everything related to webpage manipulation, interaction with the user, and the webserver. -例如,浏览器中的 JavaScript 可以完成下面这些事: +For instance, in-browser JavaScript is able to: -- 在网页中插入新的 HTML,修改现有的网页内容和网页的样式。 -- 响应用户的行为,响应鼠标的点击或移动、键盘的敲击。 -- 向远程服务器发送网络请求,下载或上传文件(所谓 [AJAX](https://en.wikipedia.org/wiki/Ajax_(programming)) 和 [COMET](https://en.wikipedia.org/wiki/Comet_(programming)) 技术)。 -- 获取或修改 cookie,向访问者提出问题、发送消息。 -- 记住客户端的数据(本地存储)。 +- Add new HTML to the page, change the existing content, modify styles. +- React to user actions, run on mouse clicks, pointer movements, key presses. +- Send requests over the network to remote servers, download and upload files (so-called [AJAX](https://en.wikipedia.org/wiki/Ajax_(programming)) and [COMET](https://en.wikipedia.org/wiki/Comet_(programming)) technologies). +- Get and set cookies, ask questions to the visitor, show messages. +- Remember the data on the client-side ("local storage"). -## 浏览器中的 JavaScript 不能做什么? +## What CAN'T in-browser JavaScript do? -为了用户的(信息)安全,在浏览器中的 JavaScript 的能力是有限的。这样主要是为了阻止邪恶的网站获得或修改用户的私人数据。 +JavaScript's abilities in the browser are limited for the sake of a user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. -这些限制的例子有: +Examples of such restrictions include: -- 网页中的 JavaScript 不能读、写、复制及执行用户磁盘上的文件或程序。它没有直接访问操作系统的功能。 +- JavaScript on a webpage may not read/write arbitrary files on the hard disk, copy them or execute programs. It has no direct access to OS functions. - 现代浏览器允许 JavaScript 做一些文件相关的操作,但是这个操作是受到限制的。仅当用户使用某个特定的动作,JavaScript 才能操作这个文件。例如,把文件“拖”到浏览器中,或者通过 `` 标签选择文件。 + Modern browsers allow it to work with files, but the access is limited and only provided if the user does certain actions, like "dropping" a file into a browser window or selecting it via an `` tag. - JavaScript 有很多方式和照相机/麦克风或者其他设备进行交互,但是这些都需要提前获得用户的授权许可。所以,启用了 JavaScript 的网页应该不会偷偷地启动网络摄像头观察你,并把你的信息发送到[美国国家安全局](https://en.wikipedia.org/wiki/National_Security_Agency)。 -- 不同的浏览器标签页之间基本彼此不相关。有时候,也会有一些关系。例如,一个标签页通过 JavaScript 打开另外一个新的标签页。但即使在这种情况下,如果两个标签页打开的不是同一个网站(域名、协议或者端口任一不相同的网站),他们都不能够相互通信。 + There are ways to interact with 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 if they come from different sites (from a different domain, protocol or port). - 这就是“同源策略”。为了解决“同源策略”问题,两个标签页必须**都**包含一些处理这个问题的特殊的 JavaScript 代码,并均允许数据交换,这样才能够实现两个同源标签页的数据交换。本教程会讲到这部分相关的知识。 + This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and contain a special JavaScript code that handles it. We'll cover that in the tutorial. - 这个限制也是为了用户的信息安全。例如,用户打开的 `http://anysite.com` 网页的 JavaScript 肯定不能访问 `http://gmail.com`(另外一个标签页打开的网页)也不能从那里窃取信息。 -- JavaScript 通过互联网可以轻松地和当前网页域名的服务器进行通讯。但是从其他网站/域名的服务器中获取数据的能力是受限的。尽管这可以实现,但是需要来自远程服务器的明确协议(在 HTTP 头中)。这也是为了用户的数据安全。 + 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` 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) -浏览器环境外的 JavaScript 一般没有这些限制。例如服务端的 JavaScript 就没有这些限制。现代浏览器还允许安装可能会要求扩展权限的插件或扩展。 +Such limits do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugin/extensions which may ask for extended permissions. -## 是什么使得 JavaScript 与众不同? +## What makes JavaScript unique? -至少有 **3** 件事值得一提: +There are at least *three* great things about JavaScript: ```compare -+ 和 HTML/CSS 完全的集成。 -+ 使用简单的工具完成简单的任务。 -+ 被所有的主流浏览器支持,并且默认开启。 ++ Full integration with HTML/CSS. ++ Simple things are done simply. ++ Supported by all major browsers and enabled by default. ``` -满足这三条的浏览器技术也只有 JavaScript 了。 +JavaScript is the only browser technology that combines these three things. -这就是为什么 JavaScript 与众不同!这也是为什么它是创建浏览器界面的最普遍的工具。 +That's what makes JavaScript unique. That's why it's the most widespread tool for creating browser interfaces. -此外,JavaScript 还支持创建服务器,移动端应用程序等。 +That said, JavaScript also allows to create servers, mobile applications, etc. -## 比 JavaScript “更好”的语言 +## Languages "over" JavaScript -不同的人喜欢不同的功能,JavaScript 的语法也不能够满足所有人的需求。 +The syntax of JavaScript does not suit everyone's needs. Different people want different features. -这是正常的,因为每个人的项目和需求都不一样。 +That's to be expected, because projects and requirements are different for everyone. -所以,最近出现了很多不同的语言,这些语言在浏览器中执行之前,都会被**编译**(转化)成 JavaScript。 +So recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser. -现代化的工具使得编译速度非常快速且透明,实际上允许开发人员使用另一种语言编写代码并将其自动转换为 JavaScript。 +Modern tools make the transpilation very fast and transparent, actually allowing developers to code in another language and auto-converting it "under the hood". -这些编程语言的例子有: +Examples of such languages: -- [CoffeeScript](http://coffeescript.org/) 是 JavaScript 的语法糖,它语法简短,明确简洁。通常使用 Ruby 的人喜欢用。 -- [TypeScript](http://www.typescriptlang.org/) 将注意力集中在增加严格的数据类型。这样就能简化开发,也能用于开发复杂的系统。TypeScript 是微软开发的。 -- [Flow](http://flow.org/) 也添加了数据类型,但是以一种不同的方式。由 Facebook 开发。 -- [Dart](https://www.dartlang.org/) 是一门独立的语言。它拥有自己的引擎用于在非浏览器环境中运行(如:手机应用),它也能被编译成 JavaScript 。由 Google 开发。 +- [CoffeeScript](https://coffeescript.org/) is a "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. -还有很多其他的语言。当然,即使我们在使用这些语言,我们也需要知道 JavaScript。因为学习 JavaScript 可以让我们真正明白我们自己在做什么。 +There are more. Of course, even if we use one of transpiled languages, we should also know JavaScript to really understand what we're doing. -## 总结 +## Summary -- JavaScript 最开始是为浏览器设计的一门语言,但是现在也被用于很多其他的环境。 -- 现在,JavaScript 是一门在浏览器中使用最广、并且能够很好集成 HTML/CSS 的语言。 -- 有很多其他的语言可以被编译成 JavaScript,这些语言还提供了更多的功能。最好还是了解一下这些语言,至少在掌握了 JavaScript 之后简单地看一下。 +- 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. diff --git a/1-js/01-getting-started/1-intro/limitations.svg b/1-js/01-getting-started/1-intro/limitations.svg index 1239b0196d..76ea43fd7a 100644 --- a/1-js/01-getting-started/1-intro/limitations.svg +++ b/1-js/01-getting-started/1-intro/limitations.svg @@ -1 +1 @@ -https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info +https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file 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 9e1953265d..3fa2433363 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -1,41 +1,37 @@ -# 手册与规范 +# Manuals and specifications -这本书是一个**教程**。它旨在帮助你逐渐掌握 JavaScript 这门语言。但是一旦你已经熟悉了这门语言的基础知识,你就会需要其他资料。 +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. -## 规范 +## Specification -**ECMA-262 规范**包含了大部分深入的、详细的、规范化的关于 JavaScript 的信息。这份规范明确地定义了这门语言。 +[The ECMA-262 specification](https://www.ecma-international.org/publications/standards/Ecma-262.htm) contains the most in-depth, detailed and formalized information about JavaScript. It defines the language. -但正因其规范化,对于新手来说难以理解。所以如果你需要知道关于这门语言细节最权威的信息来源,这份规范就很适合你(去阅读)。但是它并不适合日常使用。 +But being that formalized, it's difficult to understand at first. So if you need the most trustworthy source of information about the language details, the specification is the right place. But it's not for everyday use. -最新的规范草案在此 。 +A new specification version is released every year. Between these releases, the latest specification draft is at . -想要知道最新最前沿且将要“标准化”的功能,请看这里的提案 。 +To read about new bleeding-edge features, including those that are "almost standard" (so-called "stage 3"), see proposals at . -当然,如果你正在做浏览器相关的开发工作,那么本教程的 [第二节](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. -## 手册 +## Manuals -- **MDN(Mozilla)JavaScript 索引**是一本带有用例和其他信息的手册。它是一个获取关于个别语言函数、方法等深入信息的很好的来源。 +- **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 . - 虽然,利用互联网搜索通常是最好的选择。只需在查询时输入“MDN [关键字]”,例如 搜索 `parseInt` 函数。 +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. -- **MSDN** —— 一本微软的手册,它包含大量的信息,包括 JavaScript(在里面经常被写成 JScript)。如果有人需要关于 Internet Explorer 的规范细节,最好去看:。 +## Compatibility tables - 我们还可以在使用互联网搜索时使用如 “RegExp MSDN” 或 “RegExp MSDN jscript” 这样的词条。 +JavaScript is a developing language, new features get added regularly. -## 兼容性表 +To see their support among browser-based and other engines, see: -JavaScript 还是一门还在发展中的语言,经常会添加一些新的功能。 +- - 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. -- —— 每个功能都列有一个支持信息表格,例如想看哪个引擎支持现代加密(cryptography)函数:。 -- —— 一份列有语言功能以及引擎是否支持这些功能的表格。 - -所有这些资源在实际开发中都有用武之地,因为他们包含了语言细节以及它们被支持的程度等非常有价值的信息。 - -为了不要让你在真正需要深入了解特定功能的时候捉襟见肘,请记住它们(或者这一页)。 +Please remember them (or this page) for the cases when you need in-depth information about a particular feature. 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 4ff15a45bf..5a86f2a7f1 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -1,46 +1,44 @@ -# 代码编辑器 +# Code editors -程序员接触时间最长的就是代码编辑器。 +A code editor is the place where programmers spend most of their time. -代码编辑器主要分两种:IDE(集成开发环境)和轻量编辑器。很多人喜欢这两种各选一个。 +There are two main types of code editors: IDEs and lightweight editors. Many people use one tool of each type. ## IDE -[IDE](https://en.wikipedia.org/wiki/Integrated_development_environment)(集成开发环境)是用于管理整个项目具有强大功能的编辑器。顾名思义,它不仅仅是一个编辑器,而且还是个完整的开发环境。 +The term [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) refers to a powerful editor with many features that usually operates on a "whole project." As the name suggests, it's not just an editor, but a full-scale "development environment." -IDE 加载项目(通常包含很多文件),并且允许在不同文件之间切换。IDE 还提供基于整个项目(不仅仅是打开的文件)的自动补全功能,集成版本控制(如 [git](https://git-scm.com/))、集成测试环境等一些其他“项目层面”的东西。 +An IDE loads the project (which can be many files), allows navigation between files, provides autocompletion based on the whole project (not just the open file), and integrates with a version management system (like [git](https://git-scm.com/)), a testing environment, and other "project-level" stuff. -如果你还没考虑好选哪一款 IDE,可以考虑下面两个: +If you haven't selected an IDE yet, consider the following options: -- [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). -对于 Windows 系统来说,也有个叫 “Visual Studio” 的 IDE,请不要跟 “Visual Studio Code” 混淆。“Visual Studio” 是一个收费的、强大的 Windows 专用编辑器,它十分适合于 .NET 开发。用它进行 JavaScript 开发也不错。“Visual Studio” 有个免费的版本 [Visual Studio Community](https://www.visualstudio.com/vs/community/)。 +For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code". "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. It's also good at JavaScript. There's also a free version [Visual Studio Community](https://www.visualstudio.com/vs/community/). -大多数 IDE 是收费的,但是他们都可以试用。购买 IDE 的费用对于一名合格的程序员的薪水来说,肯定算不了什么,所以去选一个对你来说最好的吧。 +Many IDEs are paid, but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you. -## 轻量编辑器 +## Lightweight editors -“轻量编辑器”没有 IDE 功能那么强大,但是他们一般很快、优雅而且简单。 +"Lightweight editors" are not as powerful as IDEs, but they're fast, elegant and simple. -“轻量编辑器”主要用于立即打开编辑一个文件。 +They are mainly used to open and edit a file instantly. -“轻量编辑器”和 IDE 最大的区别是,IDE 一般在项目中使用,这也就意味着在开启的时候要加载很多数据,如果需要的话,在使用的过程中还会分析项目的结构等。如果我们只需要编辑一个文件,那么“轻量编辑器”会更快。 +The main difference between a "lightweight editor" and an "IDE" is that an IDE works on a project-level, so it loads much more data on start, analyzes the project structure if needed and so on. A lightweight editor is much faster if we need only one file. -实际上,“轻量编辑器”一般都有各种各样的插件,这些插件可以做目录级(directory-level)的语法分析和补全代码。所以“轻量编辑器”和 IDE 也没有严格的界限。 +In practice, lightweight editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict border between a lightweight editor and an IDE. -下面是一些值得你关注的“轻量编辑器”: +The following options deserve your attention: -- [Atom](https://atom.io/)(跨平台,免费)。 -- [Visual Studio Code](https://code.visualstudio.com/)(跨平台,免费)。 -- [Sublime Text](http://www.sublimetext.com)(跨平台,共享软件)。 -- [Notepad++](https://notepad-plus-plus.org/)(Windows,免费)。 -- [Vim](http://www.vim.org/) 和 [Emacs](https://www.gnu.org/software/emacs/) 很棒,前提是你知道怎么用。 +- [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). +- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. -## 不要争吵 +## Let's not argue -上面列表中的编辑器都是我和我的朋友(他们都是我认为很优秀的开发人员)已经使用了很长时间并且很满意的。 +The editors in the lists above are those that either I or my friends whom I consider good developers have been using for a long time and are happy with. -世上还有很多其他很好的编辑器,你可以选择一个你最喜欢的。 +There are other great editors in our big world. Please choose the one you like the most. -选择编辑器就像选择其他工具一样。要看你的项目,以及个人的习惯和喜好。 +The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index f3b1d6fef6..50926d4f76 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -1,63 +1,63 @@ -# 开发者控制台 +# Developer console -代码是很容易出现错误的。你也很可能犯错误...... 哦,我在说什么?只要你是人,你一定会犯错误(在写代码的时候),除非你是[机器人](https://en.wikipedia.org/wiki/Bender_(Futurama))。 +Code is prone to errors. You will quite likely make errors... Oh, what am I talking about? You are *absolutely* going to make errors, at least if you're a human, not a [robot](https://en.wikipedia.org/wiki/Bender_(Futurama)). -但在浏览器中,默认情况下用户是看不到错误的。所以,如果脚本中有错误,我们看不到是什么错误,更不能够修复它。 +But in the browser, users don't see errors by default. So, if something goes wrong in the script, we won't see what's broken and can't fix it. -为了发现错误并获得一些与脚本相关且有用的信息,浏览器内置了“开发者工具”。 +To see errors and get a lot of other useful information about scripts, "developer tools" have been embedded in browsers. -通常,开发者倾向于使用 Chrome 或 Firefox 进行开发,因为它们有最好的开发者工具。一些其他的浏览器也提供开发者工具,有时还具有一些特殊的功能,通常它们都是在追赶 Chrome 或 Firefox。所以大多数人都有“最喜欢”的浏览器,当遇到某个浏览器独有的问题的时候,人们就会切换到其他的浏览器。 +Most developers lean towards Chrome or Firefox for development because those browsers have the best developer tools. Other browsers also provide developer tools, sometimes with special features, but are usually playing "catch-up" to Chrome or Firefox. So most developers have a "favorite" browser and switch to others if a problem is browser-specific. -开发者工具很强大,功能丰富。首先,我们将学习如何打开它们,查找错误和运行 JavaScript 命令。 +Developer tools are potent; they have many features. To start, we'll learn how to open them, look at errors, and run JavaScript commands. ## Google Chrome -打开网页 [bug.html](bug.html)。 +Open the page [bug.html](bug.html). -在 JavaScript 代码中有一个错误。一般的访问者看不到这个错误,所以让我们打开开发者工具看看吧。 +There's an error in the JavaScript code on it. It's hidden from a regular visitor's eyes, so let's open developer tools to see it. -按下 `key:F12` 键,如果你使用 Mac,试试 `key:Cmd+Opt+J`。 +Press `key:F12` or, if you're on Mac, then `key:Cmd+Opt+J`. -开发者工具会被打开,Console 标签页是默认的标签页。 +The developer tools will open on the Console tab by default. -就像这样: +It looks somewhat like this: ![chrome](chrome.png) -具体什么样,要看你的 Chrome 版本。它随着时间一直在变,但是都很类似。 +The exact look of developer tools depends on your version of Chrome. It changes from time to time but should be similar. -- 在这我们能看到红色的错误提示信息。这个场景中,脚本里有一个未知的 “lalala” 命令。 -- 在右边,有个可点击的链接 `bug.html:12`。这个链接会链接到错误发生的行号。 +- Here we can see the red-colored error message. In this case, the script contains an unknown "lalala" command. +- On the right, there is a clickable link to the source `bug.html:12` with the line number where the error has occurred. -在错误信息的下方,有个 `>` 标志。它代表“命令行”,在“命令行”中,我们可以输入 JavaScript 命令,按下 `key:Enter` 来执行。 +Below the error message, there is a blue `>` symbol. It marks a "command line" where we can type JavaScript commands. Press `key:Enter` to run them. -现在,我们能看到错误就够了。稍后,在 章节中,我们会重新更加深入地讨论开发者工具。 +Now we can see errors, and that's enough for a start. We'll come back to developer tools later and cover debugging more in-depth in the chapter . -```smart header="多行输入" -通常,当我们向控制台输入一行代码后,按 `key:Enter`,这行代码就会立即执行。 +```smart header="Multi-line input" +Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. -如果想要插入多行代码,请按 `key:Shift+Enter` 来进行换行。这样就可以输入长片段的 JavaScript 代码了。 +To insert multiple lines, press `key:Shift+Enter`. This way one can enter long fragments of JavaScript code. ``` -## Firefox、Edge 和其他浏览器 +## Firefox, Edge, and others -大多数其他的浏览器都是通过 `key:F12` 来打开开发者工具。 +Most other browsers use `key:F12` to open developer tools. -他们的外观和感觉都非常相似,一旦你学会了他们中的一个(可以先尝试 Chrome),其他的也就很快了。 +The look & feel of them is quite similar. Once you know how to use one of these tools (you can start with Chrome), you can easily switch to another. ## Safari -Safari(Mac 系统中的浏览器,Windows 和 Linux 系统不支持)有一点点不同。我们需要先开启“开发菜单”。 +Safari (Mac browser, not supported by Windows/Linux) is a little bit special here. We need to enable the "Develop menu" first. -打开“偏好设置”,选择“高级”选项。选中最下方的那个选择框。 +Open Preferences and go to the "Advanced" pane. There's a checkbox at the bottom: ![safari](safari.png) -现在,我们通过 `key:Cmd+Opt+C` 就能打开或关闭控制台了。另外注意,有一个名字为“开发”的顶部菜单出现了。它有很多命令和选项。 +Now `key:Cmd+Opt+C` can toggle the console. Also, note that the new top menu item named "Develop" has appeared. It has many commands and options. -## 总结 +## Summary -* 开发者工具允许我们查看错误、执行命令、检查变量等等。 -* 在 Windows 系统中,可以通过 `key:F12` 开启开发者工具。Mac 系统下,Chrome 需要使用 `key:Cmd+Opt+J`,Safari 使用 `key:Cmd+Opt+C`(需要提前开启)。 +- Developer tools allow us to see errors, run commands, examine variables, and much more. +- They can be opened with `key:F12` for most browsers on Windows. Chrome for Mac needs `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (need to enable first). -现在我们的环境准备好了。下一章,我们将正式开始学习 JavaScript。 +Now we have the environment ready. In the next section, we'll get down to JavaScript. diff --git a/1-js/01-getting-started/4-devtools/chrome.png b/1-js/01-getting-started/4-devtools/chrome.png index a506389072..4cb3ea2f46 100644 Binary files a/1-js/01-getting-started/4-devtools/chrome.png and b/1-js/01-getting-started/4-devtools/chrome.png differ diff --git a/1-js/01-getting-started/index.md b/1-js/01-getting-started/index.md index 14d9223ce1..b327c78603 100644 --- a/1-js/01-getting-started/index.md +++ b/1-js/01-getting-started/index.md @@ -1,3 +1,3 @@ -# 简介 +# An introduction -介绍 JavaScript 语言及其开发环境。 +About the JavaScript language and the environment to develop with it. diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html new file mode 100644 index 0000000000..ff1d871b08 --- /dev/null +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md index e69de29bb2..77b3abbb53 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md @@ -0,0 +1,2 @@ + +[html src="index.html"] diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md b/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md index 07cbf248f1..afed6a91d3 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md @@ -2,10 +2,11 @@ importance: 5 --- -# 显示一个提示语 +# Show an alert -创建一个页面,然后显示一个消息 “I'm JavaScript!”。 +Create a page that shows a message "I'm JavaScript!". -在沙箱中或者在你的硬盘上做这件事都无所谓,只要确保它能运行起来。 +Do it in a sandbox, or on your hard drive, doesn't matter, just ensure that it works. [demo src="solution"] + diff --git a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md index 48fb62cc22..f42c41e6db 100644 --- a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md +++ b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md @@ -1,8 +1,8 @@ -HTML 代码: +The HTML code: [html src="index.html"] -同一个文件夹中的 `alert.js` 文件: +For the file `alert.js` in the same folder: [js src="alert.js"] diff --git a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md index 38c7deccec..26168d6a76 100644 --- a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md +++ b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# 使用外部的脚本显示一个提示语 +# Show an alert with an external script -打开前一个任务 的答案。将脚本的内容提取到一个外部的 `alert.js` 文件中,放置在相同的文件夹中。 +Take the solution of the previous task . Modify it by extracting the script content into an external file `alert.js`, residing in the same folder. -打开页面,确保它能够工作。 +Open the page, ensure that the alert works. 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 271ede28ba..35f82bf5d7 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -1,17 +1,17 @@ # Hello, world! -本教程的这一部分内容是关于 JavaScript 语言本身的。 +This part of the tutorial is about core JavaScript, the language itself. -但是,我们需要一个工作环境来运行我们的脚本,由于本教程是在线的,所以浏览器是一个不错的选择。我们会尽可能少地使用浏览器特定的命令(比如 `alert`),所以如果你打算专注于另一个环境(比如 Node.js),你就不必多花时间来关心这些特定指令了。我们将在本教程的 [下一部分](/ui) 中专注于浏览器中的 JavaScript。 +But we need a working environment to run our scripts and, since this book is online, the browser is a good choice. We'll keep the amount of browser-specific commands (like `alert`) to a minimum so that you don't spend time on them if you plan to concentrate on another environment (like Node.js). We'll focus on JavaScript in the browser in the [next part](/ui) of the tutorial. -首先,让我们看看如何将脚本添加到网页上。对于服务器端环境(如 Node.js),你只需要使用诸如 `"node my.js"` 的命令行来执行它。 +So first, let's see how we attach a script to a webpage. For server-side environments (like Node.js), you can execute the script with a command like `"node my.js"`. -## “script” 标签 +## The "script" tag -JavaScript 程序可以在 ` */!* -

...script 标签之后

+

...After the script.

@@ -35,24 +35,24 @@ JavaScript 程序可以在 ` ``` - 现代 JavaScript 中已经不这样使用了。这些注释是用于不支持 ` ``` -这里,`/path/to/script.js` 是脚本文件从站点根目录开始的绝对路径。当然也可以提供当前页面的相对路径。例如,`src ="script.js"` 表示当前文件夹中的 `"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. -我们也可以提供一个完整的 URL 地址,例如: +We can give a full URL as well. For instance: ```html - + ``` -要附加多个脚本,请使用多个标签: +To attach several scripts, use multiple tags: ```html @@ -90,29 +90,29 @@ JavaScript 程序可以在 ` ``` -我们必须进行选择,要么使用外部的 ` @@ -122,11 +122,11 @@ JavaScript 程序可以在 `` 的方式插入。 +- We can use a ``. -有关浏览器脚本以及它们和网页的关系,还有很多可学的。但是请记住,教程的这部分主要是针对 JavaScript 语言本身的,所以我们不该被浏览器特定的实现分散自己的注意力。我们将使用浏览器作为运行 JavaScript 的一种方式,这种方式非常便于我们在线阅读,但这只是很多种方式中的一种。 +There is much more to learn about browser scripts and their interaction with the webpage. But let's keep in mind that this part of the tutorial is devoted to the JavaScript language, so we shouldn't distract ourselves with browser-specific implementations of it. We'll be using the browser as a way to run JavaScript, which is very convenient for online reading, but only one of many. diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index a3c1321fa6..e81fd343df 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -1,44 +1,44 @@ -# 代码结构 +# Code structure -我们将要学习的第一个内容就是构建代码块。 +The first thing we'll study is the building blocks of code. -## 语句 +## Statements -语句是执行操作的语法结构和命令。 +Statements are syntax constructs and commands that perform actions. -我们已经见过了 `alert('Hello, world!')` 这样可以用来显示消息的语句。 +We've already seen a statement, `alert('Hello, world!')`, which shows the message "Hello, world!". -我们可以在代码中编写任意数量的语句。语句之间可以使用分号进行分割。 +We can have as many statements in our code as we want. Statements can be separated with a semicolon. -例如,我们将 "Hello World" 这条信息一分为二: +For example, here we split "Hello World" into two alerts: ```js run no-beautify alert('Hello'); alert('World'); ``` -通常,每条语句独占一行,以提高代码的可读性: +Usually, statements are written on separate lines to make the code more readable: ```js run no-beautify alert('Hello'); alert('World'); ``` -## 分号 [#semicolon] +## Semicolons [#semicolon] -当存在分行符(line break)时,在大多数情况下可以省略分号。 +A semicolon may be omitted in most cases when a line break exists. -下面的代码也是可以运行的: +This would also work: ```js run no-beautify alert('Hello') alert('World') ``` -在这,JavaScript 将分行符理解成“隐式”的分号。这也被称为 [自动分号插入](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion)。 +Here, JavaScript interprets the line break as an "implicit" semicolon. This is called an [automatic semicolon insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion). -**在大多数情况下,换行意味着一个分号。但是“大多数情况”并不意味着“总是”!** +**In most cases, a newline implies a semicolon. But "in most cases" does not mean "always"!** -有很多换行并不是分号的例子,例如: +There are cases when a newline does not mean a semicolon. For example: ```js run no-beautify alert(3 + @@ -46,114 +46,110 @@ alert(3 + + 2); ``` -代码输出 `6`,因为 JavaScript 并没有在这里插入分号。显而易见的是,如果一行以加号 `"+"` 结尾,那么这是一个“不完整的表达式”,不需要分号。所以,这个例子得到了预期的结果。 +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. -**但存在 JavaScript 无法确定是否真的需要自动插入分号的情况。** +**But there are situations where JavaScript "fails" to assume a semicolon where it is really needed.** -这种情况下发生的错误是很难被找到和解决的。 +Errors which occur in such cases are quite hard to find and fix. -````smart header="一个错误的例子" -如果你好奇地想知道一个这种错误的具体例子,那你可以看看下面这段代码: +````smart header="An example of an error" +If you're curious to see a concrete example of such an error, check this code out: ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` -你不需要考虑方括号 `[]` 和 `forEach` 的含义,现在它们并不重要,之后我们会学习它们。让我们先记住这段代码的运行结果:先显示 `1`,然后显示 `2`。 +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`. -现在我们在代码前面插入一个 `alert` 语句,并且不加分号: +Now let's remove the semicolon after the `alert`: ```js run no-beautify -alert("There will be an error") +alert("Hello") -[1, 2].forEach(alert) +[1, 2].forEach(alert); ``` -现在,如果我们运行代码,只有第一个 `alert` 语句的内容被显示了出来,随后我们收到了一个错误! - -但是,如果我们在第一个 `alert` 语句末尾加上一个分号,就工作正常了: -```js run -alert("All fine now"); +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. -[1, 2].forEach(alert) -``` +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. -现在,我们能得到 "All fine now",然后是 `1` 和 `2`。 +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. -出现无分号变量(variant)的错误,是因为 JavaScript 并不会在方括号 `[...]` 前添加一个隐式的分号。 - -所以,因为没有自动插入分号,第一个例子中的代码被视为了一条简单的语句,我们从引擎看到的是这样的: +Here's how the engine sees it: ```js run no-beautify -alert("There will be an error")[1, 2].forEach(alert) +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. +```` -## 注释 [#code-comments] +We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them. -随着时间推移,程序变得越来越复杂。为代码添加 **注释** 来描述它做了什么和为什么要这样做,变得非常有必要了。 +## Comments [#code-comments] -你可以在脚本的任何地方添加注释,它们并不会影响代码的执行,因为引擎会直接忽略它们。 +As time goes on, programs become more and more complex. It becomes necessary to add *comments* which describe what the code does and why. -**单行注释以两个正斜杠字符 `//` 开始。** +Comments can be put into any place of a script. They don't affect its execution because the engine simply ignores them. -这一行的剩余部分是注释。它可能独占一行或者跟随在一条语句的后面。 +**One-line comments start with two forward slash characters `//`.** -就像这样: +The rest of the line is a comment. It may occupy a full line of its own or follow a statement. +Like here: ```js run -// 这行注释独占一行 +// This comment occupies a line of its own alert('Hello'); -alert('World'); // 这行注释跟随在语句后面 +alert('World'); // This comment follows the statement ``` -**多行注释以一个正斜杠和星号开始 "/*" 并以一个星号和正斜杆结束 "*/"。** +**Multiline comments start with a forward slash and an asterisk /* and end with an asterisk and a forward slash */.** -就像这样: +Like this: ```js run -/* 两个消息的例子。 -这是一个多行注释。 +/* An example with two messages. +This is a multiline comment. */ alert('Hello'); alert('World'); ``` -注释的内容被忽略了,所以如果我们在 /* ... */ 中放入代码,并不会执行。 +The content of comments is ignored, so if we put code inside /* ... */, it won't execute. -有时候,可以很方便地临时禁用代码: +Sometimes it can be handy to temporarily disable a part of code: ```js run -/* 注释代码 +/* Commenting out the code alert('Hello'); */ alert('World'); ``` -```smart header="使用热键!" -在大多数的编辑器中,一行代码可以使用 `key:Ctrl+/` 热键进行单行注释,诸如 `key:Ctrl+Shift+/` 的热键可以进行多行注释(选择代码,然后按下热键)。对于 Mac 电脑,应使用 `key:Cmd` 而不是 `key:Ctrl`,使用 `key:Option` 而不是 `key:Shift`。 +```smart header="Use hotkeys!" +In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl` and `key:Option` instead of `key:Shift`. ``` -````warn header="不支持注释嵌套!" -不要在 `/*...*/` 内嵌套另一个 `/*...*/`。 +````warn header="Nested comments are not supported!" +There may not be `/*...*/` inside another `/*...*/`. -下面这段代码报错而无法执行: +Such code will die with an error: ```js run no-beautify /* - /* 嵌套注释 ?!? */ + /* nested comment ?!? */ */ alert( 'World' ); ``` ```` -对你的代码进行注释,这还有什么可犹豫的! +Please, don't hesitate to comment your code. -注释会增加代码总量,但这一点也不是什么问题。有很多工具可以帮你在把代码部署到服务器之前缩减代码。这些工具会移除注释,这样注释就不会出现在发布的脚本中。所以,注释对我们的生产没有任何负面影响。 +Comments increase the overall code footprint, but that's not a problem at all. There are many tools which minify code before publishing to a production server. They remove comments, so they don't appear in the working scripts. Therefore, comments do not have negative effects on production at all. -在后面的教程中,会有一章 的内容解释如何更好地写注释。 +Later in the tutorial there will be a chapter that also explains how to write better comments. diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index b45d8ac468..9586733cc8 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -1,86 +1,89 @@ -# 现代模式,"use strict" +# The modern mode, "use strict" -长久以来,JavaScript 不断向前发展且并未带来任何兼容性问题。新的特性被加入,旧的功能也没有改变。 +For a long time, JavaScript evolved without compatibility issues. New features were added to the language while old functionality didn't change. -这么做有利于兼容旧代码,但缺点是 JavaScript 创造者的任何错误或不完善的决定也将永远被保留在 JavaScript 语言中。 +That had the benefit of never breaking existing code. But the downside was that any mistake or an imperfect decision made by JavaScript's creators got stuck in the language forever. -这种情况一直持续到 2009 年 ECMAScript 5 (ES5) 的出现。ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。为了保证旧的功能能够使用,大部分的修改是默认不生效的。你需要一个特殊的指令 —— `"use strict"` 来明确地激活这些特性。 +This was the case until 2009 when ECMAScript 5 (ES5) appeared. It added new features to the language and modified some of the existing ones. To keep the old code working, most such modifications are off by default. You need to explicitly enable them with a special directive: `"use strict"`. ## "use strict" -这个指令看上去像一个字符串 `"use strict"` 或者 `'use strict'`。当它处于脚本文件的顶部时,则整个脚本文件都将以“现代”模式进行工作。 +The directive looks like a string: `"use strict"` or `'use strict'`. When it is located at the top of a script, the whole script works the "modern" way. -比如: +For example: ```js "use strict"; -// 代码以现代模式工作 +// this code works the modern way ... ``` -稍后我们才会学习函数(一种组合命令的方式)。 +Quite soon we're going to learn functions (a way to group commands), so let's note in advance that `"use strict"` can be put at the beginning of a function. Doing that enables strict mode in that function only. But usually people use it for the whole script. -但我们可以提前了解一下,`"use strict"` 可以被放在函数主体的开头,而不是整个脚本的开头。这样则可以只在该函数中启用严格模式。但通常人们会在整个脚本中启用严格模式。 +````warn header="Ensure that \"use strict\" is at the top" +Please make sure that `"use strict"` is at the top of your scripts, otherwise strict mode may not be enabled. -````warn header="确保 \"use strict\" 出现在最顶部" -请确保 `"use strict"` 出现在脚本的最顶部,否则严格模式可能无法启用。 - -这里的严格模式就没有被启用: +Strict mode isn't enabled here: ```js no-strict alert("some code"); -// 下面的 "use strict" 会被忽略,必须在最顶部。 +// "use strict" below is ignored--it must be at the top "use strict"; -// 严格模式没有被激活 +// strict mode is not activated ``` -只有注释可以出现在 `"use strict"` 的上面。 +Only comments may appear above `"use strict"`. ```` -```warn header="没有办法取消 `use strict`" -没有类似于 `"no use strict"` 这样的指令可以使程序返回默认模式。 +```warn header="There's no way to cancel `use strict`" +There is no directive like `"no use strict"` that reverts the engine to old behavior. -一旦进入了严格模式,就没有回头路了。 +Once we enter strict mode, there's no going back. ``` -## 浏览器控制台 +## Browser console + +When you use a [developer console](info:devtools) to run code, please note that it doesn't `use strict` by default. -以后,当你使用浏览器控制台去测试功能时,请注意 `use strict` 默认不会被启动。 +Sometimes, when `use strict` makes a difference, you'll get incorrect results. -有时,使用 `use strict` 会产生一些不一样的影响,你会得到错误的结果。 +So, how to actually `use strict` in the console? -你可以试试按下 `key:Shift+Enter` 去输入多行代码,然后将 `use strict` 置顶,就像这样: +First, you can try to press `key:Shift+Enter` to input multiple lines, and put `use strict` on top, like this: ```js -'use strict'; -// ...你的代码 -<按下 Enter 以运行> +'use strict'; +// ...your code + ``` -它在大部分浏览器中都有效,像 Firefox 和 Chrome。 +It works in most browsers, namely Firefox and Chrome. -如果依然不行,那确保 `use strict` 被开启的最可靠的方法是,像这样将代码输入到控制台: +If it doesn't, e.g. in an old browser, there's an ugly, but reliable way to ensure `use strict`. Put it inside this kind of wrapper: ```js (function() { 'use strict'; - // ...你的代码... + // ...your code here... })() ``` -## 总是使用 "use strict" +## Should we "use strict"? + +The question may sound obvious, but it's not so. + +One could recommend to start scripts with `"use strict"`... But you know what's cool? + +Modern JavaScript supports "classes" and "modules" - advanced language structures (we'll surely get to them), that enable `use strict` automatically. So we don't need to add the `"use strict"` directive, if we use them. -我们还没说到使用 `"use strict"` 与“默认”模式的区别。 +**So, for now `"use strict";` is a welcome guest at the top of your scripts. Later, when your code is all in classes and modules, you may omit it.** -在接下来的章节中,当我们学习语言功能时,我们会标注严格模式与默认模式的差异。幸运的是,差异其实没有那么多。并且这些差异可以让我们更好地编程。 +As of now, we've got to know about `use strict` in general. -当前,一般来说了解这些就够了: +In the next chapters, as we learn language features, we'll see the differences between the strict and old modes. Luckily, there aren't many and they actually make our lives better. -1. `"use strict"` 指令将浏览器引擎转换为“现代”模式,改变一些内建特性的行为。我们会在之后的学习中了解这些细节。 -2. 严格模式通过将 `"use strict"` 放置在整个脚本或函数的顶部来启用。一些新语言特性诸如 "classes" 和 "modules" 也会自动开启严格模式。 -3. 所有的现代浏览器都支持严格模式。 -4. 我们建议始终使用 `"use strict"` 启动脚本。本教程的所有例子都默认采用严格模式,除非特别指定(非常少)。 +All examples in this tutorial assume strict mode unless (very rarely) specified otherwise. diff --git a/1-js/02-first-steps/04-variables/1-hello-variables/solution.md b/1-js/02-first-steps/04-variables/1-hello-variables/solution.md index 896f6ccaa4..9249e1c84e 100644 --- a/1-js/02-first-steps/04-variables/1-hello-variables/solution.md +++ b/1-js/02-first-steps/04-variables/1-hello-variables/solution.md @@ -1,7 +1,7 @@ -下面的代码,每一行都对应着任务列表中的对应项。 +In the code below, each line corresponds to the item in the task list. ```js run -let admin, name; // 一次声明两个变量。 +let admin, name; // can declare two variables at once name = "John"; diff --git a/1-js/02-first-steps/04-variables/1-hello-variables/task.md b/1-js/02-first-steps/04-variables/1-hello-variables/task.md index a784647cdb..84f009e8c7 100644 --- a/1-js/02-first-steps/04-variables/1-hello-variables/task.md +++ b/1-js/02-first-steps/04-variables/1-hello-variables/task.md @@ -2,9 +2,9 @@ importance: 2 --- -# 使用变量 +# Working with variables -1. 声明两个变量:`admin` 和 `name`。 -2. 将值 `"John"` 赋给 `name`。 -3. 从 `name` 变量中拷贝其值给 `admin`。 -4. 使用 `alert` 显示 `admin` 的值(必须输出 "John")。 +1. Declare two variables: `admin` and `name`. +2. Assign the value `"John"` to `name`. +3. Copy the value from `name` to `admin`. +4. Show the value of `admin` using `alert` (must output "John"). 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 823de90641..392f4e26f1 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 @@ -1,21 +1,21 @@ -## 代表我们星球的变量 +## The variable for our planet -这很简单: +That's simple: ```js let ourPlanetName = "Earth"; ``` -注意,我们也可以用一个更简短的名字 `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. -## 网站当前访问者的名字 +## The name of the current visitor ```js let currentUserName = "John"; ``` -同样,如果我们的确知道用户就是当前的用户的话,我们可以使用更短的变量名 `userName`。 +Again, we could shorten that to `userName` if we know for sure that the user is current. -现代编辑器的自动补全可以让长变量名变得容易编写。不要浪费这个特性。一个名字中包含三个词挺好的。 +Modern editors and autocomplete make long variable names easy to write. Don't save on them. A name with 3 words in it is fine. -如果你的编辑器没有合适的自动补全功能,换 [一个新的吧](/code-editors)。 +And if your editor does not have proper autocompletion, get [a new one](/code-editors). diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/task.md b/1-js/02-first-steps/04-variables/2-declare-variables/task.md index a048299cf6..f364badf4c 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/task.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/task.md @@ -2,7 +2,7 @@ importance: 3 --- -# 给出正确的名字 +# Giving the right name -1. 使用我们的星球的名字创建一个变量。你会怎么命名这个变量? -2. 创建一个变量来存储当前浏览者的名字。你会怎么命名这个变量? +1. Create a variable with the name of our planet. How would you name such a variable? +2. Create a variable to store the name of a current visitor to a website. How would you name that variable? diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md index 088b8cd213..acd643fded 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md @@ -1,5 +1,5 @@ -我们通常用大写字母表示“硬编码(hard-coded)”的常量。或者,换句话说就是,当值在执行之前或在被写入代码的时候,我们就知道值是什么了。 +We generally use upper case for constants that are "hard-coded". Or, in other words, when the value is known prior to execution and directly written into the code. -在这个代码中 `birthday` 确实是这样的。因此我们可以使用大写。 +In this code, `birthday` is exactly like that. So we could use the upper case for it. -在对照组中,`age` 是在程序运行时计算出的。今天我们有一个年龄,一年以后我们就会有另一个。它在某种意义上不会随着代码的执行而改变。但与 `birthday` 相比,它还是有一定的可变性:它是计算出来的,因此我们应该使用小写。 +In contrast, `age` is evaluated in run-time. Today we have one age, a year after we'll have another one. It is constant in a sense that it does not change through the code execution. But it is a bit "less of a constant" than `birthday`: it is calculated, so we should keep the lower case for it. 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 914d998f3c..5fd18f90a8 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 @@ -2,9 +2,9 @@ importance: 4 --- -# 大写的常量? +# Uppercase const? -检查下面的代码: +Examine the following code: ```js const birthday = '18.04.1982'; @@ -12,12 +12,13 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` -这里我们有一个 `birthday` 日期常量和通过一些代码(为了保持简短这里没有提供,因为这些细节在这无关紧要)从 `birthday` 计算出的 `age` 常量。 +Here we have a constant `birthday` date and the `age` is calculated from `birthday` with the help of some code (it is not provided for shortness, and because details don't matter here). -对于 `birthday` 使用大写方式正确吗?那么 `age` 呢?或者两者都用? +Would it be right to use upper case for `birthday`? For `age`? Or even for both? ```js -const BIRTHDAY = '18.04.1982'; // 使用大写? +const BIRTHDAY = '18.04.1982'; // make uppercase? -const AGE = someCode(BIRTHDAY); // 使用大写? +const AGE = someCode(BIRTHDAY); // make uppercase? ``` + diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index b9a33cbb82..4c2d09de34 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -1,61 +1,61 @@ -# 变量 +# Variables -大多数情况下,JavaScript 应用需要处理信息。这有两个例子: -1. 一个网上商店 —— 这里的信息可能包含正在售卖的商品和购物车。 -2. 一个聊天应用 —— 这里的信息可能包括用户和消息等等。 +Most of the time, a JavaScript application needs to work with information. Here are two examples: +1. An online shop -- the information might include goods being sold and a shopping cart. +2. A chat application -- the information might include users, messages, and much more. -变量就是用来储存这些信息的。 +Variables are used to store this information. -## 变量 +## A variable -[变量](https://en.wikipedia.org/wiki/Variable_(computer_science)) 是数据的“命名存储”。我们可以使用变量来保存商品、访客和其他信息。 +A [variable](https://en.wikipedia.org/wiki/Variable_(computer_science)) is a "named storage" for data. We can use variables to store goodies, visitors, and other data. -在 JavaScript 中创建一个变量,我们需要用到 `let` 关键字。 +To create a variable in JavaScript, use the `let` keyword. -下面的语句创建(也可以称为 **声明** 或者 **定义**)了一个名称为 “message” 的变量: +The statement below creates (in other words: *declares*) a variable with the name "message": ```js let message; ``` -现在,我们可以通过赋值操作符 `=` 为变量添加一些数据: +Now, we can put some data into it by using the assignment operator `=`: ```js let message; *!* -message = 'Hello'; // 保存字符串 +message = 'Hello'; // store the string 'Hello' in the variable named message */!* ``` -现在这个字符串已经保存到与该变量相关联的内存区域了,我们可以通过使用该变量名称访问它: +The string is now saved into the memory area associated with the variable. We can access it using the variable name: ```js run let message; message = 'Hello!'; *!* -alert(message); // 显示变量内容 +alert(message); // shows the variable content */!* ``` -简洁一点,我们可以将变量定义和赋值合并成一行: +To be concise, we can combine the variable declaration and assignment into a single line: ```js run -let message = 'Hello!'; // 定义变量,并且赋值 +let message = 'Hello!'; // define the variable and assign the value alert(message); // Hello! ``` -也可以在一行中声明多个变量: +We can also declare multiple variables in one line: ```js no-beautify let user = 'John', age = 25, message = 'Hello'; ``` -看上去代码长度更短,但并不推荐这样。为了更好的可读性,请一行只声明一个变量。 +That might seem shorter, but we don't recommend it. For the sake of better readability, please use a single line per variable. -多行变量声明有点长,但更容易阅读: +The multiline variant is a bit longer, but easier to read: ```js let user = 'John'; @@ -63,14 +63,15 @@ let age = 25; let message = 'Hello'; ``` -一些程序员采用下面的形式书写多个变量: +Some people also define multiple variables in this multiline style: + ```js no-beautify let user = 'John', age = 25, message = 'Hello'; ``` -……甚至使用“逗号在前”的形式: +...Or even in the "comma-first" style: ```js no-beautify let user = 'John' @@ -78,47 +79,47 @@ let user = 'John' , message = 'Hello'; ``` -技术上讲,这些变体都有一样的效果。所以,这是个个人品味和审美方面的问题。 - +Technically, all these variants do the same thing. So, it's a matter of personal taste and aesthetics. -````smart header="`var` 而不是 `let`" -在较旧的脚本中,你也可能发现另一个关键字 `var`,而不是 `let`: +````smart header="`var` instead of `let`" +In older scripts, you may also find another keyword: `var` instead of `let`: ```js *!*var*/!* message = 'Hello'; ``` -`var` 关键字与 `let` **大体** 相同,也用来声明变量,但稍微有些不同,也有点“老派”。 +The `var` keyword is *almost* the same as `let`. It also declares a variable, but in a slightly different, "old-school" way. -`let` 和 `var` 之间有些微妙的差别,但目前对于我们来说并不重要。我们将会在 章节中介绍它们。 +There are subtle differences between `let` and `var`, but they do not matter for us yet. We'll cover them in detail in the chapter . ```` -## 一个现实生活的类比 +## A real-life analogy -如果将变量想象成一个“数据”的盒子,盒子上有一个唯一的标注盒子名字的贴纸。这样我们能更轻松地掌握“变量”的概念。 +We can easily grasp the concept of a "variable" if we imagine it as a "box" for data, with a uniquely-named sticker on it. -例如,变量 `message` 可以被想象成一个标有 `"message"` 的盒子,盒子里面的值为 `"Hello!"`: +For instance, the variable `message` can be imagined as a box labeled `"message"` with the value `"Hello!"` in it: ![](variable.svg) -我们可以在盒子内放入任何值。 +We can put any value in the box. + +We can also change it as many times as we want: -并且,这个盒子的值,我们想改变多少次,就可以改变多少次: ```js run let message; message = 'Hello!'; -message = 'World!'; // 值改变了 +message = 'World!'; // value changed alert(message); ``` -当值改变的时候,之前的数据就被从变量中删除了: +When the value is changed, the old data is removed from the variable: ![](variable-change.svg) -我们还可以声明两个变量,然后将其中一个变量的数据拷贝到另一个变量。 +We can also declare two variables and copy data from one into the other. ```js run let hello = 'Hello world!'; @@ -126,135 +127,148 @@ let hello = 'Hello world!'; let message; *!* -// 将字符串 'Hello world' 从变量 hello 拷贝到 message +// copy 'Hello world' from hello into message message = hello; */!* -// 现在两个变量保存着相同的数据 +// now two variables hold the same data alert(hello); // Hello world! alert(message); // Hello world! ``` -```smart header="函数式语言" -有趣的是,也存在禁止更改变量值的 [函数式](https://en.wikipedia.org/wiki/Functional_programming) 编程语言。比如 [Scala](http://www.scala-lang.org/) 或 [Erlang](http://www.erlang.org/)。 +````warn header="Declaring twice triggers an error" +A variable should be declared only once. + +A repeated declaration of the same variable is an error: + +```js run +let message = "This"; + +// repeated 'let' leads to an error +let message = "That"; // SyntaxError: 'message' has already been declared +``` +So, we should declare a variable once and then refer to it without `let`. +```` + +```smart header="Functional languages" +It's interesting to note that there exist [functional](https://en.wikipedia.org/wiki/Functional_programming) programming languages, like [Scala](http://www.scala-lang.org/) or [Erlang](http://www.erlang.org/) that forbid changing variable values. -在这种类型的语言中,一旦值保存在盒子中,就永远存在。如果你试图保存其他值,它会强制创建一个新盒子(声明一个新变量),无法重用之前的变量。 +In such languages, once the value is stored "in the box", it's there forever. If we need to store something else, the language forces us to create a new box (declare a new variable). We can't reuse the old one. -虽然第一次看上去有点奇怪,但是这些语言有很大的发展潜力。不仅如此,在某些领域,比如并行计算,这个限制有一定的好处。研究这样的一门语言(即使不打算很快就用上它)有助于开阔视野。 +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. Studying such a language (even if you're not planning to use it soon) is recommended to broaden the mind. ``` -## 变量命名 [#variable-naming] +## Variable naming [#variable-naming] -JavaScript 的变量命名有两个限制: +There are two limitations on variable names in JavaScript: -1. 变量名称必须仅包含字母,数字,符号 `$` 和 `_`。 -2. 首字符必须非数字。 +1. The name must contain only letters, digits, or the symbols `$` and `_`. +2. The first character must not be a digit. -有效的命名,例如: +Examples of valid names: ```js let userName; let test123; ``` -如果命名包括多个单词,通常采用驼峰式命名法([camelCase](https://en.wikipedia.org/wiki/CamelCase))。也就是,单词一个接一个,除了第一个单词,其他的每个单词都以大写字母开头:`myVeryLongName`。 +When the name contains multiple words, [camelCase](https://en.wikipedia.org/wiki/CamelCase) is commonly used. That is: words go one after another, each word except first starting with a capital letter: `myVeryLongName`. -有趣的是,美元符号 `'$'` 和下划线 `'_'` 也可以用于变量命名。它们是正常的符号,就跟字母一样,没有任何特殊的含义。 +What's interesting -- the dollar sign `'$'` and the underscore `'_'` can also be used in names. They are regular symbols, just like letters, without any special meaning. -下面的命名是有效的: +These names are valid: ```js run untrusted -let $ = 1; // 使用 "$" 声明一个变量 -let _ = 2; // 现在用 "_" 声明一个变量 +let $ = 1; // declared a variable with the name "$" +let _ = 2; // and now a variable with the name "_" alert($ + _); // 3 ``` -下面的变量命名不正确: +Examples of incorrect variable names: ```js no-beautify -let 1a; // 不能以数字开始 +let 1a; // cannot start with a digit -let my-name; // 连字符 '-' 不允许用于变量命名 +let my-name; // hyphens '-' aren't allowed in the name ``` -```smart header="区分大小写" -命名为 `apple` 和 `AppLE` 的变量是不同的两个变量。 +```smart header="Case matters" +Variables named `apple` and `APPLE` are two different variables. ``` -````smart header="允许非英文字母,但不推荐" -可以使用任何语言,包括西里尔字母(cyrillic letters)甚至是象形文字,就像这样: +````smart header="Non-Latin letters are allowed, but not recommended" +It is possible to use any language, including cyrillic letters or even hieroglyphs, like this: ```js let имя = '...'; let 我 = '...'; ``` -技术上讲,完全没有错误,这样的命名是完全允许的,但是用英文进行变量命名是国际传统。哪怕我们正在写一个很小的脚本,它也有可能有很长的生命周期。某个时候,来自其他国家的人可能会阅读它。 +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 some time. ```` -````warn header="保留字" -有一张 [保留字列表](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords),这张表中的保留字无法用作变量命名,因为它们被用于编程语言本身了。 +````warn header="Reserved names" +There is a [list of reserved words](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords), which cannot be used as variable names because they are used by the language itself. -比如,`let`、`class`、`return`、`function` 都被保留了。 +For example: `let`, `class`, `return`, and `function` are reserved. -下面的代码将会抛出一个语法错误: +The code below gives a syntax error: ```js run no-beautify -let let = 5; // 不能用 "let" 来命名一个变量,错误! -let return = 5; // 同样,不能使用 "return",错误! +let let = 5; // can't name a variable "let", error! +let return = 5; // also can't name it "return", error! ``` ```` -````warn header="未采用 `use strict` 下的赋值" +````warn header="An assignment without `use strict`" -一般,我们需要在使用一个变量前定义它。但是在早期,我们可以不使用 `let` 进行变量声明,而可以简单地通过赋值来创建一个变量。现在如果我们不在脚本中使用 `use strict` 声明启用严格模式,这仍然可以正常工作,这是为了保持对旧脚本的兼容。 +Normally, we need to define a variable before using it. But in the old times, it was technically possible to create a variable by a mere assignment of the value without using `let`. This still works now if we don't put `use strict` in our scripts to maintain compatibility with old scripts. ```js run no-strict -// 注意:这个例子中没有 "use strict" +// note: no "use strict" in this example -num = 5; // 如果变量 "num" 不存在,就会被创建 +num = 5; // the variable "num" is created if it didn't exist alert(num); // 5 ``` -上面这是个糟糕的做法,严格模式下会报错。 +This is a bad practice and would cause an error in strict mode: -```js run untrusted +```js "use strict"; *!* -num = 5; // 错误:num 未定义 +num = 5; // error: num is not defined */!* ``` ```` -## 常量 +## Constants -声明一个常数(不变)变量,可以使用 `const` 而非 `let`: +To declare a constant (unchanging) variable, use `const` instead of `let`: ```js const myBirthday = '18.04.1982'; ``` -使用 `const` 声明的变量称为“常量”。它们不能被修改,如果你尝试修改就会发现报错: +Variables declared using `const` are called "constants". They cannot be reassigned. An attempt to do so would cause an error: ```js run const myBirthday = '18.04.1982'; -myBirthday = '01.01.2001'; // 错误,不能对常量重新赋值 +myBirthday = '01.01.2001'; // error, can't reassign the constant! ``` -当程序员能确定这个变量永远不会改变的时候,就可以使用 `const` 来确保这种行为,并且清楚地向别人传递这一事实。 - +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and clearly communicate that fact to everyone. -### 大写形式的常数 +### Uppercase constants -一个普遍的做法是将常量用作别名,以便记住那些在执行之前就已知的难以记住的值。 +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known prior to execution. -使用大写字母和下划线来命名这些常量。 +Such constants are named using capital letters and underscores. -例如,让我们以所谓的“web”(十六进制)格式为颜色声明常量: +For instance, let's make constants for colors in so-called "web" (hexadecimal) format: ```js run const COLOR_RED = "#F00"; @@ -262,69 +276,70 @@ const COLOR_GREEN = "#0F0"; const COLOR_BLUE = "#00F"; const COLOR_ORANGE = "#FF7F00"; -// ……当我们需要选择一个颜色 +// ...when we need to pick a color let color = COLOR_ORANGE; alert(color); // #FF7F00 ``` -好处: +Benefits: + +- `COLOR_ORANGE` is much easier to remember than `"#FF7F00"`. +- It is much easier to mistype `"#FF7F00"` than `COLOR_ORANGE`. +- When reading the code, `COLOR_ORANGE` is much more meaningful than `#FF7F00`. -- `COLOR_ORANGE` 比 `"#FF7F00"` 更容易记忆。 -- 比起 `COLOR_ORANGE` 而言,`"#FF7F00"` 更容易输错。 -- 阅读代码时,`COLOR_ORANGE` 比 `#FF7F00` 更易懂。 +When should we use capitals for a constant and when should we name it normally? Let's make that clear. -什么时候该为常量使用大写命名,什么时候进行常规命名?让我们弄清楚一点。 +Being a "constant" just means that a variable's value never changes. But there are constants that are known prior to execution (like a hexadecimal value for red) and there are constants that are *calculated* in run-time, during the execution, but do not change after their initial assignment. -作为一个“常数”,意味着值永远不变。但是有些常量在执行之前就已知了(比如红色的十六进制值),还有些在执行期间被“计算”出来,但初始赋值之后就不会改变。 +For instance: -例如: ```js -const pageLoadTime = /* 网页加载所需的时间 */; +const pageLoadTime = /* time taken by a webpage to load */; ``` -`pageLoadTime` 的值在页面加载之前是未知的,所以采用常规命名。但是它仍然是个常量,因为赋值之后不会改变。 +The value of `pageLoadTime` is not known prior to the page load, so it's named normally. But it's still a constant because it doesn't change after assignment. -换句话说,大写命名的常量仅用作“硬编码(hard-coded)”值的别名。 +In other words, capital-named constants are only used as aliases for "hard-coded" values. -## 正确命名变量 +## Name things right -谈到变量,还有一件非常重要的事。 +Talking about variables, there's one more extremely important thing. -一个变量名应该有一个清晰、明显的含义,对其存储的数据进行描述。 +A variable name should have a clean, obvious meaning, describing the data that it stores. -变量命名是编程过程中最重要且最复杂的技能之一。快速地浏览变量的命名就知道代码是一个初学者还是有经验的开发者写的。 +Variable naming is one of the most important and complex skills in programming. A quick 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-labeled. Or, in other words, when the variables have good names. -声明变量之前,多花点时间思考它的更好的命名。你会受益良多。 +Please spend time thinking about the right name for a variable before declaring it. Doing so will repay you handsomely. -一些可以遵循的规则: +Some good-to-follow rules are: -- 使用易读的命名,比如 `userName` 或者 `shoppingCart`。 -- 离诸如 `a`、`b`、`c` 这种缩写和短名称远一点,除非你真的知道你在干什么。 -- 变量名在能够准确描述变量的同时要足够简洁。不好的例子就是 `data` 和 `value`,这样的名称等于什么都没说。如果能够非常明显地从上下文知道数据和值所表达的含义,这样使用它们也是可以的。 -- 脑海中的术语要和团队保持一致。如果站点的访客称为“用户”,则我们采用相关的变量命名,比如 `currentUser` 或者 `newUser`,而不要使用 `currentVisitor` 或者一个 `newManInTown`。 +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, `c`, unless you really 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 own mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. -听上去很简单?确实如此,但是在实践中选择一个一目了然的变量名称并非如此简单。大胆试试吧。 +Sounds simple? Indeed it is, but creating descriptive and concise variable names in practice is not. Go for it. -```smart header="重用还是新建?" -最后一点,有一些懒惰的程序员,倾向于重用现有的变量,而不是声明一个新的变量。 +```smart header="Reuse or create?" +And the last note. There are some lazy programmers who, instead of declaring new variables, tend to reuse existing ones. -结果是,这个变量就像是被扔进不同东西盒子,但没有改变它的贴纸。现在里面是什么?谁知道呢。我们需要靠近一点,仔细检查才能知道。 +As a result, their variables are like boxes into which people throw different things without changing their stickers. What's inside the box now? Who knows? We need to come closer and check. -这样的程序员节省了一点变量声明的时间,但却在调试代码的时候损失数十倍时间。 +Such programmers save a little bit on variable declaration but lose ten times more on debugging. -额外声明一个变量绝对是利大于弊的。 +An extra variable is good, not evil. -现代的 JavaScript 压缩器和浏览器都很够很好地对代码进行优化,所以不会产生性能问题。为不同的值使用不同的变量可以帮助引擎对代码进行优化。 +Modern JavaScript minifiers and browsers optimize code well enough, so it won't create performance issues. Using different variables for different values can even help the engine optimize your code. ``` -## 总结 +## Summary -我们可以使用 `var`、`let` 或 `const` 声明变量来存储数据。 +We can declare variables to store data by using the `var`, `let`, or `const` keywords. -- `let` — 现代的变量声明方式。 -- `var` — 老旧的变量声明方式。一般情况下,我们不会再使用它。但是,我们会在 章节介绍 `var` 和 `let` 的微妙差别,以防你需要它们。 -- `const` — 类似于 `let`,但是变量的值无法被修改。 +- `let` -- is a modern variable declaration. +- `var` -- is an old-school variable declaration. Normally we don't use it at all, but we'll cover subtle differences from `let` in the chapter , just in case you need them. +- `const` -- is like `let`, but the value of the variable can't be changed. -变量应当以一种容易理解变量内部是什么的方式进行命名。 +Variables should be named in a way that allows us to easily understand what's inside them. diff --git a/1-js/02-first-steps/04-variables/variable-change.svg b/1-js/02-first-steps/04-variables/variable-change.svg index 19363d8dc3..1b26792380 100644 --- a/1-js/02-first-steps/04-variables/variable-change.svg +++ b/1-js/02-first-steps/04-variables/variable-change.svg @@ -1,37 +1 @@ - - - - variable-change.svg - Created with sketchtool. - - - - - - - - - "World!" - - - - - - "Hello!" - - - - - - message - - - - - - - - - - - \ No newline at end of file +"World!""Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/04-variables/variable.svg b/1-js/02-first-steps/04-variables/variable.svg index 9f967d19b5..1c3d8b0cbe 100644 --- a/1-js/02-first-steps/04-variables/variable.svg +++ b/1-js/02-first-steps/04-variables/variable.svg @@ -1,26 +1 @@ - - - - variable.svg - Created with sketchtool. - - - - - - - - - - "Hello!" - - - - - - message - - - - - \ No newline at end of file +"Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/05-types/1-string-quotes/solution.md b/1-js/02-first-steps/05-types/1-string-quotes/solution.md index b2d12c7f20..68a13c15b2 100644 --- a/1-js/02-first-steps/05-types/1-string-quotes/solution.md +++ b/1-js/02-first-steps/05-types/1-string-quotes/solution.md @@ -1,15 +1,15 @@ -反引号将包装在 `${...}` 中的表达式嵌入到了字符串。 +Backticks embed the expression inside `${...}` into the string. ```js run let name = "Ilya"; -// 表达式为数字 1 +// the expression is a number 1 alert( `hello ${1}` ); // hello 1 -// 表达式是一个字符串 "name" +// the expression is a string "name" alert( `hello ${"name"}` ); // hello name -// 表达式是一个变量,嵌入进去了。 +// the expression is a variable, embed it alert( `hello ${name}` ); // hello Ilya ``` diff --git a/1-js/02-first-steps/05-types/1-string-quotes/task.md b/1-js/02-first-steps/05-types/1-string-quotes/task.md index e1ac37466c..14ea6b4d66 100644 --- a/1-js/02-first-steps/05-types/1-string-quotes/task.md +++ b/1-js/02-first-steps/05-types/1-string-quotes/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 字符串的引号 +# String quotes -下面的脚本会输出什么? +What is the output of the script? ```js let name = "Ilya"; @@ -14,4 +14,4 @@ alert( `hello ${1}` ); // ? alert( `hello ${"name"}` ); // ? alert( `hello ${name}` ); // ? -``` +``` \ No newline at end of file diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index af82b7b9b3..a697548a6f 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -1,89 +1,109 @@ -# 数据类型 +# Data types -JavaScript 中的变量可以保存任何数据。变量在前一刻可以是个字符串,下一刻就可以变成 number 类型: +A value in JavaScript is always of a certain type. For example, a string or a number. + +There are eight basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail. + +We can put any type in a variable. For example, a variable can at one moment be a string and then store a number: ```js -// 没有错误 +// no error let message = "hello"; message = 123456; ``` -允许这种操作的编程语言称为“动态类型”(dynamically typed)的编程语言,意思是虽然编程语言中有不同的数据类型,但是你定义的变量并不会在定义后,被限制为某一数据类型。 - -在 JavaScript 中有八种基本的数据类型。这一章我们会学习数据类型的基本知识,在下一章我们会对他们一一进行详细讲解。 +Programming languages that allow such things, such as JavaScript, are called "dynamically typed", meaning that there exist data types, but variables are not bound to any of them. -## Number 类型 +## Number ```js let n = 123; n = 12.345; ``` -*number* 类型代表整数和浮点数。 +The *number* type represents both integer and floating point numbers. -数字可以有很多操作,比如,乘法 `*`、除法 `/`、加法 `+`、减法 `-` 等等。 +There are many operations for numbers, e.g. multiplication `*`, division `/`, addition `+`, subtraction `-`, and so on. -除了常规的数字,还包括所谓的“特殊数值("special numeric values")”也属于这种类型:`Infinity`、`-Infinity` 和 `NaN`。 +Besides regular numbers, there are so-called "special numeric values" which also belong to this data type: `Infinity`, `-Infinity` and `NaN`. -- `Infinity` 代表数学概念中的 [无穷大](https://en.wikipedia.org/wiki/Infinity) ∞。是一个比任何数字都大的特殊值。 +- `Infinity` represents the mathematical [Infinity](https://en.wikipedia.org/wiki/Infinity) ∞. It is a special value that's greater than any number. - 我们可以通过除以 0 来得到它: + We can get it as a result of division by zero: ```js run alert( 1 / 0 ); // Infinity ``` - 或者在代码中直接使用它: + Or just reference it directly: ```js run alert( Infinity ); // Infinity ``` -- `NaN` 代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果,比如: +- `NaN` represents a computational error. It is a result of an incorrect or an undefined mathematical operation, for instance: ```js run - alert( "not a number" / 2 ); // NaN,这样的除法是错误的 + alert( "not a number" / 2 ); // NaN, such division is erroneous ``` - `NaN` 是粘性的。任何对 `NaN` 的进一步操作都会返回 `NaN`: + `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`: ```js run - alert( "not a number" / 2 + 5 ); // NaN + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "not a number" / 2 - 1 ); // NaN ``` - 所以,如果在数学表达式中有一个 `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`). -```smart header="数学运算是安全的" -在 JavaScript 中做数学运算是安全的。我们可以做任何事:除以 0,将非数字字符串视为数字,等等。 +```smart header="Mathematical operations are safe" +Doing maths is "safe" in JavaScript. We can do anything: divide by zero, treat non-numeric strings as numbers, etc. -脚本永远不会因为一个致命的错误(“死亡”)而停止。最坏的情况下,我们会得到 `NaN` 的结果。 +The script will never stop with a fatal error ("die"). At worst, we'll get `NaN` as the result. ``` -特殊的数值属于 "number" 类型。当然,对“特殊的数值”这个词的一般认识是,它们并不是数字。 +Special numeric values formally belong to the "number" type. Of course they are not numbers in the common sense of this word. + +We'll see more about working with numbers in the chapter . + +## BigInt [#bigint-type] -我们将在 一节中学习数字的更多细节。 +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 +``` -## BigInt 类型 +So to say, all odd integers greater than (253-1) can't be stored at all in the "number" type. -在 JavaScript 中,"number" 类型无法代表大于 253(或小于 -253)的整数,这是其内部表示形式导致的技术限制。这大约是 16 位的十进制数字,因此在大多数情况下,这个限制不是问题,但有时我们需要很大的数字,例如用于加密或微秒精度的时间戳。 +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. -`BigInt` 类型是最近被添加到 JavaScript 语言中的,用于表示任意长度的整数。 +`BigInt` type was recently added to the language to represent integers of arbitrary length. -通过将 `n` 附加到整数字段的末尾来创建 `BigInt`。 +A `BigInt` value is created by appending `n` to the end of an integer: ```js -// 尾部的 "n" 表示这是一个 BigInt 类型 +// the "n" at the end means it's a BigInt const bigInt = 1234567890123456789012345678901234567890n; ``` -由于很少需要 `BigInt` 类型的数字,因此我们在单独的章节 中专门对其进行介绍。 +As `BigInt` numbers are rarely needed, we don't cover them here, but devoted them a separate chapter . Read it when you need such big numbers. -```smart header="兼容性问题" -目前 Firefox 和 Chrome 已经支持 `BigInt` 了,但 Safari/IE/Edge 还没有。 + +```smart header="Compatibility issues" +Right now, `BigInt` is supported in Firefox/Chrome/Edge/Safari, but not in IE. ``` -## String 类型 +You can check [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) to know which versions of a browser are supported. + +## String -JavaScript 中的字符串必须被括在引号里。 +A string in JavaScript must be surrounded by quotes. ```js let str = "Hello"; @@ -91,126 +111,122 @@ let str2 = 'Single quotes are ok too'; let phrase = `can embed another ${str}`; ``` -在 JavaScript 中,有三种包含字符串的方式。 +In JavaScript, there are 3 types of quotes. -1. 双引号:`"Hello"`. -2. 单引号:`'Hello'`. -3. 反引号:`Hello`. +1. Double quotes: `"Hello"`. +2. Single quotes: `'Hello'`. +3. Backticks: `Hello`. -双引号和单引号都是“简单”引用,在 JavaScript 中两者几乎没有什么差别。 +Double and single quotes are "simple" quotes. There's practically no difference between them in JavaScript. -反引号是 **功能扩展** 引号。它们允许我们通过将变量和表达式包装在 `${…}` 中,来将它们嵌入到字符串中。例如: +Backticks are "extended functionality" quotes. They allow us to embed variables and expressions into a string by wrapping them in `${…}`, for example: ```js run let name = "John"; -// 嵌入一个变量 +// embed a variable alert( `Hello, *!*${name}*/!*!` ); // Hello, John! -// 嵌入一个表达式 +// embed an expression alert( `the result is *!*${1 + 2}*/!*` ); // the result is 3 ``` -`${…}` 内的表达式会被计算,计算结果会成为字符串的一部分。可以在 `${…}` 内放置任何东西:诸如名为 `name` 的变量,或者诸如 `1 + 2` 的算数表达式,或者其他一些更复杂的。 +The expression inside `${…}` is evaluated and the result becomes a part of the string. We can put anything in there: a variable like `name` or an arithmetical expression like `1 + 2` or something more complex. -需要注意的是,这仅仅在反引号内有效,其他引号不允许这种嵌入。 +Please note that this can only be done in backticks. Other quotes don't have this embedding functionality! ```js run -alert( "the result is ${1 + 2}" ); // the result is ${1 + 2}(使用双引号则不会计算 ${…} 中的内容) +alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (double quotes do nothing) ``` -我们会在 一节中学习字符串的更多细节。 +We'll cover strings more thoroughly in the chapter . -```smart header="JavaScript 中没有 *character* 类型。" -在一些语言中,单个字符有一个特殊的 "character" 类型,在 C 语言和 Java 语言中被称为 "char"。 +```smart header="There is no *character* type." +In some languages, there is a special "character" type for a single character. For example, in the C language and in Java it is called "char". -在 JavaScript 中没有这种类型。只有一种 `string` 类型,一个字符串可以包含一个或多个字符。 +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. ``` -## Boolean 类型(逻辑类型) +## Boolean (logical type) -boolean 类型仅包含两个值:`true` 和 `false`。 +The boolean type has only two values: `true` and `false`. -这种类型通常用于存储表示 yes 或 no 的值:`true` 意味着 “yes,正确”,`false` 意味着 “no,不正确”。 +This type is commonly used to store yes/no values: `true` means "yes, correct", and `false` means "no, incorrect". -比如: +For instance: ```js let nameFieldChecked = true; // yes, name field is checked let ageFieldChecked = false; // no, age field is not checked ``` -布尔值也可作为比较的结果: +Boolean values also come as a result of comparisons: ```js run let isGreater = 4 > 1; -alert( isGreater ); // true(比较的结果是 "yes") +alert( isGreater ); // true (the comparison result is "yes") ``` -更详细的内容将会在 一节中介绍。 +We'll cover booleans more deeply in the chapter . -## "null" 值 +## The "null" value -特殊的 `null` 值不属于上述任何一种类型。 +The special `null` value does not belong to any of the types described above. -它构成了一个独立的类型,只包含 `null` 值: +It forms a separate type of its own which contains only the `null` value: ```js let age = null; ``` -相比较于其他编程语言,JavaScript 中的 `null` 不是一个“对不存在的 `object` 的引用”或者 “null 指针”。 +In JavaScript, `null` is not a "reference to a non-existing object" or a "null pointer" like in some other languages. -JavaScript 中的 `null` 仅仅是一个代表“无”、“空”或“值未知”的特殊值。 +It's just a special value which represents "nothing", "empty" or "value unknown". -上面的代码表示,由于某些原因,`age` 是未知或空的。 +The code above states that `age` is unknown. -## "undefined" 值 +## The "undefined" value -特殊值 `undefined` 和 `null` 一样自成类型。 +The special value `undefined` also stands apart. It makes a type of its own, just like `null`. -`undefined` 的含义是 `未被赋值`。 +The meaning of `undefined` is "value is not assigned". -如果一个变量已被声明,但未被赋值,那么它的值就是 `undefined`: +If a variable is declared, but not assigned, then its value is `undefined`: ```js run -let x; +let age; -alert(x); // 弹出 "undefined" +alert(age); // shows "undefined" ``` -原理上来说,可以为任何变量赋值为 `undefined`: +Technically, it is possible to explicitly assign `undefined` to a variable: ```js run -let x = 123; +let age = 100; -x = undefined; +// change the value to undefined +age = undefined; -alert(x); // "undefined" +alert(age); // "undefined" ``` -……但是不建议这样做。通常,使用使用 `null` 将一个“空”或者“未知”的值写入变量中,`undefined` 仅仅用于检验,例如查看变量是否被赋值或者其他类似的操作。 - -## object 类型和 symbol 类型 +...But we don't recommend doing that. Normally, one uses `null` to assign an "empty" or "unknown" value to a variable, while `undefined` is reserved as a default initial value for unassigned things. -`object` 类型是一个特殊的类型。 +## Objects and Symbols -其他所有的数据类型都被称为“原生类型”,因为它们的值只包含一个单独的内容(字符串、数字或者其他)。相反,`object` 则用于储存数据集合和更复杂的实体。在充分了解原生类型之后,我们将会在 一节中介绍 `object`。 +The `object` type is special. -`symbol` 类型用于创建对象的唯一标识符。我们在这里提到 `symbol` 类型是为了学习的完整性,但我们会在学完 `object` 类型后再学习它。 +All other types are called "primitive" because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities. -## typeof 运算符 [#type-typeof] +Being that important, objects deserve a special treatment. We'll deal with them later in the chapter , after we learn more about primitives. -`typeof` 运算符返回参数的类型。当我们想要分别处理不同类型值的时候,或者想快速进行数据类型检验时,非常有用。 +The `symbol` type is used to create unique identifiers for objects. We have to mention it here for the sake of completeness, but also postpone the details till we know objects. -它支持两种语法形式: +## The typeof operator [#type-typeof] -1. 作为运算符:`typeof x`。 -2. 函数形式:`typeof(x)`。 +The `typeof` operator returns the type of the argument. It's useful when we want to process values of different types differently or just want to do a quick check. -换言之,有括号和没有括号,得到的结果是一样的。 - -对 `typeof x` 的调用会以字符串的形式返回数据类型: +A call to `typeof x` returns a string with the type name: ```js typeof undefined // "undefined" @@ -238,29 +254,41 @@ typeof alert // "function" (3) */!* ``` -最后三行可能需要额外的说明: +The last three lines may need additional explanation: + +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. -1. `Math` 是一个提供数学运算的内建 `object`。我们会在 一节中学习它。此处仅作为一个 `object` 的示例。 -2. `typeof null` 的结果是 `"object"`。这其实是不对的。官方也承认了这是 `typeof` 运算符的问题,现在只是为了兼容性而保留了下来。当然,`null` 不是一个 `object`。`null` 有自己的类型,它是一个特殊值。再次强调,这是 JavaScript 语言的一个错误。 -3. `typeof alert` 的结果是 `"function"`,因为 `alert` 在 JavaScript 语言中是一个函数。我们会在下一章学习函数,那时我们会了解到,在 JavaScript 语言中没有一个特别的 "function" 类型。函数隶属于 `object` 类型。但是 `typeof` 会对函数区分对待。这不是很正确的做法,但在实际编程中非常方便。 +```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 -JavaScript 中有八种基本的数据类型(译注:前七种为基本数据类型,也称为原始类型,而 `object` 为复杂数据类型)。 +There are 8 basic data types in JavaScript. -- `number` 用于任何类型的数字:整数或浮点数,在 ±253 范围内的整数。 -- `bigint` 用于任意长度的整数。 -- `string` 用于字符串:一个字符串可以包含一个或多个字符,所以没有单独的单字符类型。 -- `boolean` 用于 `true` 和 `false`。 -- `null` 用于未知的值 —— 只有一个 `null` 值的独立类型。 -- `undefined` 用于未定义的值 —— 只有一个 `undefined` 值的独立类型。 -- `symbol` 用于唯一的标识符。 -- `object` 用于更复杂的数据结构。 +- 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. -我们可以通过 `typeof` 运算符查看存储在变量中的数据类型。 +The `typeof` operator allows us to see which type is stored in a variable. -- 两种形式:`typeof x` 或者 `typeof(x)`。 -- 以字符串的形式返回类型名称,例如 `"string"`。 -- `typeof null` 会返回 `"object"` —— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个 `object`。 +- 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. -在接下来的章节中,我们将重点介绍原生类型值,当你掌握了原生数据类型后,我们将继续学习 `object`。 +In the next chapters, we'll concentrate on primitive values and once we're familiar with them, we'll move on to objects. diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md similarity index 84% rename from 1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md rename to 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md index 0d7930aa57..903ee7ff35 100644 --- a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md @@ -1,11 +1,11 @@ -JavaScript 代码: +JavaScript-code: ```js demo run let name = prompt("What is your name?", ""); alert(name); ``` -整个页面的代码: +The full page: ```html diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md new file mode 100644 index 0000000000..a65a654e05 --- /dev/null +++ b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md @@ -0,0 +1,9 @@ +importance: 4 + +--- + +# A simple page + +Create a web-page that asks for a name and outputs it. + +[demo] 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 new file mode 100644 index 0000000000..ef0f333cb5 --- /dev/null +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -0,0 +1,105 @@ +# Interaction: alert, prompt, confirm + +As we'll be using the browser as our demo environment, let's see a couple of functions to interact with the user: `alert`, `prompt` and `confirm`. + +## alert + +This one we've seen already. It shows a message and waits for the user to press "OK". + +For example: + +```js run +alert("Hello"); +``` + +The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc, until they have dealt with the window. In this case -- until they press "OK". + +## prompt + +The function `prompt` accepts two arguments: + +```js no-beautify +result = prompt(title, [default]); +``` + +It shows a modal window with a text message, an input field for the visitor, and the buttons OK/Cancel. + +`title` +: The text to show the visitor. + +`default` +: An optional second parameter, the initial value for the input field. + +```smart header="The square brackets in syntax `[...]`" +The square brackets around `default` in the syntax above denote that the parameter is optional, not required. +``` + +The visitor can type something in the prompt input field and press OK. Then we get that text in the `result`. Or they can cancel the input by pressing Cancel or hitting the `key:Esc` key, then we get `null` as the `result`. + +The call to `prompt` returns the text from the input field or `null` if the input was canceled. + +For instance: + +```js run +let age = prompt('How old are you?', 100); + +alert(`You are ${age} years old!`); // You are 100 years old! +``` + +````warn header="In IE: always supply a `default`" +The second parameter is optional, but if we don't supply it, Internet Explorer will insert the text `"undefined"` into the prompt. + +Run this code in Internet Explorer to see: + +```js run +let test = prompt("Test"); +``` + +So, for prompts to look good in IE, we recommend always providing the second argument: + +```js run +let test = prompt("Test", ''); // <-- for IE +``` +```` + +## confirm + +The syntax: + +```js +result = confirm(question); +``` + +The function `confirm` shows a modal window with a `question` and two buttons: OK and Cancel. + +The result is `true` if OK is pressed and `false` otherwise. + +For example: + +```js run +let isBoss = confirm("Are you the boss?"); + +alert( isBoss ); // true if OK is pressed +``` + +## Summary + +We covered 3 browser-specific functions to interact with visitors: + +`alert` +: shows a message. + +`prompt` +: shows a message asking the user to input text. It returns the text or, if Cancel button or `key:Esc` is clicked, `null`. + +`confirm` +: shows a message and waits for the user to press "OK" or "Cancel". It returns `true` for OK and `false` for Cancel/`key:Esc`. + +All these methods are modal: they pause script execution and don't allow the visitor to interact with the rest of the page until the window has been dismissed. + +There are two limitations shared by all the methods above: + +1. The exact location of the modal window is determined by the browser. Usually, it's in the center. +2. The exact look of the window also depends on the browser. We can't modify it. + +That is the price for simplicity. There are other ways to show nicer windows and richer interaction with the visitor, but if "bells and whistles" do not matter much, these methods work just fine. diff --git a/1-js/02-first-steps/06-type-conversions/article.md b/1-js/02-first-steps/06-type-conversions/article.md deleted file mode 100644 index eb89c95698..0000000000 --- a/1-js/02-first-steps/06-type-conversions/article.md +++ /dev/null @@ -1,148 +0,0 @@ -# 类型转换 - -大多数情况下,运算符和函数会自动将赋予他们的值转换为正确的类型。 - -比如,`alert` 会自动将任何值都转换为字符串以进行显示。算术运算符会将值转换为数字。 - -在某些情况下,我们需要将值显式地转换为我们期望的类型。 - -```smart header="对象还未纳入讨论中" -本章不会讨论 object 类型。先学习原始类型,之后我们会学习 object 类型。我们会在 一节中学习对象的类型转换。 -``` - -## 字符串转换 - -当我们需要一个字符串形式的值时,就会进行字符串转换。 - -比如,`alert(value)` 将 `value` 转换为字符串类型,然后显示这个值。 - -我们也可以显式地调用 `String(value)` 来将 `value` 转换为字符串类型: - -```js run -let value = true; -alert(typeof value); // boolean - -*!* -value = String(value); // 现在,值是一个字符串形式的 "true" -alert(typeof value); // string -*/!* -``` - -字符串转换最明显。`false` 变成 `"false"`,`null` 变成 `"null"` 等。 - -## 数字型转换 - -在算术函数和表达式中,会自动进行 number 类型转换。 - -比如,当把除法 `/` 用于非 number 类型: - -```js run -alert( "6" / "2" ); // 3, string 类型的值被自动转换成 number 类型后进行计算 -``` - -我们也可以使用 `Number(value)` 显式地将这个 `value` 转换为 number 类型。 - -```js run -let str = "123"; -alert(typeof str); // string - -let num = Number(str); // 变成 number 类型 123 - -alert(typeof num); // number -``` - -当我们从 string 类型源(如文本表单)中读取一个值,但期望输入一个数字时,通常需要进行显式转换。 - -如果该字符串不是一个有效的数字,转换的结果会是 `NaN`。例如: - -```js run -let age = Number("an arbitrary string instead of a number"); - -alert(age); // NaN,转换失败 -``` - -number 类型转换规则: - -| 值 | 变成…… | -| --- | --- | -| `undefined` | `NaN` | -| `null` | `0` | -|true 和 false | `1` and `0` | -| `string` | 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 `0`。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 `NaN`。 | - -例子: - -```js run -alert( Number(" 123 ") ); // 123 -alert( Number("123z") ); // NaN(从字符串“读取”数字,读到 "z" 时出现错误) -alert( Number(true) ); // 1 -alert( Number(false) ); // 0 -``` - -请注意 `null` 和 `undefined` 在这有点不同:`null` 变成数字 `0`,`undefined` 变成 `NaN`。(译注:此外,字符串转换为 number 类型时,除了 `undefined`、`null` 和 `boolean` 三种特殊情况,只有字符串是由空格和数字组成时,才能转换成功,否则会出现 error 返回 `NaN`。) - -大多数数学运算符也执行这种转换,我们将在下一节中进行介绍。 - -## 布尔型转换 - -布尔(boolean)类型转换是最简单的一个。 - -它发生在逻辑运算中(稍后我们将进行条件判断和其他类似的东西),但是也可以通过调用 Boolean(value) 显式地进行转换。 - -转换规则如下: - -- 直观上为“空”的值(如 `0`、空字符串、`null`、`undefined` 和 `NaN`)将变为 `false`。 -- 其他值变成 `true`。 - -比如: - -```js run -alert( Boolean(1) ); // true -alert( Boolean(0) ); // false - -alert( Boolean("hello") ); // true -alert( Boolean("") ); // false -``` - -````warn header="请注意:包含 0 的字符串 `\"0\"` 是 `true`" -一些编程语言(比如 PHP)视 `"0"` 为 `false`。但在 JavaScript 中,非空的字符串总是 `true`。 - -```js run -alert( Boolean("0") ); // true -alert( Boolean(" ") ); // 空白, 也是 true (任何非空字符串是 true) -``` -```` - -## 总结 - -有三种常用的类型转换:转换为 string 类型、转换为 number 类型和转换为 boolean 类型。 - -**字符串转换** —— 转换发生在输出内容的时候,也可以通过 `String(value)` 进行显式转换。原始类型值的 string 类型转换通常是很明显的。 - -**数字型转换** —— 转换发生在进行算术操作时,也可以通过 `Number(value)` 进行显式转换。 - -数字型转换遵循以下规则: - -| 值 | 变成…… | -|-------|-------------| -| `undefined` | `NaN` | -| `null` | `0` | -| true / false | `1 / 0` | -| `string` | “按原样读取”字符串,两端的空白会被忽略。空字符串变成 `0`。转换出错则输出 `NaN`。 | - -**布尔型转换** —— 转换发生在进行逻辑操作时,也可以通过 `Boolean(value)` 进行显式转换。 - -布尔型转换遵循以下规则: - -| 值 | 变成…… | -|-------|-------------| -| `0`, `null`, `undefined`, `NaN`, `""` | `false` | -| 其他值 | `true` | - - -上述的大多数规则都容易理解和记忆。人们通常会犯错误的值得注意的例子有以下几个: - -- 对 `undefined` 进行数字型转换时,输出结果为 `NaN`,而非 `0`。 -- 对 `"0"` 和只有空格的字符串(比如:`" "`)进行布尔型转换时,输出结果为 `true`。 - -我们在本小结没有讲 object 类型的转换。在我们学习完更多关于 JavaScript 的基本知识后,我们会在专门介绍 object 的章节 中详细讲解 object 类型转换。 diff --git a/1-js/02-first-steps/07-operators/1-increment-order/solution.md b/1-js/02-first-steps/07-operators/1-increment-order/solution.md deleted file mode 100644 index 09da387a33..0000000000 --- a/1-js/02-first-steps/07-operators/1-increment-order/solution.md +++ /dev/null @@ -1,17 +0,0 @@ - -答案如下: - -- `a = 2` -- `b = 2` -- `c = 2` -- `d = 1` - -```js run no-beautify -let a = 1, b = 1; - -alert( ++a ); // 2,前置操作符返回最新值 -alert( b++ ); // 1,后置操作符返回旧值 - -alert( a ); // 2,自增一次 -alert( b ); // 2,自增一次 -``` diff --git a/1-js/02-first-steps/07-operators/1-increment-order/task.md b/1-js/02-first-steps/07-operators/1-increment-order/task.md deleted file mode 100644 index 8a9e100dfe..0000000000 --- a/1-js/02-first-steps/07-operators/1-increment-order/task.md +++ /dev/null @@ -1,14 +0,0 @@ -importance: 5 - ---- - -# 后置操作符和前置操作符 - -以下代码中变量 `a`、`b`、`c`、`d` 的最终值分别是多少? - -```js -let a = 1, b = 1; - -let c = ++a; // ? -let d = b++; // ? -``` diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/solution.md b/1-js/02-first-steps/07-operators/2-assignment-result/solution.md deleted file mode 100644 index 52a16f749f..0000000000 --- a/1-js/02-first-steps/07-operators/2-assignment-result/solution.md +++ /dev/null @@ -1,4 +0,0 @@ -答案如下: - -- `a = 4`(乘以 2) -- `x = 5`(相当于计算 1 + 4) diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/task.md b/1-js/02-first-steps/07-operators/2-assignment-result/task.md deleted file mode 100644 index dec68a728e..0000000000 --- a/1-js/02-first-steps/07-operators/2-assignment-result/task.md +++ /dev/null @@ -1,13 +0,0 @@ -importance: 3 - ---- - -# 赋值结果 - -下面这段代码运行完成后,代码中的 `a` 和 `x` 的值是多少? - -```js -let a = 2; - -let x = 1 + (a *= 2); -``` diff --git a/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/solution.md deleted file mode 100644 index ed7f029b13..0000000000 --- a/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/solution.md +++ /dev/null @@ -1,26 +0,0 @@ - -```js no-beautify -"" + 1 + 0 = "10" // (1) -"" - 1 + 0 = -1 // (2) -true + false = 1 -6 / "3" = 2 -"2" * "3" = 6 -4 + 5 + "px" = "9px" -"$" + 4 + 5 = "$45" -"4" - 2 = 2 -"4px" - 2 = NaN -7 / 0 = Infinity -" -9 " + 5 = " -9 5" // (3) -" -9 " - 5 = -14 // (4) -null + 1 = 1 // (5) -undefined + 1 = NaN // (6) -" \t \n" - 2 = -2 // (7) -``` - -1. 有字符串的加法 `"" + 1`,首先会将数字 `1` 转换为一个字符串:`"" + 1 = "1"`,然后我们得到 `"1" + 0`,再次应用同样的规则得到最终的结果。 -2. 减法 `-`(像大多数数学运算一样)只能用于数字,它会使空字符串 `""` 转换为 `0`。 -3. 带字符串的加法会将数字 `5` 加到字符串之后。 -4. 减法始终将字符串转换为数字,因此它会使 `" -9 "` 转换为数字 `-9`(忽略了字符串首尾的空格)。 -5. `null` 经过数字转换之后会变为 `0`。 -6. `undefined` 经过数字转换之后会变为 `NaN`。 -7. 字符串转换为数字时,会忽略字符串的首尾处的空格字符。在这里,整个字符串由空格字符组成,包括 `\t`、`\n` 以及它们之间的“常规”空格。因此,类似于空字符串,所以会变为 `0`。 diff --git a/1-js/02-first-steps/07-operators/article.md b/1-js/02-first-steps/07-operators/article.md deleted file mode 100644 index 49ac8d764a..0000000000 --- a/1-js/02-first-steps/07-operators/article.md +++ /dev/null @@ -1,444 +0,0 @@ -# 运算符 - -我们从学校里了解到过很多运算符,比如说加号 `+`、乘号 `*`、减号 `-` 等。 - -在这个章节,我们将关注一些在学校数学课程中没有涵盖的运算符。 - -## 术语:「一元运算符」、「二元运算符」、「运算元」 - -在正式开始前,我们先简单浏览一下常用术语。 - -- **运算元** —— 运算符应用的对象。比如说乘法运算 `5 * 2`,有两个运算元:左运算元 `5` 和右运算元 `2`。有时候人们也称其为「参数」。 -- 如果一个运算符对应的只有一个运算元,那么它是 **一元运算符**。比如说一元负号运算符(unary negation)`-`,它的作用是对数字进行正负转换: - - ```js run - let x = 1; - - *!* - x = -x; - */!* - alert( x ); // -1,一元负号运算符生效 - ``` -- 如果一个运算符拥有两个运算元,那么它是 **二元运算符**。减号还存在二元运算符形式: - - ```js run no-beautify - let x = 1, y = 3; - alert( y - x ); // 2,二元运算符减号做减运算 - ``` - - 严格地说,在上面的示例中,我们使用一个相同的符号表征了两个不同的运算符:负号运算符,即反转符号的一元运算符,减法运算符,是从另一个数减去一个数的二进制运算符。 - -## 字符串连接功能,二元运算符 + - -下面,让我们看一下在学校数学课程范围外的 JavaScript 运算符特性。 - -通常,加号 `+` 用于求和。 - -但是如果加号 `+` 被应用于字符串,它将合并(连接)各个字符串: - -```js -let s = "my" + "string"; -alert(s); // mystring -``` - -注意:只要其中一个运算元是字符串,那么另一个运算元也将被转化为字符串。 - -举个例子: - -```js run -alert( '1' + 2 ); // "12" -alert( 2 + '1' ); // "21" -``` - -可以看出,字符串在前和在后并不影响这个规则。简单来说:如果任一运算元是字符串,那么其它运算元也将被转化为字符串。 - -但是,请注意:运算符的运算方向是由左至右。如果是两个数字,后面再跟一个字符串,那么两个数字会先相加,再转化为字符串: - - -```js run -alert(2 + 2 + '1' ); // "41" 而不是 "221" -``` - -字符串连接和转化是二元运算符加号 `+` 的一个特性。其它的数学运算符都只对数字有效。通常,他们会把运算元转化为数字。 - -举个例子,减法和除法: - -```js run -alert( 2 - '1' ); // 1 -alert( '6' / '2' ); // 3 -``` - -## 数字转化功能,一元运算符 + - -加号 `+` 有两种形式。一种是上面我们刚刚讨论的二元运算符,还有一种是一元运算符。 - -一元运算符加号,或者说,加号 `+` 应用于单个值,对数字没有任何作用。但是如果运算元不是数字,加号 `+` 则会将其转化为数字。 - -例如: - -```js run -// 对数字无效 -let x = 1; -alert( +x ); // 1 - -let y = -2; -alert( +y ); // -2 - -*!* -// 转化非数字 -alert( +true ); // 1 -alert( +"" ); // 0 -*/!* -``` - -它的效果和 `Number(...)` 相同,但是更加简短。 - -我们经常会有将字符串转化为数字的需求。比如,如果我们正在从 HTML 表单中取值,通常得到的都是字符串。如果我们想对他们求和,该怎么办? - -二元运算符加号会把他们合并成字符串: - -```js run -let apples = "2"; -let oranges = "3"; - -alert( apples + oranges ); // "23",二元运算符加号合并字符串 -``` - -如果我们想把它们当做数字对待,我们需要转化它们,然后再求和: - -```js run -let apples = "2"; -let oranges = "3"; - -*!* -// 在二元运算符加号起作用之前,所有的值都被转化为了数字 -alert( +apples + +oranges ); // 5 -*/!* - -// 更长的写法 -// alert( Number(apples) + Number(oranges) ); // 5 -``` - -从一个数学家的视角来看,大量的加号可能很奇怪。但是从一个程序员的视角,没什么好奇怪的:一元运算符加号首先起作用,他们将字符串转为数字,然后二元运算符加号对它们进行求和。 - -为什么一元运算符先于二元运算符作用于运算元?接下去我们将讨论到,这是由于它们拥有 **更高的优先级**。 - -## 运算符优先级 - -如果一个表达式拥有超过一个运算符,执行的顺序则由 **优先级** 决定。换句话说,所有的运算符中都隐含着优先级顺序。 - -从小学开始,我们就知道在表达式 `1 + 2 * 2` 中,乘法先于加法计算。这就是一个优先级问题。乘法比加法拥有 **更高的优先级**。 - -圆括号拥有最高优先级,所以如果我们对现有的运算顺序不满意,我们可以使用圆括号来修改运算顺序,就像这样:`(1 + 2) * 2`。 - -在 JavaScript 中有众多运算符。每个运算符都有对应的优先级数字。数字越大,越先执行。如果优先级相同,则按照由左至右的顺序执行。 - -这是一个摘抄自 Mozilla 的 [优先级表](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence)(你没有必要把这全记住,但要记住一元运算符优先级高于二元运算符): - -| 优先级 | 名称 | 符号 | -|------------|------|------| -| ... | ... | ... | -| 17 | 一元加号 | `+` | -| 17 | 一元负号 | `-` | -| 15 | 乘号 | `*` | -| 15 | 除号 | `/` | -| 13 | 加号 | `+` | -| 13 | 减号 | `-` | -| ... | ... | ... | -| 3 | 赋值符 | `=` | -| ... | ... | ... | - -我们可以看到,「一元运算符加号」的优先级是 `16`,高于「二元运算符加号」的优先级 `13`。这也是为什么表达式 `"+apples + +oranges"` 中的一元加号先生效,然后才是二元加法。 - -## 赋值运算符 - -我们知道赋值符号 `=` 也是一个运算符。从优先级表中可以看到它的优先级非常低,只有 `3`。 - -这也是为什么,当我们赋值时,比如 `x = 2 * 2 + 1`,所有的计算先执行,然后 `=` 才执行,将计算结果存储到 `x`。 - -```js -let x = 2 * 2 + 1; - -alert( x ); // 5 -``` - -链式赋值也是可以的: - -```js run -let a, b, c; - -*!* -a = b = c = 2 + 2; -*/!* - -alert( a ); // 4 -alert( b ); // 4 -alert( c ); // 4 -``` - -链式赋值由右向左执行。首先执行最右侧表达式 `2 + 2`,然后将结果赋值给左侧:`c`、`b`、`a`。最后,所有的变量都共享一个值。 - -````smart header="赋值运算符 `\"=\"` 会返回一个值" -每个运算符都有一个返回值。对于以加号 `+` 或者乘号 `*` 为例的大部分运算符而言,这一点很显然。对于赋值运算符而言,这一点同样适用。 - -语句 `x = value` 把 `value` 的值写入 `x` **然后返回 x**。 - -下面是一个在复杂语句中使用赋值的例子: - -```js run -let a = 1; -let b = 2; - -*!* -let c = 3 - (a = b + 1); -*/!* - -alert( a ); // 3 -alert( c ); // 0 -``` - -上面这个例子,`(a = b + 1)` 的结果是赋给 `a` 的值(也就是 `3`)。然后该值被用于进一步的运算。 - -这段代码是不是很好玩儿?我们应该理解它的原理,因为我们有时会在第三方库中见到这样的写法,但我们自己不应该这样写。这样的小技巧让代码变得整洁度和可读性都很差。 -```` - -## 求余运算符 % - -求余运算符 `%` 尽管看上去是个百分号,但它和百分数没有什么关系。 - -`a % b` 的结果是 `a` 除以 `b` 的余数。 - -举个例子: - -```js run -alert( 5 % 2 ); // 1 是 5 / 2 的余数 -alert( 8 % 3 ); // 2 是 8 / 3 的余数 -alert( 6 % 3 ); // 0 是 6 / 3 的余数 -``` - -## 幂运算符 ** - -幂运算符 `**` 是最近被加入到 JavaScript 中的。 - -对于自然数 `b`,`a ** b` 的结果是 `a` 与自己相乘 `b` 次。 - -举个例子: - -```js run -alert( 2 ** 2 ); // 4 (2 * 2) -alert( 2 ** 3 ); // 8 (2 * 2 * 2) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) -``` - -这个运算符对于 `a` 和 `b` 是非整数的情况依然适用。 - -例如: - -```js run -alert( 4 ** (1/2) ); // 2 (1/2 幂相当于开方,这是数学常识) -alert( 8 ** (1/3) ); // 2 (1/3 幂相当于开三次方) -``` - -## 自增/自减 - - - -对一个数进行加一、减一是最常见的数学运算符之一。 - -所以,对此有一些专门的运算符: - -- **自增** `++` 将变量与 1 相加: - - ```js run no-beautify - let counter = 2; - counter++; // 和 counter = counter + 1 效果一样,但是更简洁 - alert( counter ); // 3 - ``` -- **自减** `--` 将变量与 1 相减: - - ```js run no-beautify - let counter = 2; - counter--; // 和 counter = counter - 1 效果一样,但是更简洁 - alert( counter ); // 1 - ``` - -```warn -自增/自减只能应用于变量。试一下,将其应用于数值(比如 `5++`)则会报错。 -``` - -运算符 `++` 和 `--` 可以置于变量前,也可以置于变量后。 - -- 当运算符置于变量后,被称为「后置形式」:`counter++`。 -- 当运算符置于变量前,被称为「前置形式」:`++counter`。 - -两者都做同一件事:将变量 `counter` 与 `1` 相加。 - -那么他们有区别吗?有,但只有当我们使用 `++/--` 的返回值时才能看到区别。 - -详细点说。我们知道,所有的运算符都有返回值。自增/自减也不例外。前置形式返回一个新的值,但后置返回原来的值(做加法/减法之前的值)。 - -为了直观看到区别,看下面的例子: - -```js run -let counter = 1; -let a = ++counter; // (*) - -alert(a); // *!*2*/!* -``` - -`(*)` 所在的行是前置形式 `++counter`,对 `counter` 做自增运算,返回的是新的值 `2`。因此 `alert` 显示的是 `2`。 - -下面让我们看看后置形式: - -```js run -let counter = 1; -let a = counter++; // (*) 将 ++counter 改为 counter++ - -alert(a); // *!*1*/!* -``` - -`(*)` 所在的行是后置形式 `counter++`,它同样对 `counter` 做加法,但是返回的是 **旧值**(做加法之前的值)。因此 `alert` 显示的是 `1`。 - -总结: - -- 如果自增/自减的值不会被使用,那么两者形式没有区别: - - ```js run - let counter = 0; - counter++; - ++counter; - alert( counter ); // 2,以上两行作用相同 - ``` -- 如果我们想要对变量进行自增操作,**并且** 需要立刻使用自增后的值,那么我们需要使用前置形式: - - ```js run - let counter = 0; - alert( ++counter ); // 1 - ``` -- 如果我们想要将一个数加一,但是我们想使用其自增之前的值,那么我们需要使用后置形式: - - ```js run - let counter = 0; - alert( counter++ ); // 0 - ``` - -````smart header="自增/自减和其它运算符的对比" -`++/--` 运算符同样可以在表达式内部使用。它们的优先级比绝大部分的算数运算符要高。 - -举个例子: - -```js run -let counter = 1; -alert( 2 * ++counter ); // 4 -``` - -与下方例子对比: - -```js run -let counter = 1; -alert( 2 * counter++ ); // 2,因为 counter++ 返回的是「旧值」 -``` - -尽管从技术层面上来说可行,但是这样的写法会降低代码的可阅读性。在一行上做多个操作 —— 这样并不好。 - -当阅读代码时,快速的视觉「纵向」扫描会很容易漏掉 `counter++`,这样的自增操作并不明显。 - -我们建议「一行一个操作」模式: - -```js run -let counter = 1; -alert( 2 * counter ); -counter++; -``` -```` - -## 位运算符 - -位运算符把运算元当做 32 位整数,并在它们的二进制表现形式上操作。 - -这些运算符不是 JavaScript 特有的。大部分的编程语言都支持这些运算符。 - -下面是位运算符: - -- 按位与 ( `&` ) -- 按位或 ( `|` ) -- 按位异或 ( `^` ) -- 按位非 ( `~` ) -- 左移 ( `<<` ) -- 右移 ( `>>` ) -- 无符号右移 ( `>>>` ) - -这些操作使用得非常少。为了理解它们,我们需要探讨底层的数字表达形式,现在不是做这个的最好时机。尤其是我们现在不会立刻使用它。如果你感兴趣,可以阅读 MDN 中的 [位运算符](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) 相关文章。当有相关实际需求的时候再去阅读是更明智的选择。 - -## 修改并替换 - -我们经常需要对一个变量进行操作,并把计算得到的新结果存储在这个变量中。 - -举个例子: - -```js -let n = 2; -n = n + 5; -n = n * 2; -``` - -这个操作可以通过使用运算符 `+=` 和 `*=` 进行简化: - -```js run -let n = 2; -n += 5; // now n = 7 (同 n = n + 5) -n *= 2; // now n = 14 (同n = n * 2) - -alert( n ); // 14 -``` - -简短的「修改并替换」 运算符对所有的运算符包括位运算符都有效:`/=`、`-=`等等。 - -这些运算符和正常的赋值运算符拥有相同的优先级,因此它们会在其它大部分运算完成之后运行: - -```js run -let n = 2; - -n *= 3 + 5; - -alert( n ); // 16(右侧计算首先进行,和 n *= 8 相同) -``` - -## 逗号运算符 - -逗号运算符 `,` 是最少见最不常使用的运算符之一。有时候它会被用来写更简短的代码,因此为了能够理解代码,我们需要了解它。 - -逗号运算符能让我们处理多个语句,使用 `,` 将它们分开。每个语句都运行了,但是只有最后的语句的结果会被返回。 - -举个例子: - -```js run -*!* -let a = (1 + 2, 3 + 4); -*/!* - -alert( a ); // 7(3 + 4 的结果) -``` - -这里,第一个语句 `1 + 2` 运行了,但是它的结果被丢弃了。随后计算 `3 + 4`,并且该计算结果被返回。 - -```smart header="逗号运算符的优先级非常低" -请注意逗号运算符的优先级非常低,比 `=` 还要低,因此上面你的例子中圆括号非常重要。 - -如果没有圆括号:`a = 1 + 2, 3 + 4` 会先执行 `+`,将数值相加得到 `a = 3, 7`,然后赋值运算符 `=` 执行, 'a = 3',然后逗号之后的数值 `7` 不会再执行,它被忽略掉了。相当于 `(a = 1 + 2), 3 + 4`。 -``` - -为什么我们需要这样一个运算符,它只返回最后一个值呢? - -有时候,人们会使用它把几个操作放在一行上来进行复杂的运算。 - -举个例子: - -```js -// 一行上有三个运算符 -for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { - ... -} -``` - -这样的技巧在许多 JavaScript 框架中都有使用,这也是为什么我们提到它。但是通常它并不能提升代码的可读性,使用它之前,我们要想清楚。 diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md new file mode 100644 index 0000000000..e92e97c80f --- /dev/null +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -0,0 +1,150 @@ +# Type Conversions + +Most of the time, operators and functions automatically convert the values given to them to the right type. + +For example, `alert` automatically converts any value to a string to show it. Mathematical operations convert values to numbers. + +There are also cases when we need to explicitly convert a value to the expected type. + +```smart header="Not talking about objects yet" +In this chapter, we won't cover objects. For now, we'll just be talking about primitives. + +Later, after we learn about objects, in the chapter we'll see how objects fit in. +``` + +## String Conversion + +String conversion happens when we need the string form of a value. + +For example, `alert(value)` does it to show the value. + +We can also call the `String(value)` function to convert a value to a string: + +```js run +let value = true; +alert(typeof value); // boolean + +*!* +value = String(value); // now value is a string "true" +alert(typeof value); // string +*/!* +``` + +String conversion is mostly obvious. A `false` becomes `"false"`, `null` becomes `"null"`, etc. + +## Numeric Conversion + +Numeric conversion happens in mathematical functions and expressions automatically. + +For example, when division `/` is applied to non-numbers: + +```js run +alert( "6" / "2" ); // 3, strings are converted to numbers +``` + +We can use the `Number(value)` function to explicitly convert a `value` to a number: + +```js run +let str = "123"; +alert(typeof str); // string + +let num = Number(str); // becomes a number 123 + +alert(typeof num); // number +``` + +Explicit conversion is usually required when we read a value from a string-based source like a text form but expect a number to be entered. + +If the string is not a valid number, the result of such a conversion is `NaN`. For instance: + +```js run +let age = Number("an arbitrary string instead of a number"); + +alert(age); // NaN, conversion failed +``` + +Numeric conversion rules: + +| Value | Becomes... | +|-------|-------------| +|`undefined`|`NaN`| +|`null`|`0`| +|true and false | `1` and `0` | +| `string` | Whitespaces 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`. | + +Examples: + +```js run +alert( Number(" 123 ") ); // 123 +alert( Number("123z") ); // NaN (error reading a number at "z") +alert( Number(true) ); // 1 +alert( Number(false) ); // 0 +``` + +Please note that `null` and `undefined` behave differently here: `null` becomes zero while `undefined` becomes `NaN`. + +Most mathematical operators also perform such conversion, we'll see that in the next chapter. + +## Boolean Conversion + +Boolean conversion is the simplest one. + +It happens in logical operations (later we'll meet condition tests and other similar things) but can also be performed explicitly with a call to `Boolean(value)`. + +The conversion rule: + +- Values that are intuitively "empty", like `0`, an empty string, `null`, `undefined`, and `NaN`, become `false`. +- Other values become `true`. + +For instance: + +```js run +alert( Boolean(1) ); // true +alert( Boolean(0) ); // false + +alert( Boolean("hello") ); // true +alert( Boolean("") ); // false +``` + +````warn header="Please note: the string with zero `\"0\"` is `true`" +Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript, a non-empty string is always `true`. + +```js run +alert( Boolean("0") ); // true +alert( Boolean(" ") ); // spaces, also true (any non-empty string is true) +``` +```` + +## Summary + +The three most widely used type conversions are to string, to number, and to boolean. + +**`String Conversion`** -- Occurs when we output something. Can be performed with `String(value)`. The conversion to string is usually obvious for primitive values. + +**`Numeric Conversion`** -- Occurs in math operations. Can be performed with `Number(value)`. + +The conversion follows the rules: + +| Value | Becomes... | +|-------|-------------| +|`undefined`|`NaN`| +|`null`|`0`| +|true / false | `1 / 0` | +| `string` | The string is read "as is", whitespaces from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | + +**`Boolean Conversion`** -- Occurs in logical operations. Can be performed with `Boolean(value)`. + +Follows the rules: + +| Value | Becomes... | +|-------|-------------| +|`0`, `null`, `undefined`, `NaN`, `""` |`false`| +|any other value| `true` | + + +Most of these rules are easy to understand and memorize. The notable exceptions where people usually make mistakes are: + +- `undefined` is `NaN` as a number, not `0`. +- `"0"` and space-only strings like `" "` are true as a boolean. + +Objects aren't covered here. We'll return to them later in the chapter that is devoted exclusively to objects after we learn more basic things about JavaScript. diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md deleted file mode 100644 index 121cb9caef..0000000000 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md +++ /dev/null @@ -1,20 +0,0 @@ - -```js no-beautify -5 > 4 → true -"apple" > "pineapple" → false -"2" > "12" → true -undefined == null → true -undefined === null → false -null == "\n0\n" → false -null === +"\n0\n" → false -``` - -结果的原因: - -1. 数字间比较大小,显然得 true。 -2. 按词典顺序比较,得 false。`"a"` 比 `"p"` 小。 -3. 与第 2 题同理,首位字符 `"2"` 大于 `"1"`。 -4. `null` 只与 `undefined` 互等。 -5. 严格相等模式下,类型不同得 false。 -6. 与第 4 题同理,`null` 只与 `undefined` 相等。 -7. 不同类型严格不相等。 diff --git a/1-js/02-first-steps/08-comparison/article.md b/1-js/02-first-steps/08-comparison/article.md deleted file mode 100644 index f84c731968..0000000000 --- a/1-js/02-first-steps/08-comparison/article.md +++ /dev/null @@ -1,209 +0,0 @@ -# 值的比较 - -我们知道,在数学中有很多用于比较大小的运算符: - -- 大于 / 小于:a > ba < b。 -- 大于等于 / 小于等于:a >= ba <= b。 -- 检测两个值的相等:`a == b`(注意表达式中是两个等号 `=`,若写为单个等号 `a = b` 则表示赋值)。 -- 检测两个值不相等,在数学中使用 符号,而在 JavaScript 中则通过在赋值符号前加叹号表示:a != b。 - -## 比较结果为 Boolean 类型 - -和其他操作符一样,比较操作符也会有返回值,返回值为布尔值(Boolean)。 - -- `true` —— 表示“yes(是)”,“correct(正确)”或“the truth(真相)”。 -- `false` —— 表示“no(否)”,“wrong(错误)”或“not the truth(非真相)”。 - -示例: - -```js run -alert( 2 > 1 ); // true(正确) -alert( 2 == 1 ); // false(错误) -alert( 2 != 1 ); // true(正确) -``` - -和其他类型的值一样,比较的结果可以被赋值给任意变量: - -```js run -let result = 5 > 4; // 把比较的结果赋值给 result -alert( result ); // true -``` - -## 字符串比较 - -在比较字符串的大小时,JavaScript 会使用“字典(dictionary)”或“词典(lexicographical)”顺序进行判定。 - -换言之,字符串是按字符(母)逐个进行比较的。 - -例如: - -```js run -alert( 'Z' > 'A' ); // true -alert( 'Glow' > 'Glee' ); // true -alert( 'Bee' > 'Be' ); // true -``` - -字符串的比较算法非常简单: - -1. 首先比较两个字符串的首位字符大小。 -2. 如果一方字符较大(或较小),则该字符串大于(或小于)另一个字符串。算法结束。 -3. 否则,如果两个字符串的首位字符相等,则继续取出两个字符串各自的后一位字符进行比较。 -4. 重复上述步骤进行比较,直到比较完成某字符串的所有字符为止。 -5. 如果两个字符串的字符同时用完,那么则判定它们相等,否则未结束(还有未比较的字符)的字符串更大。 - -在上面的例子中,`'Z' > 'A'` 在算法的第 1 步就得到了返回结果,而字符串 `Glow` 与 `Glee` 则继续逐个字符比较: - -1. `G` 和 `G` 相等。 -2. `l` 和 `l` 相等。 -3. `o` 比 `e` 大,算法停止,第一个字符串大于第二个。 - -```smart header="非真正的字典顺序,而是 Unicode 编码顺序" -在上面的算法中,比较大小的逻辑与字典或电话簿中的排序很像,但也不完全相同。 - -比如说,字符串比较对字母大小写是敏感的。大写的 `"A"` 并不等于小写的 `"a"`。哪一个更大呢?实际上小写的 `"a"` 更大。这是因为在 JavaScript 使用的内部编码表中(Unicode),小写字母的字符索引值更大。我们会在 这章讨论更多关于字符串的细节。 -``` - -## 不同类型间的比较 - -当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。 - -例如: - -```js run -alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2 -alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1 -``` - -对于布尔类型值,`true` 会被转化为 `1`、`false` 转化为 `0`。 - -例如: - -```js run -alert( true == 1 ); // true -alert( false == 0 ); // true -``` - -````smart header="一个有趣的现象" -有时候,以下两种情况会同时发生: - -- 若直接比较两个值,其结果是相等的。 -- 若把两个值转为布尔值,它们可能得出完全相反的结果,即一个是 `true`,一个是 `false`。 - -例如: - -```js run -let a = 0; -alert( Boolean(a) ); // false - -let b = "0"; -alert( Boolean(b) ); // true - -alert(a == b); // true! -``` - -对于 JavaScript 而言,这种现象其实挺正常的。因为 JavaScript 会把待比较的值转化为数字后再做比较(因此 `"0"` 变成了 `0`)。若只是将一个变量转化为 `Boolean` 值,则会使用其他的类型转换规则。 -```` - -## 严格相等 - -普通的相等性检查 `==` 存在一个问题,它不能区分出 `0` 和 `false`: - -```js run -alert( 0 == false ); // true -``` - -也同样无法区分空字符串和 `false`: - -```js run -alert( '' == false ); // true -``` - -这是因为在比较不同类型的值时,处于相等判断符号 `==` 两侧的值会先被转化为数字。空字符串和 `false` 也是如此,转化后它们都为数字 0。 - -如果我们需要区分 `0` 和 `false`,该怎么办? - -**严格相等操作符 `===` 在进行比较时不会做任何的类型转换。** - -换句话说,如果 `a` 和 `b` 属于不同的数据类型,那么 `a === b` 不会做任何的类型转换而立刻返回 `false`。 - -让我们试试: - -```js run -alert( 0 === false ); // false,因为被比较值的数据类型不同 -``` - -同样的,与“不相等”符号 `!=` 类似,“严格不相等”表示为 `!==`。 - -严格相等的操作符虽然写起来稍微长一些,但是它能够很清楚地显示代码意图,降低你犯错的可能性。 - -## 对 null 和 undefined 进行比较 - -当使用 `null` 或 `undefined` 与其他值进行比较时,其返回结果常常出乎你的意料。 - -当使用严格相等 `===` 比较二者时 -: 它们不相等,因为它们属于不同的类型。 - - ```js run - alert( null === undefined ); // false - ``` - -当使用非严格相等 `==` 比较二者时 -: JavaScript 存在一个特殊的规则,会判定它们相等。他们俩就像“一对恋人”,仅仅等于对方而不等于其他任何的值(只在非严格相等下成立)。 - - ```js run - alert( null == undefined ); // true - ``` - -当使用数学式或其他比较方法 `< > <= >=` 时: -: `null/undefined` 会被转化为数字:`null` 被转化为 `0`,`undefined` 被转化为 `NaN`。 - -下面让我们看看,这些规则会带来什么有趣的现象。同时更重要的是,我们需要从中学会如何远离这些特性带来的“陷阱”。 - -### 奇怪的结果:null vs 0 - -通过比较 `null` 和 0 可得: - -```js run -alert( null > 0 ); // (1) false -alert( null == 0 ); // (2) false -alert( null >= 0 ); // (3) *!*true*/!* -``` - -是的,上面的结果完全打破了你对数学的认识。在最后一行代码显示“`null` 大于等于 0”的情况下,前两行代码中一定会有一个是正确的,然而事实表明它们的结果都是 false。 - -为什么会出现这种反常结果,这是因为相等性检测 `==` 和普通比较符 `> < >= <=` 的代码逻辑是相互独立的。进行值的比较时,`null` 会被转化为数字,因此它被转化为了 `0`。这就是为什么(3)中 `null >= 0` 返回值是 true,(1)中 `null > 0` 返回值是 false。 - -另一方面,`undefined` 和 `null` 在相等性检测 `==` 中不会进行任何的类型转换,它们有自己独立的比较规则,所以除了它们之间互等外,不会等于任何其他的值。这就解释了为什么(2)中 `null == 0` 会返回 false。 - -### 特立独行的 undefined - -`undefined` 不应该被与其他值进行比较: - -```js run -alert( undefined > 0 ); // false (1) -alert( undefined < 0 ); // false (2) -alert( undefined == 0 ); // false (3) -``` - -为何它看起来如此厌恶 0?返回值都是 false! - -原因如下: - -- `(1)` 和 `(2)` 都返回 `false` 是因为 `undefined` 在比较中被转换为了 `NaN`,而 `NaN` 是一个特殊的数值型值,它与任何值进行比较都会返回 `false`。 -- `(3)` 返回 `false` 是因为这是一个相等性检测,而 `undefined` 只与 `null` 相等,不会与其他值相等。 - -### 规避错误 - -我们为何要研究上述示例?我们需要时刻记得这些古怪的规则吗?不,其实不需要。虽然随着代码写得越来越多,我们对这些规则也都会烂熟于胸,但是我们需要更为可靠的方法来避免潜在的问题: - -除了严格相等 `===` 外,其他凡是有 `undefined/null` 参与的比较,我们都需要额外小心。 - -除非你非常清楚自己在做什么,否则永远不要使用 `>= > < <=` 去比较一个可能为 `null/undefined` 的变量。对于取值可能是 `null/undefined` 的变量,请按需要分别检查它的取值情况。 - -## 总结 - -- 比较运算符始终返回布尔值。 -- 字符串的比较,会按照“词典”顺序逐字符地比较大小。 -- 当对不同类型的值进行比较时,它们会先被转化为数字(不包括严格相等检测)再进行比较。 -- 在非严格相等 `==` 下,`null` 和 `undefined` 相等且各自不等于任何其他的值。 -- 在使用 `>` 或 `<` 进行比较时,需要注意变量可能为 `null/undefined` 的情况。比较好的方法是单独检查变量是否等于 `null/undefined`。 diff --git a/1-js/02-first-steps/08-operators/1-increment-order/solution.md b/1-js/02-first-steps/08-operators/1-increment-order/solution.md new file mode 100644 index 0000000000..8a44d798eb --- /dev/null +++ b/1-js/02-first-steps/08-operators/1-increment-order/solution.md @@ -0,0 +1,18 @@ + +The answer is: + +- `a = 2` +- `b = 2` +- `c = 2` +- `d = 1` + +```js run no-beautify +let a = 1, b = 1; + +alert( ++a ); // 2, prefix form returns the new value +alert( b++ ); // 1, postfix form returns the old value + +alert( a ); // 2, incremented once +alert( b ); // 2, incremented once +``` + diff --git a/1-js/02-first-steps/08-operators/1-increment-order/task.md b/1-js/02-first-steps/08-operators/1-increment-order/task.md new file mode 100644 index 0000000000..7db0923890 --- /dev/null +++ b/1-js/02-first-steps/08-operators/1-increment-order/task.md @@ -0,0 +1,14 @@ +importance: 5 + +--- + +# The postfix and prefix forms + +What are the final values of all variables `a`, `b`, `c` and `d` after the code below? + +```js +let a = 1, b = 1; + +let c = ++a; // ? +let d = b++; // ? +``` diff --git a/1-js/02-first-steps/08-operators/2-assignment-result/solution.md b/1-js/02-first-steps/08-operators/2-assignment-result/solution.md new file mode 100644 index 0000000000..e3113b4cd3 --- /dev/null +++ b/1-js/02-first-steps/08-operators/2-assignment-result/solution.md @@ -0,0 +1,5 @@ +The answer is: + +- `a = 4` (multiplied by 2) +- `x = 5` (calculated as 1 + 4) + diff --git a/1-js/02-first-steps/08-operators/2-assignment-result/task.md b/1-js/02-first-steps/08-operators/2-assignment-result/task.md new file mode 100644 index 0000000000..5345c9485b --- /dev/null +++ b/1-js/02-first-steps/08-operators/2-assignment-result/task.md @@ -0,0 +1,13 @@ +importance: 3 + +--- + +# Assignment result + +What are the values of `a` and `x` after the code below? + +```js +let a = 2; + +let x = 1 + (a *= 2); +``` 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 new file mode 100644 index 0000000000..dfd061cb68 --- /dev/null +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -0,0 +1,25 @@ + +```js no-beautify +"" + 1 + 0 = "10" // (1) +"" - 1 + 0 = -1 // (2) +true + false = 1 +6 / "3" = 2 +"2" * "3" = 6 +4 + 5 + "px" = "9px" +"$" + 4 + 5 = "$45" +"4" - 2 = 2 +"4px" - 2 = NaN +" -9 " + 5 = " -9 5" // (3) +" -9 " - 5 = -14 // (4) +null + 1 = 1 // (5) +undefined + 1 = NaN // (6) +" \t \n" - 2 = -2 // (7) +``` + +1. 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`. diff --git a/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md similarity index 60% rename from 1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md rename to 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md index 3bd73f0d8d..068420c7d3 100644 --- a/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 类型转换 +# Type conversions -下面这些表达式的结果是什么? +What are results of these expressions? ```js no-beautify "" + 1 + 0 @@ -16,7 +16,6 @@ true + false "$" + 4 + 5 "4" - 2 "4px" - 2 -7 / 0 " -9 " + 5 " -9 " - 5 null + 1 @@ -24,4 +23,4 @@ undefined + 1 " \t \n" - 2 ``` -好好思考一下,把它们写下来然后和答案比较一下。 +Think well, write down and then compare with the answer. 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 new file mode 100644 index 0000000000..209a0702c4 --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md @@ -0,0 +1,32 @@ +The reason is that prompt returns user input as a string. + +So variables have values `"1"` and `"2"` respectively. + +```js run +let a = "1"; // prompt("First number?", 1); +let b = "2"; // prompt("Second number?", 2); + +alert(a + b); // 12 +``` + +What we should do is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`. + +For example, right before `prompt`: + +```js run +let a = +prompt("First number?", 1); +let b = +prompt("Second number?", 2); + +alert(a + b); // 3 +``` + +Or in the `alert`: + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); + +alert(+a + +b); // 3 +``` + +Using both unary and binary `+` in the latest code. Looks funny, doesn't it? diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/task.md b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md new file mode 100644 index 0000000000..b3ea4a3a3c --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md @@ -0,0 +1,18 @@ +importance: 5 + +--- + +# Fix the addition + +Here's a code that asks the user for two numbers and shows their sum. + +It works incorrectly. The output in the example below is `12` (for default prompt values). + +Why? Fix it. The result should be `3`. + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); + +alert(a + b); // 12 +``` diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md new file mode 100644 index 0000000000..fbf2cbf9a8 --- /dev/null +++ b/1-js/02-first-steps/08-operators/article.md @@ -0,0 +1,479 @@ +# Basic operators, maths + +We know many operators from school. They are things like addition `+`, multiplication `*`, subtraction `-`, and so on. + +In this chapter, we’ll start with simple operators, then concentrate on JavaScript-specific aspects, not covered by school arithmetic. + +## Terms: "unary", "binary", "operand" + +Before we move on, let's grasp some common terminology. + +- *An operand* -- is what operators are applied to. For instance, in the multiplication of `5 * 2` there are two operands: the left operand is `5` and the right operand is `2`. Sometimes, people call these "arguments" instead of "operands". +- An operator is *unary* if it has a single operand. For example, the unary negation `-` reverses the sign of a number: + + ```js run + let x = 1; + + *!* + x = -x; + */!* + alert( x ); // -1, unary negation was applied + ``` +- An operator is *binary* if it has two operands. The same minus exists in binary form as well: + + ```js run no-beautify + let x = 1, y = 3; + alert( y - x ); // 2, binary minus subtracts values + ``` + + Formally, in the examples above we have two different operators that share the same symbol: the negation operator, a unary operator that reverses the sign, and the subtraction operator, a binary operator that subtracts one number from another. + +## Maths + +The following math operations are supported: + +- Addition `+`, +- Subtraction `-`, +- Multiplication `*`, +- Division `/`, +- Remainder `%`, +- Exponentiation `**`. + +The first four are straightforward, while `%` and `**` need a few words about them. + +### Remainder % + +The remainder operator `%`, despite its appearance, is not related to percents. + +The result of `a % b` is the [remainder](https://en.wikipedia.org/wiki/Remainder) of the integer division of `a` by `b`. + +For instance: + +```js run +alert( 5 % 2 ); // 1, a remainder of 5 divided by 2 +alert( 8 % 3 ); // 2, a remainder of 8 divided by 3 +``` + +### Exponentiation ** + +The exponentiation operator `a ** b` raises `a` to the power of `b`. + +In school maths, we write that as ab. + +For instance: + +```js run +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 ½: + +```js run +alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root) +alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) +``` + + +## String concatenation with binary + + +Let's meet features of JavaScript operators that are beyond school arithmetics. + +Usually, the plus operator `+` sums numbers. + +But, if the binary `+` is applied to strings, it merges (concatenates) them: + +```js +let s = "my" + "string"; +alert(s); // mystring +``` + +Note that if any of the operands is a string, then the other one is converted to a string too. + +For example: + +```js run +alert( '1' + 2 ); // "12" +alert( 2 + '1' ); // "21" +``` + +See, it doesn't matter whether the first operand is a string or the second one. + +Here's a more complex example: + +```js run +alert(2 + 2 + '1' ); // "41" and not "221" +``` + +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"`. + +The binary `+` is the only operator that supports strings in such a way. Other arithmetic operators work only with numbers and always convert their operands to numbers. + +Here's the demo for subtraction and division: + +```js run +alert( 6 - '2' ); // 4, converts '2' to a number +alert( '6' / '2' ); // 3, converts both operands to numbers +``` + +## Numeric conversion, unary + + +The plus `+` exists in two forms: the binary form that we used above and the unary form. + +The unary plus or, in other words, the plus operator `+` applied to a single value, doesn't do anything to numbers. But if the operand is not a number, the unary plus converts it into a number. + +For example: + +```js run +// No effect on numbers +let x = 1; +alert( +x ); // 1 + +let y = -2; +alert( +y ); // -2 + +*!* +// Converts non-numbers +alert( +true ); // 1 +alert( +"" ); // 0 +*/!* +``` + +It actually does the same thing as `Number(...)`, but is shorter. + +The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. What if we want to sum them? + +The binary plus would add them as strings: + +```js run +let apples = "2"; +let oranges = "3"; + +alert( apples + oranges ); // "23", the binary plus concatenates strings +``` + +If we want to treat them as numbers, we need to convert and then sum them: + +```js run +let apples = "2"; +let oranges = "3"; + +*!* +// both values converted to numbers before the binary plus +alert( +apples + +oranges ); // 5 +*/!* + +// the longer variant +// alert( Number(apples) + Number(oranges) ); // 5 +``` + +From a mathematician's standpoint, the abundance of pluses may seem strange. But from a programmer's standpoint, there's nothing special: unary pluses are applied first, they convert strings to numbers, and then the binary plus sums them up. + +Why are unary pluses applied to values before the binary ones? As we're going to see, that's because of their *higher precedence*. + +## Operator precedence + +If an expression has more than one operator, the execution order is defined by their *precedence*, or, in other words, the default priority order of operators. + +From school, we all know that the multiplication in the expression `1 + 2 * 2` should be calculated before the addition. That's exactly the precedence thing. The multiplication is said to have *a higher precedence* than the addition. + +Parentheses override any precedence, so if we're not satisfied with the default order, we can use them to change it. For example, write `(1 + 2) * 2`. + +There are many operators in JavaScript. Every operator has a corresponding precedence number. The one with the larger number executes first. If the precedence is the same, the execution order is from left to right. + +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 | +|------------|------|------| +| ... | ... | ... | +| 15 | unary plus | `+` | +| 15 | unary negation | `-` | +| 14 | exponentiation | `**` | +| 13 | multiplication | `*` | +| 13 | division | `/` | +| 12 | addition | `+` | +| 12 | subtraction | `-` | +| ... | ... | ... | +| 2 | assignment | `=` | +| ... | ... | ... | + +As we can see, the "unary plus" has a priority of `15` which is higher than the `12` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. + +## Assignment + +Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `2`. + +That's why, when we assign a variable, like `x = 2 * 2 + 1`, the calculations are done first and then the `=` is evaluated, storing the result in `x`. + +```js +let x = 2 * 2 + 1; + +alert( x ); // 5 +``` + +### Assignment = returns a value + +The fact of `=` being an operator, not a "magical" language construct has an interesting implication. + +All operators in JavaScript return a value. That's obvious for `+` and `-`, but also true for `=`. + +The call `x = value` writes the `value` into `x` *and then returns it*. + +Here's a demo that uses an assignment as part of a more complex expression: + +```js run +let a = 1; +let b = 2; + +*!* +let c = 3 - (a = b + 1); +*/!* + +alert( a ); // 3 +alert( c ); // 0 +``` + +In the example above, the result of expression `(a = b + 1)` is the value which was assigned to `a` (that is `3`). It is then used for further evaluations. + +Funny code, isn't it? We should understand how it works, because sometimes we see it in JavaScript libraries. + +Although, please don't write the code like that. Such tricks definitely don't make code clearer or readable. + +### Chaining assignments + +Another interesting feature is the ability to chain assignments: + +```js run +let a, b, c; + +*!* +a = b = c = 2 + 2; +*/!* + +alert( a ); // 4 +alert( b ); // 4 +alert( c ); // 4 +``` + +Chained assignments evaluate from right to left. First, the rightmost expression `2 + 2` is evaluated and then assigned to the variables on the left: `c`, `b` and `a`. At the end, all the variables share a single value. + +Once again, for the purposes of readability it's better to split such code into few lines: + +```js +c = 2 + 2; +b = c; +a = c; +``` +That's easier to read, especially when eye-scanning the code fast. + +## Modify-in-place + +We often need to apply an operator to a variable and store the new result in that same variable. + +For example: + +```js +let n = 2; +n = n + 5; +n = n * 2; +``` + +This notation can be shortened using the operators `+=` and `*=`: + +```js run +let n = 2; +n += 5; // now n = 7 (same as n = n + 5) +n *= 2; // now n = 14 (same as n = n * 2) + +alert( n ); // 14 +``` + +Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=`, etc. + +Such operators have the same precedence as a normal assignment, so they run after most other calculations: + +```js run +let n = 2; + +n *= 3 + 5; + +alert( n ); // 16 (right part evaluated first, same as n *= 8) +``` + +## Increment/decrement + + + +Increasing or decreasing a number by one is among the most common numerical operations. + +So, there are special operators for it: + +- **Increment** `++` increases a variable by 1: + + ```js run no-beautify + let counter = 2; + counter++; // works the same as counter = counter + 1, but is shorter + alert( counter ); // 3 + ``` +- **Decrement** `--` decreases a variable by 1: + + ```js run no-beautify + let counter = 2; + counter--; // works the same as counter = counter - 1, but is shorter + alert( counter ); // 1 + ``` + +```warn +Increment/decrement can only be applied to variables. Trying to use it on a value like `5++` will give an error. +``` + +The operators `++` and `--` can be placed either before or after a variable. + +- When the operator goes after the variable, it is in "postfix form": `counter++`. +- The "prefix form" is when the operator goes before the variable: `++counter`. + +Both of these statements do the same thing: increase `counter` by `1`. + +Is there any difference? Yes, but we can only see it if we use the returned value of `++/--`. + +Let's clarify. As we know, all operators return a value. Increment/decrement is no exception. The prefix form returns the new value while the postfix form returns the old value (prior to increment/decrement). + +To see the difference, here's an example: + +```js run +let counter = 1; +let a = ++counter; // (*) + +alert(a); // *!*2*/!* +``` + +In the line `(*)`, the *prefix* form `++counter` increments `counter` and returns the new value, `2`. So, the `alert` shows `2`. + +Now, let's use the postfix form: + +```js run +let counter = 1; +let a = counter++; // (*) changed ++counter to counter++ + +alert(a); // *!*1*/!* +``` + +In the line `(*)`, the *postfix* form `counter++` also increments `counter` but returns the *old* value (prior to increment). So, the `alert` shows `1`. + +To summarize: + +- If the result of increment/decrement is not used, there is no difference in which form to use: + + ```js run + let counter = 0; + counter++; + ++counter; + alert( counter ); // 2, the lines above did the same + ``` +- If we'd like to increase a value *and* immediately use the result of the operator, we need the prefix form: + + ```js run + let counter = 0; + alert( ++counter ); // 1 + ``` +- If we'd like to increment a value but use its previous value, we need the postfix form: + + ```js run + let counter = 0; + alert( counter++ ); // 0 + ``` + +````smart header="Increment/decrement among other operators" +The operators `++/--` can be used inside expressions as well. Their precedence is higher than most other arithmetical operations. + +For instance: + +```js run +let counter = 1; +alert( 2 * ++counter ); // 4 +``` + +Compare with: + +```js run +let counter = 1; +alert( 2 * counter++ ); // 2, because counter++ returns the "old" value +``` + +Though technically okay, such notation usually makes code less readable. One line does multiple things -- not good. + +While reading code, a fast "vertical" eye-scan can easily miss something like `counter++` and it won't be obvious that the variable increased. + +We advise a style of "one line -- one action": + +```js run +let counter = 1; +alert( 2 * counter ); +counter++; +``` +```` + +## Bitwise operators + +Bitwise operators treat arguments as 32-bit integer numbers and work on the level of their binary representation. + +These operators are not JavaScript-specific. They are supported in most programming languages. + +The list of operators: + +- AND ( `&` ) +- OR ( `|` ) +- XOR ( `^` ) +- NOT ( `~` ) +- LEFT SHIFT ( `<<` ) +- RIGHT SHIFT ( `>>` ) +- ZERO-FILL RIGHT SHIFT ( `>>>` ) + +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. + +## Comma + +The comma operator `,` is one of the rarest and most unusual operators. Sometimes, it's used to write shorter code, so we need to know it in order to understand what's going on. + +The comma operator allows us to evaluate several expressions, dividing them with a comma `,`. Each of them is evaluated but only the result of the last one is returned. + +For example: + +```js run +*!* +let a = (1 + 2, 3 + 4); +*/!* + +alert( a ); // 7 (the result of 3 + 4) +``` + +Here, the first expression `1 + 2` is evaluated and its result is thrown away. Then, `3 + 4` is evaluated and returned as the result. + +```smart header="Comma has a very low precedence" +Please note that the comma operator has very low precedence, lower than `=`, so parentheses are important in the example above. + +Without them: `a = 1 + 2, 3 + 4` evaluates `+` first, summing the numbers into `a = 3, 7`, then the assignment operator `=` assigns `a = 3`, and the rest is ignored. It's like `(a = 1 + 2), 3 + 4`. +``` + +Why do we need an operator that throws away everything except the last expression? + +Sometimes, people use it in more complex constructs to put several actions in one line. + +For example: + +```js +// three operations in one line +for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { + ... +} +``` + +Such tricks are used in many JavaScript frameworks. That's why we're mentioning them. But usually they don't improve code readability so we should think well before using them. diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md deleted file mode 100644 index b7cc537bbd..0000000000 --- a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md +++ /dev/null @@ -1,9 +0,0 @@ -importance: 4 - ---- - -# 创建一个简单的页面 - -创建一个要求用户输入 `name`,并通过浏览器窗口对键入的内容进行输出的 web 页面。 - -[demo] diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/article.md b/1-js/02-first-steps/09-alert-prompt-confirm/article.md deleted file mode 100644 index 75975bb9f5..0000000000 --- a/1-js/02-first-steps/09-alert-prompt-confirm/article.md +++ /dev/null @@ -1,109 +0,0 @@ -# 交互:alert、prompt 和 confirm - -本教程的这部分内容主要使用原生 JavaScript,你无需针对特定环境进行调整。 - -但我们仍然会使用浏览器作为演示环境。所以我们至少应该知道一些用户界面函数。在这一节,我们一起来熟悉一下浏览器中 `alert`、`prompt` 和 `confirm` 函数的用法。 - -## alert - -语法: - -```js -alert(message); -``` - -运行这行代码,浏览器会弹出一个信息弹窗并暂停脚本,直到用户点击了“确定”。 - -举个例子: - -```js run -alert("Hello"); -``` - -弹出的这个带有信息的小窗口被称为 **模态窗**。"modal" 意味着用户不能与页面的其他部分(例如点击其他按钮等)进行交互,直到他们处理完窗口。在上面示例这种情况下 —— 直到用户点击“确定”按钮。 - -## prompt - -`prompt` 函数接收两个参数: - -```js no-beautify -result = prompt(title, [default]); -``` - -浏览器会显示一个带有文本消息的模态窗口,还有 input 框和确定/取消按钮。 - -`title` -: 显示给用户的文本 - -`default` -: 可选的第二个参数,指定 input 框的初始值。 - -用户可以在 prompt 对话框的 input 框内输入一些内容,然后点击确定。或者他们可以通过按“取消”按钮或按下键盘的 `key:Esc` 键,以取消输入。 - -`prompt` 将返回用户在 `input` 框内输入的文本,如果用户取消了输入,则返回 `null`。 - -举个例子: - -```js run -let age = prompt('How old are you?', 100); - -alert(`You are ${age} years old!`); // You are 100 years old! -``` - -````warn header="IE 浏览器会提供默认值" -第二个参数是可选的。但是如果我们不提供的话,Internet Explorer 会把 `"undefined"` 插入到 prompt。 - -我们可以在 Internet Explorer 中运行下面这行代码来看看效果: - -```js run -let test = prompt("Test"); -``` - -所以,为了 prompt 在 IE 中有好的效果,我们建议始终提供第二个参数: - -```js run -let test = prompt("Test", ''); // <-- for IE -``` -```` - -## confirm - -语法: - -```js -result = confirm(question); -``` - -`confirm` 函数显示一个带有 `question` 以及确定和取消两个按钮的模态窗口。 - -点击确定返回 `true`,点击取消返回 `false`。 - -例如: - -```js run -let isBoss = confirm("Are you the boss?"); - -alert( isBoss ); // 如果“确定”按钮被按下,则显示 true -``` - -## 总结 - -我们学习了与用户交互的 3 个浏览器的特定函数: - -`alert` -: 显示信息。 - -`prompt` -: 显示信息要求用户输入文本。点击确定返回文本,点击取消或按下 `key:Esc` 键返回 `null`。 - -`confirm` -: 显示信息等待用户点击确定或取消。点击确定返回 `true`,点击取消或按下 `key:Esc` 键返回 `false`。 - -这些方法都是模态的:它们暂停脚本的执行,并且不允许用户与该页面的其余部分进行交互,直到窗口被解除。 - -上述所有方法共有两个限制: - -1. 模态窗口的确切位置由浏览器决定。通常在页面中心。 -2. 窗口的确切外观也取决于浏览器。我们不能修改它。 - -这就是简单的代价。还有其他一些方法可以显示更漂亮的窗口,并与用户进行更丰富的交互,但如果“花里胡哨”不是非常重要,那使用本节讲的这些方法也挺好。 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 new file mode 100644 index 0000000000..632b1cf4ea --- /dev/null +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md @@ -0,0 +1,21 @@ + + +```js no-beautify +5 > 4 → true +"apple" > "pineapple" → false +"2" > "12" → true +undefined == null → true +undefined === null → false +null == "\n0\n" → false +null === +"\n0\n" → false +``` + +Some of the reasons: + +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. diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md similarity index 71% rename from 1-js/02-first-steps/08-comparison/1-comparison-questions/task.md rename to 1-js/02-first-steps/09-comparison/1-comparison-questions/task.md index 2c4446fefe..be7f75ddd9 100644 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 值的比较 +# Comparisons -以下表达式的执行结果是? +What will be the result for these expressions? ```js no-beautify 5 > 4 diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md new file mode 100644 index 0000000000..a69317feea --- /dev/null +++ b/1-js/02-first-steps/09-comparison/article.md @@ -0,0 +1,216 @@ +# Comparisons + +We know many comparison operators from maths. + +In JavaScript they are written like this: + +- 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. + +## Boolean is the result + +All comparison operators return a boolean value: + +- `true` -- means "yes", "correct" or "the truth". +- `false` -- means "no", "wrong" or "not the truth". + +For example: + +```js run +alert( 2 > 1 ); // true (correct) +alert( 2 == 1 ); // false (wrong) +alert( 2 != 1 ); // true (correct) +``` + +A comparison result can be assigned to a variable, just like any value: + +```js run +let result = 5 > 4; // assign the result of the comparison +alert( result ); // true +``` + +## String comparison + +To see whether a string is greater than another, JavaScript uses the so-called "dictionary" or "lexicographical" order. + +In other words, strings are compared letter-by-letter. + +For example: + +```js run +alert( 'Z' > 'A' ); // true +alert( 'Glow' > 'Glee' ); // true +alert( 'Bee' > 'Be' ); // true +``` + +The algorithm to compare two strings is simple: + +1. Compare the first character of both strings. +2. If the first character from the first string is greater (or less) than the other string's, then the first string is greater (or less) than the second. We're done. +3. Otherwise, if both strings' first characters are the same, compare the second characters the same way. +4. Repeat until the end of either string. +5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater. + +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: + +1. `G` is the same as `G`. +2. `l` is the same as `l`. +3. `o` is greater than `e`. Stop here. The first string is greater. + +```smart header="Not a real dictionary, but Unicode order" +The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it's not exactly the same. + +For instance, case matters. A capital letter `"A"` is not equal to the lowercase `"a"`. Which one is greater? The lowercase `"a"`. Why? Because the lowercase character has a greater index in the internal encoding table JavaScript uses (Unicode). We'll get back to specific details and consequences of this in the chapter . +``` + +## Comparison of different types + +When comparing values of different types, JavaScript converts the values to numbers. + +For example: + +```js run +alert( '2' > 1 ); // true, string '2' becomes a number 2 +alert( '01' == 1 ); // true, string '01' becomes a number 1 +``` + +For boolean values, `true` becomes `1` and `false` becomes `0`. + +For example: + +```js run +alert( true == 1 ); // true +alert( false == 0 ); // true +``` + +````smart header="A funny consequence" +It is possible that at the same time: + +- Two values are equal. +- One of them is `true` as a boolean and the other one is `false` as a boolean. + +For example: + +```js run +let a = 0; +alert( Boolean(a) ); // false + +let b = "0"; +alert( Boolean(b) ); // true + +alert(a == b); // true! +``` + +From JavaScript's standpoint, this result is quite normal. An equality check converts values using the numeric conversion (hence `"0"` becomes `0`), while the explicit `Boolean` conversion uses another set of rules. +```` + +## Strict equality + +A regular equality check `==` has a problem. It cannot differentiate `0` from `false`: + +```js run +alert( 0 == false ); // true +``` + +The same thing happens with an empty string: + +```js run +alert( '' == false ); // true +``` + +This happens because operands of different types are converted to numbers by the equality operator `==`. An empty string, just like `false`, becomes a zero. + +What to do if we'd like to differentiate `0` from `false`? + +**A strict equality operator `===` checks the equality without type conversion.** + +In other words, if `a` and `b` are of different types, then `a === b` immediately returns `false` without an attempt to convert them. + +Let's try it: + +```js run +alert( 0 === false ); // false, because the types are different +``` + +There is also a "strict non-equality" operator `!==` analogous to `!=`. + +The strict equality operator is a bit longer to write, but makes it obvious what's going on and leaves less room for errors. + +## Comparison with null and undefined + +There's a non-intuitive behavior when `null` or `undefined` are compared to other values. + +For a strict equality check `===` +: These values are different, because each of them is a different type. + + ```js run + alert( null === undefined ); // false + ``` + +For a non-strict check `==` +: There's a special rule. These two are a "sweet couple": they equal each other (in the sense of `==`), but not any other value. + + ```js run + alert( null == undefined ); // true + ``` + +For maths and other comparisons `< > <= >=` +: `null/undefined` are converted to numbers: `null` becomes `0`, while `undefined` becomes `NaN`. + +Now let's see some funny things that happen when we apply these rules. And, what's more important, how to not fall into a trap with them. + +### Strange result: null vs 0 + +Let's compare `null` with a zero: + +```js run +alert( null > 0 ); // (1) false +alert( null == 0 ); // (2) false +alert( null >= 0 ); // (3) *!*true*/!* +``` + +Mathematically, that's strange. The last result states that "`null` is greater than or equal to zero", so in one of the comparisons above it must be `true`, but they are both false. + +The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, treating it as `0`. That's why (3) `null >= 0` is true and (1) `null > 0` is false. + +On the other hand, the equality check `==` for `undefined` and `null` is defined such that, without any conversions, they equal each other and don't equal anything else. That's why (2) `null == 0` is false. + +### An incomparable undefined + +The value `undefined` shouldn't be compared to other values: + +```js run +alert( undefined > 0 ); // false (1) +alert( undefined < 0 ); // false (2) +alert( undefined == 0 ); // false (3) +``` + +Why does it dislike zero so much? Always false! + +We get these results because: + +- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN` and `NaN` is a special numeric value which returns `false` for all comparisons. +- The equality check `(3)` returns `false` because `undefined` only equals `null`, `undefined`, and no other value. + +### Avoid problems + +Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to avoid problems with them: + +- Treat any comparison with `undefined/null` except the strict equality `===` with exceptional care. +- Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately. + +## Summary + +- Comparison operators return a boolean value. +- Strings are compared letter-by-letter in the "dictionary" order. +- When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check). +- The values `null` and `undefined` equal `==` each other and do not equal any other value. +- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Checking for `null/undefined` separately is a good idea. diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md index a0b5dc4f6c..51f1d46809 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md @@ -1,11 +1,12 @@ -**是的,它会** +**Yes, it will.** -任何非空字符串(`"0"` 不是空字符串)的逻辑值都是 `true`。 +Any string except an empty one (and `"0"` is not empty) becomes `true` in the logical context. -我们可以执行下面的代码来进行验证: +We can run and check: ```js run if ("0") { alert( 'Hello' ); } ``` + diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md index 90597ed2d0..5f16cda857 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md @@ -2,12 +2,13 @@ importance: 5 --- -# if(值为 0 的字符串) +# if (a string with zero) -`alert` 弹窗会出来吗? +Will `alert` be shown? ```js if ("0") { alert( 'Hello' ); } ``` + diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg index 7f57f46756..47b020aab1 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg @@ -1 +1 @@ -开始你不知道? “ECMAScript”!答对了!JavaScript 的 “官方”名称 是什么?其他ECMAScript +BeginYou don't know? “ECMAScript”!Right!What's the “official” name of JavaScript?OtherECMAScript \ No newline at end of file diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index 0ca4d36046..4305584fa6 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 @@ -2,13 +2,12 @@ importance: 2 --- -# JavaScript 的名字 +# The name of JavaScript -使用 `if..else` 结构,实现提问 "What is the "official" name of JavaScript?" 的代码 +Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?' -如果访问者输入了 "ECMAScript",输出就提示 "Right!",否则 — 输出:"Didn't know? ECMAScript!" +If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!" ![](ifelse_task2.svg) [demo src="ifelse_task2"] - diff --git a/1-js/02-first-steps/10-ifelse/3-sign/task.md b/1-js/02-first-steps/10-ifelse/3-sign/task.md index 6400a93505..0c5d0e0084 100644 --- a/1-js/02-first-steps/10-ifelse/3-sign/task.md +++ b/1-js/02-first-steps/10-ifelse/3-sign/task.md @@ -2,14 +2,14 @@ importance: 2 --- -# 显示符号 +# Show the sign -使用 `if..else` 语句,编写代码实现通过 `prompt` 获取一个数字并用 `alert` 显示结果: +Using `if..else`, write the code which gets a number via `prompt` and then shows in `alert`: -- 如果这个数字大于 0,就显示 `1`, -- 如果这个数字小于 0,就显示 `-1`, -- 如果这个数字等于 0,就显示 `0`。 +- `1`, if the value is greater than zero, +- `-1`, if less than zero, +- `0`, if equals zero. -在这个任务中,我们假设输入永远是一个数字。 +In this task we assume that the input is always a number. [demo src="if_sign"] diff --git a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md index 5618c85357..6bdf8453ea 100644 --- a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md +++ b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 使用 '?' 重写 'if' 语句 +# Rewrite 'if' into '?' -使用条件运算符 `'?'` 重写下面的 `if` 语句: +Rewrite this `if` using the conditional operator `'?'`: ```js let result; diff --git a/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md b/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md index ecb07db2bd..4f7d994a21 100644 --- a/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md +++ b/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 使用 '?' 重写 'if..else' 语句 +# Rewrite 'if..else' into '?' -使用多个三元运算符 `'?'` 重写下面的 `if..else` 语句。 +Rewrite `if..else` using multiple ternary operators `'?'`. -为了增强代码可读性,建议将代码分成多行。 +For readability, it's recommended to split the code into multiple lines. ```js let message; diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 7cbdf5ec05..51514062f1 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -1,14 +1,14 @@ -# 条件运算符:if 和 '?' +# Conditional branching: if, '?' -有时我们需要根据不同条件执行不同的操作。 +Sometimes, we need to perform different actions based on different conditions. -我们可以使用 `if` 语句和条件运算符 `?`(也称为“问号”运算符)来实现。 +To do that, we can use the `if` statement and the conditional operator `?`, that's also called a "question mark" operator. -## "if" 语句 +## The "if" statement -`if(...)` 语句计算括号里的条件表达式,如果计算结果是 `true`,就会执行对应的代码块。 +The `if(...)` statement evaluates a condition in parentheses and, if the result is `true`, executes a block of code. -例如: +For example: ```js run let year = prompt('In which year was ECMAScript-2015 specification published?', ''); @@ -18,9 +18,9 @@ if (year == 2015) alert( 'You are right!' ); */!* ``` -在上面这个例子中,条件是一个简单的相等性检查(`year == 2015`),但它还可以更复杂。 +In the example above, the condition is a simple equality check (`year == 2015`), but it can be much more complex. -如果有多个语句要执行,我们必须将要执行的代码块封装在大括号内: +If we want to execute more than one statement, we have to wrap our code block inside curly braces: ```js if (year == 2015) { @@ -29,66 +29,66 @@ if (year == 2015) { } ``` -建议每次使用 if 语句都用大括号 `{}` 来包装代码块,即使只有一条语句。这样可以提高代码可读性。 +We recommend wrapping your code block with curly braces `{}` every time you use an `if` statement, even if there is only one statement to execute. Doing so improves readability. -## 布尔转换 +## Boolean conversion -`if (…)` 语句会计算圆括号内的表达式,并将计算结果转换为布尔型。 +The `if (…)` statement evaluates the expression in its parentheses and converts the result to a boolean. -让我们回顾一下 一章中的转换规则: +Let's recall the conversion rules from the chapter : -- 数字 `0`、空字符串 `""`、`null`、`undefined` 和 `NaN` 都会被转换成 `false`。因为他们被称为 “falsy” 值。 -- 其他值被转换为 `true`,所以它们被称为 “truthy”。 +- A number `0`, an empty string `""`, `null`, `undefined`, and `NaN` all become `false`. Because of that they are called "falsy" values. +- Other values become `true`, so they are called "truthy". -所以,下面这个条件下的代码永远不会执行: +So, the code under this condition would never execute: ```js -if (0) { // 0 是 falsy +if (0) { // 0 is falsy ... } ``` -……但下面的条件 —— 始终有效: +...and inside this condition -- it always will: ```js -if (1) { // 1 是 truthy +if (1) { // 1 is truthy ... } ``` -我们也可以将未计算的布尔值传入 `if` 语句,像这样: +We can also pass a pre-evaluated boolean value to `if`, like this: ```js -let cond = (year == 2015); // 相等运算符的结果是 true 或 false +let cond = (year == 2015); // equality evaluates to true or false if (cond) { ... } ``` -## "else" 语句 +## The "else" clause -`if` 语句有时会包含一个可选的 “else” 块。如果判断条件不成立,就会执行它内部的代码。 +The `if` statement may contain an optional "else" block. It executes when the condition is falsy. -例如: +For example: ```js run -let year = prompt('In which year was ECMAScript-2015 specification published?', ''); +let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); if (year == 2015) { alert( 'You guessed it right!' ); } else { - alert( 'How can you be so wrong?' ); // 2015 以外的任何值 + alert( 'How can you be so wrong?' ); // any value except 2015 } ``` -## 多个条件:"else if" +## Several conditions: "else if" -有时我们需要测试一个条件的几个变体。我们可以通过使用 `else if` 子句实现。 +Sometimes, we'd like to test several variants of a condition. The `else if` clause lets us do that. -例如: +For example: ```js run -let year = prompt('In which year was ECMAScript-2015 specification published?', ''); +let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); if (year < 2015) { alert( 'Too early...' ); @@ -99,15 +99,15 @@ if (year < 2015) { } ``` -在上面的代码中,JavaScript 先先检查 `year < 2015`。如果条件不符合,就会转到下一个条件 `year > 2015`。如果这个条件也不符合,则会显示最后一个 `alert`。 +In the code above, JavaScript first checks `year < 2015`. If that is falsy, it goes to the next condition `year > 2015`. If that is also falsy, it shows the last `alert`. -可以有更多的 `else if` 块。结尾的 `else` 是可选的。 +There can be more `else if` blocks. The final `else` is optional. -## 条件运算符 '?' +## Conditional operator '?' -有时我们需要根据一个条件去赋值一个变量。 +Sometimes, we need to assign a variable depending on a condition. -如下所示: +For instance: ```js run no-beautify let accessAllowed; @@ -124,49 +124,49 @@ if (age > 18) { alert(accessAllowed); ``` -所谓的“条件”或“问号”操作符让我们可以更简短地达到目的。 +The so-called "conditional" or "question mark" operator lets us do that in a shorter and simpler way. -这个操作符通过问号 `?` 表示。有时它被称为三元运算符,被称为“三元”是因为该操作符中有三个操作数。实际上它是 JavaScript 中唯一一个有这么多操作数的操作符。 +The operator is represented by a question mark `?`. Sometimes it's called "ternary", because the operator has three operands. It is actually the one and only operator in JavaScript which has that many. -语法: +The syntax is: ```js let result = condition ? value1 : value2; ``` -计算条件结果,如果结果为真,则返回 `value1`,否则返回 `value2`。 +The `condition` is evaluated: if it's truthy then `value1` is returned, otherwise -- `value2`. -例如: +For example: ```js let accessAllowed = (age > 18) ? true : false; ``` -技术上讲,我们可以省略 `age > 18` 外面的括号。问号运算符的优先级较低,所以它会在比较运算符 `>` 后执行。 +Technically, we can omit the parentheses around `age > 18`. The question mark operator has a low precedence, so it executes after the comparison `>`. -下面这个示例会执行和前面那个示例相同的操作: +This example will do the same thing as the previous one: ```js -// 比较运算符 “age > 18” 首先执行 -//(不需要将其包含在括号中) +// the comparison operator "age > 18" executes first anyway +// (no need to wrap it into parentheses) let accessAllowed = age > 18 ? true : false; ``` -但括号可以使代码可读性更强,所以我们建议使用它们。 +But parentheses make the code more readable, so we recommend using them. ````smart -在上面的例子中,你可以不适用问号运算符,因为比较本身就返回 `true/false`: +In the example above, you can avoid using the question mark operator because the comparison itself returns `true/false`: ```js -// 下面代码同样可以实现 +// the same let accessAllowed = age > 18; ``` ```` -## 多个 '?' +## Multiple '?' -使用一系列问号 `?` 运算符可以返回一个取决于多个条件的值。 +A sequence of question mark operators `?` can return a value that depends on more than one condition. -例如: +For instance: ```js run let age = prompt('age?', 18); @@ -178,14 +178,14 @@ let message = (age < 3) ? 'Hi, baby!' : alert( message ); ``` -可能很难一下子看出发生了什么。但经过仔细观察,我们可以看到它只是一个普通的检查序列。 +It may be difficult at first to grasp what's going on. But after a closer look, we can see that it's just an ordinary sequence of tests: -1. 第一个问号检查 `age < 3`。 -2. 如果为真 — 返回 `'Hi, baby!'`。否则,会继续执行冒号 `":"` 后的表达式,检查 `age < 18`。 -3. 如果为真 — 返回 `'Hello!'`。否则,会继续执行下一个冒号 `":"` 后的表达式,检查 `age < 100`。 -4. 如果为真 — 返回 `'Greetings!'`。否则,会继续执行最后一个冒号 `":"` 后面的表达式,返回 `'What an unusual age!'`。 +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!'`. -这是使用 `if..else` 实现上面的逻辑的写法: +Here's how this looks using `if..else`: ```js if (age < 3) { @@ -199,9 +199,9 @@ if (age < 3) { } ``` -## '?' 的非常规使用 +## Non-traditional use of '?' -有时可以使用问号 `?` 来代替 `if` 语句: +Sometimes the question mark `?` is used as a replacement for `if`: ```js run no-beautify let company = prompt('Which company created JavaScript?', ''); @@ -212,15 +212,15 @@ let company = prompt('Which company created JavaScript?', ''); */!* ``` -根据条件 `company =='Netscape'`,要么执行 `?` 后面的第一个表达式并显示对应内容,要么执行第二个表达式并显示对应内容。 +Depending on the condition `company == 'Netscape'`, either the first or the second expression after the `?` gets executed and shows an alert. -在这里我们不是把结果赋值给变量。而是根据条件执行不同的代码。 +We don't assign a result to a variable here. Instead, we execute different code depending on the condition. -**不建议这样使用问号运算符。** +**It's not recommended to use the question mark operator in this way.** -这种写法比 `if` 语句更短,对一些程序员很有吸引力。但它的可读性差。 +The notation is shorter than the equivalent `if` statement, which appeals to some programmers. But it is less readable. -下面是使用 `if` 语句实现相同功能的代码,进行下比较: +Here is the same code using `if` for comparison: ```js run no-beautify let company = prompt('Which company created JavaScript?', ''); @@ -234,6 +234,6 @@ if (company == 'Netscape') { */!* ``` -因为我们的眼睛垂直扫描代码。所以,跨越几行的代码块比长而水平的代码更易于理解。 +Our eyes scan the code vertically. Code blocks which span several lines are easier to understand than a long, horizontal instruction set. -问号 `?` 的作用是根据条件返回一个或另一个值。请正确使用它。当需要执行不同的代码分支时,请使用 `if`。 +The purpose of the question mark operator `?` is to return one value or another depending on its condition. Please use it for exactly that. Use `if` when you need to execute different branches of code. diff --git a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md index e4146accfc..8869d32e6b 100644 --- a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md +++ b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md @@ -1,4 +1,4 @@ -结果是 `2`,这是第一个真值。 +The answer is `2`, that's the first truthy value. ```js run alert( null || 2 || undefined ); diff --git a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md index 512fd6f33a..a7c9addfc7 100644 --- a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md +++ b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 或运算的结果是什么? +# What's the result of OR? -如下代码将会输出什么? +What is the code below going to output? ```js alert( null || 2 || undefined ); diff --git a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md index ca1777795a..f85b563662 100644 --- a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md +++ b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md @@ -1,13 +1,13 @@ -答案:首先是 `1`,然后是 `2`。 +The answer: first `1`, then `2`. ```js run alert( alert(1) || 2 || alert(3) ); ``` -对 `alert` 的调用没有返回值。或者说返回的是 `undefined`。 +The call to `alert` does not return a value. Or, in other words, it returns `undefined`. -1. 第一个或运算 `||` 对它的左值 `alert(1)` 进行了计算。这就显示了第一条信息 `1`。 -2. 函数 `alert` 返回了 `undefined`,所以或运算继续检查第二个操作数以寻找真值。 -3. 第二个操作数 `2` 是真值,所以执行就中断了。`2` 被返回,并且被外层的 alert 显示。 +1. The first OR `||` evaluates its left operand `alert(1)`. That shows the first message with `1`. +2. The `alert` returns `undefined`, so OR goes on to the second operand searching for a truthy value. +3. The second operand `2` is truthy, so the execution is halted, `2` is returned and then shown by the outer alert. -这里不会显示 `3`,因为运算没有抵达 `alert(3)`。 +There will be no `3`, because the evaluation does not reach `alert(3)`. diff --git a/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md b/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md index fac364cc24..3908fa2ece 100644 --- a/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md +++ b/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# 或运算和 alerts 的结果是什么? +# What's the result of OR'ed alerts? -下面的代码将会输出什么? +What will the code below output? ```js alert( alert(1) || 2 || alert(3) ); 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 4ed9b855b9..368b594094 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 @@ -答案:`null`,因为它是列表中第一个假值。 +The answer: `null`, because it's the first falsy value from the list. ```js run -alert( 1 && null && 2 ); +alert(1 && null && 2); ``` diff --git a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md index 2331e3e223..043d431e41 100644 --- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md +++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 与操作的结果是什么? +# What is the result of AND? -下面这段代码将会显示什么? +What is this code going to show? ```js alert( 1 && null && 2 ); diff --git a/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md b/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md index db53c22065..b6fb10d720 100644 --- a/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md +++ b/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md @@ -1,9 +1,10 @@ -答案:`1`,然后 `undefined`。 +The answer: `1`, and then `undefined`. ```js run alert( alert(1) && alert(2) ); ``` -调用 `alert` 返回了 `undefined`(它只展示消息,所以没有有意义的返回值)。 +The call to `alert` returns `undefined` (it just shows a message, so there's no meaningful return). + +Because of that, `&&` evaluates the left operand (outputs `1`), and immediately stops, because `undefined` is a falsy value. And `&&` looks for a falsy value and returns it, so it's done. -因此,`&&` 计算了它左边的操作数(显示 `1`),然后立即停止了,因为 `undefined` 是一个假值。`&&` 就是寻找假值然后返回它,所以运算结束。 diff --git a/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md b/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md index ac5717cc6e..69f877b955 100644 --- a/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md +++ b/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# 与运算连接的 alerts 的结果是什么? +# What is the result of AND'ed alerts? -这段代码将会显示什么? +What will this code show? ```js alert( alert(1) && alert(2) ); diff --git a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md index c98fae47c1..25e3568f8b 100644 --- a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md +++ b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md @@ -1,16 +1,16 @@ -答案:`3`。 +The answer: `3`. ```js run alert( null || 2 && 3 || 4 ); ``` -与运算 `&&` 的优先级比 `||` 高,所以它第一个被执行。 +The precedence of AND `&&` is higher than `||`, so it executes first. -结果是 `2 && 3 = 3`,所以表达式变成了: +The result of `2 && 3 = 3`, so the expression becomes: ``` null || 3 || 4 ``` -现在的结果就是第一个真值:`3`。 +Now the result is the first truthy value: `3`. diff --git a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md index 05b7f18aa7..b18bb9c514 100644 --- a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md +++ b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 或运算、与运算、或运算串联的结果 +# The result of OR AND OR -结果将会是什么? +What will the result be? ```js alert( null || 2 && 3 || 4 ); 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 3a89fa7742..fc9e336c15 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 @@ -2,8 +2,8 @@ importance: 3 --- -# 检查值是否位于范围区间内 +# Check the range between -写一个“if”条件句来检查 `age` 是否位于 `14` 到 `90` 的闭区间。 +Write an `if` condition to check that `age` is between `14` and `90` inclusively. -“闭区间”意味着,`age` 的值可以取 `14` 或 `90`。 +"Inclusively" means that `age` can reach the edges `14` or `90`. diff --git a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/solution.md b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/solution.md index dffe0b3e17..d1946a9673 100644 --- a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/solution.md +++ b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/solution.md @@ -1,10 +1,10 @@ -第一个表达式: +The first variant: ```js if (!(age >= 14 && age <= 90)) ``` -第二个表达式: +The second variant: ```js if (age < 14 || age > 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 8a36a624d8..9b947d00f8 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 @@ -2,8 +2,8 @@ importance: 3 --- -# 检测值是否位于范围之外 +# Check the range outside -写一个 `if` 条件句,检查 `age` 是否不位于 14 到 90 的闭区间。 +Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively. -创建两个表达式:第一个用非运算 `!`,第二个不用。 +Create two variants: the first one using NOT `!`, the second one -- without it. diff --git a/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md b/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md index fb533026b3..2105097584 100644 --- a/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md +++ b/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md @@ -1,19 +1,19 @@ -答案:第一个和第三个将会被执行。 +The answer: the first and the third will execute. -详解: +Details: ```js run -// 执行。 -// -1 || 0 的结果为 -1,真值 +// Runs. +// The result of -1 || 0 = -1, truthy if (-1 || 0) alert( 'first' ); -// 不执行。 -// -1 && 0 = 0,假值 +// Doesn't run +// -1 && 0 = 0, falsy if (-1 && 0) alert( 'second' ); -// 执行 -// && 运算的优先级比 || 高 -// 所以 -1 && 1 先执行,给出如下运算链: +// Executes +// Operator && has a higher precedence than || +// so -1 && 1 executes first, giving us the chain: // null || -1 && 1 -> null || 1 -> 1 if (null || -1 && 1) alert( 'third' ); ``` diff --git a/1-js/02-first-steps/11-logical-operators/8-if-question/task.md b/1-js/02-first-steps/11-logical-operators/8-if-question/task.md index 466673e2a9..55987121b0 100644 --- a/1-js/02-first-steps/11-logical-operators/8-if-question/task.md +++ b/1-js/02-first-steps/11-logical-operators/8-if-question/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 一个关于 "if" 的问题 +# A question about "if" -下面哪一个 `alert` 将会被执行? +Which of these `alert`s are going to execute? -`if(...)` 语句内表达式的结果是什么? +What will the results of the expressions be inside `if(...)`? ```js if (-1 || 0) alert( 'first' ); diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg index f16e37a603..d22b518a91 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg @@ -1,101 +1 @@ - - - - ifelse_task.svg - Created with sketchtool. - - - - - - - - Begin - - - - - - Canceled - - - - - - Canceled - - - - - - Welcome! - - - - - - I don't know you - - - - - - Wrong password - - - - - - - - - Who's there? - - - - - - Password? - - - - - - Cancel - - - - - - Cancel - - - - - - Admin - - - - - - TheMaster - - - - - - - - Other - - - - - - Other - - - - - \ No newline at end of file +BeginCanceledCanceledWelcome!I don't know youWrong passwordWho's there?Password?CancelCancelAdminTheMasterOtherOther \ 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 126eaddbea..604606259f 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,23 +3,23 @@ ```js run demo let userName = prompt("Who's there?", ''); -if (userName == 'Admin') { +if (userName === 'Admin') { let pass = prompt('Password?', ''); - if (pass == 'TheMaster') { + if (pass === 'TheMaster') { alert( 'Welcome!' ); - } else if (pass == '' || pass == null) { + } else if (pass === '' || pass === null) { alert( 'Canceled' ); } else { alert( 'Wrong password' ); } -} else if (userName == '' || userName == null) { +} else if (userName === '' || userName === null) { alert( 'Canceled' ); } else { alert( "I don't know you" ); } ``` -请注意 `if` 块中水平方向的缩进。技术上是非必需的,但会增加代码的可读性。 +Note the vertical indents inside the `if` blocks. They are technically not required, but make the code more readable. diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md index b66d57d3a5..290a52642f 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md @@ -2,24 +2,24 @@ importance: 3 --- -# 登陆验证 +# Check the login -实现使用 `prompt` 进行登陆校验的代码。 +Write the code which asks for a login with `prompt`. -如果访问者输入 `"Admin"`,那么使用 `prompt` 引导获取密码,如果输入的用户名为空或者按下了 `key:Esc` 键 —— 显示 "Canceled",如果是其他字符串 —— 显示 "I don't know you"。 +If the visitor enters `"Admin"`, then `prompt` for a password, if the input is an empty line or `key:Esc` -- show "Canceled", if it's another string -- then show "I don't know you". -密码的校验规则如下: +The password is checked as follows: -- 如果输入的是 "TheMaster",显示 "Welcome!", -- 其他字符串 —— 显示 "Wrong password", -- 空字符串或取消了输入,显示 "Canceled."。 +- If it equals "TheMaster", then show "Welcome!", +- Another string -- show "Wrong password", +- For an empty string or cancelled input, show "Canceled" -流程图: +The schema: ![](ifelse_task.svg) -请使用嵌套的 `if` 块。注意代码整体的可读性。 +Please use nested `if` blocks. Mind the overall readability of the code. -提示:将空字符串输入,prompt 会获取到一个空字符串 `''`。Prompt 运行过程中,按下 `key:ESC` 键会得到 `null`。 +Hint: passing an empty input to a prompt returns an empty string `''`. Pressing `key:ESC` during a prompt returns `null`. [demo] 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 767727d511..78c4fd2f1b 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -1,24 +1,24 @@ -# 逻辑运算符 +# Logical operators -JavaScript 里有三个逻辑运算符:`||`(或),`&&`(与),`!`(非)。 +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. -虽然他们被称为“逻辑”运算符,但这些运算符却可以被应用于任意类型的值,而不仅仅是布尔值。他们的结果也同样可以是任意类型。 +Although they are called "logical", they can be applied to values of any type, not only boolean. Their result can also be of any type. -让我们来详细看一下。 +Let's see the details. -## ||(或) +## || (OR) -两个竖线符号表示了“或”运算: +The "OR" operator is represented with two vertical line symbols: ```js result = a || b; ``` -在传统的编程中,逻辑或仅能够操作布尔值。如果参与运算的任意一个参数为 `true`,返回的结果就为 `true`,否则返回 `false`。 +In classical programming, the logical OR is meant to manipulate boolean values only. If any of its arguments are `true`, it returns `true`, otherwise it returns `false`. -在 JavaScript 中,逻辑运算符更加灵活强大。但是首先我们看一下操作数是布尔值的时候发生了什么。 +In JavaScript, the operator is a little bit trickier and more powerful. But first, let's see what happens with boolean values. -下面是四种可能的逻辑组合: +There are four possible logical combinations: ```js run alert( true || true ); // true @@ -27,21 +27,21 @@ alert( true || false ); // true alert( false || false ); // false ``` -正如我们所见,除了两个操作数都是 `false` 的情况,结果都是 `true`。 +As we can see, the result is always `true` except for the case when both operands are `false`. -如果操作数不是布尔值,那么它将会被转化为布尔值来参与运算。 +If an operand is not a boolean, it's converted to a boolean for the evaluation. -例如,数字 `1` 将会被作为 `true`,数字 `0` 则作为 `false`: +For instance, the number `1` is treated as `true`, the number `0` as `false`: ```js run -if (1 || 0) { // 工作原理相当于 if( true || false ) +if (1 || 0) { // works just like if( true || false ) alert( 'truthy!' ); } ``` -大多数情况,逻辑或 `||` 会被用在 `if` 语句中,用来测试是否有 **任何** 给定的条件为 `true`。 +Most of the time, OR `||` is used in an `if` statement to test if *any* of the given conditions is `true`. -例如: +For example: ```js run let hour = 9; @@ -53,111 +53,98 @@ if (hour < 10 || hour > 18) { } ``` -我们可以传入更多的条件: +We can pass more conditions: ```js run let hour = 12; let isWeekend = true; if (hour < 10 || hour > 18 || isWeekend) { - alert( 'The office is closed.' ); // 是周末 + alert( 'The office is closed.' ); // it is the weekend } ``` -## 或运算寻找第一个真值 +## OR "||" finds the first truthy value [#or-finds-the-first-truthy-value] -上文提到的逻辑处理多少有些传统了。下面让我们看看 JavaScript 的“附加”特性。 +The logic described above is somewhat classical. Now, let's bring in the "extra" features of JavaScript. -拓展的算法如下所示。 +The extended algorithm works as follows. -给定多个参与或运算的值: +Given multiple OR'ed values: ```js result = value1 || value2 || value3; ``` -或运算符 `||` 做了如下的事情: +The OR `||` operator does the following: -- 从左到右依次计算操作数。 -- 处理每一个操作数时,都将其转化为布尔值。如果结果是 `true`,就停止计算,返回这个操作数的初始值。 -- 如果所有的操作数都被计算过(也就是,转换结果都是 `false`),则返回最后一个操作数。 +- Evaluates operands from left to right. +- For each operand, converts it to boolean. If the result is `true`, stops and returns the original value of that operand. +- If all operands have been evaluated (i.e. all were `false`), returns the last operand. -返回的值是操作数的初始形式,不会做布尔转换。 +A value is returned in its original form, without the conversion. -也就是,一个或 `"||"` 运算的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值。 +In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found. -例如: +For instance: ```js run -alert( 1 || 0 ); // 1(1 是真值) -alert( true || 'no matter what' ); //(true 是真值) +alert( 1 || 0 ); // 1 (1 is truthy) -alert( null || 1 ); // 1(1 是第一个真值) -alert( null || 0 || 1 ); // 1(第一个真值) -alert( undefined || null || 0 ); // 0(所有的转化结果都是 false,返回最后一个值) +alert( null || 1 ); // 1 (1 is the first truthy value) +alert( null || 0 || 1 ); // 1 (the first truthy value) + +alert( undefined || null || 0 ); // 0 (all falsy, returns the last value) ``` -与“纯粹的、传统的、仅仅处理布尔值的或运算”相比,这个规则就引起了一些很有趣的用法。 +This leads to some interesting usage compared to a "pure, classical, boolean-only OR". -1. **获取变量列表或者表达式的第一个真值。** +1. **Getting the first truthy value from a list of variables or expressions.** - 假设我们有几个变量,它们可能包含某些数据或者是 `null/undefined`。我们需要选出第一个包含数据的变量。 + 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): ```js run - let currentUser = null; - let defaultUser = "John"; + let firstName = ""; + let lastName = ""; + let nickName = "SuperCoder"; *!* - let name = currentUser || defaultUser || "unnamed"; + alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder */!* - - alert( name ); // 选出了 “John” — 第一个真值 ``` - 如果 `currentUser` 和 `defaultUser` 都是假值,那么结果就是 `"unnamed"`。 -2. **短路取值。** + If all variables were falsy, `"Anonymous"` would show up. - 操作数不仅仅可以是值,还可以是任意表达式。或运算会从左到右计算并测试每个操作数。当找到第一个真值,计算就会停止,并返回这个值。这个过程就叫做“短路取值”,因为它尽可能地减少从左到右计算的次数。 - - 当表达式作为第二个参数并且有一定的副作用(side effects),比如变量赋值的时候,短路取值的情况就清楚可见。 +2. **Short-circuit evaluation.** - 如果我们运行下面的例子,`x` 将不会被赋值: + Another feature of OR `||` operator is the so-called "short-circuit" evaluation. - ```js run no-beautify - let x; + It means that `||` processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument. - *!*true*/!* || (x = 1); - - alert(x); // undefined,因为 (x = 1) 没有被执行 - ``` + 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. - 如果第一个参数是 `false`,或运算将会继续,并计算第二个参数,也就会运行赋值操作。 + In the example below, only the second message is printed: ```js run no-beautify - let x; - - *!*false*/!* || (x = 1); - - alert(x); // 1 + *!*true*/!* || alert("not printed"); + *!*false*/!* || alert("printed"); ``` - 赋值操作只是一个很简单的情况。可能有副作用,如果计算没有到达,副作用就不会发生。 - - 正如我们所见,这种用法是“`if` 语句的简短方式”。第一个操作数被转化为布尔值,如果是假,那么第二个参数就会被执行。 + In the first line, the OR `||` operator stops the evaluation immediately upon seeing `true`, so the `alert` isn't run. - 大多数情况下,最好使用“常规的” `if` 语句,这样代码可读性更高,但是有时候这种方式会很简洁。 + Sometimes, people use this feature to execute commands only if the condition on the left part is falsy. -## &&(与) +## && (AND) -两个 & 符号表示 `&&` 与操作: +The AND operator is represented with two ampersands `&&`: ```js result = a && b; ``` -传统的编程中,当两个操作数都是真值,与操作返回 `true`,否则返回 `false`: +In classical programming, AND returns `true` if both operands are truthy and `false` otherwise: ```js run alert( true && true ); // true @@ -166,79 +153,80 @@ alert( true && false ); // false alert( false && false ); // false ``` -使用 `if` 语句的例子: +An example with `if`: ```js run let hour = 12; let minute = 30; if (hour == 12 && minute == 30) { - alert( 'Time is 12:30' ); + alert( 'The time is 12:30' ); } ``` -就像或运算一样,与运算的操作数可以是任意类型的值: +Just as with OR, any value is allowed as an operand of AND: ```js run -if (1 && 0) { // 作为 true && false 来执行 +if (1 && 0) { // evaluated as true && false alert( "won't work, because the result is falsy" ); } ``` -## 与操作寻找第一个假值 +## AND "&&" finds the first falsy value -给出多个参加与运算的值: +Given multiple AND'ed values: ```js result = value1 && value2 && value3; ``` -与运算 `&&` 做了如下的事: +The AND `&&` operator does the following: -- 从左到右依次计算操作数。 -- 将处理每一个操作数时,都将其转化为布尔值。如果结果是 `false`,就停止计算,并返回这个操作数的初始值。 -- 如果所有的操作数都被计算过(也就是,转换结果都是 `true`),则返回最后一个操作数。 +- Evaluates operands from left to right. +- For each operand, converts it to a boolean. If the result is `false`, stops and returns the original value of that operand. +- If all operands have been evaluated (i.e. all were truthy), returns the last operand. -换句话说,与操作符返回第一个假值,如果没有假值就返回最后一个值。 +In other words, AND returns the first falsy value or the last value if none were found. -上面的规则和或运算很像。区别就是与运算返回第一个假值而或操作返回第一个真值。 +The rules above are similar to OR. The difference is that AND returns the first *falsy* value while OR returns the first *truthy* one. -例如: +Examples: ```js run -// 如果第一个操作符是真值, -// 与操作返回第二个操作数: +// if the first operand is truthy, +// AND returns the second operand: alert( 1 && 0 ); // 0 alert( 1 && 5 ); // 5 -// 如果第一个操作符是假值, -// 与操作直接返回它。第二个操作数被忽略 +// if the first operand is falsy, +// AND returns it. The second operand is ignored alert( null && 5 ); // null alert( 0 && "no matter what" ); // 0 ``` -我们也可以在一行代码上串联多个值。查看第一个假值是否被返回: +We can also pass several values in a row. See how the first falsy one is returned: ```js run alert( 1 && 2 && null && 3 ); // null ``` -如果所有的值都是真值,最后一个值将会被返回: +When all values are truthy, the last value is returned: ```js run -alert( 1 && 2 && 3 ); // 3,最后一个值 +alert( 1 && 2 && 3 ); // 3, the last one ``` -````smart header="与运算 `&&` 在或操作符 `||` 之前执行" -与运算 `&&` 的优先级比或运算 `||` 要高。 +````smart header="Precedence of AND `&&` is higher than OR `||`" +The precedence of AND `&&` operator is higher than OR `||`. -所以代码 `a && b || c && d` 完全跟 `&&` 表达式加了括号一样:`(a && b) || (c && d)`。 +So the code `a && b || c && d` is essentially the same as if the `&&` expressions were in parentheses: `(a && b) || (c && d)`. ```` -就像或运算一样,与运算 `&&` 有时候能够代替 `if`。 +````warn header="Don't replace `if` with `||` or `&&`" +Sometimes, people use the AND `&&` operator as a "shorter way to write `if`". -例如: +For instance: ```js run let x = 1; @@ -246,58 +234,56 @@ let x = 1; (x > 0) && alert( 'Greater than zero!' ); ``` -`&&` 右边的代码只有运算抵达到那里才能被执行。也就是,当且仅当 `(x > 0)` 返回了真值。 +The action in the right part of `&&` would execute only if the evaluation reaches it. That is, only if `(x > 0)` is true. -所以我们基本可以类似地得到: +So we basically have an analogue for: ```js run let x = 1; -if (x > 0) { - alert( 'Greater than zero!' ); -} +if (x > 0) alert( 'Greater than zero!' ); ``` -带 `&&` 的代码变体看上去更短。但是 `if` 的含义更明显,可读性也更高。 +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. +```` -所以建议是根据目的选择代码的结构。需要条件判断就用 `if`,需要与运算就用 `&&`。 -## !(非) +## ! (NOT) -感叹符号 `!` 表示布尔非运算。 +The boolean NOT operator is represented with an exclamation sign `!`. -语法相当简单: +The syntax is pretty simple: ```js result = !value; ``` -操作符接受一个参数,并按如下运作: +The operator accepts a single argument and does the following: -1. 将操作数转化为布尔类型:`true/false`。 -2. 返回相反的值。 +1. Converts the operand to boolean type: `true/false`. +2. Returns the inverse value. -例如: +For instance: ```js run alert( !true ); // false alert( !0 ); // true ``` -两个非运算 `!!` 有时候用来将某个值转化为布尔类型: +A double NOT `!!` is sometimes used for converting a value to boolean type: ```js run alert( !!"non-empty string" ); // true alert( !!null ); // false ``` -也就是,第一个非运算将该值转化为布尔类型并取反,第二个非运算再次取反。最后我们就得到了一个任意值到布尔值的转化。 +That is, the first NOT converts the value to boolean and returns the inverse, and the second NOT inverses it again. In the end, we have a plain value-to-boolean conversion. -有更多详细的方法可以完成同样的事 —— 一个内置的 `Boolean` 函数: +There's a little more verbose way to do the same thing -- a built-in `Boolean` function: ```js run alert( Boolean("non-empty string") ); // true alert( Boolean(null) ); // false ``` -非运算符 `!` 的优先级在所有逻辑运算符里面最高,所以它总是在 `&&` 和 `||` 前执行。 +The precedence of NOT `!` is the highest of all logical operators, so it always executes first, before `&&` or `||`. 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 new file mode 100644 index 0000000000..2d046aa90e --- /dev/null +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -0,0 +1,169 @@ +# Nullish coalescing operator '??' + +[recent browser="new"] + +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: + +```js +result = (a !== null && a !== undefined) ? a : b; +``` + +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 not defined) +``` + +Here's the example with `user` assigned to a name: + +```js run +let user = "John"; + +alert(user ?? "Anonymous"); // John (user defined) +``` + +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: + +```js run +let firstName = null; +let lastName = null; +let nickName = "Supercoder"; + +// shows the first defined value: +*!* +alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder +*/!* +``` + +## Comparison with || + +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 exists 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. + +For example, consider this: + +```js run +let height = 0; + +alert(height || 100); // 100 +alert(height ?? 100); // 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. + +## Precedence + +The precedence of the `??` operator is the same as `||`. They both equal `4` 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: + +```js run +let height = null; +let width = null; + +// important: use parentheses +let area = (height ?? 100) * (width ?? 50); + +alert(area); // 5000 +``` + +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. + +The code below triggers a syntax error: + +```js run +let x = 1 && 2 ?? 3; // Syntax error +``` + +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 `??`. + +Use explicit parentheses to work around it: + +```js run +*!* +let x = (1 && 2) ?? 3; // Works +*/!* + +alert(x); // 2 +``` + +## Summary + +- The nullish coalescing operator `??` provides a short way to choose the first "defined" value from a list. + + It's used to assign default values to variables: + + ```js + // set height=100, if height is null or undefined + height = height ?? 100; + ``` + +- 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. diff --git a/1-js/02-first-steps/12-while-for/1-loop-last-value/solution.md b/1-js/02-first-steps/12-while-for/1-loop-last-value/solution.md deleted file mode 100644 index c0c4b2fdcf..0000000000 --- a/1-js/02-first-steps/12-while-for/1-loop-last-value/solution.md +++ /dev/null @@ -1,25 +0,0 @@ -答案是:`1`。 - -```js run -let i = 3; - -while (i) { - alert( i-- ); -} -``` - -每次循环迭代都将 `i` 减 `1`。当检查到 `i = 0` 时,`while(i)` 循环停止。 - -因此,此循环执行的步骤如下(“循环展开”): - -```js -let i = 3; - -alert(i--); // 显示 3,i 减至 2 - -alert(i--) // 显示 2,i 减至 1 - -alert(i--) // 显示 1,i 减至 0 - -// 完成,while(i) 检查循环条件并停止循环 -``` diff --git a/1-js/02-first-steps/12-while-for/1-loop-last-value/task.md b/1-js/02-first-steps/12-while-for/1-loop-last-value/task.md deleted file mode 100644 index 00f8b055e2..0000000000 --- a/1-js/02-first-steps/12-while-for/1-loop-last-value/task.md +++ /dev/null @@ -1,15 +0,0 @@ -importance: 3 - ---- - -# 最后一次循环的值 - -此代码最后一次 alert 值是多少?为什么? - -```js -let i = 3; - -while (i) { - alert( i-- ); -} -``` diff --git a/1-js/02-first-steps/12-while-for/2-which-value-while/solution.md b/1-js/02-first-steps/12-while-for/2-which-value-while/solution.md deleted file mode 100644 index 3d403727dd..0000000000 --- a/1-js/02-first-steps/12-while-for/2-which-value-while/solution.md +++ /dev/null @@ -1,31 +0,0 @@ -这个题目展现了 i++/++i 两种形式在比较中导致的不同结果。 - -1. **从 1 到 4** - - ```js run - let i = 0; - while (++i < 5) alert( i ); - ``` - - 第一个值是 `i = 1`,因为 `++i` 首先递增 `i` 然后返回新值。因此先比较 `1 < 5` 然后通过 `alert` 显示 `1`。 - - 然后按照 `2, 3, 4…` —— 数值一个接着一个被显示出来。在比较中使用的都是递增后的值,因为 `++` 在变量前。 - - 最终,`i = 4` 时,在 `++i < 5` 的比较中,`i` 值递增至 `5`,所以 `while(5 < 5)` 不符合循环条件,循环停止。所以没有显示 `5`。 - -2. **从 1 到 5** - - ```js run - let i = 0; - while (i++ < 5) alert( i ); - ``` - - 第一个值也是 `i = 1`。后缀形式 `i++` 递增 `i` 然后返回 **旧** 值,因此比较 `i++ < 5` 将使用 `i = 0`(与 `++i < 5` 不同)。 - - 但 `alert` 调用是独立的。这是在递增和比较之后执行的另一条语句。因此它得到了当前的 `i = 1`。 - - 接下来是 `2, 3,4…` - - 我们在 `i = 4` 时暂停,前缀形式 `++i` 会递增 `i` 并在比较中使用新值 `5`。但我们这里是后缀形式 `i++`。因此,它将 `i` 递增到 `5`,但返回旧值。因此实际比较的是 `while(4 < 5)` —— true,程序继续执行 `alert`。 - - `i = 5` 是最后一个值,因为下一步比较 `while(5 < 5)` 为 false。 diff --git a/1-js/02-first-steps/12-while-for/2-which-value-while/task.md b/1-js/02-first-steps/12-while-for/2-which-value-while/task.md deleted file mode 100644 index fc96156209..0000000000 --- a/1-js/02-first-steps/12-while-for/2-which-value-while/task.md +++ /dev/null @@ -1,22 +0,0 @@ -importance: 4 - ---- - -# while 循环显示哪些值? - -对于每次循环,写出你认为会显示的值,然后与答案进行比较。 - -以下两个循环的 `alert` 值是否相同? - -1. 前缀形式 `++i`: - - ```js - let i = 0; - while (++i < 5) alert( i ); - ``` -2. 后缀形式 `i++` - - ```js - let i = 0; - while (i++ < 5) alert( i ); - ``` diff --git a/1-js/02-first-steps/12-while-for/3-which-value-for/solution.md b/1-js/02-first-steps/12-while-for/3-which-value-for/solution.md deleted file mode 100644 index 7c881e0b4e..0000000000 --- a/1-js/02-first-steps/12-while-for/3-which-value-for/solution.md +++ /dev/null @@ -1,17 +0,0 @@ -**答案:在这两种情况下都是从 `0` 到 `4`。** - -```js run -for (let i = 0; i < 5; ++i) alert( i ); - -for (let i = 0; i < 5; i++) alert( i ); -``` - -这可以很容易地从 `for` 算法中推导出: - -1. 在一切开始之前执行 `i = 0`。 -2. 检查 `i < 5` 条件 -3. 如果 `true` —— 执行循环体并 `alert(i)`,然后进行 `i++` - -递增 `i++` 与检查条件(2)分开。这只是另一种写法。 - -在这没使用返回的递增值,因此 `i++` 和 `++i`之间没有区别。 diff --git a/1-js/02-first-steps/12-while-for/3-which-value-for/task.md b/1-js/02-first-steps/12-while-for/3-which-value-for/task.md deleted file mode 100644 index 42395c1e37..0000000000 --- a/1-js/02-first-steps/12-while-for/3-which-value-for/task.md +++ /dev/null @@ -1,20 +0,0 @@ -importance: 4 - ---- - -# "for" 循环显示哪些值? - -对于每次循环,写下它将显示的值。然后与答案进行比较。 - -两次循环 `alert` 值是否相同? - -1. 后缀形式: - - ```js - for (let i = 0; i < 5; i++) alert( i ); - ``` -2. 前缀形式: - - ```js - for (let i = 0; i < 5; ++i) alert( i ); - ``` diff --git a/1-js/02-first-steps/12-while-for/4-for-even/task.md b/1-js/02-first-steps/12-while-for/4-for-even/task.md deleted file mode 100644 index 0b696db33d..0000000000 --- a/1-js/02-first-steps/12-while-for/4-for-even/task.md +++ /dev/null @@ -1,9 +0,0 @@ -importance: 5 - ---- - -# 使用 for 循环输出偶数 - -使用 `for` 循环输出从 `2` 到 `10` 的偶数。 - -[demo] diff --git a/1-js/02-first-steps/12-while-for/5-replace-for-while/task.md b/1-js/02-first-steps/12-while-for/5-replace-for-while/task.md deleted file mode 100644 index 8de08c5740..0000000000 --- a/1-js/02-first-steps/12-while-for/5-replace-for-while/task.md +++ /dev/null @@ -1,14 +0,0 @@ -importance: 5 - ---- - -# 用 "while" 替换 "for" - -重写代码,在保证不改变其行为的情况下,将 `for` 循环更改为 `while`(输出应保持不变)。 - -```js run -for (let i = 0; i < 3; i++) { - alert( `number ${i}!` ); -} -``` - diff --git a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md b/1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md deleted file mode 100644 index 745e8040a6..0000000000 --- a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md +++ /dev/null @@ -1,15 +0,0 @@ - -```js run demo -let num; - -do { - num = prompt("Enter a number greater than 100?", 0); -} while (num <= 100 && num); -``` - -两个检查都为真时,继续执行 `do..while` 循环: - -1. 检查 `num <= 100` —— 即输入值仍然不大于 `100`。 -2. 当 `num` 为 `null` 或空字符串时,`&& num` 的结果为 false。那么 `while` 循环也会停止。 - -P.S. 如果 `num` 为 `null`,那么 `num <= 100` 为 `true`。因此用户单击取消,如果没有第二次检查,循环就不会停止。两次检查都是必须的。 diff --git a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/task.md b/1-js/02-first-steps/12-while-for/6-repeat-until-correct/task.md deleted file mode 100644 index 0b5a0a0ced..0000000000 --- a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/task.md +++ /dev/null @@ -1,12 +0,0 @@ -importance: 5 ---- - -# 重复输入,直到正确为止 - -编写一个提示用户输入大于 `100` 的数字的循环。如果用户输入其他数值 —— 请他重新输入。 - -循环一直在请求一个数字,直到用户输入了一个大于 `100` 的数字、取消输入或输入了一个空行为止。 - -在这我们假设用户只会输入数字。在本题目中,不需要对非数值输入进行特殊处理。 - -[demo] diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md b/1-js/02-first-steps/12-while-for/7-list-primes/solution.md deleted file mode 100644 index 5d645fa0b3..0000000000 --- a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md +++ /dev/null @@ -1,31 +0,0 @@ -这个题目有很多解法。 - -我们使用一个嵌套循环: - -```js -对于间隔中的每个 i { - 检查在 1~i 之间,是否有 i 的除数 - 如果有 => 这个 i 不是素数 - 如果没有 => 这个 i 是素数,输出出来 -} -``` - -使用标签的代码: - -```js run -let n = 10; - -nextPrime: -for (let i = 2; i <= n; i++) { // 对每个自然数 i - - for (let j = 2; j < i; j++) { // 寻找一个除数…… - if (i % j == 0) continue nextPrime; // 不是素数,则继续检查下一个 - } - - alert( i ); // 输出素数 -} -``` - -这段代码有很大的优化空间。例如,我们可以从 `2` 到 `i` 的平方根之间的数中寻找除数。无论怎样,如果我们想要在很大的数字范围内实现高效率,我们需要改变实现方法,依赖高等数学和复杂算法,如[二次筛选法(Quadratic sieve)](https://en.wikipedia.org/wiki/Quadratic_sieve),[普通数域筛选法(General number field sieve)](https://en.wikipedia.org/wiki/General_number_field_sieve)等。 - -译注:素数也称为质数,对本答案的代码进一步优化,其实就是一道 LeetCode 算法题,感兴趣的可以点击链接查看如何通过 [埃拉托斯特尼筛法筛选素数](https://dingxuewen.com/leetcode-js-leviding/easy/204.count-primes/204.count-primes.html)。 diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/task.md b/1-js/02-first-steps/12-while-for/7-list-primes/task.md deleted file mode 100644 index cbcef14d32..0000000000 --- a/1-js/02-first-steps/12-while-for/7-list-primes/task.md +++ /dev/null @@ -1,17 +0,0 @@ -importance: 3 - ---- - -# 输出素数(prime) - -大于 `1` 且不能被除了 `1` 和它本身以外的任何数整除的整数叫做[素数](https://en.wikipedia.org/wiki/Prime_number)。 - -换句话说,`n > 1` 且不能被 `1` 和 `n` 以外的任何数整除的整数,被称为素数。 - -例如,`5` 是素数,因为它不能被 `2`、`3` 和 `4` 整除,会产生余数。 - -**写一个可以输出 `2` 到 `n` 之间的所有素数的代码。** - -当 `n = 10`,结果输出 `2、3、5、7`。 - -P.S. 代码应适用于任何 `n`,而不是对任何固定值进行硬性调整。 diff --git a/1-js/02-first-steps/12-while-for/article.md b/1-js/02-first-steps/12-while-for/article.md deleted file mode 100644 index 084e0e7da1..0000000000 --- a/1-js/02-first-steps/12-while-for/article.md +++ /dev/null @@ -1,386 +0,0 @@ -# 循环:while 和 for - -我们经常需要重复执行一些操作。 - -例如,我们需要将列表中的商品逐个输出,或者运行相同的代码将数字 1 到 10 逐个输出。 - -**循环** 是一种重复运行同一代码的方法。 - -## "while" 循环 - -`while` 循环的语法如下: - -```js -while (condition) { - // 代码 - // 所谓的“循环体” -} -``` - -当 `condition` 为 `true` 时,执行循环体的 `code`。 - -例如,以下将循环输出当 `i < 3` 时的 `i` 值: - -```js run -let i = 0; -while (i < 3) { // 依次显示 0、1 和 2 - alert( i ); - i++; -} -``` - -循环体的单次执行叫作 **一次迭代**。上面示例中的循环进行了三次迭代。 - -如果上述示例中没有 `i++`,那么循环(理论上)会永远重复执行下去。实际上,浏览器提供了阻止这种循环的方法,我们可以通过终止进程,来停掉服务器端的 JavaScript。 - -任何表达式或变量都可以是循环条件,而不仅仅是比较。在 `while` 中的循环条件会被计算,计算结果会被转化为布尔值。 - -例如,`while (i != 0)` 可简写为 `while (i)`: - -```js run -let i = 3; -*!* -while (i) { // 当 i 变成 0 时,条件为 false,循环终止 -*/!* - alert( i ); - i--; -} -``` - -````smart header="单行循环体不需要大括号" -如果循环体只有一条语句,则可以省略大括号 `{…}`: - -```js run -let i = 3; -*!* -while (i) alert(i--); -*/!* -``` -```` - -## "do..while" 循环 - -使用 `do..while` 语法可以将条件检查移至循环体 **下面**: - -```js -do { - // 循环体 -} while (condition); -``` - -循环首先执行循环体,然后检查条件,当条件为真时,重复执行循环体。 - -例如: - -```js run -let i = 0; -do { - alert( i ); - i++; -} while (i < 3); -``` - -这种形式的语法很少使用,除非你希望不管条件是否为真,循环体 **至少执行一次**。通常我们更倾向于使用另一个形式:`while(…) {…}`。 - -## "for" 循环 - -`for` 循环更加复杂,但它是最常使用的循环形式。 - -`for` 循环看起来就像这样: - -```js -for (begin; condition; step) { - // ……循环体…… -} -``` - -我们通过示例来了解一下这三个部分的含义。下述循环从 `i` 等于 `0` 到 `3`(但不包括 `3`)运行 `alert(i)`: - -```js run -for (let i = 0; i < 3; i++) { // 结果为 0、1、2 - alert(i); -} -``` - -我们逐个部分分析 `for` 循环: - -| 语句段 | | | -| ----- | ----------- |----------------------------------------------------------------------------| -| begin | `i = 0` | 进入循环时执行一次。 | -| condition | `i < 3` | 在每次循环迭代之前检查,如果为 false,停止循环。 | -| body(循环体) | `alert(i)` | 条件为真时,重复运行。 | -| step | `i++` | 在每次循环体迭代后执行。 | - -一般循环算法的工作原理如下: - -``` -开始运行 -→ (如果 condition 成立 → 运行 body 然后运行 step) -→ (如果 condition 成立 → 运行 body 然后运行 step) -→ (如果 condition 成立 → 运行 body 然后运行 step) -→ ... -``` - -所以,`begin` 执行一次,然后进行迭代:每次检查 `condition` 后,执行 `body` 和 `step`。 - -如果你这是第一次接触循环,那么回到这个例子,在一张纸上重现它逐步运行的过程,可能会对你有所帮助。 - -以下是在这个示例中发生的事: - -```js -// for (let i = 0; i < 3; i++) alert(i) - -// 开始 -let i = 0 -// 如果条件为真,运行下一步 -if (i < 3) { alert(i); i++ } -// 如果条件为真,运行下一步 -if (i < 3) { alert(i); i++ } -// 如果条件为真,运行下一步 -if (i < 3) { alert(i); i++ } -// ……结束,因为现在 i == 3 -``` - -````smart header="内联变量声明" -这里“计数”变量 `i` 是在循环中声明的。这叫做“内联”变量声明。这样的变量只在循环中可见。 - -```js run -for (*!*let*/!* i = 0; i < 3; i++) { - alert(i); // 0, 1, 2 -} -alert(i); // 错误,没有这个变量。 -``` - -除了定义一个变量,我们也可以使用现有的变量: - -```js run -let i = 0; - -for (i = 0; i < 3; i++) { // 使用现有的变量 - alert(i); // 0, 1, 2 -} - -alert(i); //3,可见,因为是在循环之外声明的 -``` - -```` - - -### 省略语句段 - -`for` 循环的任何语句段都可以被省略。 - -例如,如果我们在循环开始时不需要做任何事,我们就可以省略 `begin` 语句段。 - -就像这样: - -```js run -let i = 0; // 我们已经声明了 i 并对它进行了赋值 - -for (; i < 3; i++) { // 不再需要 "begin" 语句段 - alert( i ); // 0, 1, 2 -} -``` - -我们也可以移除 `step` 语句段: - -```js run -let i = 0; - -for (; i < 3;) { - alert( i++ ); -} -``` - -该循环与 `while (i < 3)` 等价。 - -实际上我们可以删除所有内容,从而创建一个无限循环: - -```js -for (;;) { - // 无限循环 -} -``` - -请注意 `for` 的两个 `;` 必须存在,否则会出现语法错误。 - -## 跳出循环 - -通常条件为假时,循环会终止。 - -但我们随时都可以使用 `break` 指令强制退出。 - -例如,下面这个循环要求用户输入一系列数字,在输入的内容不是数字时“终止”循环。 - -```js run -let sum = 0; - -while (true) { - - let value = +prompt("Enter a number", ''); - -*!* - if (!value) break; // (*) -*/!* - - sum += value; - -} -alert( 'Sum: ' + sum ); -``` - -如果用户输入空行或取消输入,在 `(*)` 行的 `break` 指令会被激活。它立刻终止循环,将控制权传递给循环后的第一行,即,`alert`。 - -根据需要,"无限循环 + `break`" 的组合非常适用于不必在循环开始/结束时检查条件,但需要在中间甚至是主体的多个位置进行条件检查的情况。 - -## 继续下一次迭代 [#continue] - -`continue` 指令是 `break` 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)。 - -如果我们完成了当前的迭代,并且希望继续执行下一次迭代,我们就可以使用它。 - -下面这个循环使用 `continue` 来只输出奇数: - -```js run no-beautify -for (let i = 0; i < 10; i++) { - -  //如果为真,跳过循环体的剩余部分。 - *!*if (i % 2 == 0) continue;*/!* - - alert(i); // 1,然后 3,5,7,9 -} -``` - -对于偶数的 `i` 值,`continue` 指令会停止本次循环的继续执行,将控制权传递给下一次 `for` 循环的迭代(使用下一个数字)。因此 `alert` 仅被奇数值调用。 - -````smart header="`continue` 指令利于减少嵌套" -显示奇数的循环可以像下面这样: - -```js run -for (let i = 0; i < 10; i++) { - - if (i % 2) { - alert( i ); - } - -} -``` - -从技术角度看,它与上一个示例完全相同。当然,我们可以将代码包装在 `if` 块而不使用 `continue`。 - -但在副作用方面,它多创建了一层嵌套(大括号内的 `alert` 调用)。如果 `if` 中代码有多行,则可能会降低代码整体的可读性。 -```` - -````warn header="禁止 `break/continue` 在 ‘?’ 的右边" -请注意非表达式的语法结构不能与三元运算符 `?` 一起使用。特别是 `break/continue` 这样的指令是不允许这样使用的。 - -例如,我们使用如下代码: - -```js -if (i > 5) { - alert(i); -} else { - continue; -} -``` - -……用问号重写: - - -```js no-beautify -(i > 5) ? alert(i) : *!*continue*/!*; // continue 不允许在这个位置 -``` - -……代码会停止运行,并显示有语法错误。 - -这是不(建议)使用问号 `?` 操作符替代 `if` 语句的另一个原因。 -```` - -## break/continue 标签 - -有时候我们需要从一次从多层嵌套的循环中跳出来。 - -例如,下述代码中我们的循环使用了 `i` 和 `j`,从 `(0,0)` 到 `(3,3)` 提示坐标 `(i, j)`: - -```js run no-beautify -for (let i = 0; i < 3; i++) { - - for (let j = 0; j < 3; j++) { - - let input = prompt(`Value at coords (${i},${j})`, ''); - - // 如果我想从这里退出并直接执行 alert('Done!') - } -} - -alert('Done!'); -``` - -我们需要提供一种方法,以在用户取消输入时来停止这个过程。 - -在 `input` 之后的普通 `break` 只会打破内部循环。这还不够 —— 标签可以实现这一功能! - -**标签** 是在循环之前带有冒号的标识符: -```js -labelName: for (...) { - ... -} -``` - -`break ` 语句跳出循环至标签处: - -```js run no-beautify -*!*outer:*/!* for (let i = 0; i < 3; i++) { - - for (let j = 0; j < 3; j++) { - - let input = prompt(`Value at coords (${i},${j})`, ''); - - // 如果是空字符串或被取消,则中断并跳出这两个循环。 - if (!input) *!*break outer*/!*; // (*) - - // 用得到的值做些事…… - } -} -alert('Done!'); -``` - -上述代码中,`break outer` 向上寻找名为 `outer` 的标签并跳出当前循环。 - -因此,控制权直接从 `(*)` 转至 `alert('Done!')`。 - -我们还可以将标签移至单独一行: - -```js no-beautify -outer: -for (let i = 0; i < 3; i++) { ... } -``` - -`continue` 指令也可以与标签一起使用。在这种情况下,执行跳转到标记循环的下一次迭代。 - -````warn header="标签并不允许“跳到”所有位置" -标签不允许我们跳到代码的任意位置。 - -例如,这样做是不可能的: -```js -break label; // 无法跳转到这个标签 - -label: for (...) -``` - -只有在循环内部才能调用 `break/continue`,并且标签必须位于指令上方的某个位置。 -```` - -## 总结 - -我们学习了三种循环: - -- `while` —— 每次迭代之前都要检查条件。 -- `do..while` —— 每次迭代后都要检查条件。 -- `for (;;)` —— 每次迭代之前都要检查条件,可以使用其他设置。 - -通常使用 `while(true)` 来构造“无限”循环。这样的循环和其他循环一样,都可以通过 `break` 指令来终止。 - -如果我们不想在当前迭代中做任何事,并且想要转移至下一次迭代,那么可以使用 `continue` 指令。 - -`break/continue` 支持循环前的标签。标签是 `break/continue` 跳出嵌套循环以转到外部的唯一方法。 diff --git a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/solution.md b/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/solution.md deleted file mode 100644 index 4a8435c0dd..0000000000 --- a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/solution.md +++ /dev/null @@ -1,20 +0,0 @@ -为了精确实现 `switch` 的功能,`if` 必须使用严格相等 `'==='`。 - -对于给定的字符串,一个简单的 `'=='` 也可以。 - -```js no-beautify -if(browser == 'Edge') { - alert("You've got the Edge!"); -} else if (browser == 'Chrome' - || browser == 'Firefox' - || browser == 'Safari' - || browser == 'Opera') { - alert( 'Okay we support these browsers too' ); -} else { - alert( 'We hope that this page looks ok!' ); -} -``` - -请注意:将 `browser == 'Chrome' || browser == 'Firefox' …` 结构分成多行可读性更高。 - -但 `switch` 结构更清晰明了。 diff --git a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/solution.md b/1-js/02-first-steps/13-switch/2-rewrite-if-switch/solution.md deleted file mode 100644 index 7bdba13388..0000000000 --- a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/solution.md +++ /dev/null @@ -1,27 +0,0 @@ -前两个检查为前两个 `case`,第三个检查分为两种情况: - -```js run -let a = +prompt('a?', ''); - -switch (a) { - case 0: - alert( 0 ); - break; - - case 1: - alert( 1 ); - break; - - case 2: - case 3: - alert( '2,3' ); -*!* - break; -*/!* -} -``` - -请注意:最后的 `break` 不是必须的。但是为了让代码可扩展我们要把它加上。 - -有可能之后我们想要再添加一个 `case`,例如 `case 4`。如果我们忘记在它之前添加一个 break,那么在 case 3 执行结束后可能会出现错误。所以这是一种自我保险。 - diff --git a/1-js/02-first-steps/13-switch/article.md b/1-js/02-first-steps/13-switch/article.md deleted file mode 100644 index 0bda78de8d..0000000000 --- a/1-js/02-first-steps/13-switch/article.md +++ /dev/null @@ -1,173 +0,0 @@ -# "switch" 语句 - -`switch` 语句可以替代多个 `if` 判断。 - -`switch` 语句为多分支选择的情况提供了一个更具描述性的方式。 - -## 语法 - -`switch` 语句有至少一个 `case` 代码块和一个可选的 `default` 代码块。 - -就像这样: - -```js no-beautify -switch(x) { - case 'value1': // if (x === 'value1') - ... - [break] - - case 'value2': // if (x === 'value2') - ... - [break] - - default: - ... - [break] -} -``` - -- 比较 `x` 值与第一个 `case`(也就是 `value1`)是否严格相等,然后比较第二个 `case`(`value2`)以此类推。 -- 如果相等,`switch` 语句就执行相应 `case` 下的代码块,直到遇到最靠近的 `break` 语句(或者直到 `switch` 语句末尾)。 -- 如果没有符合的 case,则执行 `default` 代码块(如果 `default` 存在)。 - -## 举个例子 - -`switch` 的例子(高亮的部分是执行的 `case` 部分): - -```js run -let a = 2 + 2; - -switch (a) { - case 3: - alert( 'Too small' ); - break; -*!* - case 4: - alert( 'Exactly!' ); - break; -*/!* - case 5: - alert( 'Too large' ); - break; - default: - alert( "I don't know such values" ); -} -``` - -这里的 `switch` 从第一个 `case` 分支开始将 `a` 的值与 `case` 后的值进行比较,第一个 `case` 后的值为 `3` 匹配失败。 - -然后比较 `4`。匹配,所以从 `case 4` 开始执行直到遇到最近的 `break`。 - -**如果没有 `break`,程序将不经过任何检查就会继续执行下一个 `case`。** - -无 `break` 的例子: - -```js run -let a = 2 + 2; - -switch (a) { - case 3: - alert( 'Too small' ); -*!* - case 4: - alert( 'Exactly!' ); - case 5: - alert( 'Too big' ); - default: - alert( "I don't know such values" ); -*/!* -} -``` - -在上面的例子中我们会看到连续执行的三个 `alert`: - -```js -alert( 'Exactly!' ); -alert( 'Too big' ); -alert( "I don't know such values" ); -``` - -````smart header="任何表达式都可以成为 `switch/case` 的参数" -`switch` 和 `case` 都允许任意表达式。 - -比如: - -```js run -let a = "1"; -let b = 0; - -switch (+a) { -*!* - case b + 1: - alert("this runs, because +a is 1, exactly equals b+1"); - break; -*/!* - - default: - alert("this doesn't run"); -} -``` -这里 `+a` 返回 `1`,这个值跟 `case` 中 `b + 1` 相比较,然后执行对应的代码。 -```` - -## "case" 分组 - -共享同一段代码的几个 `case` 分支可以被分为一组: - -比如,如果我们想让 `case 3` 和 `case 5` 执行同样的代码: - -```js run no-beautify -let a = 2 + 2; - -switch (a) { - case 4: - alert('Right!'); - break; - -*!* - case 3: // (*) 下面这两个 case 被分在一组 - case 5: - alert('Wrong!'); - alert("Why don't you take a math class?"); - break; -*/!* - - default: - alert('The result is strange. Really.'); -} -``` - -现在 `3` 和 `5` 都显示相同的信息。 - -`switch/case` 有通过 case 进行“分组”的能力,其实是 switch 语句没有 `break` 时的副作用。因为没有 `break`,`case 3` 会从 `(*)` 行执行到 `case 5`。 - -## 类型很关键 - -强调一下,这里的相等是严格相等。被比较的值必须是相同的类型才能进行匹配。 - -比如,我们来看下面的代码: - -```js run -let arg = prompt("Enter a value?") -switch (arg) { - case '0': - case '1': - alert( 'One or zero' ); - break; - - case '2': - alert( 'Two' ); - break; - - case 3: - alert( 'Never executes!' ); - break; - default: - alert( 'An unknown value' ) -} -``` - -1. 在 `prompt` 对话框输入 `0`、`1`,第一个 `alert` 弹出。 -2. 输入 `2`,第二个 `alert` 弹出。 -3. 但是输入 `3`,因为 `prompt` 的结果是字符串类型的 `"3"`,不严格相等 `===` 于数字类型的 `3`,所以 `case 3` 不会执行!因此 `case 3` 部分是一段无效代码。所以会执行 `default` 分支。 - diff --git a/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md new file mode 100644 index 0000000000..43ee4aad3d --- /dev/null +++ b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md @@ -0,0 +1,25 @@ +The answer: `1`. + +```js run +let i = 3; + +while (i) { + alert( i-- ); +} +``` + +Every loop iteration decreases `i` by `1`. The check `while(i)` stops the loop when `i = 0`. + +Hence, the steps of the loop form the following sequence ("loop unrolled"): + +```js +let i = 3; + +alert(i--); // shows 3, decreases i to 2 + +alert(i--) // shows 2, decreases i to 1 + +alert(i--) // shows 1, decreases i to 0 + +// done, while(i) check stops the loop +``` diff --git a/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md new file mode 100644 index 0000000000..3b847dfa2d --- /dev/null +++ b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md @@ -0,0 +1,15 @@ +importance: 3 + +--- + +# Last loop value + +What is the last value alerted by this code? Why? + +```js +let i = 3; + +while (i) { + alert( i-- ); +} +``` diff --git a/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md new file mode 100644 index 0000000000..4953598769 --- /dev/null +++ b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md @@ -0,0 +1,30 @@ +The task demonstrates how postfix/prefix forms can lead to different results when used in comparisons. + +1. **From 1 to 4** + + ```js run + let i = 0; + while (++i < 5) alert( i ); + ``` + + The first value is `i = 1`, because `++i` first increments `i` and then returns the new value. So the first comparison is `1 < 5` and the `alert` shows `1`. + + Then follow `2, 3, 4…` -- the values show up one after another. The comparison always uses the incremented value, because `++` is before the variable. + + Finally, `i = 4` is incremented to `5`, the comparison `while(5 < 5)` fails, and the loop stops. So `5` is not shown. +2. **From 1 to 5** + + ```js run + let i = 0; + while (i++ < 5) alert( i ); + ``` + + The first value is again `i = 1`. The postfix form of `i++` increments `i` and then returns the *old* value, so the comparison `i++ < 5` will use `i = 0` (contrary to `++i < 5`). + + But the `alert` call is separate. It's another statement which executes after the increment and the comparison. So it gets the current `i = 1`. + + Then follow `2, 3, 4…` + + Let's stop on `i = 4`. The prefix form `++i` would increment it and use `5` in the comparison. But here we have the postfix form `i++`. So it increments `i` to `5`, but returns the old value. Hence the comparison is actually `while(4 < 5)` -- true, and the control goes on to `alert`. + + The value `i = 5` is the last one, because on the next step `while(5 < 5)` is false. diff --git a/1-js/02-first-steps/13-while-for/2-which-value-while/task.md b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md new file mode 100644 index 0000000000..298213237b --- /dev/null +++ b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md @@ -0,0 +1,22 @@ +importance: 4 + +--- + +# Which values does the while loop show? + +For every loop iteration, write down which value it outputs and then compare it with the solution. + +Both loops `alert` the same values, or not? + +1. The prefix form `++i`: + + ```js + let i = 0; + while (++i < 5) alert( i ); + ``` +2. The postfix form `i++` + + ```js + let i = 0; + while (i++ < 5) alert( i ); + ``` diff --git a/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md new file mode 100644 index 0000000000..e2e28e75b4 --- /dev/null +++ b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md @@ -0,0 +1,17 @@ +**The answer: from `0` to `4` in both cases.** + +```js run +for (let i = 0; i < 5; ++i) alert( i ); + +for (let i = 0; i < 5; i++) alert( i ); +``` + +That can be easily deducted from the algorithm of `for`: + +1. Execute once `i = 0` before everything (begin). +2. Check the condition `i < 5` +3. If `true` -- execute the loop body `alert(i)`, and then `i++` + +The increment `i++` is separated from the condition check (2). That's just another statement. + +The value returned by the increment is not used here, so there's no difference between `i++` and `++i`. diff --git a/1-js/02-first-steps/13-while-for/3-which-value-for/task.md b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md new file mode 100644 index 0000000000..bfefa63f53 --- /dev/null +++ b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md @@ -0,0 +1,20 @@ +importance: 4 + +--- + +# Which values get shown by the "for" loop? + +For each loop write down which values it is going to show. Then compare with the answer. + +Both loops `alert` same values or not? + +1. The postfix form: + + ```js + for (let i = 0; i < 5; i++) alert( i ); + ``` +2. The prefix form: + + ```js + for (let i = 0; i < 5; ++i) alert( i ); + ``` diff --git a/1-js/02-first-steps/12-while-for/4-for-even/solution.md b/1-js/02-first-steps/13-while-for/4-for-even/solution.md similarity index 52% rename from 1-js/02-first-steps/12-while-for/4-for-even/solution.md rename to 1-js/02-first-steps/13-while-for/4-for-even/solution.md index 5498001bba..e8e66bb47c 100644 --- a/1-js/02-first-steps/12-while-for/4-for-even/solution.md +++ b/1-js/02-first-steps/13-while-for/4-for-even/solution.md @@ -8,4 +8,4 @@ for (let i = 2; i <= 10; i++) { } ``` -我们使用 "modulo" 运算符 `%` 来获取余数,并检查奇偶性。 +We use the "modulo" operator `%` to get the remainder and check for the evenness here. diff --git a/1-js/02-first-steps/13-while-for/4-for-even/task.md b/1-js/02-first-steps/13-while-for/4-for-even/task.md new file mode 100644 index 0000000000..ff34e7e40f --- /dev/null +++ b/1-js/02-first-steps/13-while-for/4-for-even/task.md @@ -0,0 +1,9 @@ +importance: 5 + +--- + +# Output even numbers in the loop + +Use the `for` loop to output even numbers from `2` to `10`. + +[demo] diff --git a/1-js/02-first-steps/12-while-for/5-replace-for-while/solution.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/solution.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/5-replace-for-while/solution.md rename to 1-js/02-first-steps/13-while-for/5-replace-for-while/solution.md diff --git a/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md new file mode 100644 index 0000000000..0c69d9c2d5 --- /dev/null +++ b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md @@ -0,0 +1,14 @@ +importance: 5 + +--- + +# Replace "for" with "while" + +Rewrite the code changing the `for` loop to `while` without altering its behavior (the output should stay same). + +```js run +for (let i = 0; i < 3; i++) { + alert( `number ${i}!` ); +} +``` + 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 new file mode 100644 index 0000000000..c7de5f09b0 --- /dev/null +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md @@ -0,0 +1,15 @@ + +```js run demo +let num; + +do { + num = prompt("Enter a number greater than 100?", 0); +} while (num <= 100 && num); +``` + +The loop `do..while` repeats while both checks are truthy: + +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. + +P.S. If `num` is `null` then `num <= 100` is `true`, so without the 2nd check the loop wouldn't stop if the user clicks CANCEL. Both checks are required. diff --git a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md new file mode 100644 index 0000000000..0788ee76e4 --- /dev/null +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md @@ -0,0 +1,13 @@ +importance: 5 + +--- + +# Repeat until the input is correct + +Write a loop which prompts for a number greater than `100`. If the visitor enters another number -- ask them to input again. + +The loop must ask for a number until either the visitor enters a number greater than `100` or cancels the input/enters an empty line. + +Here we can assume that the visitor only inputs numbers. There's no need to implement a special handling for a non-numeric input in this task. + +[demo] diff --git a/1-js/02-first-steps/13-while-for/7-list-primes/solution.md b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md new file mode 100644 index 0000000000..b4b64b6faa --- /dev/null +++ b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md @@ -0,0 +1,29 @@ +There are many algorithms for this task. + +Let's use a nested loop: + +```js +For each i in the interval { + check if i has a divisor from 1..i + if yes => the value is not a prime + if no => the value is a prime, show it +} +``` + +The code using a label: + +```js run +let n = 10; + +nextPrime: +for (let i = 2; i <= n; i++) { // for each i... + + for (let j = 2; j < i; j++) { // look for a divisor.. + if (i % j == 0) continue nextPrime; // not a prime, go next i + } + + alert( i ); // a prime +} +``` + +There's a lot of space to optimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc. diff --git a/1-js/02-first-steps/13-while-for/7-list-primes/task.md b/1-js/02-first-steps/13-while-for/7-list-primes/task.md new file mode 100644 index 0000000000..6344b9f6f8 --- /dev/null +++ b/1-js/02-first-steps/13-while-for/7-list-primes/task.md @@ -0,0 +1,17 @@ +importance: 3 + +--- + +# Output prime numbers + +An integer number greater than `1` is called a [prime](https://en.wikipedia.org/wiki/Prime_number) if it cannot be divided without a remainder by anything except `1` and itself. + +In other words, `n > 1` is a prime if it can't be evenly divided by anything except `1` and `n`. + +For example, `5` is a prime, because it cannot be divided without a remainder by `2`, `3` and `4`. + +**Write the code which outputs prime numbers in the interval from `2` to `n`.** + +For `n = 10` the result will be `2,3,5,7`. + +P.S. The code should work for any `n`, not be hard-tuned for any fixed value. diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md new file mode 100644 index 0000000000..d1b749888f --- /dev/null +++ b/1-js/02-first-steps/13-while-for/article.md @@ -0,0 +1,411 @@ +# Loops: while and for + +We often need to repeat actions. + +For example, outputting goods from a list one after another or just running the same code for each number from 1 to 10. + +*Loops* are a way to repeat the same code multiple times. + +```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 + +The `while` loop has the following syntax: + +```js +while (condition) { + // code + // so-called "loop body" +} +``` + +While the `condition` is truthy, the `code` from the loop body is executed. + +For instance, the loop below outputs `i` while `i < 3`: + +```js run +let i = 0; +while (i < 3) { // shows 0, then 1, then 2 + alert( i ); + i++; +} +``` + +A single execution of the loop body is called *an iteration*. The loop in the example above makes three iterations. + +If `i++` was missing from the example above, the loop would repeat (in theory) forever. In practice, the browser provides ways to stop such loops, and in server-side JavaScript, we can kill the process. + +Any expression or variable can be a loop condition, not just comparisons: the condition is evaluated and converted to a boolean by `while`. + +For instance, a shorter way to write `while (i != 0)` is `while (i)`: + +```js run +let i = 3; +*!* +while (i) { // when i becomes 0, the condition becomes falsy, and the loop stops +*/!* + alert( i ); + i--; +} +``` + +````smart header="Curly braces are not required for a single-line body" +If the loop body has a single statement, we can omit the curly braces `{…}`: + +```js run +let i = 3; +*!* +while (i) alert(i--); +*/!* +``` +```` + +## The "do..while" loop + +The condition check can be moved *below* the loop body using the `do..while` syntax: + +```js +do { + // loop body +} while (condition); +``` + +The loop will first execute the body, then check the condition, and, while it's truthy, execute it again and again. + +For example: + +```js run +let i = 0; +do { + alert( i ); + i++; +} while (i < 3); +``` + +This form of syntax should only be used when you want the body of the loop to execute **at least once** regardless of the condition being truthy. Usually, the other form is preferred: `while(…) {…}`. + +## The "for" loop + +The `for` loop is more complex, but it's also the most commonly used loop. + +It looks like this: + +```js +for (begin; condition; step) { + // ... loop body ... +} +``` + +Let's learn the meaning of these parts by example. The loop below runs `alert(i)` for `i` from `0` up to (but not including) `3`: + +```js run +for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2 + alert(i); +} +``` + +Let's examine the `for` statement part-by-part: + +| part | | | +|-------|----------|----------------------------------------------------------------------------| +| 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. | + +The general loop algorithm works like this: + +``` +Run begin +→ (if condition → run body and run step) +→ (if condition → run body and run step) +→ (if condition → run body and run step) +→ ... +``` + +That is, `begin` executes once, and then it iterates: after each `condition` test, `body` and `step` are executed. + +If you are new to loops, it could help to go back to the example and reproduce how it runs step-by-step on a piece of paper. + +Here's exactly what happens in our case: + +```js +// for (let i = 0; i < 3; i++) alert(i) + +// run begin +let i = 0 +// if condition → run body and run step +if (i < 3) { alert(i); i++ } +// if condition → run body and run step +if (i < 3) { alert(i); i++ } +// if condition → run body and run step +if (i < 3) { alert(i); i++ } +// ...finish, because now i == 3 +``` + +````smart header="Inline variable declaration" +Here, the "counter" variable `i` is declared right in the loop. This is called an "inline" variable declaration. Such variables are visible only inside the loop. + +```js run +for (*!*let*/!* i = 0; i < 3; i++) { + alert(i); // 0, 1, 2 +} +alert(i); // error, no such variable +``` + +Instead of defining a variable, we could use an existing one: + +```js run +let i = 0; + +for (i = 0; i < 3; i++) { // use an existing variable + alert(i); // 0, 1, 2 +} + +alert(i); // 3, visible, because declared outside of the loop +``` +```` + +### Skipping parts + +Any part of `for` can be skipped. + +For example, we can omit `begin` if we don't need to do anything at the loop start. + +Like here: + +```js run +let i = 0; // we have i already declared and assigned + +for (; i < 3; i++) { // no need for "begin" + alert( i ); // 0, 1, 2 +} +``` + +We can also remove the `step` part: + +```js run +let i = 0; + +for (; i < 3;) { + alert( i++ ); +} +``` + +This makes the loop identical to `while (i < 3)`. + +We can actually remove everything, creating an infinite loop: + +```js +for (;;) { + // repeats without limits +} +``` + +Please note that the two `for` semicolons `;` must be present. Otherwise, there would be a syntax error. + +## Breaking the loop + +Normally, a loop exits when its condition becomes falsy. + +But we can force the exit at any time using the special `break` directive. + +For example, the loop below asks the user for a series of numbers, "breaking" when no number is entered: + +```js run +let sum = 0; + +while (true) { + + let value = +prompt("Enter a number", ''); + +*!* + if (!value) break; // (*) +*/!* + + sum += value; + +} +alert( 'Sum: ' + sum ); +``` + +The `break` directive is activated at the line `(*)` if the user enters an empty line or cancels the input. It stops the loop immediately, passing control to the first line after the loop. Namely, `alert`. + +The combination "infinite loop + `break` as needed" is great for situations when a loop's condition must be checked not in the beginning or end of the loop, but in the middle or even in several places of its body. + +## Continue to the next iteration [#continue] + +The `continue` directive is a "lighter version" of `break`. It doesn't stop the whole loop. Instead, it stops the current iteration and forces the loop to start a new one (if the condition allows). + +We can use it if we're done with the current iteration and would like to move on to the next one. + +The loop below uses `continue` to output only odd values: + +```js run no-beautify +for (let i = 0; i < 10; i++) { + + // if true, skip the remaining part of the body + *!*if (i % 2 == 0) continue;*/!* + + alert(i); // 1, then 3, 5, 7, 9 +} +``` + +For even values of `i`, the `continue` directive stops executing the body and passes control to the next iteration of `for` (with the next number). So the `alert` is only called for odd values. + +````smart header="The `continue` directive helps decrease nesting" +A loop that shows odd values could look like this: + +```js run +for (let i = 0; i < 10; i++) { + + if (i % 2) { + alert( i ); + } + +} +``` + +From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`. + +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. +```` + +````warn header="No `break/continue` to the right side of '?'" +Please note that syntax constructs that are not expressions cannot be used with the ternary operator `?`. In particular, directives such as `break/continue` aren't allowed there. + +For example, if we take this code: + +```js +if (i > 5) { + alert(i); +} else { + continue; +} +``` + +...and rewrite it using a question mark: + +```js no-beautify +(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here +``` + +...it stops working: there's a syntax error. + +This is just another reason not to use the question mark operator `?` instead of `if`. +```` + +## Labels for break/continue + +Sometimes we need to break out from multiple nested loops at once. + +For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(2,2)`: + +```js run no-beautify +for (let i = 0; i < 3; i++) { + + for (let j = 0; j < 3; j++) { + + let input = prompt(`Value at coords (${i},${j})`, ''); + + // what if we want to exit from here to Done (below)? + } +} + +alert('Done!'); +``` + +We need a way to stop the process if the user cancels the input. + +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: + +```js +labelName: for (...) { + ... +} +``` + +The `break ` statement in the loop below breaks out to the label: + +```js run no-beautify +*!*outer:*/!* for (let i = 0; i < 3; i++) { + + for (let j = 0; j < 3; j++) { + + let input = prompt(`Value at coords (${i},${j})`, ''); + + // if an empty string or canceled, then break out of both loops + if (!input) *!*break outer*/!*; // (*) + + // do something with the value... + } +} + +alert('Done!'); +``` + +In the code above, `break outer` looks upwards for the label named `outer` and breaks out of that loop. + +So the control goes straight from `(*)` to `alert('Done!')`. + +We can also move the label onto a separate line: + +```js no-beautify +outer: +for (let i = 0; i < 3; i++) { ... } +``` + +The `continue` directive can also be used with a label. In this case, code execution jumps to the next iteration of the labeled loop. + +````warn header="Labels do not allow to \"jump\" anywhere" +Labels do not allow us to jump into an arbitrary place in the code. + +For example, it is impossible to do this: + +```js +break label; // jump to the label below (doesn't work) + +label: for (...) +``` + +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. +```` + +## Summary + +We covered 3 types of loops: + +- `while` -- The condition is checked before each iteration. +- `do..while` -- The condition is checked after each iteration. +- `for (;;)` -- The condition is checked before each iteration, additional settings available. + +To make an "infinite" loop, usually the `while(true)` construct is used. Such a loop, just like any other, can be stopped with the `break` directive. + +If we don't want to do anything in the current iteration and would like to forward to the next one, we can use the `continue` directive. + +`break/continue` support labels before the loop. A label is the only way for `break/continue` to escape a nested loop to go to an outer one. diff --git a/1-js/02-first-steps/14-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/14-function-basics/1-if-else-required/solution.md deleted file mode 100644 index 7e37b1289f..0000000000 --- a/1-js/02-first-steps/14-function-basics/1-if-else-required/solution.md +++ /dev/null @@ -1 +0,0 @@ -没有区别。 diff --git a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md b/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md deleted file mode 100644 index d461bb52c1..0000000000 --- a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md +++ /dev/null @@ -1,26 +0,0 @@ -importance: 4 - ---- - -# 使用 '?' 或者 '||' 重写函数 - -如果参数 `age` 大于 `18`,那么下面的函数返回 `true`。 - -否则它将会要求进行确认,并返回确认结果: - -```js -function checkAge(age) { - if (age > 18) { - return true; - } else { - return confirm('Do you have your parents permission to access this page?'); - } -} -``` - -重写这个函数并保证效果相同,不使用 `if`,且只需一行代码。 - -编写 `checkAge` 的两个变体: - -1. 使用问号运算符 `?` -2. 使用或运算符 `||` diff --git a/1-js/02-first-steps/14-function-basics/3-min/task.md b/1-js/02-first-steps/14-function-basics/3-min/task.md deleted file mode 100644 index 86291f07c9..0000000000 --- a/1-js/02-first-steps/14-function-basics/3-min/task.md +++ /dev/null @@ -1,16 +0,0 @@ -importance: 1 - ---- - -# 函数 min(a, b) - -写一个返回数字 `a` 和 `b` 中较小的那个数字的函数 `min(a,b)`。 - -例如: - -```js -min(2, 5) == 2 -min(3, -1) == -1 -min(1, 1) == 1 -``` - diff --git a/1-js/02-first-steps/14-function-basics/4-pow/task.md b/1-js/02-first-steps/14-function-basics/4-pow/task.md deleted file mode 100644 index d763b4e8b4..0000000000 --- a/1-js/02-first-steps/14-function-basics/4-pow/task.md +++ /dev/null @@ -1,19 +0,0 @@ -importance: 4 - ---- - -# 函数 pow(x,n) - -写一个函数 `pow(x,n)`,返回 `x` 的 `n` 次方。换句话说,将 `x` 与自身相乘 `n` 次,返回最终结果。 - -```js -pow(3, 2) = 3 * 3 = 9 -pow(3, 3) = 3 * 3 * 3 = 27 -pow(1, 100) = 1 * 1 * ...*1 = 1 -``` - -创建一个 web 页面,提示输入 `x` 和 `n`,然后返回 `pow(x,n)` 的运算结果。 - -[demo] - -P.S. 在这个任务中,函数应该只支持自然数 `n`:从 `1` 开始的整数。 diff --git a/1-js/02-first-steps/14-function-basics/article.md b/1-js/02-first-steps/14-function-basics/article.md deleted file mode 100644 index 57cf225cbb..0000000000 --- a/1-js/02-first-steps/14-function-basics/article.md +++ /dev/null @@ -1,476 +0,0 @@ -# 函数 - -我们经常需要在脚本的许多地方执行很相似的操作。 - -例如,当访问者登录、注销或者在其他地方时,我们需要显示一条好看的信息。 - -函数是程序的主要“构建模块”。函数使该段代码可以被调用很多次,而不需要写重复的代码。 - -我们已经看到了内置函数的示例,如 `alert(message)`、`prompt(message, default)` 和 `confirm(question)`。但我们也可以创建自己的函数。 - -## 函数声明 - -使用 **函数声明** 创建函数。 - -看起来就像这样: - -```js -function showMessage() { - alert( 'Hello everyone!' ); -} -``` - -`function` 关键字首先出现,然后是 **函数名**,然后是括号之间的 **参数** 列表(用逗号分隔,在上述示例中为空),最后是花括号之间的代码(即“函数体”)。 - -```js -function name(parameters) { - ...body... -} -``` - -我们的新函数可以通过名称调用:`showMessage()`。 - -例如: - -```js run -function showMessage() { - alert( 'Hello everyone!' ); -} - -*!* -showMessage(); -showMessage(); -*/!* -``` - -调用 `showMessage()` 执行函数的代码。这里我们会看到显示两次消息。 - -这个例子清楚地演示了函数的主要目的之一:避免代码重复。 - -如果我们需要更改消息或其显示方式,只需在一个地方修改代码:输出它的函数。 - -## 局部变量 - -在函数中声明的变量只在该函数内部可见。 - -例如: - -```js run -function showMessage() { -*!* - let message = "Hello, I'm JavaScript!"; // 局部变量 -*/!* - - alert( message ); -} - -showMessage(); // Hello, I'm JavaScript! - -alert( message ); // <-- 错误!变量是函数的局部变量 -``` - -## 外部变量 - -函数也可以访问外部变量,例如: - -```js run no-beautify -let *!*userName*/!* = 'John'; - -function showMessage() { - let message = 'Hello, ' + *!*userName*/!*; - alert(message); -} - -showMessage(); // Hello, John -``` - -函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。 - -例如: - -```js run -let *!*userName*/!* = 'John'; - -function showMessage() { - *!*userName*/!* = "Bob"; // (1) 改变外部变量 - - let message = 'Hello, ' + *!*userName*/!*; - alert(message); -} - -alert( userName ); // *!*John*/!* 在函数调用之前 - -showMessage(); - -alert( userName ); // *!*Bob*/!*,值被函数修改了 -``` - -只有在没有局部变量的情况下才会使用外部变量。 - -如果在函数内部声明了同名变量,那么函数会 **遮蔽** 外部变量。例如,在下面的代码中,函数使用局部的 `userName`,而外部变量被忽略: - -```js run -let userName = 'John'; - -function showMessage() { -*!* - let userName = "Bob"; // 声明一个局部变量 -*/!* - - let message = 'Hello, ' + userName; // *!*Bob*/!* - alert(message); -} - -// 函数会创建并使用它自己的 userName -showMessage(); - -alert( userName ); // *!*John*/!*,未被更改,函数没有访问外部变量。 -``` - -```smart header="全局变量" -任何函数之外声明的变量,例如上述代码中的外部变量 `userName`,都被称为 **全局** 变量。 - -全局变量在任意函数中都是可见的(除非被局部变量遮蔽)。 - -减少全局变量的使用是一种很好的做法。现代的代码有很少甚至没有全局变量。大多数变量存在于它们的函数中。但是有时候,全局变量能够用于存储项目级别的数据。 -``` - -## 参数 - -我们可以使用参数(也称“函数参数”)来将任意数据传递给函数。 - -在如下示例中,函数有两个参数:`from` 和 `text`。 - -```js run -function showMessage(*!*from, text*/!*) { // 参数:from 和 text - alert(from + ': ' + text); -} - -*!* -showMessage('Ann', 'Hello!'); // Ann: Hello! (*) -showMessage('Ann', "What's up?"); // Ann: What's up? (**) -*/!* -``` - -当函数在 `(*)` 和 `(**)` 行中被调用时,给定值被复制到了局部变量 `from` 和 `text`。然后函数使用它们进行计算。 - -这里还有一个例子:我们有一个变量 `from`,并将它传递给函数。请注意:函数会修改 `from`,但在函数外部看不到更改,因为函数修改的是复制的变量值副本: - - -```js run -function showMessage(from, text) { - -*!* -  from = '*' + from + '*'; // 让 "from" 看起来更优雅 -*/!* - - alert( from + ': ' + text ); -} - -let from = "Ann"; - -showMessage(from, "Hello"); // *Ann*: Hello - -// "from" 值相同,函数修改了一个局部的副本。 -alert( from ); // Ann -``` - -## 默认值 - -如果未提供参数,那么其默认值则是 `undefined`。 - -例如,之前提到的函数 `showMessage(from, text)` 可以只使用一个参数调用: - -```js -showMessage("Ann"); -``` - -那不是错误,这样调用将输出 `"Ann: undefined"`。这里没有参数 `text`,所以程序假定 `text === undefined`。 - -如果我们想在本示例中设定“默认”的 `text`,那么我们可以在 `=` 之后指定它: - -```js run -function showMessage(from, *!*text = "no text given"*/!*) { - alert( from + ": " + text ); -} - -showMessage("Ann"); // Ann: no text given -``` - -现在如果 `text` 参数未被传递,它将会得到值 `"no text given"`。 - -这里 `"no text given"` 是一个字符串,但它可以是更复杂的表达式,并且只会在缺少参数时才会被计算和分配。所以,这也是可能的: - -```js run -function showMessage(from, text = anotherFunction()) { - // anotherFunction() 仅在没有给定 text 时执行 - // 其运行结果将成为 text 的值 -} -``` - -```smart header="默认参数的计算" -在 JavaScript 中,每次函数在没带个别参数的情况下被调用,默认参数会被计算出来。 - -在上面的例子中,每次 `showMessage()` 不带 `text` 参数被调用时,`anotherFunction()` 就会被调用。 -``` - -````smart header="旧式默认参数" -旧版本的 JavaScript 不支持默认参数。所以在大多数旧版本的脚本中,你能找到其他设置默认参数的方法。 - -例如,用于 `undefined` 的显式检查: - -```js -function showMessage(from, text) { -*!* - if (text === undefined) { - text = 'no text given'; - } -*/!* - - alert( from + ": " + text ); -} -``` - -……或使用 `||` 运算符: - -```js -function showMessage(from, text) { - // 如果 text 能转为 false,那么 text 会得到“默认”值 - text = text || 'no text given'; - ... -} -``` - - -```` - - -## 返回值 - -函数可以将一个值返回到调用代码中作为结果。 - -最简单的例子是将两个值相加的函数: - -```js run no-beautify -function sum(a, b) { - *!*return*/!* a + b; -} - -let result = sum(1, 2); -alert( result ); // 3 -``` - -指令 `return` 可以在函数的任意位置。当执行到达时,函数停止,并将值返回给调用代码(分配给上述代码中的 `result`)。 - -在一个函数中可能会出现很多次 `return`。例如: - -```js run -function checkAge(age) { - if (age >= 18) { -*!* - return true; -*/!* - } else { -*!* - return confirm('Got a permission from the parents?'); -*/!* - } -} - -let age = prompt('How old are you?', 18); - -if ( checkAge(age) ) { - alert( 'Access granted' ); -} else { - alert( 'Access denied' ); -} -``` - -只使用 `return` 但没有返回值也是可行的。但这会导致函数立即退出。 - -例如: - -```js -function showMovie(age) { - if ( !checkAge(age) ) { -*!* - return; -*/!* - } - - alert( "Showing you the movie" ); // (*) - // ... -} -``` - -在上述代码中,如果 `checkAge(age)` 返回 `false`,那么 `showMovie` 将不会运行到 `alert`。 - -````smart header="空值的 `return` 或没有 `return` 的函数返回值为 `undefined`" -如果函数无返回值,它就会像返回 `undefined` 一样: - -```js run -function doNothing() { /* 没有代码 */ } - -alert( doNothing() === undefined ); // true -``` - -空值的 `return` 和 `return undefined` 等效: - -```js run -function doNothing() { - return; -} - -alert( doNothing() === undefined ); // true -``` -```` - -````warn header="不要在 `return` 与返回值之间添加新行" -对于 `return` 的长表达式,可能你会很想将其放在单独一行,如下所示: - -```js -return - (some + long + expression + or + whatever * f(a) + f(b)) -``` -但这不行,因为 JavaScript 默认会在 `return` 之后加上分号。上面这段代码和下面这段代码运行流程相同: - -```js -return*!*;*/!* - (some + long + expression + or + whatever * f(a) + f(b)) -``` - -因此,实际上它的返回值变成了空值。 - -如果我们想要将返回的表达式写成跨多行的形式,那么应该在 `return` 的同一行开始写此表达式。或者至少按照如下的方式放上左括号: - -```js -return ( - some + long + expression - + or + - whatever * f(a) + f(b) - ) -``` -然后它就能像我们预想的那样正常运行了。 -```` - -## 函数命名 [#function-naming] - -函数是行为。所以它们的名字通常是动词。它应该简短且尽可能准确地描述函数的作用。这样读代码的人就能清楚地知道这个函数的功能。 - -一种普遍的做法是用动词前缀来开始一个函数,这个前缀模糊地描述了这个动作。团队内部必须就前缀的含义达成一致。 - -例如,以 `"show"` 开头的函数通常会显示某些内容。 - -函数以 XX 开始…… - -- `"get…"` —— 返回一个值, -- `"calc…"` —— 计算某些内容, -- `"create…"` —— 创建某些内容, -- `"check…"` —— 检查某些内容并返回 boolean 值,等。 - -这类名字的示例: - -```js no-beautify -showMessage(..) // 显示信息 -getAge(..) // 返回 age(gets it somehow) -calcSum(..) // 计算求和并返回结果 -createForm(..) // 创建表格(通常会返回它) -checkPermission(..) // 检查权限并返回 true/false -``` - -有了前缀,只需瞥一眼函数名,就可以了解它的功能是什么,返回什么样的值。 - -```smart header="一个函数 —— 做一件事" -一个函数应该只包含函数名所指定的功能,而不是做更多与函数名无关的功能。 - -两个独立的操作通常需要两个函数,即使它们通常被一起调用(在这种情况下,我们可以创建第三个函数来调用这两个函数)。 - -有几个违反这一规则的例子: - -- `getAge` —— 如果它通过 `alert` 将 age 显示出来,那就有问题了(只应该是获取)。 -- `createForm` —— 如果它包含修改文档的操作,例如向文档添加一个表单,那就有问题了(只应该创建表单并返回)。 -- `checkPermission` —— 如果它显示 `access granted/denied` 消息,那就有问题了(只应执行检查并返回结果)。 - -这些例子假设函数名前缀具有通用的含义。你和你的团队可以自定义这些函数名前缀的含义,但是通常都没有太大的不同。无论怎样,你都应该对函数名前缀的含义、带特定前缀的函数可以做什么以及不可以做什么有深刻的了解。所有相同前缀的函数都应该遵守相同的规则。并且,团队成员应该形成共识。 -``` - -```smart header="非常短的函数命名" -常用的函数有时会有**非常短**的名字。 - -例如,[jQuery](http://jquery.com) 框架用 `$` 定义一个函数。[LoDash](http://lodash.com/) 库的核心函数用 `_` 命名。 - -这些都是例外,一般而言,函数名应简明扼要且具有描述性。 -``` - -## 函数 == 注释 - -函数应该简短且只有一个功能。如果这个函数功能复杂,那么把该函数拆分成几个小的函数是值得的。有时候遵循这个规则并不是那么容易,但这绝对是件好事。 - -一个单独的函数不仅更容易测试和调试 —— 它的存在本身就是一个很好的注释! - -例如,比较如下两个函数 `showPrimes(n)`。他们的功能都是输出到 `n` 的 [素数](https://en.wikipedia.org/wiki/Prime_number)。 - -第一个变体使用了一个标签: - -```js -function showPrimes(n) { - nextPrime: for (let i = 2; i < n; i++) { - - for (let j = 2; j < i; j++) { - if (i % j == 0) continue nextPrime; - } - - alert( i ); // 一个素数 - } -} -``` - -第二个变体使用附加函数 `isPrime(n)` 来检验素数: - -```js -function showPrimes(n) { - - for (let i = 2; i < n; i++) { - *!*if (!isPrime(i)) continue;*/!* - - alert(i); // 一个素数 - } -} - -function isPrime(n) { - for (let i = 2; i < n; i++) { - if ( n % i == 0) return false; - } - return true; -} -``` - -第二个变体更容易理解,不是吗?我们通过函数名(`isPrime`)就可以看出函数的功能,而不需要通过代码。人们通常把这样的代码称为 **自描述**。 - -因此,即使我们不打算重用它们,也可以创建函数。函数可以让代码结构更清晰,可读性更强。 - -## 总结 - -函数声明方式如下所示: - -```js -function name(parameters, delimited, by, comma) { - /* code */ -} -``` - -- 作为参数传递给函数的值,会被复制到函数的局部变量。 -- 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量。 -- 函数可以返回值。如果没有返回值,则其返回的结果是 `undefined`。 - -为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。 - -与不获取参数但将修改外部变量作为副作用的函数相比,获取参数、使用参数并返回结果的函数更容易理解。 - -函数命名: - -- 函数名应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的函数名能够让我们马上知道这个函数的功能是什么,会返回什么。 -- 一个函数是一个行为,所以函数名通常是动词。 -- 目前有许多优秀的函数名前缀,如 `create…`、`show…`、`get…`、`check…` 等等。使用它们来提示函数的作用吧。 - -函数是脚本的主要构建块。现在我们已经介绍了基本知识,现在我们就可以开始创建和使用函数了。但这只是学习和使用函数的开始。我们将继续学习更多函数的相关知识,更深入地研究它们的先进特征。 diff --git a/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md new file mode 100644 index 0000000000..d3e397434b --- /dev/null +++ b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md @@ -0,0 +1,20 @@ +To precisely match the functionality of `switch`, the `if` must use a strict comparison `'==='`. + +For given strings though, a simple `'=='` works too. + +```js no-beautify +if(browser == 'Edge') { + alert("You've got the Edge!"); +} else if (browser == 'Chrome' + || browser == 'Firefox' + || browser == 'Safari' + || browser == 'Opera') { + alert( 'Okay we support these browsers too' ); +} else { + alert( 'We hope that this page looks ok!' ); +} +``` + +Please note: the construct `browser == 'Chrome' || browser == 'Firefox' …` is split into multiple lines for better readability. + +But the `switch` construct is still cleaner and more descriptive. diff --git a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/task.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md similarity index 72% rename from 1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/task.md rename to 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md index 3faf06991b..f4dc0e5f19 100644 --- a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/task.md +++ b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 将 "switch" 结构重写为 "if" 结构 +# Rewrite the "switch" into an "if" -将下面 `switch` 结构的代码写成 `if..else` 结构: +Write the code using `if..else` which would correspond to the following `switch`: ```js switch (browser) { diff --git a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md new file mode 100644 index 0000000000..ed87dd94b6 --- /dev/null +++ b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md @@ -0,0 +1,26 @@ +The first two checks turn into two `case`. The third check is split into two cases: + +```js run +let a = +prompt('a?', ''); + +switch (a) { + case 0: + alert( 0 ); + break; + + case 1: + alert( 1 ); + break; + + case 2: + case 3: + alert( '2,3' ); +*!* + break; +*/!* +} +``` + +Please note: the `break` at the bottom is not required. But we put it to make the code future-proof. + +In the future, there is a chance that we'd want to add one more `case`, for example `case 4`. And if we forget to add a break before it, at the end of `case 3`, there will be an error. So that's a kind of self-insurance. diff --git a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/task.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md similarity index 66% rename from 1-js/02-first-steps/13-switch/2-rewrite-if-switch/task.md rename to 1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md index 04b592938a..ec99d098db 100644 --- a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/task.md +++ b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md @@ -2,9 +2,9 @@ importance: 4 --- -# 将 "if" 结构重写为 "switch" 结构 +# Rewrite "if" into "switch" -用 `switch` 重写以下代码: +Rewrite the code below using a single `switch` statement: ```js run let a = +prompt('a?', ''); diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md new file mode 100644 index 0000000000..d86babcec0 --- /dev/null +++ b/1-js/02-first-steps/14-switch/article.md @@ -0,0 +1,172 @@ +# The "switch" statement + +A `switch` statement can replace multiple `if` checks. + +It gives a more descriptive way to compare a value with multiple variants. + +## The syntax + +The `switch` has one or more `case` blocks and an optional default. + +It looks like this: + +```js no-beautify +switch(x) { + case 'value1': // if (x === 'value1') + ... + [break] + + case 'value2': // if (x === 'value2') + ... + [break] + + default: + ... + [break] +} +``` + +- The value of `x` is checked for a strict equality to the value from the first `case` (that is, `value1`) then to the second (`value2`) and so on. +- If the equality is found, `switch` starts to execute the code starting from the corresponding `case`, until the nearest `break` (or until the end of `switch`). +- If no case is matched then the `default` code is executed (if it exists). + +## An example + +An example of `switch` (the executed code is highlighted): + +```js run +let a = 2 + 2; + +switch (a) { + case 3: + alert( 'Too small' ); + break; +*!* + case 4: + alert( 'Exactly!' ); + break; +*/!* + case 5: + alert( 'Too big' ); + break; + default: + alert( "I don't know such values" ); +} +``` + +Here the `switch` starts to compare `a` from the first `case` variant that is `3`. The match fails. + +Then `4`. That's a match, so the execution starts from `case 4` until the nearest `break`. + +**If there is no `break` then the execution continues with the next `case` without any checks.** + +An example without `break`: + +```js run +let a = 2 + 2; + +switch (a) { + case 3: + alert( 'Too small' ); +*!* + case 4: + alert( 'Exactly!' ); + case 5: + alert( 'Too big' ); + default: + alert( "I don't know such values" ); +*/!* +} +``` + +In the example above we'll see sequential execution of three `alert`s: + +```js +alert( 'Exactly!' ); +alert( 'Too big' ); +alert( "I don't know such values" ); +``` + +````smart header="Any expression can be a `switch/case` argument" +Both `switch` and `case` allow arbitrary expressions. + +For example: + +```js run +let a = "1"; +let b = 0; + +switch (+a) { +*!* + case b + 1: + alert("this runs, because +a is 1, exactly equals b+1"); + break; +*/!* + + default: + alert("this doesn't run"); +} +``` +Here `+a` gives `1`, that's compared with `b + 1` in `case`, and the corresponding code is executed. +```` + +## Grouping of "case" + +Several variants of `case` which share the same code can be grouped. + +For example, if we want the same code to run for `case 3` and `case 5`: + +```js run no-beautify +let a = 3; + +switch (a) { + case 4: + alert('Right!'); + break; + +*!* + case 3: // (*) grouped two cases + case 5: + alert('Wrong!'); + alert("Why don't you take a math class?"); + break; +*/!* + + default: + alert('The result is strange. Really.'); +} +``` + +Now both `3` and `5` show the same message. + +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`. + +## Type matters + +Let's emphasize that the equality check is always strict. The values must be of the same type to match. + +For example, let's consider the code: + +```js run +let arg = prompt("Enter a value?"); +switch (arg) { + case '0': + case '1': + alert( 'One or zero' ); + break; + + case '2': + alert( 'Two' ); + break; + + case 3: + alert( 'Never executes!' ); + break; + default: + alert( 'An unknown value' ); +} +``` + +1. For `0`, `1`, the first `alert` runs. +2. For `2` the second `alert` runs. +3. But for `3`, the result of the `prompt` is a string `"3"`, which is not strictly equal `===` to the number `3`. So we've got a dead code in `case 3`! The `default` variant will execute. 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 new file mode 100644 index 0000000000..e3a0df77c4 --- /dev/null +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md @@ -0,0 +1,3 @@ +No difference! + +In both cases, `return confirm('Did parents allow you?')` executes exactly when the `if` condition is falsy. \ No newline at end of file diff --git a/1-js/02-first-steps/14-function-basics/1-if-else-required/task.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md similarity index 52% rename from 1-js/02-first-steps/14-function-basics/1-if-else-required/task.md rename to 1-js/02-first-steps/15-function-basics/1-if-else-required/task.md index ebb783854e..4f69a5c8c3 100644 --- a/1-js/02-first-steps/14-function-basics/1-if-else-required/task.md +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md @@ -2,11 +2,11 @@ importance: 4 --- -# 是否需要 “else”? +# Is "else" required? -如果参数 `age` 大于 `18`,那么下面的函数将返回 `true`。 +The following function returns `true` if the parameter `age` is greater than `18`. -否则它将会要求进行确认,并返回确认结果: +Otherwise it asks for a confirmation and returns its result: ```js function checkAge(age) { @@ -21,7 +21,7 @@ function checkAge(age) { } ``` -如果 `else` 被删除,函数的工作方式会不同吗? +Will the function work differently if `else` is removed? ```js function checkAge(age) { @@ -35,4 +35,4 @@ function checkAge(age) { } ``` -这两个变体的行为是否有区别? +Is there any difference in the behavior of these two variants? diff --git a/1-js/02-first-steps/14-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 similarity index 52% rename from 1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/solution.md rename to 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md index 4b0e10d3e5..e48502642a 100644 --- a/1-js/02-first-steps/14-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 @@ -1,4 +1,4 @@ -使用问号运算符 `'?'`: +Using a question mark operator `'?'`: ```js function checkAge(age) { @@ -6,7 +6,7 @@ function checkAge(age) { } ``` -使用或运算符 `||`(最短的变体): +Using OR `||` (the shortest variant): ```js function checkAge(age) { @@ -14,4 +14,4 @@ function checkAge(age) { } ``` -请注意此处不需要 `age > 18` 左右的括号。写上括号是为了提高可读性。 +Note that the parentheses around `age > 18` are not required here. They exist for better readability. diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md new file mode 100644 index 0000000000..46da079c0d --- /dev/null +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md @@ -0,0 +1,26 @@ +importance: 4 + +--- + +# Rewrite the function using '?' or '||' + +The following function returns `true` if the parameter `age` is greater than `18`. + +Otherwise it asks for a confirmation and returns its result. + +```js +function checkAge(age) { + if (age > 18) { + return true; + } else { + return confirm('Did parents allow you?'); + } +} +``` + +Rewrite it, to perform the same, but without `if`, in a single line. + +Make two variants of `checkAge`: + +1. Using a question mark operator `?` +2. Using OR `||` diff --git a/1-js/02-first-steps/14-function-basics/3-min/solution.md b/1-js/02-first-steps/15-function-basics/3-min/solution.md similarity index 51% rename from 1-js/02-first-steps/14-function-basics/3-min/solution.md rename to 1-js/02-first-steps/15-function-basics/3-min/solution.md index c6f72c9dd3..2236d9203f 100644 --- a/1-js/02-first-steps/14-function-basics/3-min/solution.md +++ b/1-js/02-first-steps/15-function-basics/3-min/solution.md @@ -1,4 +1,4 @@ -使用 `if` 的解决方案: +A solution using `if`: ```js function min(a, b) { @@ -10,7 +10,7 @@ function min(a, b) { } ``` -使用问号运算符 `'?'` 的解决方案: +A solution with a question mark operator `'?'`: ```js function min(a, b) { @@ -18,4 +18,4 @@ function min(a, b) { } ``` -P.S. 在 `a == b` 的情况下,返回什么都无关紧要。 +P.S. In the case of an equality `a == b` it does not matter what to return. \ No newline at end of file diff --git a/1-js/02-first-steps/15-function-basics/3-min/task.md b/1-js/02-first-steps/15-function-basics/3-min/task.md new file mode 100644 index 0000000000..50edd0d36f --- /dev/null +++ b/1-js/02-first-steps/15-function-basics/3-min/task.md @@ -0,0 +1,16 @@ +importance: 1 + +--- + +# Function min(a, b) + +Write a function `min(a,b)` which returns the least of two numbers `a` and `b`. + +For instance: + +```js +min(2, 5) == 2 +min(3, -1) == -1 +min(1, 1) == 1 +``` + diff --git a/1-js/02-first-steps/14-function-basics/4-pow/solution.md b/1-js/02-first-steps/15-function-basics/4-pow/solution.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/4-pow/solution.md rename to 1-js/02-first-steps/15-function-basics/4-pow/solution.md diff --git a/1-js/02-first-steps/15-function-basics/4-pow/task.md b/1-js/02-first-steps/15-function-basics/4-pow/task.md new file mode 100644 index 0000000000..f569320c7f --- /dev/null +++ b/1-js/02-first-steps/15-function-basics/4-pow/task.md @@ -0,0 +1,19 @@ +importance: 4 + +--- + +# Function pow(x,n) + +Write a function `pow(x,n)` that returns `x` in power `n`. Or, in other words, multiplies `x` by itself `n` times and returns the result. + +```js +pow(3, 2) = 3 * 3 = 9 +pow(3, 3) = 3 * 3 * 3 = 27 +pow(1, 100) = 1 * 1 * ...* 1 = 1 +``` + +Create a web-page that prompts for `x` and `n`, and then shows the result of `pow(x,n)`. + +[demo] + +P.S. In this task the function should support only natural values of `n`: integers up from `1`. diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md new file mode 100644 index 0000000000..79dee432d0 --- /dev/null +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -0,0 +1,533 @@ +# Functions + +Quite often we need to perform a similar action in many places of the script. + +For example, we need to show a nice-looking message when a visitor logs in, logs out and maybe somewhere else. + +Functions are the main "building blocks" of the program. They allow the code to be called many times without repetition. + +We've already seen examples of built-in functions, like `alert(message)`, `prompt(message, default)` and `confirm(question)`. But we can create functions of our own as well. + +## Function Declaration + +To create a function we can use a *function declaration*. + +It looks like this: + +```js +function showMessage() { + alert( 'Hello everyone!' ); +} +``` + +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... +} +``` + +Our new function can be called by its name: `showMessage()`. + +For instance: + +```js run +function showMessage() { + alert( 'Hello everyone!' ); +} + +*!* +showMessage(); +showMessage(); +*/!* +``` + +The call `showMessage()` executes the code of the function. Here we will see the message two times. + +This example clearly demonstrates one of the main purposes of functions: to avoid code duplication. + +If we ever need to change the message or the way it is shown, it's enough to modify the code in one place: the function which outputs it. + +## Local variables + +A variable declared inside a function is only visible inside that function. + +For example: + +```js run +function showMessage() { +*!* + let message = "Hello, I'm JavaScript!"; // local variable +*/!* + + alert( message ); +} + +showMessage(); // Hello, I'm JavaScript! + +alert( message ); // <-- Error! The variable is local to the function +``` + +## Outer variables + +A function can access an outer variable as well, for example: + +```js run no-beautify +let *!*userName*/!* = 'John'; + +function showMessage() { + let message = 'Hello, ' + *!*userName*/!*; + alert(message); +} + +showMessage(); // Hello, John +``` + +The function has full access to the outer variable. It can modify it as well. + +For instance: + +```js run +let *!*userName*/!* = 'John'; + +function showMessage() { + *!*userName*/!* = "Bob"; // (1) changed the outer variable + + let message = 'Hello, ' + *!*userName*/!*; + alert(message); +} + +alert( userName ); // *!*John*/!* before the function call + +showMessage(); + +alert( userName ); // *!*Bob*/!*, the value was modified by the function +``` + +The outer variable is only used if there's no local one. + +If a same-named variable is declared inside the function then it *shadows* the outer one. For instance, in the code below the function uses the local `userName`. The outer one is ignored: + +```js run +let userName = 'John'; + +function showMessage() { +*!* + let userName = "Bob"; // declare a local variable +*/!* + + let message = 'Hello, ' + userName; // *!*Bob*/!* + alert(message); +} + +// the function will create and use its own userName +showMessage(); + +alert( userName ); // *!*John*/!*, unchanged, the function did not access the outer variable +``` + +```smart header="Global variables" +Variables declared outside of any function, such as the outer `userName` in the code above, are called *global*. + +Global variables are visible from any function (unless shadowed by locals). + +It's a good practice to minimize the use of global variables. Modern code has few or no globals. Most variables reside in their functions. Sometimes though, they can be useful to store project-level data. +``` + +## Parameters + +We can pass arbitrary data to functions using parameters. + +In the example below, the function has two parameters: `from` and `text`. + +```js run +function showMessage(*!*from, text*/!*) { // parameters: from, text + alert(from + ': ' + text); +} + +*!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*) +*!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**) +``` + +When the function is called in lines `(*)` and `(**)`, the given values are copied to local variables `from` and `text`. Then the function uses them. + +Here's one more example: we have a variable `from` and pass it to the function. Please note: the function changes `from`, but the change is not seen outside, because a function always gets a copy of the value: + +```js run +function showMessage(from, text) { + +*!* + from = '*' + from + '*'; // make "from" look nicer +*/!* + + alert( from + ': ' + text ); +} + +let from = "Ann"; + +showMessage(from, "Hello"); // *Ann*: Hello + +// the value of "from" is the same, the function modified a local copy +alert( from ); // Ann +``` + +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`. + +For instance, the aforementioned function `showMessage(from, text)` can be called with a single argument: + +```js +showMessage("Ann"); +``` + +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 `=`: + +```js run +function showMessage(from, *!*text = "no text given"*/!*) { + alert( from + ": " + text ); +} + +showMessage("Ann"); // Ann: no text given +``` + +Now if the `text` parameter is not passed, it will get the value `"no text given"` + +Here `"no text given"` is a string, but it can be a more complex expression, which is only evaluated and assigned if the parameter is missing. So, this is also possible: + +```js run +function showMessage(from, text = anotherFunction()) { + // anotherFunction() only executed if no text given + // its result becomes the value of text +} +``` + +```smart header="Evaluation of default parameters" +In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. + +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 not in the function declaration, but at a later stage. + +We can check if the parameter is passed during the function execution, by comparing it with `undefined`: + +```js run +function showMessage(text) { + // ... + +*!* + if (text === undefined) { // if the parameter is missing + text = 'empty message'; + } +*/!* + + alert(text); +} + +showMessage(); // empty message +``` + +...Or we could use the `||` operator: + +```js +function showMessage(text) { + // if text is undefined or otherwise falsy, set it to 'empty' + text = text || 'empty'; + ... +} +``` + +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 +function showCount(count) { + // if count is undefined or null, show "unknown" + alert(count ?? "unknown"); +} + +showCount(0); // 0 +showCount(null); // unknown +showCount(); // unknown +``` + +## Returning a value + +A function can return a value back into the calling code as the result. + +The simplest example would be a function that sums two values: + +```js run no-beautify +function sum(a, b) { + *!*return*/!* a + b; +} + +let result = sum(1, 2); +alert( result ); // 3 +``` + +The directive `return` can be in any place of the function. When the execution reaches it, the function stops, and the value is returned to the calling code (assigned to `result` above). + +There may be many occurrences of `return` in a single function. For instance: + +```js run +function checkAge(age) { + if (age >= 18) { +*!* + return true; +*/!* + } else { +*!* + return confirm('Do you have permission from your parents?'); +*/!* + } +} + +let age = prompt('How old are you?', 18); + +if ( checkAge(age) ) { + alert( 'Access granted' ); +} else { + alert( 'Access denied' ); +} +``` + +It is possible to use `return` without a value. That causes the function to exit immediately. + +For example: + +```js +function showMovie(age) { + if ( !checkAge(age) ) { +*!* + return; +*/!* + } + + alert( "Showing you the movie" ); // (*) + // ... +} +``` + +In the code above, if `checkAge(age)` returns `false`, then `showMovie` won't proceed to the `alert`. + +````smart header="A function with an empty `return` or without it returns `undefined`" +If a function does not return a value, it is the same as if it returns `undefined`: + +```js run +function doNothing() { /* empty */ } + +alert( doNothing() === undefined ); // true +``` + +An empty `return` is also the same as `return undefined`: + +```js run +function doNothing() { + return; +} + +alert( doNothing() === undefined ); // true +``` +```` + +````warn header="Never add a newline between `return` and the value" +For a long expression in `return`, it might be tempting to put it on a separate line, like this: + +```js +return + (some + long + expression + or + whatever * f(a) + f(b)) +``` +That doesn't work, because JavaScript assumes a semicolon after `return`. That'll work the same as: + +```js +return*!*;*/!* + (some + long + expression + or + whatever * f(a) + f(b)) +``` + +So, it effectively becomes an empty return. + +If we want the returned expression to wrap across multiple lines, we should start it at the same line as `return`. Or at least put the opening parentheses there as follows: + +```js +return ( + some + long + expression + + or + + whatever * f(a) + f(b) + ) +``` +And it will work just as we expect it to. +```` + +## Naming a function [#function-naming] + +Functions are actions. So their name is usually a verb. It should be brief, as accurate as possible and describe what the function does, so that someone reading the code gets an indication of what the function does. + +It is a widespread practice to start a function with a verbal prefix which vaguely describes the action. There must be an agreement within the team on the meaning of the prefixes. + +For instance, functions that start with `"show"` usually show something. + +Function starting with... + +- `"get…"` -- return a value, +- `"calc…"` -- calculate something, +- `"create…"` -- create something, +- `"check…"` -- check something and return a boolean, etc. + +Examples of such names: + +```js no-beautify +showMessage(..) // shows a message +getAge(..) // returns the age (gets it somehow) +calcSum(..) // calculates a sum and returns the result +createForm(..) // creates a form (and usually returns it) +checkPermission(..) // checks a permission, returns true/false +``` + +With prefixes in place, a glance at a function name gives an understanding what kind of work it does and what kind of value it returns. + +```smart header="One function -- one action" +A function should do exactly what is suggested by its name, no more. + +Two independent actions usually deserve two functions, even if they are usually called together (in that case we can make a 3rd function that calls those two). + +A few examples of breaking this rule: + +- `getAge` -- would be bad if it shows an `alert` with the age (should only get). +- `createForm` -- would be bad if it modifies the document, adding a form to it (should only create it and return). +- `checkPermission` -- would be bad if it displays the `access granted/denied` message (should only perform the check and return the result). + +These examples assume common meanings of prefixes. You and your team are free to agree on other meanings, but usually they're not much different. In any case, you should have a firm understanding of what a prefix means, what a prefixed function can and cannot do. All same-prefixed functions should obey the rules. And the team should share the knowledge. +``` + +```smart header="Ultrashort function names" +Functions that are used *very often* sometimes have ultrashort names. + +For example, the [jQuery](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`. + +These are exceptions. Generally function names should be concise and descriptive. +``` + +## Functions == Comments + +Functions should be short and do exactly one thing. If that thing is big, maybe it's worth it to split the function into a few smaller functions. Sometimes following this rule may not be that easy, but it's definitely a good thing. + +A separate function is not only easier to test and debug -- its very existence is a great comment! + +For instance, compare the two functions `showPrimes(n)` below. Each one outputs [prime numbers](https://en.wikipedia.org/wiki/Prime_number) up to `n`. + +The first variant uses a label: + +```js +function showPrimes(n) { + nextPrime: for (let i = 2; i < n; i++) { + + for (let j = 2; j < i; j++) { + if (i % j == 0) continue nextPrime; + } + + alert( i ); // a prime + } +} +``` + +The second variant uses an additional function `isPrime(n)` to test for primality: + +```js +function showPrimes(n) { + + for (let i = 2; i < n; i++) { + *!*if (!isPrime(i)) continue;*/!* + + alert(i); // a prime + } +} + +function isPrime(n) { + for (let i = 2; i < n; i++) { + if ( n % i == 0) return false; + } + return true; +} +``` + +The second variant is easier to understand, isn't it? Instead of the code piece we see a name of the action (`isPrime`). Sometimes people refer to such code as *self-describing*. + +So, functions can be created even if we don't intend to reuse them. They structure the code and make it readable. + +## Summary + +A function declaration looks like this: + +```js +function name(parameters, delimited, by, comma) { + /* code */ +} +``` + +- Values passed to a function as parameters are copied to its local variables. +- A function may access outer variables. But it works only from inside out. The code outside of the function doesn't see its local variables. +- A function can return a value. If it doesn't, then its result is `undefined`. + +To make the code clean and easy to understand, it's recommended to use mainly local variables and parameters in the function, not outer variables. + +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. + +Function naming: + +- A name should clearly describe what the function does. When we see a function call in the code, a good name instantly gives us an understanding what it does and returns. +- A function is an action, so function names are usually verbal. +- There exist many well-known function prefixes like `create…`, `show…`, `get…`, `check…` and so on. Use them to hint what a function does. + +Functions are the main building blocks of scripts. Now we've covered the basics, so we actually can start creating and using them. But that's only the beginning of the path. We are going to return to them many times, going more deeply into their advanced features. diff --git a/1-js/02-first-steps/15-function-expressions/article.md b/1-js/02-first-steps/15-function-expressions/article.md deleted file mode 100644 index 3f533c2ac1..0000000000 --- a/1-js/02-first-steps/15-function-expressions/article.md +++ /dev/null @@ -1,368 +0,0 @@ -# 函数表达式 - -在 JavaScript 中,函数不是“神奇的语言结构”,而是一种特殊的值。 - -我们在前面章节使用的语法称为 **函数声明**: - -```js -function sayHi() { - alert( "Hello" ); -} -``` - -另一种创建函数的语法称为 **函数表达式**。 - -通常会写成这样: - -```js -let sayHi = function() { - alert( "Hello" ); -}; -``` - -在这里,函数被创建并像其他赋值一样,被明确地分配给了一个变量。不管函数是被怎样定义的,都只是一个存储在变量 `sayHi` 中的值。 - -上面这两段示例代码的意思是一样的:“创建一个函数,并把它存进变量 `sayHi`”。 - -我们还可以用 `alert` 打印这个变量值: - -```js run -function sayHi() { - alert( "Hello" ); -} - -*!* -alert( sayHi ); // 显示函数代码 -*/!* -``` - -注意,最后一行代码并不会运行函数,因为 `sayHi` 后没有括号。在其他编程语言中,只要提到函数的名称都会导致函数的调用执行,但 JavaScript 可不是这样。 - -在 JavaScript 中,函数是一个值,所以我们可以把它当成值对待。上面代码显示了一段字符串值,即函数的源码。 - -的确,在某种意义上说一个函数是一个特殊值,我们可以像 `sayHi()` 这样调用它。 - -但它依然是一个值,所以我们可以像使用其他类型的值一样使用它。 - -我们可以复制函数到其他变量: - -```js run no-beautify -function sayHi() { // (1) 创建 - alert( "Hello" ); -} - -let func = sayHi; // (2) 复制 - -func(); // Hello // (3) 运行复制的值(正常运行)! -sayHi(); // Hello // 这里也能运行(为什么不行呢) -``` - -解释一下上段代码发生的细节: - -1. `(1)` 行声明创建了函数,并把它放入到变量 `sayHi`。 -2. `(2)` 行将 `sayHi` 复制到了变量 `func`。请注意:`sayHi` 后面没有括号。如果有括号,`func = sayHi()` 会把 `sayHi()` 的调用结果写进`func`,而不是 `sayHi` **函数** 本身。 -3. 现在函数可以通过 `sayHi()` 和 `func()` 两种方式进行调用。 - -注意,我们也可以在第一行中使用函数表达式来声明 `sayHi`: - -```js -let sayHi = function() { - alert( "Hello" ); -}; - -let func = sayHi; -// ... -``` - -这两种声明的函数是一样的。 - - -````smart header="为什么这里末尾会有个分号?" -你可能想知道,为什么函数表达式结尾有一个分号 `;`,而函数声明没有: - -```js -function sayHi() { - // ... -} - -let sayHi = function() { - // ... -}*!*;*/!* -``` - -答案很简单: -- 在代码块的结尾不需要加分号 `;`,像 `if { ... }`,`for { }`,`function f { }` 等语法结构后面都不用加。 -- 函数表达式是在语句内部的:`let sayHi = ...;`,作为一个值。它不是代码块而是一个赋值语句。不管值是什么,都建议在语句末尾添加分号 `;`。所以这里的分号与函数表达式本身没有任何关系,它只是用于终止语句。 -```` - -## 回调函数 - -让我们多举几个例子,看看如何将函数作为值来传递以及如何使用函数表达式。 - -我们写一个包含三个参数的函数 `ask(question, yes, no)`: - -`question` -: 关于问题的文本 - -`yes` -: 当回答为 "Yes" 时,要运行的脚本 - -`no` -: 当回答为 "No" 时,要运行的脚本 - -函数需要提出 `question`(问题),并根据用户的回答,调用 `yes()` 或 `no()`: - -```js run -*!* -function ask(question, yes, no) { - if (confirm(question)) yes() - else no(); -} -*/!* - -function showOk() { - alert( "You agreed." ); -} - -function showCancel() { - alert( "You canceled the execution." ); -} - -// 用法:函数 showOk 和 showCancel 被作为参数传入到 ask -ask("Do you agree?", showOk, showCancel); -``` - -在实际开发中,这样的的函数是非常有用的。实际开发与上述示例最大的区别是,实际开发中的函数会通过更加复杂的方式与用户进行交互,而不是通过简单的 `confirm`。在浏览器中,这样的函数通常会绘制一个漂亮的提问窗口。但这是另外一件事了。 - -`ask` 的两个参数值 `showOk` 和 `showCancel` 可以被称为 **回调函数** 或简称 **回调**。 - -主要思想是我们传递一个函数,并期望在稍后必要时将其“回调”。在我们的例子中,`showOk` 是回答 "yes" 的回调,`showCancel` 是回答 "no" 的回调。 - -我们可以用函数表达式对同样的函数进行大幅简写: - -```js run no-beautify -function ask(question, yes, no) { - if (confirm(question)) yes() - else no(); -} - -*!* -ask( - "Do you agree?", - function() { alert("You agreed."); }, - function() { alert("You canceled the execution."); } -); -*/!* -``` - -这里直接在 `ask(...)` 调用内进行函数声明。这两个函数没有名字,所以叫 **匿名函数**。这样的函数在 `ask` 外无法访问(因为没有对它们分配变量),不过这正是我们想要的。 - -这样的代码在我们的脚本中非常常见,这正符合 JavaScript 语言的思想。 - -```smart header="一个函数是表示一个“动作(action)”的值" -字符串或数字等常规值代表 **数据**。 - -函数可以被视为一个 **动作**。 - -我们可以在变量之间传递它们,并在需要时运行。 -``` - - -## 函数表达式 vs 函数声明 - -让我们来总结一下函数声明和函数表达式之间的主要区别。 - -首先是语法:如何通过代码对它们进行区分。 - -- **函数声明**:在主代码流中声明为单独的语句的函数。 - - ```js - // 函数声明 - function sum(a, b) { - return a + b; - } - ``` -- **函数表达式**:在一个表达式中或另一个语法结构中创建的函数。下面这个函数是在赋值表达式 `=` 右侧创建的: -     - ```js - // 函数表达式 - let sum = function(a, b) { - return a + b; - }; - ``` - -更细微的差别是,JavaScript 引擎会在 **什么时候** 创建函数。 - -**函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。** - -一旦代码执行到赋值表达式 `let sum = function…` 的右侧,此时就会开始创建该函数,并且可以从现在开始使用(分配,调用等)。 - -函数声明则不同。 - -**在函数声明被定义之前,它就可以被调用。** - -例如,一个全局函数声明对整个脚本来说都是可见的,无论它被写在这个脚本的哪个位置。 - -这是内部算法的原故。当 JavaScript **准备** 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。我们可以将其视为“初始化阶段”。 - -在处理完所有函数声明后,代码才被执行。所以运行时能够使用这些函数。 - -例如下面的代码会正常工作: - -```js run refresh untrusted -*!* -sayHi("John"); // Hello, John -*/!* - -function sayHi(name) { - alert( `Hello, ${name}` ); -} -``` - -函数声明 `sayHi` 是在 JavaScript 准备运行脚本时被创建的,在这个脚本的任何位置都可见。 - -……如果它是一个函数表达式,它就不会工作: - -```js run refresh untrusted -*!* -sayHi("John"); // error! -*/!* - -let sayHi = function(name) { // (*) no magic any more - alert( `Hello, ${name}` ); -}; -``` - -函数表达式在代码执行到它时才会被创建。只会发生在 `(*)` 行。为时已晚。 - -函数声明的另外一个特殊的功能是它们的块级作用域。 - -**严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。** - -例如,想象一下我们需要依赖于在代码运行过程中获得的变量 `age` 声明一个函数 `welcome()`。并且我们计划在之后的某个时间使用它。 - -如果我们使用函数声明,以下则代码不能如愿工作: - -```js run -let age = prompt("What is your age?", 18); - -// 有条件地声明一个函数 -if (age < 18) { - - function welcome() { - alert("Hello!"); - } - -} else { - - function welcome() { - alert("Greetings!"); - } - -} - -// ……稍后使用 -*!* -welcome(); // Error: welcome is not defined -*/!* -``` - -这是因为函数声明只在它所在的代码块中可见。 - -下面是另一个例子: - -```js run -let age = 16; // 拿 16 作为例子 - -if (age < 18) { -*!* - welcome(); // \ (运行) -*/!* - // | - function welcome() { // | - alert("Hello!"); // | 函数声明在声明它的代码块内任意位置都可用 - } // | - // | -*!* - welcome(); // / (运行) -*/!* - -} else { - - function welcome() { - alert("Greetings!"); - } -} - -// 在这里,我们在花括号外部调用函数,我们看不到它们内部的函数声明。 - - -*!* -welcome(); // Error: welcome is not defined -*/!* -``` - -我们怎么才能让 `welcome` 在 `if` 外可见呢? - -正确的做法是使用函数表达式,并将 `welcome` 赋值给在 `if` 外声明的变量,并具有正确的可见性。 - -下面的代码可以如愿运行: - -```js run -let age = prompt("What is your age?", 18); - -let welcome; - -if (age < 18) { - - welcome = function() { - alert("Hello!"); - }; - -} else { - - welcome = function() { - alert("Greetings!"); - }; - -} - -*!* -welcome(); // 现在可以了 -*/!* -``` - -或者我们可以使用问号运算符 `?` 来进一步对代码进行简化: - -```js run -let age = prompt("What is your age?", 18); - -let welcome = (age < 18) ? - function() { alert("Hello!"); } : - function() { alert("Greetings!"); }; - -*!* -welcome(); // 现在可以了 -*/!* -``` - - -```smart header="什么时候选择函数声明与函数表达式?" -根据经验,当我们需要声明一个函数时,首先考虑函数声明语法。它能够为组织代码提供更多的灵活性。因为我们可以在声明这些函数之前调用这些函数。 - -这对代码可读性也更好,因为在代码中查找 `function f(…) {…}` 比 `let f = function(…) {…}` 更容易。函数声明更“醒目”。 - -……但是,如果由于某种原因而导致函数声明不适合我们(我们刚刚看过上面的例子),那么应该使用函数表达式。 -``` - -## 总结 - -- 函数是值。它们可以在代码的任何地方被分配,复制或声明。 -- 如果函数在主代码流中被声明为单独的语句,则称为“函数声明”。 -- 如果该函数是作为表达式的一部分创建的,则称其“函数表达式”。 -- 在执行代码块之前,内部算法会先处理函数声明。所以函数声明在其被声明的代码块内的任何位置都是可见的。 -- 函数表达式在执行流程到达时创建。 - -在大多数情况下,当我们需要声明一个函数时,最好使用函数声明,因为函数在被声明之前也是可见的。这使我们在代码组织方面更具灵活性,通常也会使得代码可读性更高。 - -所以,仅当函数声明不适合对应的任务时,才应使用函数表达式。在本章中,我们已经看到了几个例子,以后还会看到更多的例子。 diff --git a/1-js/02-first-steps/16-arrow-functions-basics/article.md b/1-js/02-first-steps/16-arrow-functions-basics/article.md deleted file mode 100644 index 4c086df641..0000000000 --- a/1-js/02-first-steps/16-arrow-functions-basics/article.md +++ /dev/null @@ -1,111 +0,0 @@ -# 箭头函数,基础知识 - -创建函数还有另外一种非常简单的语法,并且这种方法通常比函数表达式更好。 - -它被称为“箭头函数”,因为它看起来像这样: - -```js -let func = (arg1, arg2, ...argN) => expression -``` - -……这里创建了一个函数 `func`,它接受参数 `arg1..argN`,然后使用参数对右侧的 `expression` 求值并返回其结果。 - -换句话说,它是下面这段代码的更短的版本: - -```js -let func = function(arg1, arg2, ...argN) { - return expression; -}; -``` - -让我们来看一个具体的例子: - -```js run -let sum = (a, b) => a + b; - -/* 这个箭头函数是下面这个函数的更短的版本: - -let sum = function(a, b) { - return a + b; -}; -*/ - -alert( sum(1, 2) ); // 3 -``` - -可以看到 `(a, b) => a + b` 表示一个函数接受两个名为 `a` 和 `b` 的参数。在执行时,它将对表达式 `a + b` 求值,并返回计算结果。 - -- 如果我们只有一个参数,还可以省略掉参数外的圆括号,使代码更短。 - - 例如: - - ```js run - *!* - let double = n => n * 2; - // 差不多等同于:let double = function(n) { return n * 2 } - */!* - - alert( double(3) ); // 6 - ``` - -- 如果没有参数,括号将是空的(但括号应该保留): - - ```js run - let sayHi = () => alert("Hello!"); - - sayHi(); - ``` - -箭头函数可以像函数表达式一样使用。 - -例如,动态创建一个函数: - -```js run -let age = prompt("What is your age?", 18); - -let welcome = (age < 18) ? - () => alert('Hello') : - () => alert("Greetings!"); - -welcome(); // 现在好了 -``` - -一开始,箭头函数可能看起来并不熟悉,也不容易读懂,但一旦我们看习惯了之后,这种情况很快就会改变。 - -箭头函数对于简单的单行动作来说非常方便,尤其是当我们懒得打太多字的时候。 - -## 多行的箭头函数 - -上面的例子从 `=>` 的左侧获取参数,然后使用参数计算右侧表达式的值。 - -但有时我们需要更复杂一点的东西,比如多行的表达式或语句。这也是可以做到的,但是我们应该用花括号括起来。然后使用一个普通的 `return` 将需要返回的值进行返回。 - -就像这样: - -```js run -let sum = (a, b) => { // 花括号表示开始一个多行函数 - let result = a + b; -*!* - return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return” -*/!* -}; - -alert( sum(1, 2) ); // 3 -``` - -```smart header="更多内容" -在这里,我们赞扬了箭头功能的简洁性。但还不止这些! - -箭头函数还有其他有趣的特性。 - -为了更深入地学习它们,我们首先需要了解一些 JavaScript 其他方面的知识,因此我们将在后面的 一章中再继续研究箭头函数。 - -现在,我们已经可以用箭头函数进行单行操作和回调了。 -``` - -## 总结 - -对于一行代码的函数来说,箭头函数是相当方便的。它具体有两种: - -1. 不带花括号:`(...args) => expression` — 右侧是一个表达式:函数计算表达式并返回其结果。 -2. 带花括号:`(...args) => { body }` — 花括号允许我们在函数中编写多个语句,但是我们需要显式地 `return` 来返回一些内容。 diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md new file mode 100644 index 0000000000..d502c9d27b --- /dev/null +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -0,0 +1,380 @@ +# Function expressions + +In JavaScript, a function is not a "magical language structure", but a special kind of value. + +The syntax that we used before is called a *Function Declaration*: + +```js +function sayHi() { + alert( "Hello" ); +} +``` + +There is another syntax for creating a function that is called a *Function Expression*. + +It allows us to create a new function in the middle of any expression. + +For example: + +```js +let sayHi = function() { + alert( "Hello" ); +}; +``` + +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. + +We can even print out that value using `alert`: + +```js run +function sayHi() { + alert( "Hello" ); +} + +*!* +alert( sayHi ); // shows the function code +*/!* +``` + +Please note that the last line does not run the function, because there are no parentheses after `sayHi`. There are programming languages where any mention of a function name causes its execution, but JavaScript is not like that. + +In JavaScript, a function is a value, so we can deal with it as a value. The code above shows its string representation, which is the source code. + +Surely, a function is a special value, in the sense that we can call it like `sayHi()`. + +But it's still a value. So we can work with it like with other kinds of values. + +We can copy a function to another variable: + +```js run no-beautify +function sayHi() { // (1) create + alert( "Hello" ); +} + +let func = sayHi; // (2) copy + +func(); // Hello // (3) run the copy (it works)! +sayHi(); // Hello // this still works too (why wouldn't it) +``` + +Here's what happens above in detail: + +1. The Function Declaration `(1)` creates the function and puts it into the variable named `sayHi`. +2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself. +3. Now the function can be called as both `sayHi()` and `func()`. + +We could also have used a Function Expression to declare `sayHi`, in the first line: + +```js +let sayHi = function() { // (1) create + alert( "Hello" ); +}; + +let func = sayHi; +// ... +``` + +Everything would work the same. + + +````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: + +```js +function sayHi() { + // ... +} + +let sayHi = function() { + // ... +}*!*;*/!* +``` + +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. +```` + +## Callback functions + +Let's look at more examples of passing functions as values and using function expressions. + +We'll write a function `ask(question, yes, no)` with three parameters: + +`question` +: Text of the question + +`yes` +: Function to run if the answer is "Yes" + +`no` +: Function to run if the answer is "No" + +The function should ask the `question` and, depending on the user's answer, call `yes()` or `no()`: + +```js run +*!* +function ask(question, yes, no) { + if (confirm(question)) yes() + else no(); +} +*/!* + +function showOk() { + alert( "You agreed." ); +} + +function showCancel() { + alert( "You canceled the execution." ); +} + +// usage: functions showOk, showCancel are passed as arguments to ask +ask("Do you agree?", showOk, showCancel); +``` + +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. + +**The arguments `showOk` and `showCancel` of `ask` are called *callback functions* or just *callbacks*.** + +The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for "yes" answer, and `showCancel` for "no" answer. + +We can use Function Expressions to write an equivalent, shorter function: + +```js run no-beautify +function ask(question, yes, no) { + if (confirm(question)) yes() + else no(); +} + +*!* +ask( + "Do you agree?", + function() { alert("You agreed."); }, + function() { alert("You canceled the execution."); } +); +*/!* +``` + +Here, functions are declared right inside the `ask(...)` call. They have no name, and so are called *anonymous*. Such functions are not accessible outside of `ask` (because they are not assigned to variables), but that's just what we want here. + +Such code appears in our scripts very naturally, it's in the spirit of JavaScript. + +```smart header="A function is a value representing an \"action\"" +Regular values like strings or numbers represent the *data*. + +A function can be perceived as an *action*. + +We can pass it between variables and run when we want. +``` + + +## Function Expression vs Function Declaration + +Let's formulate the key differences between Function Declarations and Expressions. + +First, the syntax: how to differentiate between them in the code. + +- *Function Declaration:* a function, declared as a separate statement, in the main code flow: + + ```js + // Function Declaration + function sum(a, b) { + return a + b; + } + ``` +- *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" `=`: + + ```js + // Function Expression + let sum = function(a, b) { + return a + b; + }; + ``` + +The more subtle difference is *when* a function is created by the JavaScript engine. + +**A Function Expression is created when the execution reaches it and is usable only from that moment.** + +Once the execution flow passes to the right side of the assignment `let sum = function…` -- here we go, the function is created and can be used (assigned, called, etc. ) from now on. + +Function Declarations are different. + +**A Function Declaration can be called earlier than it is defined.** + +For example, a global Function Declaration is visible in the whole script, no matter where it is. + +That's due to internal algorithms. When JavaScript prepares to run the script, it first looks for global Function Declarations in it and creates the functions. We can think of it as an "initialization stage". + +And after all Function Declarations are processed, the code is executed. So it has access to these functions. + +For example, this works: + +```js run refresh untrusted +*!* +sayHi("John"); // Hello, John +*/!* + +function sayHi(name) { + alert( `Hello, ${name}` ); +} +``` + +The Function Declaration `sayHi` is created when JavaScript is preparing to start the script and is visible everywhere in it. + +...If it were a Function Expression, then it wouldn't work: + +```js run refresh untrusted +*!* +sayHi("John"); // error! +*/!* + +let sayHi = function(name) { // (*) no magic any more + alert( `Hello, ${name}` ); +}; +``` + +Function Expressions are created when the execution reaches them. That would happen only in the line `(*)`. Too late. + +Another special feature of Function Declarations is their block scope. + +**In strict mode, when a Function Declaration is within a code block, it's visible everywhere inside that block. But not outside of it.** + +For instance, let's imagine that we need to declare a function `welcome()` depending on the `age` variable that we get during runtime. And then we plan to use it some time later. + +If we use Function Declaration, it won't work as intended: + +```js run +let age = prompt("What is your age?", 18); + +// conditionally declare a function +if (age < 18) { + + function welcome() { + alert("Hello!"); + } + +} else { + + function welcome() { + alert("Greetings!"); + } + +} + +// ...use it later +*!* +welcome(); // Error: welcome is not defined +*/!* +``` + +That's because a Function Declaration is only visible inside the code block in which it resides. + +Here's another example: + +```js run +let age = 16; // take 16 as an example + +if (age < 18) { +*!* + welcome(); // \ (runs) +*/!* + // | + function welcome() { // | + alert("Hello!"); // | Function Declaration is available + } // | everywhere in the block where it's declared + // | +*!* + welcome(); // / (runs) +*/!* + +} else { + + function welcome() { + alert("Greetings!"); + } +} + +// Here we're out of curly braces, +// so we can not see Function Declarations made inside of them. + +*!* +welcome(); // Error: welcome is not defined +*/!* +``` + +What can we do to make `welcome` visible outside of `if`? + +The correct approach would be to use a Function Expression and assign `welcome` to the variable that is declared outside of `if` and has the proper visibility. + +This code works as intended: + +```js run +let age = prompt("What is your age?", 18); + +let welcome; + +if (age < 18) { + + welcome = function() { + alert("Hello!"); + }; + +} else { + + welcome = function() { + alert("Greetings!"); + }; + +} + +*!* +welcome(); // ok now +*/!* +``` + +Or we could simplify it even further using a question mark operator `?`: + +```js run +let age = prompt("What is your age?", 18); + +let welcome = (age < 18) ? + function() { alert("Hello!"); } : + function() { alert("Greetings!"); }; + +*!* +welcome(); // ok now +*/!* +``` + + +```smart header="When to choose Function Declaration versus Function Expression?" +As a rule of thumb, when we need to declare a function, the first 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. + +That's also better for readability, as it's easier to look up `function f(…) {…}` in the code than `let f = function(…) {…};`. Function Declarations are more "eye-catching". + +...But if a Function Declaration does not suit us for some reason, or we need a conditional declaration (we've just seen an example), then Function Expression should be used. +``` + +## Summary + +- Functions are values. They can be assigned, copied or declared in any place of the code. +- If the function is declared as a separate statement in the main code flow, that's called a "Function Declaration". +- If the function is created as a part of an expression, it's called a "Function Expression". +- Function Declarations are processed before the code block is executed. They are visible everywhere in the block. +- Function Expressions are created when the execution flow reaches them. + +In most cases when we need to declare a function, a Function Declaration is preferable, because it is visible prior to the declaration itself. That gives us more flexibility in code organization, and is usually more readable. + +So we should use a Function Expression only when a Function Declaration is not fit for the task. We've seen a couple of examples of that in this chapter, and will see more in the future. diff --git a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md similarity index 68% rename from 1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/solution.md rename to 1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index 588a0397ab..041db18bc6 100644 --- a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,17 +1,17 @@ - -```js run -function ask(question, yes, no) { - if (confirm(question)) yes() - else no(); -} - -ask( - "Do you agree?", -*!* - () => alert("You agreed."), - () => alert("You canceled the execution.") -*/!* -); -``` - -是不是看起来精简多了? + +```js run +function ask(question, yes, no) { + if (confirm(question)) yes(); + else no(); +} + +ask( + "Do you agree?", +*!* + () => alert("You agreed."), + () => alert("You canceled the execution.") +*/!* +); +``` + +Looks short and clean, right? diff --git a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md similarity index 58% rename from 1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/task.md rename to 1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index 8d2362606f..e18c08a83e 100644 --- a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -1,17 +1,17 @@ - -# 用箭头函数重写 - -用箭头函数重写下面的函数表达式: - -```js run -function ask(question, yes, no) { - if (confirm(question)) yes() - else no(); -} - -ask( - "Do you agree?", - function() { alert("You agreed."); }, - function() { alert("You canceled the execution."); } -); -``` + +# Rewrite with arrow functions + +Replace Function Expressions with arrow functions in the code below: + +```js run +function ask(question, yes, no) { + if (confirm(question)) yes(); + else no(); +} + +ask( + "Do you agree?", + function() { alert("You agreed."); }, + function() { alert("You canceled the execution."); } +); +``` 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 new file mode 100644 index 0000000000..50c0d475da --- /dev/null +++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md @@ -0,0 +1,111 @@ +# Arrow functions, the basics + +There's another very simple and concise syntax for creating functions, that's often better than Function Expressions. + +It's called "arrow functions", because it looks like this: + +```js +let func = (arg1, arg2, ..., argN) => 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. + +In other words, it's the shorter version of: + +```js +let func = function(arg1, arg2, ..., argN) { + return expression; +}; +``` + +Let's see a concrete example: + +```js run +let sum = (a, b) => a + b; + +/* This arrow function is a shorter form of: + +let sum = function(a, b) { + return a + b; +}; +*/ + +alert( sum(1, 2) ); // 3 +``` + +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. + +- If we have only one argument, then parentheses around parameters can be omitted, making that even shorter. + + For example: + + ```js run + *!* + let double = n => n * 2; + // roughly the same as: let double = function(n) { return n * 2 } + */!* + + alert( double(3) ); // 6 + ``` + +- If there are no arguments, parentheses are empty, but they must be present: + + ```js run + let sayHi = () => alert("Hello!"); + + sayHi(); + ``` + +Arrow functions can be used in the same way as Function Expressions. + +For instance, to dynamically create a function: + +```js run +let age = prompt("What is your age?", 18); + +let welcome = (age < 18) ? + () => alert('Hello!') : + () => alert("Greetings!"); + +welcome(); +``` + +Arrow functions may appear unfamiliar and not very readable at first, but that quickly changes as the eyes get used to the structure. + +They are very convenient for simple one-line actions, when we're just too lazy to write many words. + +## Multiline arrow functions + +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). + +Like this: + +```js run +let sum = (a, b) => { // the curly brace opens a multiline function + let result = a + b; +*!* + return result; // if we use curly braces, then we need an explicit "return" +*/!* +}; + +alert( sum(1, 2) ); // 3 +``` + +```smart header="More to come" +Here we praised arrow functions for brevity. But that's not all! + +Arrow functions have other interesting features. + +To study them in-depth, we first need to get to know some other aspects of JavaScript, so we'll return to arrow functions later in the chapter . + +For now, we can already use arrow functions for one-line actions and callbacks. +``` + +## Summary + +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. diff --git a/1-js/02-first-steps/17-javascript-specials/article.md b/1-js/02-first-steps/17-javascript-specials/article.md deleted file mode 100644 index 13efdcbbc4..0000000000 --- a/1-js/02-first-steps/17-javascript-specials/article.md +++ /dev/null @@ -1,279 +0,0 @@ -# JavasScript 特性 - -本章简要回顾我们到现在为止学到的 JavaScript 特性,并特别注意了一些细节。 - -## 代码结构 - -语句用分号分隔: - -```js run no-beautify -alert('Hello'); alert('World'); -``` - -通常,换行符也被视为分隔符,因此下面的例子也能正常运行: - -```js run no-beautify -alert('Hello') -alert('World') -``` - -这就是所谓的「自动分号插入」。但有时它不起作用,例如: - -```js run -alert("There will be an error after this message") - -[1, 2].forEach(alert) -``` - -大多数代码风格指南都认为我们应该在每个语句后面都加上分号。 - -在代码块 `{...}` 后以及有代码块的语法结构(例如循环)后不需要加分号: - -```js -function f() { - // 函数声明后不需要加分号 -} - -for(;;) { - // 循环语句后不需要加分号 -} -``` - -……但即使我们在某处添加了「额外的」分号,这也不是错误。分号会被忽略的。 - -更多内容:。 - -## 严格模式 - -为了完全启用现代 JavaScript 的所有特性,我们应该在脚本顶部写上 `"use strict"` 指令。 - -```js -'use strict'; - -... -``` - -该指令必须位于 JavaScript 脚本的顶部或函数体的开头。 - -如果没有 `"use strict"`,所有东西仍可以正常工作,但是某些特性的表现方式与旧式「兼容」方式相同。我们通常更喜欢现代的方式。 - -语言的一些现代特征(比如我们将来要学习的类)会隐式地启用严格模式。 - -更多内容:。 - -## 变量 - -可以使用以下方式声明变量: - -- `let` -- `const`(不变的,不能被改变) -- `var`(旧式的,稍后会看到) - -一个变量名可以由以下组成: -- 字母和数字,但是第一个字符不能是数字。 -- 字符 `$` 和 `_` 是允许的,用法同字母。 -- 非拉丁字母和象形文字也是允许的,但通常不会使用。 - -变量是动态类型的,它们可以存储任何值: - -```js -let x = 5; -x = "John"; -``` - -有 7 种数据类型: - -- `number` — 可以是浮点数,也可以是整数, -- `string` — 字符串类型, -- `boolean` — 逻辑值:`true/false`, -- `null` — 具有单个值 `null` 的类型,表示“空”或“不存在”, -- `undefined` — 具有单个值 `undefined` 的类型,表示“未分配(未定义)”, -- `object` 和 `symbol` — 对于复杂的数据结构和唯一标识符,我们目前还没学习这个类型。 - -`typeof` 运算符返回值的类型,但有两个例外: -```js -typeof null == "object" // JavaScript 编程语言的设计错误 -typeof function(){} == "function" // 函数被特殊对待 -``` - -更多内容:。 - -## 交互 - -我们使用浏览器作为工作环境,所以基本的 UI 功能将是: - -[`prompt(question[, default])`](mdn:api/Window/prompt) -: 提出一个问题,并返回访问者输入的内容,如果他按下「取消」则返回 `null`。 - -[`confirm(question)`](mdn:api/Window/confirm) -: 提出一个问题,并建议用户在“确定”和“取消”之间进行选择。选择结果以 `true/false` 形式返回。 - -[`alert(message)`](mdn:api/Window/alert) -: 输出一个 `消息`。 - -这些函数都会产生 **模态框**,它们会暂停代码执行并阻止访问者与页面的其他部分进行交互,直到用户做出回答为止。 - -举个例子: - -```js run -let userName = prompt("Your name?", "Alice"); -let isTeaWanted = confirm("Do you want some tea?"); - -alert( "Visitor: " + userName ); // Alice -alert( "Tea wanted: " + isTeaWanted ); // true -``` - -更多内容:。 - -## 运算符 - -JavaScript 支持以下运算符: - -算数运算符 -: 常规的:`+ - * /`(加减乘除),取余运算符 `%` 和幂运算符 `**`。 - - 二进制加号 `+` 可以连接字符串。如果任何一个操作数是一个字符串,那么另一个操作数也将被转换为字符串: - - ```js run - alert( '1' + 2 ); // '12',字符串 - alert( 1 + '2' ); // '12',字符串 - ``` - -赋值 -: 简单的赋值:`a = b` 和合并了其他操作的赋值:`a * = 2`。 - -按位操作符 -: 按位运算符在最低位级上操作 32 位的整数:详见 [文档](mdn:/JavaScript/Reference/Operators/Bitwise_Operators)。 - -三元运算符 -: 唯一具有三个参数的操作:`cond ? resultA : resultB`。如果 `cond` 是真的,则返回 `resultA`,否则返回 `resultB`。 - -逻辑运算符 -: 逻辑与 `&&` 和或 `||` 执行短路运算,然后返回运算停止处的值(`true`/`false` 不是必须的)。逻辑非 `!` 将操作数转换为布尔值并返回其相反的值。 - -比较运算符 -: 对不同类型的值进行相等检查时,运算符 `==` 会将不同类型的值转换为数字(除了 `null` 和 `undefined`,它们彼此相等而没有其他情况),所以下面的例子是相等的: - - ```js run - alert( 0 == false ); // true - alert( 0 == '' ); // true - ``` - - 其他比较也将转换为数字。 - - 严格相等运算符 `===` 不会进行转换:不同的类型总是指不同的值。 - - 值 `null` 和 `undefined` 是特殊的:它们只在 `==` 下相等,且不相等于其他任何值。 - - 大于/小于比较,在比较字符串时,会按照字符顺序逐个字符地进行比较。其他类型则被转换为数字。 - -其他运算符 -: 还有很少一部分其他运算符,如逗号运算符。 - -更多内容:。 - -## 循环 - -- 我们涵盖了 3 种类型的循环: - - ```js - // 1 - while (condition) { - ... - } - - // 2 - do { - ... - } while (condition); - - // 3 - for(let i = 0; i < 10; i++) { - ... - } - ``` - -- 在 `for(let...)` 循环内部声明的变量,只在该循环内可见。但我们也可以省略 `let` 并重用已有的变量。 -- 指令 `break/continue` 允许退出整个循环/当前迭代。使用标签来打破嵌套循环。 - -更多内容:。 - -稍后我们将学习更多类型的循环来处理对象。 - -## "switch" 结构 - -"switch" 结构可以替代多个 `if` 检查。它内部使用 `===`(严格相等)进行比较。 - -例如: - -```js run -let age = prompt('Your age?', 18); - -switch (age) { - case 18: - alert("Won't work"); // prompt 的结果是一个字符串,而不是数字 - - case "18": - alert("This works!"); - break; - - default: - alert("Any value not equal to one above"); -} -``` - -详情请见:。 - -## 函数 - -我们介绍了三种在 JavaScript 中创建函数的方式: - -1. 函数声明:主代码流中的函数 - - ```js - function sum(a, b) { - let result = a + b; - - return result; - } - ``` - -2. 函数表达式:表达式上下文中的函数 - - ```js - let sum = function(a, b) { - let result = a + b; - - return result; - } - ``` - -3. 箭头函数: - - ```js - // 表达式在右侧 - let sum = (a, b) => a + b; - - // 或带 {...} 的多行语法,此处需要 return: - let sum = (a, b) => { - // ... - return a + b; - } - - // 没有参数 - let sayHi = () => alert("Hello"); - - // 有一个参数 - let double = n => n * 2; - ``` - - -- 函数可能具有局部变量:在函数内部声明的变量。这类变量只在函数内部可见。 -- 参数可以有默认值:`function sum(a = 1, b = 2) {...}`。 -- 函数总是返回一些东西。如果没有 `return` 语句,那么返回的结果是 `undefined`。 - -详细内容:请见 。 - -## 更多内容 - -这些是 JavaScript 特性的简要概述。截至目前,我们仅仅学习了基础知识。随着教程的深入,你会发现 JavaScript 的更多特性和高级特性。 diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md new file mode 100644 index 0000000000..016214e3b3 --- /dev/null +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -0,0 +1,284 @@ +# JavaScript specials + +This chapter briefly recaps the features of JavaScript that we've learned by now, paying special attention to subtle moments. + +## Code structure + +Statements are delimited with a semicolon: + +```js run no-beautify +alert('Hello'); alert('World'); +``` + +Usually, a line-break is also treated as a delimiter, so that would also work: + +```js run no-beautify +alert('Hello') +alert('World') +``` + +That's called "automatic semicolon insertion". Sometimes it doesn't work, for instance: + +```js run +alert("There will be an error after this message") + +[1, 2].forEach(alert) +``` + +Most codestyle guides agree that we should put a semicolon after each statement. + +Semicolons are not required after code blocks `{...}` and syntax constructs with them like loops: + +```js +function f() { + // no semicolon needed after function declaration +} + +for(;;) { + // no semicolon needed after the loop +} +``` + +...But even if we can put an "extra" semicolon somewhere, that's not an error. It will be ignored. + +More in: . + +## Strict mode + +To fully enable all features of modern JavaScript, we should start scripts with `"use strict"`. + +```js +'use strict'; + +... +``` + +The directive must be at the top of a script or at the beginning of a function body. + +Without `"use strict"`, everything still works, but some features behave in the old-fashioned, "compatible" way. We'd generally prefer the modern behavior. + +Some modern features of the language (like classes that we'll study in the future) enable strict mode implicitly. + +More in: . + +## Variables + +Can be declared using: + +- `let` +- `const` (constant, can't be changed) +- `var` (old-style, will see later) + +A variable name can include: +- Letters and digits, but the first character may not be a digit. +- Characters `$` and `_` are normal, on par with letters. +- Non-Latin alphabets and hieroglyphs are also allowed, but commonly not used. + +Variables are dynamically typed. They can store any value: + +```js +let x = 5; +x = "John"; +``` + +There are 8 data types: + +- `number` for both floating-point and integer numbers, +- `bigint` for integer numbers of arbitrary length, +- `string` for strings, +- `boolean` for logical values: `true/false`, +- `null` -- a type with a single value `null`, meaning "empty" or "does not exist", +- `undefined` -- a type with a single value `undefined`, meaning "not assigned", +- `object` and `symbol` -- for complex data structures and unique identifiers, we haven't learnt them yet. + +The `typeof` operator returns the type for a value, with two exceptions: +```js +typeof null == "object" // error in the language +typeof function(){} == "function" // functions are treated specially +``` + +More in: and . + +## Interaction + +We're using a browser as a working environment, so basic UI functions will be: + +[`prompt(question, [default])`](mdn:api/Window/prompt) +: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel". + +[`confirm(question)`](mdn:api/Window/confirm) +: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`. + +[`alert(message)`](mdn:api/Window/alert) +: Output a `message`. + +All these functions are *modal*, they pause the code execution and prevent the visitor from interacting with the page until they answer. + +For instance: + +```js run +let userName = prompt("Your name?", "Alice"); +let isTeaWanted = confirm("Do you want some tea?"); + +alert( "Visitor: " + userName ); // Alice +alert( "Tea wanted: " + isTeaWanted ); // true +``` + +More in: . + +## Operators + +JavaScript supports the following operators: + +Arithmetical +: Regular: `* + - /`, also `%` for the remainder and `**` for power of a number. + + The binary plus `+` concatenates strings. And if any of the operands is a string, the other one is converted to string too: + + ```js run + alert( '1' + 2 ); // '12', string + alert( 1 + '2' ); // '12', string + ``` + +Assignments +: There is a simple assignment: `a = b` and combined ones like `a *= 2`. + +Bitwise +: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed. + +Conditional +: The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. + +Logical operators +: Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped (not necessary `true`/`false`). Logical NOT `!` converts the operand to boolean type and returns the inverse value. + +Nullish coalescing operator +: The `??` operator provides a way to choose a defined value from a list of variables. The result of `a ?? b` is `a` unless it's `null/undefined`, then `b`. + +Comparisons +: Equality check `==` for values of different types converts them to a number (except `null` and `undefined` that equal each other and nothing else), so these are equal: + + ```js run + alert( 0 == false ); // true + alert( 0 == '' ); // true + ``` + + Other comparisons convert to a number as well. + + The strict equality operator `===` doesn't do the conversion: different types always mean different values for it. + + Values `null` and `undefined` are special: they equal `==` each other and don't equal anything else. + + Greater/less comparisons compare strings character-by-character, other types are converted to a number. + +Other operators +: There are few others, like a comma operator. + +More in: , , , . + +## Loops + +- We covered 3 types of loops: + + ```js + // 1 + while (condition) { + ... + } + + // 2 + do { + ... + } while (condition); + + // 3 + for(let i = 0; i < 10; i++) { + ... + } + ``` + +- The variable declared in `for(let...)` loop is visible only inside the loop. But we can also omit `let` and reuse an existing variable. +- Directives `break/continue` allow to exit the whole loop/current iteration. Use labels to break nested loops. + +Details in: . + +Later we'll study more types of loops to deal with objects. + +## The "switch" construct + +The "switch" construct can replace multiple `if` checks. It uses `===` (strict equality) for comparisons. + +For instance: + +```js run +let age = prompt('Your age?', 18); + +switch (age) { + case 18: + alert("Won't work"); // the result of prompt is a string, not a number + break; + + case "18": + alert("This works!"); + break; + + default: + alert("Any value not equal to one above"); +} +``` + +Details in: . + +## Functions + +We covered three ways to create a function in JavaScript: + +1. Function Declaration: the function in the main code flow + + ```js + function sum(a, b) { + let result = a + b; + + return result; + } + ``` + +2. Function Expression: the function in the context of an expression + + ```js + let sum = function(a, b) { + let result = a + b; + + return result; + }; + ``` + +3. Arrow functions: + + ```js + // expression on the right side + let sum = (a, b) => a + b; + + // or multi-line syntax with { ... }, need return here: + let sum = (a, b) => { + // ... + return a + b; + } + + // without arguments + let sayHi = () => alert("Hello"); + + // with a single argument + let double = n => n * 2; + ``` + + +- 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`. + +Details: see , . + +## More to come + +That was a brief list of JavaScript features. As of now we've studied only basics. Further in the tutorial you'll find more specials and advanced features of JavaScript. diff --git a/1-js/02-first-steps/index.md b/1-js/02-first-steps/index.md index fffebd4eea..31281656f8 100644 --- a/1-js/02-first-steps/index.md +++ b/1-js/02-first-steps/index.md @@ -1,3 +1,3 @@ -# JavaScript 基础知识 +# JavaScript Fundamentals -让我们来一起学习 JavaScript 脚本构建的基础知识。 +Let's learn the fundamentals of script building. \ No newline at end of file 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 0702dbf3f5..4f50fb428b 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,196 +1,195 @@ -# 在 Chrome 中调试 +# Debugging in the browser -在编写更复杂的代码前,让我们先来聊聊调试吧。 +Before writing more complex code, let's talk about debugging. -[调试](https://en.wikipedia.org/wiki/Debugging) 是指在一个脚本中找出并修复错误的过程。所有的现代浏览器和大多数其他环境都支持调试工具 —— 开发者工具中的一个令调试更加容易的特殊用户界面。它也可以让我们一步步地跟踪代码以查看当前实际运行情况。 +[Debugging](https://en.wikipedia.org/wiki/Debugging) is the process of finding and fixing errors within a script. All modern browsers and most other environments support debugging tools -- a special UI in developer tools that makes debugging much easier. It also allows to trace the code step by step to see what exactly is going on. -在这里我们将会使用 Chrome(谷歌浏览器),因为它拥有足够多的功能,其他大部分浏览器的功能也与之类似。 +We'll be using Chrome here, because it has enough features, most other browsers have a similar process. -## “资源(Sources)”面板 +## The "Sources" panel -你的 Chrome 版本可能看起来有一点不同,但是它应该还是处于很明显的位置。 +Your Chrome version may look a little bit different, but it still should be obvious what's there. -- 在 Chrome 中打开 [示例页面](debugging/index.html)。 -- 使用快捷键 `key:F12`(Mac:`key:Cmd+Opt+I`)打开开发者工具。 -- 选择 `Sources(资源)` 面板。 +- Open the [example page](debugging/index.html) in Chrome. +- Turn on developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). +- Select the `Sources` panel. -如果你是第一次这么做,那你应该会看到下面这个样子: +Here's what you should see if you are doing it for the first time: ![](chrome-open-sources.svg) -切换按钮 会打开文件列表的选项卡。 +The toggler button opens the tab with files. -让我们在预览树中点击和选择 `hello.js`。这里应该会如下图所示: +Let's click it and select `hello.js` in the tree view. Here's what should show up: ![](chrome-tabs.svg) -资源(Sources)面板包含三个部分: +The Sources panel has 3 parts: -1. **文件浏览(File Navigator)** 区域列出了 HTML、JavaScript、CSS 和包括图片在内的其他依附于此页面的文件。Chrome 扩展程序也会显示在这。 -2. **代码编辑(Code Editor)** 区域展示源码。 -3. **JavaScript 调试(JavaScript Debugging)** 区域是用于调试的,我们很快就会来探索它。 +1. The **File Navigator** pane lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too. +2. The **Code Editor** pane shows the source code. +3. The **JavaScript Debugging** pane is for debugging, we'll explore it soon. -现在你可以再次点击切换按钮 隐藏资源列表来给代码腾出一些空间。 +Now you could click the same toggler again to hide the resources list and give the code some space. -## 控制台(Console) +## Console -如果我们按下 `key:Esc`,下面会出现一个控制台,我们可以输入一些命令然后按下 `key:Enter` 来执行。 +If we press `key:Esc`, then a console opens below. We can type commands there and press `key:Enter` to execute. -语句执行完毕之后,其执行结果会显示在下面。 +After a statement is executed, its result is shown below. -例如,`1+2` 将会返回 `3`,`hello("debugger")` 函数什么也不返回,因此结果是 `undefined`: +For example, here `1+2` results in `3`, while the function call `hello("debugger")` returns nothing, so the result is `undefined`: ![](chrome-sources-console.svg) -## 断点(Breakpoints) +## Breakpoints -我们来看看 [示例页面](debugging/index.html) 发生了什么。在 `hello.js` 中,点击第 `4` 行。是的,就点击数字 `"4"` 上,不是点击代码。 +Let's examine what's going on within the code of the [example page](debugging/index.html). In `hello.js`, click at line number `4`. Yes, right on the `4` digit, not on the code. -恭喜你!你已经设置了一个断点。现在,请在第 `8` 行的数字上也点击一下。 +Congratulations! You've set a breakpoint. Please also click on the number for line `8`. -看起来应该是这样的(蓝色是你应该点击的地方): +It should look like this (blue is where you should click): ![](chrome-sources-breakpoint.svg) -**断点** 是调试器会自动暂停 JavaScript 执行的地方。 +A *breakpoint* is a point of code where the debugger will automatically pause the JavaScript execution. -当代码被暂停时,我们可以检查当前的变量,在控制台执行命令等等。换句话说,我们可以调试它。 +While the code is paused, we can examine current variables, execute commands in the console etc. In other words, we can debug it. -我们总是可以在右侧的面板中找到断点的列表。当我们在数个文件中有许多断点时,这是非常有用的。它允许我们: -- 快速跳转至代码中的断点(通过点击右侧面板中的对应的断点)。 -- 通过取消选中断点来临时禁用对应的断点。 -- 通过右键单击并选择移除来删除一个断点。 -- ……等等。 +We can always find a list of breakpoints in the right panel. That's useful when we have many breakpoints in various files. It allows us to: +- Quickly jump to the breakpoint in the code (by clicking on it in the right panel). +- Temporarily disable the breakpoint by unchecking it. +- Remove the breakpoint by right-clicking and selecting Remove. +- ...And so on. -```smart header="条件断点" -在行号上 **右键单击** 允许你创建一个 **条件** 断点。只有当给定的表达式为真(即满足条件)时才会被触发。 +```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. -当我们需要在特定的变量值或参数的情况下暂停程序执行时,这种调试方法就很有用了。 +That's handy when we need to stop only for a certain variable value or for certain function parameters. ``` -## Debugger 命令 +## The command "debugger" -我们也可以使用 `debugger` 命令来暂停代码,像这样: +We can also pause the code by using the `debugger` command in it, like this: ```js function hello(name) { let phrase = `Hello, ${name}!`; *!* - debugger; // <-- 调试器会在这停止 + debugger; // <-- the debugger stops here */!* say(phrase); } ``` -当我们在一个代码编辑器中并且不想切换到浏览器在开发者工具中查找脚本来设置断点时,这真的是非常方便。 +Such command works only when the development tools are open, otherwise the browser ignores it. +## Pause and look around -## 暂停并查看 +In our example, `hello()` is called during the page load, so the easiest way to activate the debugger (after we've set the breakpoints) is to reload the page. So let's press `key:F5` (Windows, Linux) or `key:Cmd+R` (Mac). -在我们的例子中,`hello()` 函数在页面加载期间被调用,因此激活调试器的最简单的方法(在我们已经设置了断点后)就是 —— 重新加载页面。因此让我们按下 `key:F5`(Windows,Linux)或 `key:Cmd+R`(Mac)吧。 - -设置断点之后,程序会在第 4 行暂停执行: +As the breakpoint is set, the execution pauses at the 4th line: ![](chrome-sources-debugger-pause.svg) -请打开右侧的信息下拉列表(箭头指示出的地方)。这里允许你查看当前的代码状态: +Please open the informational dropdowns to the right (labeled with arrows). They allow you to examine the current code state: -1. **`察看(Watch)` —— 显示任意表达式的当前值。** +1. **`Watch` -- shows current values for any expressions.** - 你可以点击加号 `+` 然后输入一个表达式。调试器将随时显示它的值,并在执行过程中自动重新计算该表达式。 + You can click the plus `+` and input an expression. The debugger will show its value, automatically recalculating it in the process of execution. -2. **`调用栈(Call Stack)` —— 显示嵌套的调用链。** +2. **`Call Stack` -- shows the nested calls chain.** - 此时,调试器正在 `hello()` 的调用链中,被 `index.html` 中的一个脚本调用(这里没有函数,因此显示 "anonymous") + At the current moment the debugger is inside `hello()` call, called by a script in `index.html` (no function there, so it's called "anonymous"). - 如果你点击了一个堆栈项,调试器将跳到对应的代码处,并且还可以查看其所有变量。 -3. **`作用域(Scope)` —— 显示当前的变量。** + If you click on a stack item (e.g. "anonymous"), the debugger jumps to the corresponding code, and all its variables can be examined as well. +3. **`Scope` -- current variables.** - `Local` 显示当前函数中的变量,你还可以在源代码中看到它们的值高亮显示了出来。 + `Local` shows local function variables. You can also see their values highlighted right over the source. - `Global` 显示全局变量(不在任何函数中)。 + `Global` has global variables (out of any functions). - 这里还有一个 `this` 关键字,目前我们还没有学到它,不过我们很快就会学习它了。 + There's also `this` keyword there that we didn't study yet, but we'll do that soon. -## 跟踪执行 +## Tracing the execution -现在是 **跟踪** 脚本的时候了。 +Now it's time to *trace* the script. -在右侧面板的顶部是一些关于跟踪脚本的按钮。让我们来使用它们吧。 +There are buttons for it at the top of the right panel. Let's engage them. - —— “恢复(Resume)”:继续执行,快捷键 `key:F8`。 -: 继续执行。如果没有其他的断点,那么程序就会继续执行,并且调试器不会再控制程序。 + -- "Resume": continue the execution, hotkey `key:F8`. +: Resumes the execution. If there are no additional breakpoints, then the execution just continues and the debugger loses control. - 我们点击它一下之后,我们会看到这样的情况: + Here's what we can see after a click on it: ![](chrome-sources-debugger-trace-1.svg) - 执行恢复了,执行到 `say()` 函数中的另外一个断点后暂停在了那里。看一下右边的 "Call stack"。它已经增加了一个调用信息。我们现在在 `say()` 里面。 + The execution has resumed, reached another breakpoint inside `say()` and paused there. Take a look at the "Call Stack" at the right. It has increased by one more call. We're inside `say()` now. - —— “下一步(Step)”:运行下一条指令,快捷键 `key:F9`。 -: 运行下一条语句。如果我们现在点击它,`alert` 会被显示出来。 + -- "Step": run the next command, hotkey `key:F9`. +: Run the next statement. If we click it now, `alert` will be shown. - 一次接一次地点击此按钮,整个脚本的所有语句会被逐个执行。 + Clicking this again and again will step through all script statements one by one. - -- “跨步(Step over)”:运行下一条指令,但 **不会进入到一个函数中**,快捷键 `key:F10`。 -: 跟上一条命令“下一步(Step)”类似,但如果下一条语句是函数调用则表现不同。这里的函数指的是:不是内置的如 `alert` 函数等,而是我们自己写的函数。 + -- "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). - “下一步(Step)”命令进入函数内部并在第一行暂停执行,而“跨步(Step over)”在无形中执行函数调用,跳过了函数的内部。 + 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. - 如果我们对该函数的内部执行不感兴趣,这命令会很有用。 + That's good if we're not interested to see what happens inside the function call. - —— “步入(Step into)”,快捷键 `key:F11`。 -: 和“下一步(Step)”类似,但在异步函数调用情况下表现不同。如果你刚刚才开始学 JavaScript,那么你可以先忽略此差异,因为我们还没有用到异步调用。 + -- "Step into", hotkey `key:F11`. +: That's similar to "Step", but behaves differently in case of asynchronous function calls. If you're only starting to learn JavaScript, then you can ignore the difference, as we don't have asynchronous calls yet. - 至于以后,请记住“下一步(Step)”命令会忽略异步方法,例如 `setTimeout`(约定的函数调用),它会过一段时间后再执行。而“步入(Step into)”会进入到代码中并等待(如果需要)。浏览 [DevTools 手册](https://developers.google.com/web/updates/2018/01/devtools#async) 获取更多细节。 + For the future, just note that "Step" command ignores async actions, such as `setTimeout` (scheduled function call), that execute later. The "Step into" goes into their code, waiting for them if necessary. See [DevTools manual](https://developers.google.com/web/updates/2018/01/devtools#async) for more details. - —— “步出(Step out)”:继续执行到当前函数的末尾,快捷键 `key:Shift+F11`。 -: 继续执行代码并停止在当前函数的最后一行。当我们使用 偶然地进入到一个嵌套调用,但是我们又对这个函数不感兴趣时,我们想要尽可能的继续执行到最后的时候是非常方便的。 + -- "Step out": continue the execution till the end of the current function, hotkey `key:Shift+F11`. +: Continue the execution and stop it at the very last line of the current function. That's handy when we accidentally entered a nested call using , but it does not interest us, and we want to continue to its end as soon as possible. - —— 启用/禁用所有的断点。 -: 这个按钮不会影响程序的执行。只是一个批量操作断点的开/关。 + -- enable/disable all breakpoints. +: That button does not move the execution. Just a mass on/off for breakpoints. - —— 启用/禁用出现错误时自动暂停脚本执行。 -: 当启动此功能并且开发者工具是打开着的时候,任何一个脚本的错误都会导致该脚本执行自动暂停。然后我们可以分析变量来看一下什么出错了。因此如果我们的脚本因为错误挂掉的时候,我们可以打开调试器,启用这个选项然后重载页面,查看一下哪里导致它挂掉了和当时的上下文是什么。 + -- 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. -```smart header="继续到这" -在代码中的某一行上右键,在显示的关联菜单(context menu)中点击一个非常有用的名为“继续到这里(Continue to here)”的选项。 +```smart header="Continue to here" +Right click on a line of code opens the context menu with a great option called "Continue to here". -当你想要向前移动很多步到某一行为止,但是又懒得设置一个断点时非常的方便。 +That's handy when we want to move multiple steps forward to the line, but we're too lazy to set a breakpoint. ``` -## 日志记录 +## Logging -想要输出一些东西到控制台上?`console.log` 函数可以满足你。 +To output something to console from our code, there's `console.log` function. -例如:将从 `0` 到 `4` 的值输出到控制台上: +For instance, this outputs values from `0` to `4` to console: ```js run -// 打开控制台来查看 +// open console to see for (let i = 0; i < 5; i++) { - console.log("value", i); + console.log("value,", i); } ``` -普通用户看不到这个输出,它是在控制台里面的。要想看到它 —— 要么打开开发者工具中的 Console(控制台)选项卡,要么在一个其他的选项卡中按下 `key:Esc`:这会在下方打开一个控制台。 +Regular users don't see that output, it is in the console. To see it, either open the Console panel of developer tools or press `key:Esc` while in another panel: that opens the console at the bottom. -如果我们在代码中有足够的日志记录,那么我们可以从记录中看到刚刚发生了什么,而不需要借助调试器。 +If we have enough logging in our code, then we can see what's going on from the records, without the debugger. -## 总结 +## Summary -我们可以看到,这里有 3 种方式来暂停一个脚本: -1. 一个断点。 -2. `debugger` 语句。 -3. 一个错误(如果开发者工具是打开状态,并且按钮 是开启的状态)。 +As we can see, there are three main ways to pause a script: +1. A breakpoint. +2. The `debugger` statements. +3. An error (if dev tools are open and the button is "on"). -当脚本执行暂停时,我们就可以进行调试 —— 检查变量,跟踪代码来查看执行出错的位置。 +When paused, we can debug: examine variables and trace the code to see where the execution goes wrong. -开发人员工具中的选项比本文介绍的多得多。完整的手册请点击这个链接查看:。 +There are many more options in developer tools than covered here. The full manual is at . -本章节的内容足够让你上手代码调试了,但是之后,尤其是你做了大量关于浏览器的东西后,推荐你查看上面那个链接中讲的开发者工具更高级的功能。 +The information from this chapter is enough to begin debugging, but later, especially if you do a lot of browser stuff, please go there and look through more advanced capabilities of developer tools. -对了,你也可以点击开发者工具中的其他地方来看一下会显示什么。这可能是你学习开发者工具最快的方式了。不要忘了还有右键单击和关联菜单哟。 +Oh, and also you can click at various places of dev tools and just see what's showing up. That's probably the fastest route to learn dev tools. Don't forget about the right click and context menus! diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg index 1f7d21288c..5fc6dce3aa 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg @@ -1 +1 @@ -open sources \ No newline at end of file +open sources \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg index 6fb4332f1d..63bf4966e2 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg @@ -1 +1 @@ -here's the listbreakpoints \ No newline at end of file +here's the listbreakpoints \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg index 25284d055d..3fe5f124f2 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg index 40d9515ab8..0147c2e0aa 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg @@ -1 +1 @@ -213see the outer call detailswatch expressionscurrent variables \ No newline at end of file +213see the outer call detailswatch expressionscurrent variables \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg index 0d5bde9c41..9fa1b3b8cc 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg @@ -1 +1 @@ -nested calls \ No newline at end of file +nested calls \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg index 352fbcb7c3..0167082569 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg @@ -1 +1 @@ -213 \ No newline at end of file +213 \ No newline at end of file diff --git a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md index 818b13c2a1..4facc8b291 100644 --- a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md +++ b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md @@ -1,29 +1,29 @@ -你可以注意到以下几点: +You could note the following: ```js no-beautify -function pow(x,n) // <- 参数之间没有空格 -{ // <- 花括号独占了一行 - let result=1; // <- = 号两边没有空格 - for(let i=0;i -现在,让我们详细讨论一下这些规则和它们的原因吧。 +Now let's discuss the rules and reasons for them in detail. -```warn header="没有什么规则是“必须”的" -没有什么规则是“刻在石头上”的。这些是风格偏好,而不是宗教教条。 +```warn header="There are no \"you must\" rules" +Nothing is set in stone here. These are style preferences, not religious dogmas. ``` -### 花括号 +### Curly Braces -在大多数的 JavaScript 项目中,花括号以 "Egyptian" 风格(译注:"egyptian" 风格又称 K&R 风格 — 代码段的开括号位于一行的末尾,而不是另起一行的风格)书写,左花括号与相应的关键词在同一行上 — 而不是新起一行。左括号前还应该有一个空格,如下所示: +In most JavaScript projects curly braces are written in "Egyptian" style with the opening brace on the same line as the corresponding keyword -- not on a new line. There should also be a space before the opening bracket, like this: ```js if (condition) { @@ -52,39 +52,39 @@ if (condition) { } ``` -单行构造(如 `if (condition) doSomething()`)也是一个重要的用例。我们是否应该使用花括号?如果是,那么在哪里? +A single-line construct, such as `if (condition) doSomething()`, is an important edge case. Should we use braces at all? -下面是这几种情况的注释,你可以自己判断一下它们的可读性: +Here are the annotated variants so you can judge their readability for yourself: -1. 😠 初学者常这样写。非常不好!这里不需要花括号: +1. 😠 Beginners sometimes do that. Bad! Curly braces are not needed: ```js if (n < 0) *!*{*/!*alert(`Power ${n} is not supported`);*!*}*/!* ``` -2. 😠 拆分为单独的行,不带花括号。永远不要这样做,添加新行很容易出错: +2. 😠 Split to a separate line without braces. Never do that, easy to make an error when adding new lines: ```js if (n < 0) alert(`Power ${n} is not supported`); ``` -3. 😏 写成一行,不带花括号 — 如果短的话,也是可以的: +3. 😏 One line without braces - acceptable, if it's short: ```js if (n < 0) alert(`Power ${n} is not supported`); ``` -4. 😃 最好的方式: +4. 😃 The best variant: ```js if (n < 0) { alert(`Power ${n} is not supported`); } ``` -对于很短的代码,写成一行是可以接受的:例如 `if (cond) return null`。但是代码块(最后一个示例)通常更具可读性。 +For a very brief code, one line is allowed, e.g. `if (cond) return null`. But a code block (the last variant) is usually more readable. -### 行的长度 +### Line Length -没有人喜欢读一长串代码,最好将代码分割一下。 +No one likes to read a long horizontal line of code. It's best practice to split them. -例如: +For example: ```js -// 回勾引号 ` 允许将字符串拆分为多行 +// backtick quotes ` allow to split the string into multiple lines let str = ` ECMA International's TC39 is a group of JavaScript developers, implementers, academics, and more, collaborating with the community @@ -92,7 +92,7 @@ let str = ` `; ``` -对于 `if` 语句: +And, for `if` statements: ```js if ( @@ -104,23 +104,23 @@ if ( } ``` -一行代码的最大长度应该在团队层面上达成一致。通常是 80 或 120 个字符。 +The maximum line length should be agreed upon at the team-level. It's usually 80 or 120 characters. -### 缩进 +### Indents -有两种类型的缩进: +There are two types of indents: -- **水平方向上的缩进:2 或 4 个空格。** +- **Horizontal indents: 2 or 4 spaces.** - 一个水平缩进通常由 2 或 4 个空格或者 "Tab" 制表符(key `key:Tab`)构成。选择哪一个方式是一场古老的圣战。如今空格更普遍一点。 + A horizontal indentation is made using either 2 or 4 spaces or the horizontal tab symbol (key `key:Tab`). Which one to choose is an old holy war. Spaces are more common nowadays. - 选择空格而不是 tabs 的优点之一是,这允许你做出比 “Tab” 制表符更加灵活的缩进配置。 + One advantage of spaces over tabs is that spaces allow more flexible configurations of indents than the tab symbol. - 例如,我们可以将参数与左括号对齐,像下面这样: + For instance, we can align the parameters with the opening bracket, like this: ```js no-beautify show(parameters, - aligned, // 左边有 5 个空格 + aligned, // 5 spaces padding at the left one, after, another @@ -129,9 +129,9 @@ if ( } ``` -- **垂直方向上的缩进:用于将代码拆分成逻辑块的空行。** +- **Vertical indents: empty lines for splitting code into logical blocks.** - 即使是单个函数通常也被分割为数个逻辑块。在下面的示例中,初始化的变量、主循环结构和返回值都被垂直分割了: + Even a single function can often be divided into logical blocks. In the example below, the initialization of variables, the main loop and returning the result are split vertically: ```js function pow(x, n) { @@ -145,46 +145,46 @@ if ( } ``` - 插入一个额外的空行有助于使代码更具可读性。写代码时,不应该出现连续超过 9 行都没有被垂直分割的代码。 + Insert an extra newline where it helps to make the code more readable. There should not be more than nine lines of code without a vertical indentation. -### 分号 +### Semicolons -每一个语句后面都应该有一个分号。即使它可以被跳过。 +A semicolon should be present after each statement, even if it could possibly be skipped. -有一些编程语言的分号确实是可选的,那些语言中也很少使用分号。但是在 JavaScript 中,极少数情况下,换行符有时不会被解释为分号,这时代码就容易出错。更多内容请参阅 一章的内容。 +There are languages where a semicolon is truly optional and it is rarely used. In JavaScript, though, there are cases where a line break is not interpreted as a semicolon, leaving the code vulnerable to errors. See more about that in the chapter . -如果你是一个有经验的 JavaScript 程序员,你可以选择像 [StandardJS](https://standardjs.com/) 这样的无分号的代码风格。否则,最好使用分号以避免可能出现的陷阱。大多数开发人员都应该使用分号。 +If you're an experienced JavaScript programmer, you may choose a no-semicolon code style like [StandardJS](https://standardjs.com/). Otherwise, it's best to use semicolons to avoid possible pitfalls. The majority of developers put semicolons. -### 嵌套的层级 +### Nesting Levels -尽量避免代码嵌套层级过深。 +Try to avoid nesting code too many levels deep. -例如,在循环中,有时候使用 [`continue`](info:while-for#continue) 指令以避免额外的嵌套是一个好主意。 +For example, in the loop, it's sometimes a good idea to use the [`continue`](info:while-for#continue) directive to avoid extra nesting. -例如,不应该像下面这样添加嵌套的 `if` 条件: +For example, instead of adding a nested `if` conditional like this: ```js for (let i = 0; i < 10; i++) { if (cond) { - ... // <- 又一层嵌套 + ... // <- one more nesting level } } ``` -我们可以这样写: +We can write: ```js for (let i = 0; i < 10; i++) { if (!cond) *!*continue*/!*; - ... // <- 没有额外的嵌套 + ... // <- no extra nesting level } ``` -使用 `if/else` 和 `return` 也可以做类似的事情。 +A similar thing can be done with `if/else` and `return`. -例如,下面的两个结构是相同的。 +For example, two constructs below are identical. -第一个: +Option 1: ```js function pow(x, n) { @@ -198,11 +198,11 @@ function pow(x, n) { } return result; - } + } } ``` -第二个: +Option 2: ```js function pow(x, n) { @@ -221,16 +221,16 @@ function pow(x, n) { } ``` -但是第二个更具可读性,因为 `n < 0` 这个“特殊情况”在一开始就被处理了。一旦条件通过检查,代码执行就可以进入到“主”代码流,而不需要额外的嵌套。 +The second one is more readable because the "special case" of `n < 0` is handled early on. Once the check is done we can move on to the "main" code flow without the need for additional nesting. -## 函数位置 +## Function Placement -如果你正在写几个“辅助类”的函数和一些使用它们的代码,那么有三种方式来组织这些函数。 +If you are writing several "helper" functions and the code that uses them, there are three ways to organize the functions. -1. 在调用这些函数的代码的 **上方** 声明这些函数: +1. Declare the functions *above* the code that uses them: ```js - // *!*函数声明*/!* + // *!*function declarations*/!* function createElement() { ... } @@ -243,20 +243,20 @@ function pow(x, n) { ... } - // *!*调用函数的代码*/!* + // *!*the code which uses them*/!* let elem = createElement(); setHandler(elem); walkAround(); ``` -2. 先写调用代码,再写函数 +2. Code first, then functions ```js - // *!*调用函数的代码*/!* + // *!*the code which uses the functions*/!* let elem = createElement(); setHandler(elem); walkAround(); - // --- *!*辅助类函数*/!* --- + // --- *!*helper functions*/!* --- function createElement() { ... } @@ -269,54 +269,54 @@ function pow(x, n) { ... } ``` -3. 混合:在第一次使用一个函数时,对该函数进行声明。 +3. Mixed: a function is declared where it's first used. -大多数情况下,第二种方式更好。 +Most of time, the second variant is preferred. -这是因为阅读代码时,我们首先想要知道的是“它做了什么”。如果代码先行,那么在整个程序的最开始就展示出了这些信息。之后,可能我们就不需要阅读这些函数了,尤其是他们的名字清晰地展示出了他们的功能的时候。 +That's because when reading code, we first want to know *what it does*. If the code goes first, then it becomes clear from the start. Then, maybe we won't need to read the functions at all, especially if their names are descriptive of what they actually do. -## 风格指南 +## Style Guides -风格指南包含了“如果编写”代码的通用规则,例如:使用哪个引号、用多少空格来缩进、一行代码最大长度等非常多的细节。 +A style guide contains general rules about "how to write" code, e.g. which quotes to use, how many spaces to indent, the maximal line length, etc. A lot of minor things. -当团队中的所有成员都使用相同的风格指南时,代码看起来将是统一的。无论是团队中谁写的,都是一样的风格。 +When all members of a team use the same style guide, the code looks uniform, regardless of which team member wrote it. -当然,一个团队可以制定他们自己的风格指南,但是没必要这样做。现在已经有了很多制定好的代码风格指南可供选择。 +Of course, a team can always write their own style guide, but usually there's no need to. There are many existing guides to choose from. -一些受欢迎的选择: +Some popular choices: -- [Google JavaScript 风格指南](https://google.github.io/styleguide/javascriptguide.xml) -- [Airbnb JavaScript 风格指南](https://github.com/airbnb/javascript) +- [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) +- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) - [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) - [StandardJS](https://standardjs.com/) -- 还有很多…… +- (plus many more) -如果你是一个初学者,你可以从本章中上面的内容开始。然后你可以浏览其他风格指南,并选择一个你最喜欢的。 +If you're a novice developer, start with the cheat sheet at the beginning of this chapter. Then you can browse other style guides to pick up more ideas and decide which one you like best. -## 自动检查器 +## Automated Linters -检查器(Linters)是可以自动检查代码样式,并提出改进建议的工具。 +Linters are tools that can automatically check the style of your code and make improving suggestions. -他们的妙处在于进行代码风格检查时,还可以发现一些代码错误,例如变量或函数名中的错别字。因此,即使你不想坚持某一种特定的代码风格,我也建议你安装一个检查器。 +The great thing about them is that style-checking can also find some bugs, like typos in variable or function names. Because of this feature, using a linter is recommended even if you don't want to stick to one particular "code style". -下面是一些最出名的代码检查工具: +Here are some well-known linting tools: -- [JSLint](http://www.jslint.com/) — 第一批检查器之一。 -- [JSHint](http://www.jshint.com/) — 比 JSLint 多了更多设置。 -- [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. -它们都能够做好代码检查。我使用的是 [ESLint](http://eslint.org/)。 +All of them can do the job. The author uses [ESLint](https://eslint.org/). -大多数检查器都可以与编辑器集成在一起:只需在编辑器中启用插件并配置代码风格即可。 +Most linters are integrated with many popular editors: just enable the plugin in the editor and configure the style. -例如,要使用 ESLint 你应该这样做: +For instance, for ESLint you should do the following: -1. 安装 [Node.JS](https://nodejs.org/)。 -2. 使用 `npm install -g eslint` 命令(npm 是一个 JavaScript 包安装工具)安装 ESLint。 -3. 在你的 JavaScript 项目的根目录(包含该项目的所有文件的那个文件夹)创建一个名为 `.eslintrc` 的配置文件。 -4. 在集成了 ESLint 的编辑器中安装/启用插件。大多数编辑器都有这个选项。 +1. Install [Node.js](https://nodejs.org/). +2. Install ESLint with the command `npm install -g eslint` (npm is a JavaScript package installer). +3. Create a config file named `.eslintrc` in the root of your JavaScript project (in the folder that contains all your files). +4. Install/enable the plugin for your editor that integrates with ESLint. The majority of editors have one. -下面是一个 `.eslintrc` 文件的例子: +Here's an example of an `.eslintrc` file: ```js { @@ -328,21 +328,21 @@ function pow(x, n) { }, "rules": { "no-console": 0, - "indent": ["warning", 2] + "indent": 2 } } ``` -这里的 `"extends"` 指令表示我们是基于 "eslint:recommended" 的设置项而进行设置的。之后,我们制定我们自己的规则。 +Here the directive `"extends"` denotes that the configuration is based on the "eslint:recommended" set of settings. After that, we specify our own. -你也可以从网上下载风格规则集并进行扩展。有关安装的更多详细信息,请参见 。 +It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. -此外,某些 IDE 有内置的检查器,这非常方便,但是不像 ESLint 那样可自定义。 +Also certain IDEs have built-in linting, which is convenient but not as customizable as ESLint. -## 总结 +## Summary -本章描述的(和提到的代码风格指南中的)所有语法规则,都旨在帮助你提高代码可读性。他们都是值得商榷的。 +All syntax rules described in this chapter (and in the style guides referenced) aim to increase the readability of your code. All of them are debatable. -当我们思考如何写“更好”的代码的时候,我们应该问自己的问题是:“什么可以让代码可读性更高,更容易被理解?”和“什么可以帮助我们避免错误?”这些是我们讨论和选择代码风格时要牢记的主要原则。 +When we think about writing "better" code, the questions we should ask ourselves are: "What makes the code more readable and easier to understand?" and "What can help us avoid errors?" These are the main things to keep in mind when choosing and debating code styles. -阅读流行的代码风格指南,可以帮助你了解有关代码风格的变化趋势和最佳实践的最新想法。 +Reading popular style guides will allow you to keep up to date with the latest ideas about code style trends and best practices. diff --git a/1-js/03-code-quality/02-coding-style/code-style.svg b/1-js/03-code-quality/02-coding-style/code-style.svg index 03d9760e38..739d9f1edf 100644 --- a/1-js/03-code-quality/02-coding-style/code-style.svg +++ b/1-js/03-code-quality/02-coding-style/code-style.svg @@ -1,75 +1 @@ - - - - - - - - - - function pow(x, n) { - let result = 1; - for (let i = 0; i < n; i++) { - result *= x; - } - return result; - } - let x = prompt("x?", ""); - let n = prompt("n?", ""); - if (n < 0) { - alert(`Power ${n} is not supported, - please enter an integer number, greater than 0`); - } else { - alert( pow(x, n) ); - } - - - - 2 - - - 函数名与圆括号之间和圆括号与参数之间没有空格 - 行首缩进两个空格 - 后面有一个空格 - } else { 不空行 - 嵌套调用两边各一个空格 - 逻辑块之间空一行 - 一行不要太长 - 强制使用分号 - 运算符两边有空格 - 花括号在同一行,前面有空格 - 参数之间有一个空格 - 参数之间有一个空格 - - - for/if/while… - - - - - - - - - - - - - - - - - - - - - - - - - - - - +2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between argumentsA space between parameters \ No newline at end of file diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 1a5e35a7ac..af3a06c80b 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -1,30 +1,30 @@ -# 注释 +# Comments -正如我们在 一章所了解到的那样,注释可以是以 `//` 开始的单行注释,也可以是 `/* ... */` 结构的多行注释。 +As we know from the chapter , comments can be single-line: starting with `//` and multiline: `/* ... */`. -我们通常通过注释来描述代码怎样工作和为什么这样工作。 +We normally use them to describe how and why the code works. -乍一看,写注释可能很简单,但初学者在编程的时候,经常错误地使用注释。 +At first sight, commenting might be obvious, but novices in programming often use them wrongly. -## 糟糕的注释 +## Bad comments -新手倾向于使用注释来解释“代码中发生了什么”。就像这样: +Novices tend to use comments to explain "what is going on in the code". Like this: ```js -// 这里的代码会先做这件事(……)然后做那件事(……) -// ……谁知道还有什么…… +// This code will do this thing (...) and that thing (...) +// ...and who knows what else... very; complex; code; ``` -但在好的代码中,这种“解释性”注释的数量应该是最少的。严格地说,就算没有它们,代码也应该很容易理解。 +But in good code, the amount of such "explanatory" comments should be minimal. Seriously, the code should be easy to understand without them. -关于这一点有一个很棒的原则:“如果代码不够清晰以至于需要一个注释,那么或许它应该被重写。” +There's a great rule about that: "if the code is so unclear that it requires a comment, then maybe it should be rewritten instead". -### 配方:分解函数 +### Recipe: factor out functions -有时候,用一个函数来代替一个代码片段是更好的,就像这样: +Sometimes it's beneficial to replace a code piece with a function, like here: ```js function showPrimes(n) { @@ -32,7 +32,7 @@ function showPrimes(n) { for (let i = 2; i < n; i++) { *!* - // 检测 i 是否是一个质数(素数) + // check if i is a prime number for (let j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; } @@ -43,7 +43,7 @@ function showPrimes(n) { } ``` -更好的变体,使用一个分解出来的函数 `isPrime`: +The better variant, with a factored out function `isPrime`: ```js @@ -65,21 +65,21 @@ function isPrime(n) { } ``` -现在我们可以很容易地理解代码了。函数自己就变成了一个注释。这种代码被称为 **自描述型** 代码。 +Now we can understand the code easily. The function itself becomes the comment. Such code is called *self-descriptive*. -### 配方:创建函数 +### Recipe: create functions -如果我们有一个像下面这样很长的代码块: +And if we have a long "code sheet" like this: ```js -// 在这里我们添加威士忌(译注:国外的一种酒) +// here we add whiskey for(let i = 0; i < 10; i++) { let drop = getWhiskey(); smell(drop); add(drop, glass); } -// 在这里我们添加果汁 +// here we add juice for(let t = 0; t < 3; t++) { let tomato = getTomato(); examine(tomato); @@ -90,7 +90,7 @@ for(let t = 0; t < 3; t++) { // ... ``` -我们像下面这样,将上面的代码重构为函数,可能会是一个更好的变体: +Then it might be a better variant to refactor it into functions like: ```js addWhiskey(glass); @@ -111,70 +111,70 @@ function addJuice(container) { } ``` -同样,函数本身就可以告诉我们发生了什么。没有什么地方需要注释。并且分割之后代码的结构也更好了。每一个函数做什么、需要什么和返回什么都非常地清晰。 +Once again, functions themselves tell what's going on. There's nothing to comment. And also the code structure is better when split. It's clear what every function does, what it takes and what it returns. -实际上,我们不能完全避免“解释型”注释。例如在一些复杂的算法中,会有一些出于优化的目的而做的一些巧妙的“调整”。但是通常情况下,我们应该尽可能地保持代码的简单和“自我描述”性。 +In reality, we can't totally avoid "explanatory" comments. There are complex algorithms. And there are smart "tweaks" for purposes of optimization. But generally we should try to keep the code simple and self-descriptive. -## 好的注释 +## Good comments -所以,解释性注释通常来说都是不好的。那么哪一种注释才是好的呢? +So, explanatory comments are usually bad. Which comments are good? -描述架构 -: 对组件进行高层次的整体概括,它们如何相互作用、各种情况下的控制流程是什么样的……简而言之 —— 代码的鸟瞰图。有一个专门用于构建代码的高层次架构图,以对代码进行解释的特殊编程语言 [UML](http://wikipedia.org/wiki/Unified_Modeling_Language)。绝对值得学习。 +Describe the architecture +: Provide a high-level overview of components, how they interact, what's the control flow in various situations... In short -- the bird's eye view of the code. There's a special language [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) to build high-level architecture diagrams explaining the code. Definitely worth studying. -记录函数的参数和用法 -: 有一个专门用于记录函数的语法 [JSDoc](http://en.wikipedia.org/wiki/JSDoc):用法、参数和返回值。 +Document function parameters and usage +: There's a special syntax [JSDoc](http://en.wikipedia.org/wiki/JSDoc) to document a function: usage, parameters, returned value. - 例如: - ```js - /** - * 返回 x 的 n 次幂的值。 - * - * @param {number} x 要改变的值。 - * @param {number} n 幂数,必须是一个自然数。 - * @return {number} x 的 n 次幂的值。 - */ - function pow(x, n) { - ... - } - ``` +For instance: +```js +/** + * Returns x raised to the n-th power. + * + * @param {number} x The number to raise. + * @param {number} n The power, must be a natural number. + * @return {number} x raised to the n-th power. + */ +function pow(x, n) { + ... +} +``` - 这种注释可以帮助我们理解函数的目的,并且不需要研究其内部的实现代码,就可以直接正确地使用它。 +Such comments allow us to understand the purpose of the function and use it the right way without looking in its code. - 顺便说一句,很多诸如 [WebStorm](https://www.jetbrains.com/webstorm/) 这样的编辑器,都可以很好地理解和使用这些注释,来提供自动补全和一些自动化代码检查工作。 +By the way, many editors like [WebStorm](https://www.jetbrains.com/webstorm/) can understand them as well and use them to provide autocomplete and some automatic code-checking. - 当然,也有一些像 [JSDoc 3](https://github.com/jsdoc3/jsdoc) 这样的工具,可以通过注释直接生成 HTML 文档。你可以在 阅读更多关于 JSDoc 的信息。 +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 . -为什么任务以这种方式解决? -: 写了什么代码很重要。但是为什么 **不** 那样写可能对于理解正在发生什么更重要。为什么任务是通过这种方式解决的?代码并没有给出答案。 +Why is the task solved this way? +: What's written is important. But what's *not* written may be even more important to understand what's going on. Why is the task solved exactly this way? The code gives no answer. - 如果有很多种方法都可以解决这个问题,为什么偏偏是这一种?尤其当它不是最显而易见的那一种的时候。 + If there are many ways to solve the task, why this one? Especially when it's not the most obvious one. - 没有这样的注释的话,就可能会发生下面的情况: - 1. 你(或者你的同事)打开了前一段时间写的代码,看到它不是最理想的实现方式。 - 2. 你会想:“我当时是有多蠢啊,现在我真是太聪明了”,然后用“更显而易见且正确的”方式重写了一遍。 - 3. ……重写的这股冲动劲是好的。但是在重写的过程中你发现“更显而易见”的解决方案实际上是有缺陷的。你甚至依稀地想起了为什么会这样,因为你很久之前就已经尝试过这样做了。于是你又还原了那个正确的实现,但是时间已经浪费了。 + Without such comments the following situation is possible: + 1. You (or your colleague) open the code written some time ago, and see that it's "suboptimal". + 2. You think: "How stupid I was then, and how much smarter I'm now", and rewrite using the "more obvious and correct" variant. + 3. ...The urge to rewrite was good. But in the process you see that the "more obvious" solution is actually lacking. You even dimly remember why, because you already tried it long ago. You revert to the correct variant, but the time was wasted. - 解决方案的注释非常的重要。它们可以帮助你以正确的方式继续开发。 + Comments that explain the solution are very important. They help to continue development the right way. -代码有哪些巧妙的特性?它们被用在了什么地方? -: 如果代码存在任何巧妙和不显而易见的方法,那绝对需要注释。 +Any subtle features of the code? Where they are used? +: If the code has anything subtle and counter-intuitive, it's definitely worth commenting. -## 总结 +## Summary -一个好的开发者的标志之一就是他的注释:它们的存在甚至它们的缺席(译注:在该注释的地方注释,在不需要注释的地方则不注释,甚至写得好的自描述函数本身就是一种注释)。 +An important sign of a good developer is comments: their presence and even their absence. -好的注释可以使我们更好地维护代码,一段时间之后依然可以更高效地回到代码高效开发。 +Good comments allow us to maintain the code well, come back to it after a delay and use it more effectively. -**注释这些内容:** +**Comment this:** -- 整体架构,高层次的观点。 -- 函数的用法。 -- 重要的解决方案,特别是在不是很明显时。 +- Overall architecture, high-level view. +- Function usage. +- Important solutions, especially when not immediately obvious. -**避免注释:** +**Avoid comments:** -- 描述“代码如何工作”和“代码做了什么”。 -- 避免在代码已经足够简单或代码有很好的自描述性而不需要注释的情况下,还写些没必要的注释。 +- That tell "how code works" and "what it does". +- Put them in only if it's impossible to make the code so simple and self-descriptive that it doesn't require them. -注释也被用于一些如 JSDoc3 等文档自动生成工具:他们读取注释然后生成 HTML 文档(或者其他格式的文档)。 +Comments are also used for auto-documenting tools like JSDoc3: they read them and generate HTML-docs (or docs in another format). 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 e2f9e3f210..96fdf4143c 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -1,185 +1,185 @@ -# 忍者代码 +# Ninja code -```quote author="孔子" -学而不思则罔,思而不学则殆。 +```quote author="Confucius (Analects)" +Learning without thought is labor lost; thought without learning is perilous. ``` -过去的程序员忍者使用这些技巧,来使代码维护者的头脑更加敏锐。 +Programmer ninjas of the past used these tricks to sharpen the mind of code maintainers. -代码审查大师在测试任务中寻找它们。 +Code review gurus look for them in test tasks. -一些新入门的开发者有时候甚至比忍者程序员能够更好地使用它们。 +Novice developers sometimes use them even better than programmer ninjas. -仔细阅读本文,找出你是谁 —— 一个忍者、一个新手、或者一个代码审查者? +Read them carefully and find out who you are -- a ninja, a novice, or maybe a code reviewer? -```warn header="检测到讽刺意味" -许多人试图追随忍者的脚步。只有极少数成功了。 +```warn header="Irony detected" +Many try to follow ninja paths. Few succeed. ``` -## 简洁是智慧的灵魂 +## Brevity is the soul of wit -把代码尽可能写得短。展示出你是多么的聪明啊。 +Make the code as short as possible. Show how smart you are. -在编程中,多使用一些巧妙的编程语言特性。 +Let subtle language features guide you. -例如,看一下这个三元运算符 `'?'`: +For instance, take a look at this ternary operator `'?'`: ```js -// 从一个著名的 JavaScript 库中截取的代码 +// taken from a well-known javascript library i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ``` -很酷,对吗?如果你这样写了,那些看到这一行代码并尝试去理解 `i` 的值是什么的开发者们,就会有一个“快活的”的时光了。然后会来找你寻求答案。 +Cool, right? If you write like that, a developer who comes across this line and tries to understand what is the value of `i` is going to have a merry time. Then come to you, seeking for an answer. -告诉他短一点总是更好的。引导他进入忍者之路。 +Tell them that shorter is always better. Initiate them into the paths of ninja. -## 一个字母的变量 +## One-letter variables -```quote author="老子(道德经)" -道隐无名。夫唯道善贷且成。 +```quote author="Laozi (Tao Te Ching)" +The Dao hides in wordlessness. Only the Dao is well begun and well +completed. ``` +Another way to code shorter is to use single-letter variable names everywhere. Like `a`, `b` or `c`. -另一个提高编程速度的方法是,到处使用单字母的变量名。例如 `a`、`b` 或 `c`。 +A short variable disappears in the code like a real ninja in the forest. No one will be able to find it using "search" of the editor. And even if someone does, they won't be able to "decipher" what the name `a` or `b` means. -短变量就像森林中真正的忍者一样,一下就找不到了。没有人能够通过编辑器的“搜索”功能找到它。即使有人做到了,他也不能“破译”出变量名 `a` 或 `b` 到底是什么意思。 +...But there's an exception. A real ninja will never use `i` as the counter in a `"for"` loop. Anywhere, but not here. Look around, there are many more exotic letters. For instance, `x` or `y`. -……但是有一个例外情况。一个真正的忍者绝不会在 `"for"` 循环中使用 `i` 作为计数器。在任何地方都可以,但是这里不会用。你随便一找,就能找到很多不寻常的字母。例如 `x` 或 `y`。 +An exotic variable as a loop counter is especially cool if the loop body takes 1-2 pages (make it longer if you can). Then if someone looks deep inside the loop, they won't be able to quickly figure out that the variable named `x` is the loop counter. -使用一个不寻常的变量多酷啊,尤其是在长达 1-2 页(如果可以的话,你可以写得更长)的循环体中使用的时候。如果某人要研究循环内部实现的时候,他就很难很快地找出变量 `x` 其实是循环计数器啦。 +## Use abbreviations -## 使用缩写 +If the team rules forbid the use of one-letter and vague names -- shorten them, make abbreviations. -如果团队规则中禁止使用一个字母和模糊的命名 — 那就缩短命名,使用缩写吧。 +Like this: -像这样: +- `list` -> `lst`. +- `userAgent` -> `ua`. +- `browser` -> `brsr`. +- ...etc -- `list` -> `lst` -- `userAgent` -> `ua` -- `browser` -> `brsr` -- ……等 +Only the one with truly good intuition will be able to understand such names. Try to shorten everything. Only a worthy person should be able to uphold the development of your code. -只有具有真正良好直觉的人,才能够理解这样的命名。尽可能缩短一切。只有真正有价值的人,才能够维护这种代码的开发。 +## Soar high. Be abstract. -## Soar high,抽象化。 - -```quote author="老子(道德经)" -大方无隅,
-大器晚成,
-大音希声,
-大象无形。 +```quote author="Laozi (Tao Te Ching)" +The great square is cornerless
+The great vessel is last complete,
+The great note is rarified sound,
+The great image has no form. ``` -当选择一个名字时,尽可能尝试使用最抽象的词语。例如 `obj`、`data`、`value`、`item` 和 `elem` 等。 +While choosing a name try to use the most abstract word. Like `obj`, `data`, `value`, `item`, `elem` and so on. -- **一个变量的理想名称是 `data`。** 在任何能用的地方都使用它。的确,每个变量都持有 **数据(data)**,对吧? +- **The ideal name for a variable is `data`.** Use it everywhere you can. Indeed, every variable holds *data*, right? - ……但是 `data` 已经用过了怎么办?可以尝试一下 `value`,它也很普遍。毕竟,一个变量总会有一个 **值(value)**,对吧? + ...But what to do if `data` is already taken? Try `value`, it's also universal. After all, a variable eventually gets a *value*. -- **根据变量的类型为变量命名:`str`、`num`……** +- **Name a variable by its type: `str`, `num`...** - 尝试一下吧。新手可能会诧异 — 这些名字对于忍者来说真的有用吗?事实上,有用的! + Give them a try. A young initiate may wonder -- are such names really useful for a ninja? Indeed, they are! - 一方面,变量名仍然有着一些含义。它说明了变量内是什么:一个字符串、一个数字或是其他的东西。但是当一个局外人试图理解代码时,他会惊讶地发现实际上没有任何有效信息!最终就无法修改你精心思考过的代码。 + Sure, the variable name still means something. It says what's inside the variable: a string, a number or something else. But when an outsider tries to understand the code, they'll be surprised to see that there's actually no information at all! And will ultimately fail to alter your well-thought code. - 我们可以通过代码调试,很容易地看出值的类型。但是变量名的含义呢?它存了哪一个字符串或数字? + The value type is easy to find out by debugging. But what's the meaning of the variable? Which string/number does it store? - 如果思考的深度不够,是没有办法搞明白的。 + There's just no way to figure out without a good meditation! -- **……但是如果找不到更多这样的名字呢?** 可以加一个数字:`data1, item2, elem5`…… +- **...But what if there are no more such names?** Just add a number: `data1, item2, elem5`... -## 注意测试 +## Attention test -只有一个真正细心的程序员才能理解你的代码。但是怎么检验呢? +Only a truly attentive programmer should be able to understand your code. But how to check that? -**方式之一 —— 使用相似的变量名,像 `date` 和 `data`。** +**One of the ways -- use similar variable names, like `date` and `data`.** -尽你所能地将它们混合在一起。 +Mix them where you can. -想快速阅读这种代码是不可能的。并且如果有一个错别字时……额……我们卡在这儿好长时间了,到饭点了 (⊙v⊙)。 +A quick read of such code becomes impossible. And when there's a typo... Ummm... We're stuck for long, time to drink tea. -## 智能同义词 +## Smart synonyms -```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. ``` -对 **同一个** 东西使用 **类似** 的命名,可以使生活更有趣,并且能够展现你的创造力。 +Using *similar* names for *same* things makes life more interesting and shows your creativity to the public. -例如,函数前缀。如果一个函数的功能是在屏幕上展示一个消息 — 名称可以以 `display…` 开头,例如 `displayMessage`。如果另一个函数展示别的东西,比如一个用户名,名称可以以 `show…` 开始(例如 `showName`)。 +For instance, consider function prefixes. If a function shows a message on the screen -- start it with `display…`, like `displayMessage`. And then if another function shows on the screen something else, like a user name, start it with `show…` (like `showName`). -暗示这些函数之间有微妙的差异,实际上并没有。 +Insinuate that there's a subtle difference between such functions, while there is none. -与团队中的其他忍者们达成一个协议:如果张三在他的代码中以 `display...` 来开始一个“显示”函数,那么李四可以用 `render..`,王二可以使用 `paint...`。你可以发现代码变得多么地有趣多样呀。 +Make a pact with fellow ninjas of the team: if John starts "showing" functions with `display...` in his code, then Peter could use `render..`, and Ann -- `paint...`. Note how much more interesting and diverse the code became. -……现在是帽子戏法! +...And now the hat trick! -对于有非常重要的差异的两个函数 — 使用相同的前缀。 +For two functions with important differences -- use the same prefix! -例如,`printPage(page)` 函数会使用一个打印机(printer)。`printText(text)` 函数会将文字显示到屏幕上。让一个不熟悉的读者来思考一下:“名字为 `printMessage(message)` 的函数会将消息放到哪里呢?打印机还是屏幕上?”。为了让代码真正耀眼,`printMessage(message)` 应该将消息输出到新窗口中! +For instance, the function `printPage(page)` will use a printer. And the function `printText(text)` will put the text on-screen. Let an unfamiliar reader think well over similarly named function `printMessage`: "Where does it put the message? To a printer or on the screen?". To make it really shine, `printMessage(message)` should output it in the new window! -## 重用名字 +## Reuse names -```quote author="老子(道德经)" -始制有名,
-名亦既有,
-夫亦将知止,
-知止可以不殆。 +```quote author="Laozi (Tao Te Ching)" +Once the whole is divided, the parts
+need names.
+There are already enough names.
+One must know when to stop. ``` -仅在绝对必要时才添加新变量。 +Add a new variable only when absolutely necessary. -否则,重用已经存在的名字。直接把新值写进变量即可。 +Instead, reuse existing names. Just write new values into them. -在一个函数中,尝试仅使用作为参数传递的变量。 +In a function try to use only variables passed as parameters. -这样就很难确定这个变量的值现在是什么了。也不知道它是从哪里来的。目的是提高阅读代码的人的直觉和记忆力。一个直觉较弱的人必须逐行分析代码,跟踪每个代码分支中的更改。 +That would make it really hard to identify what's exactly in the variable *now*. And also where it comes from. The purpose is to develop the intuition and memory of a person reading the code. A person with weak intuition would have to analyze the code line-by-line and track the changes through every code branch. -**这个方法的一个进阶方案是,在循环或函数中偷偷地替换掉它的值。** +**An advanced variant of the approach is to covertly (!) replace the value with something alike in the middle of a loop or a function.** -例如: +For instance: ```js function ninjaFunction(elem) { - // 基于变量 elem 进行工作的 20 行代码 + // 20 lines of code working with elem elem = clone(elem); - // 又 20 行代码,现在使用的是 clone 后的 elem 变量。 + // 20 more lines, now working with the clone of the elem! } ``` -想要在后半部分中使用 `elem` 的程序员会感到很诧异……只有在调试期间,检查代码之后,他才会发现他正在使用克隆过的变量! +A fellow programmer who wants to work with `elem` in the second half of the function will be surprised... Only during the debugging, after examining the code they will find out that they're working with a clone! -经常看到这样的代码,即使对经验丰富的忍者来说也是致命的。 +Seen in code regularly. Deadly effective even against an experienced ninja. -## 下划线的乐趣 +## Underscores for fun -在变量名前加上下划线 `_` 和 `__`。例如 `_name` 和 `__value`。如果只有你知道他们的含义,那就非常棒了。或者,加这些下划线只是为了好玩儿,没有任何含义,那就更棒了! +Put underscores `_` and `__` before variable names. Like `_name` or `__value`. It would be great if only you knew their meaning. Or, better, add them just for fun, without particular meaning at all. Or different meanings in different places. -加下划线可谓是一箭双雕。首先,代码变得更长,可读性更低;并且,你的开发者小伙伴可能会花费很长时间,来弄清楚下划线是什么意思。 +You kill two rabbits with one shot. First, the code becomes longer and less readable, and the second, a fellow developer may spend a long time trying to figure out what the underscores mean. -聪明的忍者会在代码的一个地方使用下划线,然后在其他地方刻意避免使用它们。这会使代码变得更加脆弱,并提高了代码未来出现错误的可能性。 +A smart ninja puts underscores at one spot of code and evades them at other places. That makes the code even more fragile and increases the probability of future errors. -## 展示你的爱 +## Show your love -向大家展现一下你那丰富的情感!像 `superElement`、`megaFrame` 和 `niceItem` 这样的名字一定会启发读者。 +Let everyone see how magnificent your entities are! Names like `superElement`, `megaFrame` and `niceItem` will definitely enlighten a reader. -事实上,从一方面来说,看似写了一些东西:`super..`、`mega..`、`nice..`。但从另一方面来说 — 并没有提供任何细节。阅读代码的人可能需要耗费一到两个小时的带薪工作时间,冥思苦想来寻找一个隐藏的含义。 +Indeed, from one hand, something is written: `super..`, `mega..`, `nice..` But from the other hand -- that brings no details. A reader may decide to look for a hidden meaning and meditate for an hour or two of their paid working time. -## 重叠外部变量 +## Overlap outer variables -```quote author="关尹子" -处明者不见暗中一物,
-处暗者能见明中区事。 +```quote author="Guan Yin Zi" +When in the light, can't see anything in the darkness.
+When in the darkness, can see everything in the light. ``` -对函数内部和外部的变量,使用相同的名称。很简单,不用费劲想新的名称。 +Use same names for variables inside and outside a function. As simple. No efforts to invent new names. ```js let *!*user*/!* = authenticateUser(); @@ -187,54 +187,54 @@ let *!*user*/!* = authenticateUser(); function render() { let *!*user*/!* = anotherValue(); ... - ...许多行代码... + ...many lines... ... - ... // <-- 某个程序员想要在这里使用 user 变量…… + ... // <-- a programmer wants to work with user here and... ... } ``` -在研究 `render` 内部代码的程序员可能不会注意到,有一个内部变量 `user` 屏蔽了外部的 `user` 变量。 +A programmer who jumps inside the `render` will probably fail to notice that there's a local `user` shadowing the outer one. -然后他会假设 `user` 仍然是外部的变量然后使用它,`authenticateUser()` 的结果……陷阱出来啦!你好呀,调试器…… +Then they'll try to work with `user` assuming that it's the external variable, the result of `authenticateUser()`... The trap is sprung! Hello, debugger... -## 无处不在的副作用! +## Side-effects everywhere! -有些函数看起来它们不会改变任何东西。例如 `isReady()`,`checkPermission()`,`findTags()`……它们被假定用于执行计算、查找和返回数据,而不会更改任何他们自身之外的数据。这被称为“无副作用”。 +There are functions that look like they don't change anything. Like `isReady()`, `checkPermission()`, `findTags()`... They are assumed to carry out calculations, find and return the data, without changing anything outside of them. In other words, without "side-effects". -**一个非常惊喜的技巧就是,除了主要任务之外,给它们添加一个“有用的”动作。** +**A really beautiful trick is to add a "useful" action to them, besides the main task.** -当你的同事看到被命名为 `is..`、`check..` 或 `find...` 的函数改变了某些东西的时候,他脸上肯定是一脸懵逼的表情 — 这会扩大你的理性界限。 +An expression of dazed surprise on the face of your colleague when they see a function named `is..`, `check..` or `find...` changing something -- will definitely broaden your boundaries of reason. -**另一个惊喜的方式是,返回非标准的结果。** +**Another way to surprise is to return a non-standard result.** -展示你原来的想法!让调用 `checkPermission` 时的返回值不是 `true/false`,而是一个包含检查结果的复杂对象。 +Show your original thinking! Let the call of `checkPermission` return not `true/false`, but a complex object with the results of the check. -那些尝试写 `if (checkPermission(..))` 的开发者,会很疑惑为什么它不能工作。告诉他们:“去读文档吧”。然后给出这篇文章。 +Those developers who try to write `if (checkPermission(..))`, will wonder why it doesn't work. Tell them: "Read the docs!". And give this article. -## 强大的函数! +## Powerful functions! -```quote author="老子(道德经)" -大道泛兮,
-其左可右。 +```quote author="Laozi (Tao Te Ching)" +The great Tao flows everywhere,
+both to the left and to the right. ``` -不要让函数受限于名字中写的内容。拓宽一些。 +Don't limit the function by what's written in its name. Be broader. -例如,函数 `validateEmail(email)` 可以(除了检查邮件的正确性之外)显示一个错误消息并要求重新输入邮件。 +For instance, a function `validateEmail(email)` could (besides checking the email for correctness) show an error message and ask to re-enter the email. -额外的动作在函数名称中不应该很明显。一个真正的忍者会使它们在代码中也不明显。 +Additional actions should not be obvious from the function name. A true ninja coder will make them not obvious from the code as well. -**将多个动作合并到一起,可以保护你的代码不被重用。** +**Joining several actions into one protects your code from reuse.** -想象一下,另一个开发者只想检查邮箱而不想输出任何信息。你的函数 `validateEmail(email)` 对他而言就不合适啦。所以他不会找你问关于这些函数的任何事而打断你的思考。 +Imagine, another developer wants only to check the email, and not output any message. Your function `validateEmail(email)` that does both will not suit them. So they won't break your meditation by asking anything about it. -## 总结 +## Summary -上面的所有“建议”都是从真实的代码中提炼而来的……有时候,这些代码是由有经验的开发者写的。也许比你更有经验 ;) +All "pieces of advice" above are from the real code... Sometimes, written by experienced developers. Maybe even more experienced than you are ;) -- 遵从其中的一丢丢,你的代码就会变得充满惊喜。 -- 遵从其中的一大部分,你的代码将真正成为你的代码,没有人会想改变它。 -- 遵从所有,你的代码将成为寻求启发的年轻开发者的宝贵案例。 +- Follow some of them, and your code will become full of surprises. +- Follow many of them, and your code will become truly yours, no one would want to change it. +- Follow all, and your code will become a valuable lesson for young developers looking for enlightenment. diff --git a/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/solution.md b/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/solution.md index ec29252976..4d0571b9d8 100644 --- a/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/solution.md +++ b/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/solution.md @@ -1,14 +1,14 @@ -这些测试代码展示了开发人员在编写测试代码时遇到的一些疑惑。 +The test demonstrates one of the temptations a developer meets when writing tests. -我们这里实际上有三条测试,但是用了一个函数来放置 3 个断言语句。 +What we have here is actually 3 tests, but layed out as a single function with 3 asserts. -有时用这种方式编写会更容易,但是如果发生错误,那么到底什么出错了就很不明显。 +Sometimes it's easier to write this way, but if an error occurs, it's much less obvious what went wrong. -如果错误发生在一个复杂的执行流的中间,那么我们就必须找出那个点的数据。我们必须 **调试测试**。 +If an error happens in the middle of a complex execution flow, then we'll have to figure out the data at that point. We'll actually have to *debug the test*. -将测试分成多个具有明确输入和输出的 `it` 代码块会更好。 +It would be much better to break the test into multiple `it` blocks with clearly written inputs and outputs. -像是这样: +Like this: ```js describe("Raises x to power n", function() { it("5 in the power of 1 equals 5", function() { @@ -25,9 +25,9 @@ describe("Raises x to power n", function() { }); ``` -我们使用 `describe` 和一组 `it` 代码块替换掉了单个的 `it`。现在,如果某个测试失败了,我们可以清楚地看到数据是什么。 +We replaced the single `it` with `describe` and a group of `it` blocks. Now if something fails we would see clearly what the data was. -此外,我们可以通过编写 `it.only` 而不是 `it` 来隔离单个测试,并以独立模式运行它: +Also we can isolate a single test and run it in standalone mode by writing `it.only` instead of `it`: ```js @@ -37,7 +37,7 @@ describe("Raises x to power n", function() { }); *!* - // Mocha 将只运行这个代码块 + // Mocha will run only this block it.only("5 in the power of 2 equals 25", function() { assert.equal(pow(5, 2), 25); }); diff --git a/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/task.md b/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/task.md index bb27e89a42..66fece09a8 100644 --- a/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/task.md +++ b/1-js/03-code-quality/05-testing-mocha/3-pow-test-wrong/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 测试代码中有什么错误? +# What's wrong in the test? -下面这个 `pow` 的测试代码有什么错误? +What's wrong in the test of `pow` below? ```js it("Raises x to the power n", function() { @@ -21,4 +21,4 @@ it("Raises x to the power n", function() { }); ``` -附:从语法上来说这些测试代码是正确且通过的。 +P.S. Syntactically the test is correct and passes. 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 53cde6a3b8..79f1903706 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -1,42 +1,42 @@ -# 使用 Mocha 进行自动化测试 +# Automated testing with Mocha -自动化测试将被用于进一步的任务中,并且还将被广泛应用在实际项目中。 +Automated testing will be used in further tasks, and it's also widely used in real projects. -## 为什么我们需要测试? +## Why do we need tests? -当我们在写一个函数时,我们通常可以想象出它应该做什么:哪些参数会给出哪些结果。 +When we write a function, we can usually imagine what it should do: which parameters give which results. -在开发期间,我们可以通过运行程序来检查它并将结果与预期进行比较。例如,我们可以在控制台中这么做。 +During development, we can check the function by running it and comparing the outcome with the expected one. For instance, we can do it in the console. -如果出了问题 —— 那么我们会修复代码,然后再一次运行并检查结果 —— 直到它工作为止。 +If something is wrong -- then we fix the code, run again, check the result -- and so on till it works. -但这样的手动“重新运行”是不完美的。 +But such manual "re-runs" are imperfect. -**当通过手动重新运行来测试代码时,很容易漏掉一些东西。** +**When testing a code by manual re-runs, it's easy to miss something.** -例如,我们要创建一个函数 `f`。写一些代码,然后测试:`f(1)` 可以执行,但是 `f(2)` 不执行。我们修复了一下代码,现在 `f(2)` 可以执行了。看起来已经搞定了?但是我们忘了重新测试 `f(1)`。这样有可能会导致出现错误。 +For instance, we're creating a function `f`. Wrote some code, testing: `f(1)` works, but `f(2)` doesn't work. We fix the code and now `f(2)` works. Looks complete? But we forgot to re-test `f(1)`. That may lead to an error. -这是非常典型的。当我们在开发一些东西时,我们会保留很多可能需要的用例。但是不要想着程序员在每一次代码修改后都去检查所有的案例。所以这就很容易造成修复了一个问题却造成另一个问题的情况。 +That's very typical. When we develop something, we keep a lot of possible use cases in mind. But it's hard to expect a programmer to check all of them manually after every change. So it becomes easy to fix one thing and break another one. -**自动化测试意味着测试是独立于代码的。它们以各种方式运行我们的函数,并将结果与预期结果进行比较。** +**Automated testing means that tests are written separately, in addition to the code. They run our functions in various ways and compare results with the expected.** -## 行为驱动开发(BDD) +## Behavior Driven Development (BDD) -我们来使用一种名为 [行为驱动开发](http://en.wikipedia.org/wiki/Behavior-driven_development) 或简言为 BDD 的技术。 +Let's start with a technique named [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) or, in short, BDD. -**BDD 包含了三部分内容:测试、文档和示例。** +**BDD is three things in one: tests AND documentation AND examples.** -为了理解 BDD,我们将研究一个实际的开发案例。 +To understand BDD, we'll examine a practical case of development. -## 开发 “pow”:规范 +## Development of "pow": the spec -我们想要创建一个函数 `pow(x, n)` 来计算 `x` 的 `n` 次幂(`n` 为整数)。我们假设 `n≥0`。 +Let's say we want to make a function `pow(x, n)` that raises `x` to an integer power `n`. We assume that `n≥0`. -这个任务只是一个例子:JavaScript 中有一个 `**` 操作符可以用于幂运算。但是在这里我们专注于可以应用于更复杂任务的开发流程上。 +That task is just an example: there's the `**` operator in JavaScript that can do that, but here we concentrate on the development flow that can be applied to more complex tasks as well. -在创建函数 `pow` 的代码之前,我们可以想象函数应该做什么并且描述出来。 +Before creating the code of `pow`, we can imagine what the function should do and describe it. -这样的描述被称作 **规范(specification, spec)**,包含用例的描述以及针对它们的测试,如下所示: +Such description is called a *specification* or, in short, a spec, and contains descriptions of use cases together with tests for them, like this: ```js describe("pow", function() { @@ -48,95 +48,95 @@ describe("pow", function() { }); ``` -正如你所看到的,一个规范包含三个主要的模块: +A spec has three main building blocks that you can see above: `describe("title", function() { ... })` -: 表示我们正在描述的功能是什么。在我们的例子中我们正在描述函数 `pow`。用于组织“工人(workers)” —— `it` 代码块。 +: What functionality we're describing? In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks. `it("use case description", function() { ... })` -: `it` 里面的描述部分,我们以一种 **易于理解** 的方式描述特定的用例,第二个参数是用于对其进行测试的函数。 +: In the title of `it` we *in a human-readable way* describe the particular use case, and the second argument is a function that tests it. `assert.equal(value1, value2)` -: `it` 块中的代码,如果实现是正确的,它应该在执行的时候不产生任何错误。 +: The code inside `it` block, if the implementation is correct, should execute without errors. - `assert.*` 函数用于检查 `pow` 函数是否按照预期工作。在这里我们使用了其中之一 —— `assert.equal`,它会对参数进行比较,如果它们不相等则会抛出一个错误。这里它检查了 `pow(2, 3)` 的值是否等于 `8`。还有其他类型的比较和检查,我们将在后面介绍到。 + Functions `assert.*` are used to check whether `pow` works as expected. Right here we're using one of them -- `assert.equal`, it compares arguments and yields an error if they are not equal. Here it checks that the result of `pow(2, 3)` equals `8`. There are other types of comparisons and checks, that we'll add later. -规范可以被执行,它将运行在 `it` 块中指定的测试。我们稍后会看到。 +The specification can be executed, and it will run the test specified in `it` block. We'll see that later. -## 开发流程 +## The development flow -开发流程通常看起来像这样: +The flow of development usually looks like this: -1. 编写初始规范,测试最基本的功能。 -2. 创建一个最初始的实现。 -3. 检查它是否工作,我们运行测试框架 [Mocha](http://mochajs.org/)(很快会有更多细节)来运行测试。当功能未完成时,将显示错误。我们持续修正直到一切都能工作。 -4. 现在我们有一个带有测试的能工作的初步实现。 -5. 我们增加更多的用例到规范中,或许目前的程序实现还不支持。无法通过测试。 -6. 回到第 3 步,更新程序直到测试不会抛出错误。 -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](http://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. -如此来看,开发就是不断地 **迭代**。我们写规范,实现它,确保测试通过,然后写更多的测试,确保它们工作等等。最后,我们有了一个能工作的实现和针对它的测试。 +So, the development is *iterative*. We write the spec, implement it, make sure tests pass, then write more tests, make sure they work etc. At the end we have both a working implementation and tests for it. -让我们在我们的开发案例中看看这个开发流程吧。 +Let's see this development flow in our practical case. -在我们的案例中,第一步已经完成了:我们有一个针对 `pow` 的初始规范。因此让我们来实现它吧。但在此之前,让我们用一些 JavaScript 库来运行测试,就是看看测试是通过了还是失败了。 +The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail). -## 行为规范 +## The spec in action -在本教程中,我们将使用以下 JavaScript 库进行测试: +Here in the tutorial we'll be using the following JavaScript libraries for tests: -- [Mocha](http://mochajs.org/) —— 核心框架:提供了包括通用型测试函数 `describe` 和 `it`,以及用于运行测试的主函数。 -- [Chai](http://chaijs.com) —— 提供很多断言(assertion)支持的库。它提供了很多不同的断言,现在我们只需要用 `assert.equal`。 -- [Sinon](http://sinonjs.org/) —— 用于监视函数、模拟内置函数和其他函数的库,我们在后面才会用到它。 +- [Mocha](http://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. +- [Chai](http://chaijs.com) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. +- [Sinon](http://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. -这些库都既适用于浏览器端,也适用于服务器端。这里我们将使用浏览器端的变体。 +These libraries are suitable for both in-browser and server-side testing. Here we'll consider the browser variant. -包含这些框架和 `pow` 规范的完整的 HTML 页面: +The full HTML page with these frameworks and `pow` spec: ```html src="index.html" ``` -该页面可分为五个部分: +The page can be divided into five parts: -1. `` —— 添加用于测试的第三方库和样式文件。 -2. ` +In a browser, global functions and variables declared with `var` (not `let/const`!) become the property of the global object: - - ``` +```js run untrusted refresh +var gVar = 5; -4. 而且,虽然是小问题但仍然重要的一点是:全局范围内 `this` 的值是 `window`。 - - ```js untrusted run no-strict refresh - alert(this); // window - ``` - -为什么这样做?在语言创建时,将多个方面合并到单一 `window` 对象中的想法就是“简化”,但此后许多事情发生了变化,小型脚本变成了需要恰当构架的大型应用程序。 - -不同脚本(可能来自不同的源)之间的变量可以互相访问好不好? - -并不好,因为它可能导致命名冲突:相同的变量名可以在两个脚本中被用于不同目的,因此这些变量名将相互冲突。 - -到现在为止,这个多用途的 `window` 被认为是语言中的设计错误。 - -幸运的是,有一条 “走出地狱的道路”,被称为 “Javascript 模块”。 - -如果我们在 ` - ``` +alert(window.gVar); // 5 (became a property of the global object) +``` -- 两个模块的变量彼此不可见: +Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions). - ```html run - +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. - - ``` +If we used `let` instead, such thing wouldn't happen: -- 然后最后一个小问题是,模块中 `this` 的顶级值是 `undefined`(为什么它一定得是 `window` ?): +```js run untrusted refresh +let gLet = 5; - ```html run - - ``` +alert(window.gLet); // undefined (doesn't become a property of the global object) +``` -**使用 ` -``` - -运行后会发现,`i` 值只在整个计数过程完成后才显示。 - -接下来用 `setTimeout` 对任务进行分割,这样就能在每一轮运行的间隙观察到变化了,效果要好得多: +First timers run immediately (just as written in the spec), and then we see `9, 15, 20, 24...`. The 4+ ms obligatory delay between invocations comes into play. -```html run -
+The similar thing happens if we use `setInterval` instead of `setTimeout`: `setInterval(f)` runs `f` few times with zero-delay, and afterwards with 4+ ms delay. - -``` - -现在就可以观察到 `
` 里 `i` 值的增长过程了。 - -## 总结 +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. +```` -- `setInterval(func, delay, ...args)` 和 `setTimeout(func, delay, ...args)` 可以让 `func` 定期或经历一段延时后一次性执行。 -- 要取消函数的执行需要调用 `clearInterval/clearTimeout`,只需将 `setInterval/setTimeout` 返回的值传入即可。 -- 嵌套 `setTimeout` 比 `setInterval` 用起来更加灵活,同时也能保证每一轮执行的最小时间间隔。 -- 0 延时调度 `setTimeout(...,0)` 用来安排在当前代码执行完时,需要尽快执行的函数。 +## Summary -`setTimeout(...,0)` 的一些用法示例: -- 将耗费 CPU 的任务分割成多块,这样脚本运行不会进入“挂起”状态。 -- 进程繁忙时也能让浏览器抽身做其它事情(例如绘制进度条)。 +- 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. -有一点需要注意,所有的调度方法都不能**保证**延时的准确性,所以在调度代码中,万不可依赖它。 +Please note that all scheduling methods do not *guarantee* the exact delay. -浏览器内部的定时器会因各种原因而出现降速情况,譬如: -- 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. -如果出现以上情况,定时器的最高精度(最高精确延时)可能会降到 300 毫秒,甚至是 1000 毫秒,具体以浏览器及其设置为准。 +All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg index 23ca0e0e5d..bce7d6a843 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg @@ -1,40 +1 @@ - - - - setinterval-interval.svg - Created with sketchtool. - - - - - - func(1) - - - - - - func(2) - - - - - - func(3) - - - - - 100 - - - 200 - - - 300 - - - - - - \ No newline at end of file +func(1)func(2)func(3)100200300 \ No newline at end of file diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg index a9a7cd0970..d6d233b2ba 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg @@ -1,39 +1 @@ - - - - settimeout-interval.svg - Created with sketchtool. - - - - - - func(1) - - - - - - func(2) - - - - - - func(3) - - - - - - - 100 - - - 100 - - - - - - \ No newline at end of file +func(1)func(2)func(3)100100 \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js index 9ef503703b..d5a09efb36 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js @@ -1,11 +1,12 @@ function spy(func) { function wrapper(...args) { + // using ...args instead of arguments to store "real" array in wrapper.calls wrapper.calls.push(args); - return func.apply(this, arguments); + return func.apply(this, args); } wrapper.calls = []; return wrapper; -} \ No newline at end of file +} diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md index 0766e7c508..0c8a211b49 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md @@ -1 +1 @@ -在这里,我们可以使用 `calls.push(args)` 来存储日志中的所有参数,并使用 `f.apply(this, args)` 来转发调用。 +The wrapper returned by `spy(f)` should store all arguments and then use `f.apply` to forward the call. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md index 18919a1923..a3843107c9 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md @@ -2,17 +2,17 @@ importance: 5 --- -# 间谍装饰器 +# Spy decorator -创建一个装饰器 `spy(func)`,它应该返回一个包装器,它在 `calls` 属性中保存所有函数调用。 +Create a decorator `spy(func)` that should return a wrapper that saves all calls to function in its `calls` property. -每个调用都保存为一个参数数组。 +Every call is saved as an array of arguments. -例如: +For instance: ```js function work(a, b) { - alert( a + b ); // work 是一种任意的函数或方法 + alert( a + b ); // work is an arbitrary function or method } *!* @@ -27,4 +27,4 @@ for (let args of work.calls) { } ``` -附:该装饰器有时用于单元测试,它的高级形式是 [Sinon.JS](http://sinonjs.org/) 库中的 `sinon.spy`。 +P.S. That decorator is sometimes useful for unit-testing. Its advanced form is `sinon.spy` in [Sinon.JS](http://sinonjs.org/) library. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md index baf35bbe63..24bb4d4484 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md @@ -1,6 +1,6 @@ -解决方案: +The solution: -```js +```js run demo function delay(f, ms) { return function() { @@ -8,20 +8,25 @@ function delay(f, ms) { }; } + +let f1000 = delay(alert, 1000); + +f1000("test"); // shows "test" after 1000ms ``` -注意这里是如何使用箭头函数的。我们知道,箭头函数没有自己的 `this` 和 `arguments`,所以 `f.apply(this, arguments)`从包装器中获取 `this` 和 `arguments`。 +Please note how an arrow function is used here. As we know, arrow functions do not have own `this` and `arguments`, so `f.apply(this, arguments)` takes `this` and `arguments` from the wrapper. + +If we pass a regular function, `setTimeout` would call it without arguments and `this=window` (assuming we're in the browser). -如果我们传递一个常规函数,`setTimeout` 将调用它且不带参数和 `this=window`(在浏览器中),所以我们需要编写更多代码来从包装器传递它们: +We still can pass the right `this` by using an intermediate variable, but that's a little bit more cumbersome: ```js function delay(f, ms) { - // added variables to pass this and arguments from the wrapper inside setTimeout return function(...args) { - let savedThis = this; + let savedThis = this; // store this into an intermediate variable setTimeout(function() { - f.apply(savedThis, args); + f.apply(savedThis, args); // use it here }, ms); }; diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md index 5181a5c6f4..c04c68d7ef 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 延迟装饰器 +# Delaying decorator -创建一个装饰器 `delay(f, ms)`,将每次调用 `f` 延迟 `ms` 毫秒。 +Create a decorator `delay(f, ms)` that delays each call of `f` by `ms` milliseconds. -例如: +For instance: ```js function f(x) { @@ -17,10 +17,10 @@ function f(x) { let f1000 = delay(f, 1000); let f1500 = delay(f, 1500); -f1000("test"); // 在 1000 ms 后展示 "test" -f1500("test"); // 在 1500 ms 后展示 "test" +f1000("test"); // shows "test" after 1000ms +f1500("test"); // shows "test" after 1500ms ``` -换句话说,`delay(f, ms)` 返回的是延迟 `ms` 后的 `f` 的变体。 +In other words, `delay(f, ms)` returns a "delayed by `ms`" variant of `f`. -在上面的代码中,`f` 是单个参数的函数,但是你的解决方案应该传递所有参数和上下文 `this`。 +In the code above, `f` is a function of a single argument, but your solution should pass all arguments and the context `this`. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js index 065a77d1f9..661dd0cf41 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js @@ -1,15 +1,7 @@ -function debounce(f, ms) { - - let isCooldown = false; - +function debounce(func, ms) { + let timeout; return function() { - if (isCooldown) return; - - f.apply(this, arguments); - - isCooldown = true; - - setTimeout(() => isCooldown = false, ms); + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, arguments), ms); }; - -} \ No newline at end of file +} diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js index 16dc171e1a..750e649f83 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js @@ -1,41 +1,48 @@ -describe("debounce", function() { - before(function() { +describe('debounce', function () { + before(function () { this.clock = sinon.useFakeTimers(); }); - after(function() { + after(function () { this.clock.restore(); }); - it("calls the function at maximum once in ms milliseconds", function() { - let log = ''; + it('for one call - runs it after given ms', function () { + const f = sinon.spy(); + const debounced = debounce(f, 1000); - function f(a) { - log += a; - } + debounced('test'); + assert(f.notCalled, 'not called immediately'); + this.clock.tick(1000); + assert(f.calledOnceWith('test'), 'called after 1000ms'); + }); - f = debounce(f, 1000); + it('for 3 calls - runs the last one after given ms', function () { + const f = sinon.spy(); + const debounced = debounce(f, 1000); - f(1); // runs at once - f(2); // ignored + debounced('a'); + setTimeout(() => debounced('b'), 200); // ignored (too early) + setTimeout(() => debounced('c'), 500); // runs (1000 ms passed) + this.clock.tick(1000); - setTimeout(() => f(3), 100); // ignored (too early) - setTimeout(() => f(4), 1100); // runs (1000 ms passed) - setTimeout(() => f(5), 1500); // ignored (less than 1000 ms from the last run) + assert(f.notCalled, 'not called after 1000ms'); - this.clock.tick(5000); - assert.equal(log, "14"); + this.clock.tick(500); + + assert(f.calledOnceWith('c'), 'called after 1500ms'); }); - it("keeps the context of the call", function() { + it('keeps the context of the call', function () { let obj = { f() { assert.equal(this, obj); - } + }, }; obj.f = debounce(obj.f, 1000); - obj.f("test"); + obj.f('test'); + this.clock.tick(5000); }); - -}); \ No newline at end of file + +}); diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg new file mode 100644 index 0000000000..e624ce0203 --- /dev/null +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg @@ -0,0 +1 @@ +200ms1500ms1000ms0cf(a)f(b)f(c)500mstimecalls: after 1000ms \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html new file mode 100644 index 0000000000..e3b4d5842f --- /dev/null +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html @@ -0,0 +1,24 @@ + + + +Function handler is called on this input: +
+ + +

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

+ + + \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md index 337ac78928..83e75f3158 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md @@ -1,31 +1,13 @@ - - -```js run no-beautify -function debounce(f, ms) { - - let isCooldown = false; - +```js demo +function debounce(func, ms) { + let timeout; return function() { - if (isCooldown) return; - - f.apply(this, arguments); - - isCooldown = true; - - setTimeout(() => isCooldown = false, ms); + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, arguments), ms); }; - } -``` - -对 `debounce` 的调用返回一个包装器。可能有两种状态: -- `isCooldown = false` —— 准备好执行 -- `isCooldown = true` —— 等待时间结束 - -在第一次调用 `isCooldown` 是假的,所以调用继续进行,状态变为 `true`。 - -当 `isCooldown` 为真时,所有其他调用都被忽略。 +``` -然后 `setTimeout` 在给定的延迟后将其恢复为 `false`。 +A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout. 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 af4d58b4dd..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 @@ -2,23 +2,50 @@ importance: 5 --- -# 去抖装饰器 +# Debounce decorator -`debounce(f, ms)` 装饰器的结果应该是一个包装器,它最多允许每隔 “ms” 毫秒调用一次 `f`。 +The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments. -换句话说,当我们多次调用 “debounced” 函数时,它保证忽略距离上次调用在 “ms” 毫秒内的调用。 +In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`). -例如: +For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`. -```js no-beautify -let f = debounce(alert, 1000); +Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call. -f(1); // 立即执行 -f(2); // 忽略 +![](debounce.svg) -setTimeout( () => f(3), 100); // 忽略(只过去了 100 ms) -setTimeout( () => f(4), 1100); // 运行 -setTimeout( () => f(5), 1500); // 忽略(离上一次执行不超过 1000 ms) +...And it will get the arguments of the very last call, other calls are ignored. + +Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)): + +```js +let f = _.debounce(alert, 1000); + +f("a"); +setTimeout( () => f("b"), 200); +setTimeout( () => f("c"), 500); +// debounced function waits 1000ms after the last call and then runs: alert("c") +``` + +Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished. + +There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result. + +In a web-browser, we can setup an event handler -- a function that's called on every change of an input field. Normally, an event handler is called very often, for every typed key. But if we `debounce` it by 1000ms, then it will be only called once, after 1000ms after the last input. + +```online + +In this live example, the handler puts the result into a box below, try it: + +[iframe border=1 src="debounce" height=200] + +See? The second input calls the debounced function, so its content is processed after 1000ms from the last input. ``` -在实践中,对于那些用于检索/更新的函数而言,当我们知道在短时间内基本不会有什么新内容的时候,`debounce` 就显得很有用,所以最好不要浪费资源。 +So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else. + +It waits the given time after the last call, and then runs its function, that can process the result. + +The task is to implement `debounce` decorator. + +Hint: that's just a few lines if you think about it :) diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js index 5339c8d117..e671438f6f 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js @@ -7,8 +7,8 @@ describe("throttle(f, 1000)", function() { } before(function() { - f1000 = throttle(f, 1000); this.clock = sinon.useFakeTimers(); + f1000 = throttle(f, 1000); }); it("the first call runs now", function() { @@ -44,4 +44,20 @@ describe("throttle(f, 1000)", function() { this.clock.restore(); }); -}); \ No newline at end of file +}); + +describe('throttle', () => { + + it('runs a forwarded call once', done => { + let log = ''; + const f = str => log += str; + const f10 = throttle(f, 10); + f10('once'); + + setTimeout(() => { + assert.equal(log, 'once'); + done(); + }, 20); + }); + +}); 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 4f1b857441..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 @@ -1,4 +1,4 @@ -```js +```js demo function throttle(func, ms) { let isThrottled = false, @@ -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) { @@ -30,10 +29,10 @@ function throttle(func, ms) { } ``` -调用 `throttle(func, ms)` 返回 `wrapper`。 +A call to `throttle(func, ms)` returns `wrapper`. -1. 在第一次调用期间,`wrapper` 只运行 `func` 并设置冷却状态 (`isThrottled = true`)。 -2. 在这种状态下,所有调用都记忆在 `savedArgs/savedThis` 中。请注意,上下文和参数都同样重要,应该记住。我们需要他们同时重现这个调用。 -3. ...然后在 `ms` 毫秒过后,`setTimeout` 触发。冷却状态被删除 (`isThrottled = false`)。如果我们忽略了调用,则使用最后记忆的参数和上下文执行 `wrapper` +1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`). +2. In this state all calls are memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call. +3. After `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`) and, if we had ignored calls, `wrapper` is executed with the last memorized arguments and context. -第3步不是 `func`,而是 `wrapper`,因为我们不仅需要执行 `func`,而是再次进入冷却状态并设置超时以重置它。 +The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it. 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 f6543e17e2..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 @@ -2,37 +2,42 @@ importance: 5 --- -# 节流装饰器 +# Throttle decorator -创建一个“节流”装饰器 `throttle(f, ms)` —— 返回一个包装器,每隔 “ms” 毫秒将调用最多传递给 `f` 一次。那些属于“冷却”时期的调用被忽略了。 +Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. -**与**`debounce` **的区别 —— 如果被忽略的调用是冷却期间的最后一次,那么它会在延迟结束时执行。** +When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. -让我们检查一下真实应用程序,以便更好地理解这个需求,并了解它的来源。 +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. -**例如,我们想要跟踪鼠标移动。** +In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds. -在浏览器中,我们可以设置一个函数,鼠标的每次微小的运动都执行,并在移动时获取指针位置。在活动鼠标使用期间,此功能通常非常频繁地运行,可以是每秒 100 次(每 10 毫秒)。 +Let's check the real-life application to better understand that requirement and to see where it comes from. -**跟踪功能应更新网页上的一些信息。** +**For instance, we want to track mouse movements.** -更新函数 `update()` 太重了,无法在每次微小动作上执行。每 100 毫秒更频繁地制作一次也没有任何意义。 +In a browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). +**We'd like to update some information on the web-page when the pointer moves.** -因此我们将 `throttle(update, 100)` 指定为在每次鼠标移动时运行的函数,而不是原始的 `update()`。装饰器将经常调用,但 `update()` 最多每 100ms 调用一次。 +...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms. -在视觉上,它看起来像这样: +So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms. -1. 对于第一个鼠标移动,装饰变体将调用传递给 `update`。这很重要,用户会立即看到我们对他行动的反应。 -2. 然后当鼠标移动时,直到 “100ms” 没有任何反应。装饰的变体忽略了调用。 -3. 在 `100ms` 结束时 - 最后一个坐标发生了一次 `update`。 -4. 然后,最后,鼠标停在某处。装饰的变体等到 `100ms`到期,然后用最后一个坐标运行 `update`。因此,也许最重要的是处理最终的鼠标坐标。 +Visually, it will look like this: -一个代码示例: +1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately. +2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls. +3. At the end of `100ms` -- one more `update` happens with the last coordinates. +4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed. + +A code example: ```js function f(a) { - console.log(a) -}; + console.log(a); +} // f1000 passes calls to f at maximum once per 1000 ms let f1000 = throttle(f, 1000); @@ -45,4 +50,4 @@ f1000(3); // (throttling, 1000ms not out yet) // ...outputs 3, intermediate value 2 was ignored ``` -附:参数和传递给 `f1000` 的上下文 `this` 应该传递给原始的 `f`。 +P.S. Arguments and the context `this` passed to `f1000` should be passed to the original `f`. 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 7b80e478c6..c5d785493c 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 @@ -1,20 +1,20 @@ -# 装饰和转发,call/apply +# Decorators and forwarding, call/apply -JavaScript在处理函数时提供了非凡的灵活性。它们可以被传递,用作对象,现在我们将看到如何在它们之间**转发**并**装饰**它们。 +JavaScript gives exceptional flexibility when dealing with functions. They can be passed around, used as objects, and now we'll see how to *forward* calls between them and *decorate* them. -## 透明缓存 +## Transparent caching -假设我们有一个函数 `slow(x)` ,它是 CPU 重负载的,但它的结果是稳定的。换句话说,对于相同的 `x`,它总是返回相同的结果。 +Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result. -如果经常调用该函数,我们可能希望缓存(记住)不同 `x` 的结果,以避免在重新计算上花费额外的时间。 +If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations. -但是我们不是将这个功能添加到 `slow()` 中,而是创建一个包装器。正如我们将要看到的,这样做有很多好处。 +But instead of adding that functionality into `slow()` we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so. -下面是代码和解释: +Here's the code, and explanations follow: ```js run function slow(x) { - // 这里可能会有重负载的CPU密集型工作 + // there can be a heavy CPU-intensive job here alert(`Called with ${x}`); return x; } @@ -23,68 +23,65 @@ function cachingDecorator(func) { let cache = new Map(); return function(x) { - if (cache.has(x)) { // 如果结果在 map 里 - return cache.get(x); // 返回它 + if (cache.has(x)) { // if there's such key in cache + return cache.get(x); // read the result from it } - let result = func(x); // 否则就调用函数 + let result = func(x); // otherwise call func - cache.set(x, result); // 然后把结果缓存起来 + cache.set(x, result); // and cache (remember) the result return result; }; } slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) 被缓存起来了 -alert( "Again: " + slow(1) ); // 一样的 +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) 被缓存起来了 -alert( "Again: " + slow(2) ); // 也是一样 +alert( slow(2) ); // slow(2) is cached and the result returned +alert( "Again: " + slow(2) ); // slow(2) result returned from cache ``` -在上面的代码中,`cachingDecorator` 是一个**装饰器**:一个特殊的函数,它接受另一个函数并改变它的行为。 +In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. -我们的想法是,我们可以为任何函数调用 `cachingDecorator`,它将返回缓存包装器。这很好,因为我们有很多函数可以使用这样的特性,而我们需要做的就是将 `cachingDecorator` 应用于它们。 +The idea is that we can call `cachingDecorator` for any function, and it will return the caching wrapper. That's great, because we can have many functions that could use such a feature, and all we need to do is to apply `cachingDecorator` to them. -通过将缓存与主函数代码分开,我们还可以使主函数代码变得更简单。 +By separating caching from the main function code we also keep the main code simpler. -现在让我们详细了解它的工作原理吧。 - -`cachingDecorator(func)` 的结果是一个“包装器”:`function(x)` 将 `func(x)` 的调用 "包装" 到缓存逻辑中: +The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic: ![](decorator-makecaching-wrapper.svg) -正如我们所看到的,包装器返回 `func(x)` "的结果"。从外部代码中,包装的 `slow` 函数仍然是一样的。它只是在其函数体中添加了一个缓存。 - -总而言之,使用单独的 `cachingDecorator` 而不是改变 `slow` 本身的代码有几个好处: +From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior. -- `cachingDecorator` 是可重用的。我们可以将它应用于另一个函数。 -- 缓存逻辑是独立的,它没有增加 `slow` 本身的复杂性(如果有的话)。 -- 如果需要,我们可以组合多个装饰器(其他装饰器将遵循同样的逻辑)。 +To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself: +- The `cachingDecorator` is reusable. We can apply it to another function. +- The caching logic is separate, it did not increase the complexity of `slow` itself (if there was any). +- We can combine multiple decorators if needed (other decorators will follow). -## 使用 “func.call” 作为上下文 +## Using "func.call" for the context -上面提到的缓存装饰器不适合使用对象方法。 +The caching decorator mentioned above is not suited to work with object methods. -例如,在下面的代码中,`worker.slow()` 装饰后停止工作: +For instance, in the code below `worker.slow()` stops working after the decoration: ```js run -// 我们将让 work 缓存一个 slow 起来 +// we'll make worker.slow caching let worker = { someMethod() { return 1; }, slow(x) { - // 显然, 这里会有一个 CPU 重负载的任务 + // scary CPU-heavy task here alert("Called with " + x); return x * this.someMethod(); // (*) } }; -// 和之前一样的代码 +// same code as before function cachingDecorator(func) { let cache = new Map(); return function(x) { @@ -99,49 +96,49 @@ function cachingDecorator(func) { }; } -alert( worker.slow(1) ); // 之前的函数起作用了 +alert( worker.slow(1) ); // the original method works -worker.slow = cachingDecorator(worker.slow); // 现在让它缓存起来 +worker.slow = cachingDecorator(worker.slow); // now make it caching *!* alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined */!* ``` -错误发生在试图访问 `this.someMethod` 并且失败的行 `(*)` 中。你能明白为什么吗? +The error occurs in the line `(*)` that tries to access `this.someMethod` and fails. Can you see why? -原因是包装器将原始函数调用为 `(**)` 行中的 `func(x)`。并且,当这样调用时,函数得到 `this = undefined`。 +The reason is that the wrapper calls the original function as `func(x)` in the line `(**)`. And, when called like that, the function gets `this = undefined`. -如果我们试图运行下面的代码,会观察到类似的问题: +We would observe a similar symptom if we tried to run: ```js let func = worker.slow; func(2); ``` -因此,包装器将调用传递给原始方法,但没有上下文 `this`。因此错误。 +So, the wrapper passes the call to the original method, but without the context `this`. Hence the error. -我们来解决这个问题。 +Let's fix it. -有一个特殊的内置函数方法 [func.call(context, ...args)](mdn:js/Function/call),允许调用一个显式设置 `this` 的函数。 +There's a special built-in function method [func.call(context, ...args)](mdn:js/Function/call) that allows to call a function explicitly setting `this`. -语法如下: +The syntax is: ```js func.call(context, arg1, arg2, ...) ``` -它运行 `func`,提供的第一个参数作为 `this`,后面的作为参数。 +It runs `func` providing the first argument as `this`, and the next as the arguments. -简单地说,这两个调用几乎相同: +To put it simply, these two calls do almost the same: ```js func(1, 2, 3); func.call(obj, 1, 2, 3) ``` -他们都调用的是 `func`,参数是 `1`,`2` 和 `3`。唯一的区别是 `func.call` 也将 `this` 设置为 `obj`。 +They both call `func` with arguments `1`, `2` and `3`. The only difference is that `func.call` also sets `this` to `obj`. -例如,在下面的代码中,我们在不同对象的上下文中调用 `sayHi`:`sayHi.call(user)` 运行 `sayHi` 提供 `this=user`,下一行设置 `this=admin`: +As an example, in the code below we call `sayHi` in the context of different objects: `sayHi.call(user)` runs `sayHi` providing `this=user`, and the next line sets `this=admin`: ```js run function sayHi() { @@ -151,12 +148,12 @@ function sayHi() { let user = { name: "John" }; let admin = { name: "Admin" }; -// 使用 call 将不同的对象传递为 "this" -sayHi.call( user ); // this = John -sayHi.call( admin ); // this = Admin +// use call to pass different objects as "this" +sayHi.call( user ); // John +sayHi.call( admin ); // Admin ``` -在这里我们用 `call` 用给定的上下文和短语调用 `say`: +And here we use `call` to call `say` with the given context and phrase: ```js run @@ -170,9 +167,7 @@ let user = { name: "John" }; say.call( user, "Hello" ); // John: Hello ``` - -在我们的例子中,我们可以在包装器中使用 `call` 将上下文传递给原始函数: - +In our case, we can use `call` in the wrapper to pass the context to the original function: ```js run let worker = { @@ -193,32 +188,32 @@ function cachingDecorator(func) { return cache.get(x); } *!* - let result = func.call(this, x); // "this" 现在被正确的传递了 + let result = func.call(this, x); // "this" is passed correctly now */!* cache.set(x, result); return result; }; } -worker.slow = cachingDecorator(worker.slow); // 现在让他缓存起来 +worker.slow = cachingDecorator(worker.slow); // now make it caching -alert( worker.slow(2) ); // 生效了 -alert( worker.slow(2) ); // 生效了, 不会调用原始的函数了。被缓存起来了 +alert( worker.slow(2) ); // works +alert( worker.slow(2) ); // works, doesn't call the original (cached) ``` -现在一切都很好。 +Now everything is fine. -为了清楚地说明,让我们更深入地了解 `this` 是如何传递的: +To make it all clear, let's see more deeply how `this` is passed along: -1. 在经过装饰之后,`worker.slow` 现在是包装器 `function (x) { ... }`。 -2. 因此,当执行 `worker.slow(2)` 时,包装器将 `2` 作为参数并且 `this=worker`(它是点之前的对象)。 -3. 在包装器内部,假设结果尚未缓存,`func.call(this, x)` 将当前的 `this` (`=worker`) 和当前参数 (`=2`) 传递给原始方法。 +1. After the decoration `worker.slow` is now the wrapper `function (x) { ... }`. +2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot). +3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method. -## 使用 “func.apply” 来传递多参数 +## Going multi-argument -现在让我们让 `cachingDecorator` 变得更加通用。直到现在它只使用单参数函数。 +Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions. -现在如何缓存多参数 `worker.slow` 方法? +Now how to cache the multi-argument `worker.slow` method? ```js let worker = { @@ -231,95 +226,19 @@ let worker = { worker.slow = cachingDecorator(worker.slow); ``` -我们这里有两个要解决的任务。 - -首先是如何在 `cache` map 中使用参数 `min` 和 `max` 作为键。以前,对于单个参数 `x`,我们可以只使用 `cache.set(x, result)` 来保存结果,并使用 `cache.get(x)` 来检索它。但是现在我们需要记住参数组合 * `(min,max)` 的结果。原生 `Map` 仅将单个值作为键。 - -有许多解决方案可以实现: +Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key. -1. 实现一个新的(或使用第三方)类似 map 的数据结构,它更通用并允许多键。 -2. 使用嵌套映射:`cache.set(min)` 将是一个存储对 `(max, result)` 的 `Map`。所以我们可以将 `result` 改为 `cache.get(min).get(max)`。 -3. 将两个值合并为一个。在我们的特定情况下,我们可以使用字符串 “min,max” 作为 `Map` 键。为了灵活性,我们可以允许为装饰器提供**散列函数**,它知道如何从多个中创建一个值。 +There are many solutions possible: +1. Implement a new (or use a third-party) map-like data structure that is more versatile and allows multi-keys. +2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`. +3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many. -对于许多实际应用,第三种方式已经足够好,所以我们就用这个吧。 +For many practical applications, the 3rd variant is good enough, so we'll stick to it. -要解决的第二个任务是如何将许多参数传递给 `func`。目前,包装器 `function(x)` 假设一个参数,`func.call(this, x)` 传递它。 +Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`. -在这里我们可以使用另一种内置方法 [func.apply](mdn:js/Function/apply). - -语法如下: - -```js -func.apply(context, args) -``` - -它运行 `func` 设置 `this=context` 并使用类似数组的对象 `args` 作为参数列表。 - - -例如,这两个调用几乎相同: - -```js -func(1, 2, 3); -func.apply(context, [1, 2, 3]) -``` - -两个都运行 `func` 给定的参数是 `1,2,3`。但是 `apply` 也设置了 `this = context`。 - -例如,这里 `say` 用 `this=user` 和 `messageData` 作为参数列表调用: - -```js run -function say(time, phrase) { - alert(`[${time}] ${this.name}: ${phrase}`); -} - -let user = { name: "John" }; - -let messageData = ['10:00', 'Hello']; // 成为时间和短语 - -*!* -// user 成为 this,messageData 作为参数列表传递 (time, phrase) -say.apply(user, messageData); // [10:00] John: Hello (this=user) -*/!* -``` - -`call` 和 `apply` 之间唯一的语法区别是 `call` 接受一个参数列表,而 `apply` 则接受带有一个类似数组的对象。 - -我们已经知道了 一章中的扩展运算符 `...`,它可以将数组(或任何可迭代的)作为参数列表传递。因此,如果我们将它与 `call` 一起使用,就可以实现与 `apply` 几乎相同的功能。 - -这两个调用结果几乎相同: - -```js -let args = [1, 2, 3]; - -*!* -func.call(context, ...args); // 使用 spread 运算符将数组作为参数列表传递 -func.apply(context, args); // 与使用 apply 相同 -*/!* -``` - -如果我们仔细观察,那么 `call` 和 `apply` 的使用会有一些细微的差别。 - -- 扩展运算符 `...` 允许将 **可迭代的** `参数列表` 作为列表传递给 `call`。 -- `apply` 只接受 **类似数组一样的** `参数列表`。 - -所以,这些调用方式相互补充。我们期望有一个可迭代的 `call` 实现,我们也期望有一个类似数组,`apply` 的实现。 - -如果 `参数列表` 既可迭代又像数组一样,就像真正的数组一样,那么我们在技术上可以使用它们中的任何一个,但是 `apply` 可能会更快,因为它只是一个操作。大多数 JavaScript 引擎内部优化比一对 `call + spread` 更好。 - -`apply` 最重要的用途之一是将调用传递给另一个函数,如下所示: - -```js -let wrapper = function() { - return anotherFunction.apply(this, arguments); -}; -``` - -这叫做 **呼叫转移**。`wrapper` 传递它获得的所有内容:上下文 `this` 和 `anotherFunction` 的参数并返回其结果。 - -当外部代码调用这样的 `wrapper` 时,它与原始函数的调用无法区分。 - -现在让我们把它全部加入到更强大的 `cachingDecorator` 中: +Here's a more powerful `cachingDecorator`: ```js run let worker = { @@ -340,7 +259,7 @@ function cachingDecorator(func, hash) { } *!* - let result = func.apply(this, arguments); // (**) + let result = func.call(this, ...arguments); // (**) */!* cache.set(key, result); @@ -358,17 +277,58 @@ alert( worker.slow(3, 5) ); // works alert( "Again " + worker.slow(3, 5) ); // same (cached) ``` -现在,包装器可以使用任意数量的参数进行操作。 +Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below). -这里有两个变化: +There are two changes: -- 在 `(*)` 行中它调用 `hash` 来从 `arguments` 创建一个单独的键。这里我们使用一个简单的 “连接” 函数,将参数 `(3, 5)` 转换为键 “3,5”。更复杂的情况可能需要其他散列函数。 -- 然后 `(**)` 使用 `func.apply` 传递上下文和包装器获得的所有参数(无论多少)到原始函数。 +- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions. +- Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function. +## func.apply -## 借用一种方法 [#method-borrowing] +Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`. -现在让我们在散列函数中做一个小改进: +The syntax of built-in method [func.apply](mdn:js/Function/apply) is: + +```js +func.apply(context, args) +``` + +It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments. + +The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them. + +So these two calls are almost equivalent: + +```js +func.call(context, ...args); +func.apply(context, args); +``` + +They perform the same call of `func` with given context and arguments. + +There's only a subtle difference regarding `args`: + +- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. +- The `apply` accepts only *array-like* `args`. + +...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. + +Passing all arguments along with the context to another function is called *call forwarding*. + +That's the simplest form of it: + +```js +let wrapper = function() { + return func.apply(this, arguments); +}; +``` + +When an external code calls such `wrapper`, it is indistinguishable from the call of the original function `func`. + +## Borrowing a method [#method-borrowing] + +Now let's make one more minor improvement in the hashing function: ```js function hash(args) { @@ -376,9 +336,9 @@ function hash(args) { } ``` -截至目前,它仅适用于两个参数。如果它可以适配任何数量的 `args` 会更好。 +As of now, it works only on two arguments. It would be better if it could glue any number of `args`. -自然的解决方案是使用 [arr.join](mdn:js/Array/join) 函数: +The natural solution would be to use [arr.join](mdn:js/Array/join) method: ```js function hash(args) { @@ -386,21 +346,21 @@ function hash(args) { } ``` -...不幸的是,那不行。虽然我们调用 `hash(arguments)` 和 `arguments` 对象,它既可迭代又像数组一样,但它并不是真正的数组。 +...Unfortunately, that won't work. Because we are calling `hash(arguments)`, and `arguments` object is both iterable and array-like, but not a real array. -所以在它上面调用 `join` 会失败,我们可以在下面看到: +So calling `join` on it would fail, as we can see below: ```js run function hash() { *!* - alert( arguments.join() ); // 报错:arguments.join 不是函数 + alert( arguments.join() ); // Error: arguments.join is not a function */!* } hash(1, 2); ``` -不过,有一种简单的方法可以使用数组的 join 方法: +Still, there's an easy way to use array join: ```js run function hash() { @@ -412,48 +372,55 @@ function hash() { hash(1, 2); ``` -这个技巧被称为 **方法借用**。 +The trick is called *method borrowing*. + +We take (borrow) a join method from a regular array (`[].join`) and use `[].join.call` to run it in the context of `arguments`. + +Why does it work? -我们从常规数组 `[].join` 中获取(借用)连接方法。并使用 `[].join.call` 在 `arguments` 的上下文中运行它。 +That's because the internal algorithm of the native method `arr.join(glue)` is very simple. -它为什么有效? +Taken from the specification almost "as-is": -那是因为本地方法 `arr.join(glue)` 的内部算法非常简单。 +1. Let `glue` be the first argument or, if no arguments, then a comma `","`. +2. Let `result` be an empty string. +3. Append `this[0]` to `result`. +4. Append `glue` and `this[1]`. +5. Append `glue` and `this[2]`. +6. ...Do so until `this.length` items are glued. +7. Return `result`. -从规范中得出几乎“原样”: +So, technically it takes `this` and joins `this[0]`, `this[1]` ...etc together. It's intentionally written in a way that allows any array-like `this` (not a coincidence, many methods follow this practice). That's why it also works with `this=arguments`. -1. 让 `glue` 成为第一个参数,如果没有参数,则使用逗号 `","`。 -2. 让 `result` 为空字符串。 -3. 将 `this[0]` 附加到 `result`。 -4. 附加 `glue` 和 `this[1]`。 -5. 附加 `glue` 和 `this[2]`。 -6. ...直到 `this.length` 项目粘在一起。 -7. 返回 `result`。 +## Decorators and function properties -因此,从技术上讲,它需要 `this` 并将 `this[0]`,`this[1]` ...... 等加在一起。它的编写方式是允许任何类似数组的 `this`(不是巧合,许多方法遵循这种做法)。这就是为什么它也适用于 `this=arguments`。 +It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them. -## 总结 +E.g. in the example above if `slow` function had any properties on it, then `cachingDecorator(slow)` is a wrapper without them. -**装饰器**是一个改变函数行为的包装器。主要工作仍由该函数来完成。 +Some decorators may provide their own properties. E.g. a decorator may count how many times a function was invoked and how much time it took, and expose this information via wrapper properties. -除了一件小事,使用装饰器来替代函数或方法通常都是安全的。如果原始函数具有属性,例如 `func.calledCount` 或者其他什么,则装饰的函数将不提供它们。因为那是一个包装器。因此,如果使用它们,需要小心。一些装饰器提供它们自己的属性。 +There exists a way to create decorators that keep access to function properties, but this requires using a special `Proxy` object to wrap a function. We'll discuss it later in the article . -装饰器可以被视为可以添加到函数中的“特征”或“方面”。我们可以添加一个或添加许多。而这一切都没有改变它的代码! +## Summary -为了实现 `cachingDecorator`,我们研究了方法: +*Decorator* is a wrapper around a function that alters its behavior. The main job is still carried out by the function. -- [func.call(context, arg1, arg2...)](mdn:js/Function/call) —— 用给定的上下文和参数调用 `func`。 -- [func.apply(context, args)](mdn:js/Function/apply) —— 调用 `func` 将 `context` 作为 `this` 和类似数组的 `args` 传递给参数列表。 +Decorators can be seen as "features" or "aspects" that can be added to a function. We can add one or add many. And all this without changing its code! -通用 **呼叫转移** 通常使用 `apply` 完成: +To implement `cachingDecorator`, we studied methods: + +- [func.call(context, arg1, arg2...)](mdn:js/Function/call) -- calls `func` with given context and arguments. +- [func.apply(context, args)](mdn:js/Function/apply) -- calls `func` passing `context` as `this` and array-like `args` into a list of arguments. + +The generic *call forwarding* is usually done with `apply`: ```js let wrapper = function() { return original.apply(this, arguments); -} +}; ``` -当我们从一个对象中获取一个方法并在另一个对象的上下文中“调用”它时,我们也看到了一个 **方法借用** 的例子。采用数组方法并将它们应用于参数是很常见的。另一种方法是使用静态参数对象,它是一个真正的数组。 - +We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array. -在 js 领域里有很多装饰器的使用方法 。快通过解决本章的任务来检查你掌握它们的程度吧。 +There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg index 7092d120e8..9b63cb982b 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg +++ b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg @@ -1,64 +1 @@ - - - - decorator-makecaching-wrapper.svg - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - wrapper - - - around the function - - - - - \ No newline at end of file +wrapperaround the function \ No newline at end of file diff --git a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md index 79904dce00..737a14481f 100644 --- a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md @@ -1,4 +1,4 @@ -答案:`null`。 +The answer: `null`. ```js run @@ -13,6 +13,6 @@ let user = { user.g(); ``` -边界函数的上下文是硬绑定的。没有办法继续修改。 +The context of a bound function is hard-fixed. There's just no way to further change it. -所以即使我们执行 `user.g()`,源方法调用时还是 `this=null`。 +So even while we run `user.g()`, the original function is called with `this=null`. diff --git a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md index 6204673d09..6d7e1fb245 100644 --- a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 作为方法的边界函数 +# Bound function as a method -输出将会是什么? +What will be the output? ```js function f() { diff --git a/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md b/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md index f6353b3fab..97e1c28098 100644 --- a/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md @@ -1,4 +1,4 @@ -答案:**John**. +The answer: **John**. ```js run no-beautify function f() { @@ -10,6 +10,6 @@ f = f.bind( {name: "John"} ).bind( {name: "Pete"} ); f(); // John ``` -`f.bind(...)` 返回的外来的 [绑定函数](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects) 对象仅在创建的时候记忆上下文(如果提供了参数)。 +The exotic [bound function](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects) object returned by `f.bind(...)` remembers the context (and arguments if provided) only at creation time. -一个函数不能作为重复边界。 +A function cannot be re-bound. diff --git a/1-js/06-advanced-functions/10-bind/3-second-bind/task.md b/1-js/06-advanced-functions/10-bind/3-second-bind/task.md index 7b79030db7..5daf053c65 100644 --- a/1-js/06-advanced-functions/10-bind/3-second-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/3-second-bind/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 二次 bind +# Second bind -我们可以通过附加的 bind 改变 `this` 吗? +Can we change `this` by additional binding? -输出将会是什么? +What will be the output? ```js no-beautify function f() { diff --git a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md index 72e1bc0fdb..181555d95f 100644 --- a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md @@ -1,4 +1,4 @@ -答案:`undefined`. +The answer: `undefined`. -`bind` 的结果是另一个对象,它并没有 `test` 属性。 +The result of `bind` is another object. It does not have the `test` property. diff --git a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md index 29e4d0555f..d6cfb44bf8 100644 --- a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# bind 过后的函数属性 +# Function property after bind -函数有一个包含某个值的属性。`bind` 之后它会改变吗?为什么,阐述一下? +There's a value in the property of a function. Will it change after `bind`? Why, or why not? ```js run function sayHi() { @@ -17,7 +17,7 @@ let bound = sayHi.bind({ name: "John" }); -alert( bound.test ); // 输出将会是什么?为什么? +alert( bound.test ); // what will be the output? why? */!* ``` 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 7417358078..403107ca68 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,9 +1,9 @@ -发生了错误是因为 `ask` 的参数是没有绑定对象的 `loginOk/loginFail` 函数。 +The error occurs because `ask` gets functions `loginOk/loginFail` without the object. -当它调用这两个函数,它们自然的会认定 `this=undefined`。 +When it calls them, they naturally assume `this=undefined`. -让我们 `bind` 上下文: +Let's `bind` the context: ```js run function askPassword(ok, fail) { @@ -30,14 +30,14 @@ askPassword(user.loginOk.bind(user), user.loginFail.bind(user)); */!* ``` -现在它能正常工作了。 +Now it works. -另一个可以用来替换的解决办法是: +An alternative solution could be: ```js //... askPassword(() => user.loginOk(), () => user.loginFail()); ``` -通常情况下它也能正常运行,但是可能会在更复杂的场景下失效,例如在 asking 到运行 `() => user.loginOk()` 之间,`user` 可能会被重写。 - +Usually that also works and looks good. +It's a bit less reliable though in more complex situations where `user` variable might change *after* `askPassword` is called, but *before* the visitor answers and calls `() => user.loginOk()`. diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md index c4759ee016..fe6a9b4eb9 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# 为什么 this 会丢失 +# Fix a function that loses "this" -下面代码中对 `askPassword()` 的调用将会检查密码然后基于结果调用 `user.loginOk/loginFail`。 +The call to `askPassword()` in the code below should check the password and then call `user.loginOk/loginFail` depending on the answer. -但是它导致了一个错误。为什么? +But it leads to an error. Why? -修改高亮的行来让一切开始正常运行(其它行不用修改)。 +Fix the highlighted line for everything to start working right (other lines are not to be changed). ```js run function askPassword(ok, fail) { @@ -34,5 +34,3 @@ let user = { askPassword(user.loginOk, user.loginFail); */!* ``` - - diff --git a/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md index a82ec77e14..3284c943b0 100644 --- a/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md +++ b/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md @@ -1,16 +1,16 @@ - - -1. 使用封装函数,箭头函数很简洁 - - ```js - askPassword(() => user.login(true), () => user.login(false)); - ``` - - 现在它从外部变量中获得 `user`,正常运行。 - -2. 从 `user.login` 中创建偏函数,使用 `user` 作为上下文,并确定第一个参数: - - - ```js - askPassword(user.login.bind(user, true), user.login.bind(user, false)); - ``` + + +1. Either use a wrapper function, an arrow to be concise: + + ```js + askPassword(() => user.login(true), () => user.login(false)); + ``` + + Now it gets `user` from outer variables and runs it the normal way. + +2. Or create a partial function from `user.login` that uses `user` as the context and has the correct first argument: + + + ```js + askPassword(user.login.bind(user, true), user.login.bind(user, false)); + ``` diff --git a/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md index aaf92e708b..c90851c2bd 100644 --- a/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md +++ b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md @@ -1,33 +1,34 @@ -importance: 5 - ---- - -# 偏函数在登录中的应用 - -这个任务是比 略微复杂的变体。 - -`user` 对象被修改了。现在不是两个函数 `loginOk/loginFail`,现在只有一个函数 `user.login(true/false)`。 - -以下代码中,向 `askPassword` 传入什么参数,使得 `user.login(true)` 结果是 `ok`,`user.login(fasle)` 结果是 `fail`? - -```js -function askPassword(ok, fail) { - let password = prompt("Password?", ''); - if (password == "rockstar") ok(); - else fail(); -} - -let user = { - name: 'John', - - login(result) { - alert( this.name + (result ? ' logged in' : ' failed to log in') ); - } -}; - -*!* -askPassword(?, ?); // ? -*/!* -``` - -你只能更改高亮部分代码。 +importance: 5 + +--- + +# Partial application for login + +The task is a little more complex variant of . + +The `user` object was modified. Now instead of two functions `loginOk/loginFail`, it has a single function `user.login(true/false)`. + +What should we pass `askPassword` in the code below, so that it calls `user.login(true)` as `ok` and `user.login(false)` as `fail`? + +```js +function askPassword(ok, fail) { + let password = prompt("Password?", ''); + if (password == "rockstar") ok(); + else fail(); +} + +let user = { + name: 'John', + + login(result) { + alert( this.name + (result ? ' logged in' : ' failed to log in') ); + } +}; + +*!* +askPassword(?, ?); // ? +*/!* +``` + +Your changes should only modify the highlighted fragment. + diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 991d9c487c..9d705cdcdf 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -3,17 +3,17 @@ libs: --- -# 函数绑定 +# Function binding -和对象方法或者和传递对象方法一起使用 `setTimeout` 时,有一个很常见的问题:“`this` 丢失”。 +When passing object methods as callbacks, for instance to `setTimeout`, there's a known problem: "losing `this`". -突然,`this` 就停止正常运作了。这种情况在开发的初学者中很典型,但有时也会出现在有经验开发者的代码中。 +In this chapter we'll see the ways to fix it. -## 丢失 "this" +## Losing "this" -我们已经知道,在 JavaScript 中,`this` 很容易就会丢失。一旦一个方法被传递到另一个与对象分离的地方 —— `this` 就丢失了。 +We've already seen examples of losing `this`. Once a method is passed somewhere separately from the object -- `this` is lost. -下面是使用 `setTimeout` 时 `this` 时如何丢失的: +Here's how it may happen with `setTimeout`: ```js run let user = { @@ -28,22 +28,22 @@ setTimeout(user.sayHi, 1000); // Hello, undefined! */!* ``` -正如我们看到的那样,`this.firstName` 不是输出为 "John",而是 `undefined`! +As we can see, the output shows not "John" as `this.firstName`, but `undefined`! -这是因为 `setTimeout` 获取到了函数 `user.sayHi`,但它和对象分离开了。最后一行可以写为: +That's because `setTimeout` got the function `user.sayHi`, separately from the object. The last line can be rewritten as: ```js let f = user.sayHi; -setTimeout(f, 1000); // 用户上下文丢失 +setTimeout(f, 1000); // lost user context ``` -浏览器中的方法 `setTimeout` 有些特殊:它为函数的调用设定了 `this=window`(对于 Node.JS,`this` 则会成为时间对象,但其实 this 到底变成什么并不十分重要)。所以对于 `this.firstName` 它其实试图获取的是 `window.firstName`,这个变量并不存在。在其他一些类似的情况下,通常 `this` 就会成为 `undefined`。 +The method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.js, `this` becomes the timer object, but doesn't really matter here). So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases, usually `this` just becomes `undefined`. -这个需求很典型——我们希望将一个对象的方法传递到别的地方(这里——是为了调度程序)然后调用。如何确保它将会在正确的上下文中被调用呢? +The task is quite typical -- we want to pass an object method somewhere else (here -- to the scheduler) where it will be called. How to make sure that it will be called in the right context? -## 解决方案 1:包装层 +## Solution 1: a wrapper -最简单的解决方案就是使用一个包装函数: +The simplest solution is to use a wrapping function: ```js run let user = { @@ -60,17 +60,17 @@ setTimeout(function() { */!* ``` -现在它可以正常工作了,因为它从外部词法环境中获取到了 `user`,就可以正常的调用方法了。 +Now it works, because it receives `user` from the outer lexical environment, and then calls the method normally. -相同的功能,但是更简短: +The same, but shorter: ```js setTimeout(() => user.sayHi(), 1000); // Hello, John! ``` -看起来不错,但是代码结构看上去有一些漏洞。 +Looks fine, but a slight vulnerability appears in our code structure. -如果在 `setTimeout` 触发之前(一个一秒的延迟)`user` 就改变了值又会怎么样呢?那么,突然间,函数就会被的错误地调用。 +What if before `setTimeout` triggers (there's one second delay!) `user` changes value? Then, suddenly, it will call the wrong object! ```js run @@ -83,31 +83,32 @@ let user = { setTimeout(() => user.sayHi(), 1000); -// ...在一秒之内 -user = { sayHi() { alert("Another user in setTimeout!"); } }; +// ...the value of user changes within 1 second +user = { + sayHi() { alert("Another user in setTimeout!"); } +}; -// 在 setTimeout 中是另外一个 user 了?!? +// Another user in setTimeout! ``` -下一个解决方案保证了这样的事情不会发生。 +The next solution guarantees that such thing won't happen. -## 解决方案 2:bind +## Solution 2: bind -函数对象提供了一个内建方法 [bind](mdn:js/Function/bind),它可以固定住 `this`。 +Functions provide a built-in method [bind](mdn:js/Function/bind) that allows to fix `this`. -基本的语法是: +The basic syntax is: ```js -// 稍后将会有更复杂的语法 +// more complex syntax will come a little later let boundFunc = func.bind(context); -```` - -`func.bind(context)` 的结果是一个特殊的像函数一样的“外来对象”,它可以像函数一样被调用并且透明的将调用传递给 `func` 并设置 `this=context`。 +``` +The result of `func.bind(context)` is a special function-like "exotic object", that is callable as function and transparently passes the call to `func` setting `this=context`. -换句话说,调用 `boundFunc` 就像是调用 `func` 并且固定住了 `this`。 +In other words, calling `boundFunc` is like `func` with fixed `this`. -举个例子,这里 `funcUser` 将调用传递给了 `func` 同时 `this=user`: +For instance, here `funcUser` passes a call to `func` with `this=user`: ```js run let user = { @@ -124,9 +125,9 @@ funcUser(); // John */!* ``` -这里 `func.bind(user)` 作为 `func` 的“边界变量”,同时固定了 `this=user`。 +Here `func.bind(user)` as a "bound variant" of `func`, with fixed `this=user`. -所有的参数都会被传递给初始的 `func`,就像本来就是调用了它一样,例如: +All arguments are passed to the original `func` "as is", for instance: ```js run let user = { @@ -137,15 +138,15 @@ function func(phrase) { alert(phrase + ', ' + this.firstName); } -// 将 this 绑定给 user +// bind this to user let funcUser = func.bind(user); *!* -funcUser("Hello"); // Hello, John(参数 "Hello" 被传递了,并且 this=user) +funcUser("Hello"); // Hello, John (argument "Hello" is passed, and this=user) */!* ``` -下面我们来尝试一个对象的方法: +Now let's try with an object method: ```js run @@ -160,14 +161,21 @@ let user = { let sayHi = user.sayHi.bind(user); // (*) */!* +// can run it without an object sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! + +// even if the value of user changes within 1 second +// sayHi uses the pre-bound value which is reference to the old user object +user = { + sayHi() { alert("Another user in setTimeout!"); } +}; ``` -在 `(*)` 之间的行中,我们取得了方法 `user.sayHi` 然后将它和 `user` 绑定。`sayHi` 是一个“边界”方法,它可以单独调用或者传递给 `setTimeout` —— 都没关系,函数上下文都将会是正确的。 +In the line `(*)` we take the method `user.sayHi` and bind it to `user`. The `sayHi` is a "bound" function, that can be called alone or passed to `setTimeout` -- doesn't matter, the context will be right. -这里我们能够看到参数都被像正常调用原函数一样被传递了进去,但是 `this` 被 `bind` 方法固定了: +Here we can see that arguments are passed "as is", only `this` is fixed by `bind`: ```js run let user = { @@ -179,12 +187,12 @@ let user = { let say = user.say.bind(user); -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) ``` ````smart header="Convenience method: `bindAll`" -如果一个对象有很多方法,并且我们都打算将它们传递出去使用,那么我们可以在一个循环中完成绑定: +If an object has many methods and we plan to actively pass it around, then we could bind them all in a loop: ```js for (let key in user) { @@ -194,11 +202,127 @@ for (let key in user) { } ``` -JavaScript 库同样提供了方法来便捷的批量绑定,例如 lodash 中的 [_.bindAll(obj)](http://lodash.com/docs#bindAll)。 +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) in lodash. ```` -## 总结 +## Partial functions + +Until now we have only been talking about binding `this`. Let's take it a step further. + +We can bind not only `this`, but also arguments. That's rarely done, but sometimes can be handy. + +The full syntax of `bind`: + +```js +let bound = func.bind(context, [arg1], [arg2], ...); +``` + +It allows to bind context as `this` and starting arguments of the function. + +For instance, we have a multiplication function `mul(a, b)`: + +```js +function mul(a, b) { + return a * b; +} +``` + +Let's use `bind` to create a function `double` on its base: + +```js run +function mul(a, b) { + return a * b; +} + +*!* +let double = mul.bind(null, 2); +*/!* + +alert( double(3) ); // = mul(2, 3) = 6 +alert( double(4) ); // = mul(2, 4) = 8 +alert( double(5) ); // = mul(2, 5) = 10 +``` + +The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is". + +That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one. + +Please note that we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. + +The function `triple` in the code below triples the value: + +```js run +function mul(a, b) { + return a * b; +} + +*!* +let triple = mul.bind(null, 3); +*/!* + +alert( triple(3) ); // = mul(3, 3) = 9 +alert( triple(4) ); // = mul(3, 4) = 12 +alert( triple(5) ); // = mul(3, 5) = 15 +``` + +Why do we usually make a partial function? + +The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide the first argument every time as it's fixed with `bind`. + +In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience. + +For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user. + +## Going partial without context + +What if we'd like to fix some arguments, but not the context `this`? For example, for an object method. + +The native `bind` does not allow that. We can't just omit the context and jump to arguments. + +Fortunately, a function `partial` for binding only arguments can be easily implemented. + +Like this: + +```js run +*!* +function partial(func, ...argsBound) { + return function(...args) { // (*) + return func.call(this, ...argsBound, ...args); + } +} +*/!* + +// Usage: +let user = { + firstName: "John", + say(time, phrase) { + alert(`[${time}] ${this.firstName}: ${phrase}!`); + } +}; + +// add a partial method with fixed time +user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); + +user.sayNow("Hello"); +// Something like: +// [10:00] John: Hello! +``` + +The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that calls `func` with: +- Same `this` as it gets (for `user.sayNow` call it's `user`) +- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`) +- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`) + +So easy to do it with the spread syntax, right? + +Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library. + +## Summary + +Method `func.bind(context, ...args)` returns a "bound variant" of function `func` that fixes the context `this` and first arguments if given. + +Usually we apply `bind` to fix `this` for an object method, so that we can pass it somewhere. For example, to `setTimeout`. -方法 `func.bind(context, ...args)` 返回了一个函数 `func` 的“边界变量”,它固定了上下文 `this` 和参数(如果给定了)。 +When we fix some arguments of an existing function, the resulting (less universal) function is called *partially applied* or *partial*. -通常我们应用 `bind` 来固定对象方法的 `this`,这样我们就可以把它们传递到其他地方使用。例如,传递给 `setTimeout`。在现代开发中,需要使用`bind`的原因有很多,我们接下来将会遇到它们的。 +Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it. 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 444d62b162..8730277ad7 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -1,26 +1,26 @@ -# 深入研究箭头函数 +# Arrow functions revisited -我们深入研究下箭头函数 +Let's revisit arrow functions. -箭头功能不仅仅是用来简写的快速方法。 +Arrow functions are not just a "shorthand" for writing small stuff. They have some very specific and useful features. -JavaScript 充满了需要编写一个小函数其他地方执行的情况。 +JavaScript is full of situations where we need to write a small function that's executed somewhere else. -例如: +For instance: -- `arr.forEach(func)` -- `forEach` 对每个数组元素执行 `func`。 -- `setTimeout(func)` -- `func` 由内置调度程序执行。 -- ......等等。 +- `arr.forEach(func)` -- `func` is executed by `forEach` for every array item. +- `setTimeout(func)` -- `func` is executed by the built-in scheduler. +- ...there are more. -JavaScript 的精髓在于创建一个函数并将其传递到某个地方。 +It's in the very spirit of JavaScript to create a function and pass it somewhere. -在这种函数依赖当前上下文。 +And in such functions we usually don't want to leave the current context. That's where arrow functions come in handy. -## 箭头功能没有 "this" +## Arrow functions have no "this" -正如我们从 一章所学的那样,箭头函数没有 `this`。如果访问 `this`,则从外部获取。 +As we remember from the chapter , arrow functions do not have `this`. If `this` is accessed, it is taken from the outside. -例如,我们可以用它在对象方法中迭代: +For instance, we can use it to iterate inside an object method: ```js run let group = { @@ -39,9 +39,9 @@ let group = { group.showList(); ``` -这里 `forEach` 中使用了箭头函数,所以 `this.title` 和外部方法 `showList` 完全一样。那就是:`group.title`。 +Here in `forEach`, the arrow function is used, so `this.title` in it is exactly the same as in the outer method `showList`. That is: `group.title`. -如果我们使用正常函数,则会出现错误: +If we used a "regular" function, there would be an error: ```js run let group = { @@ -52,7 +52,7 @@ let group = { *!* this.students.forEach(function(student) { // Error: Cannot read property 'title' of undefined - alert(this.title + ': ' + student) + alert(this.title + ': ' + student); }); */!* } @@ -61,33 +61,33 @@ let group = { group.showList(); ``` -报错是因为 `forEach` 默认情况下运行带 `this=undefined` 的函数,因此尝试访问是 `undefined.title`。 +The error occurs because `forEach` runs functions with `this=undefined` by default, so the attempt to access `undefined.title` is made. -但箭头函数就没事,因为它们没有`this`。 +That doesn't affect arrow functions, because they just don't have `this`. ```warn header="Arrow functions can't run with `new`" -不具有 `this` 自然意味着另一个限制:箭头函数不能用作构造函数。他们不能用 `new` 调用。 +Not having `this` naturally means another limitation: arrow functions can't be used as constructors. They can't be called with `new`. ``` ```smart header="Arrow functions VS bind" -箭头函数 `=>` 和正常函数通过 `.bind(this)` 调用有一个微妙的区别: +There's a subtle difference between an arrow function `=>` and a regular function called with `.bind(this)`: -- `.bind(this)` 创建该函数的 “绑定版本”。 -- 箭头函数 `=>` 不会创建任何绑定。该函数根本没有 `this`。在外部上下文中,`this` 的查找与普通变量搜索完全相同。 +- `.bind(this)` creates a "bound version" of the function. +- The arrow `=>` doesn't create any binding. The function simply doesn't have `this`. The lookup of `this` is made exactly the same way as a regular variable search: in the outer lexical environment. ``` -## 箭头函数没有 "arguments"(参数) +## Arrows have no "arguments" -箭头函数也没有 `arguments` 变量。 +Arrow functions also have no `arguments` variable. -因为我们需要用当前的 `this` 和 `arguments` 转发一个调用,所有这对于装饰者来说非常好。 +That's great for decorators, when we need to forward a call with the current `this` and `arguments`. -例如,`defer(f, ms)` 得到一个函数,并返回一个包装函数,以 `毫秒` 为单位延迟调用: +For instance, `defer(f, ms)` gets a function and returns a wrapper around it that delays the call by `ms` milliseconds: ```js run function defer(f, ms) { return function() { - setTimeout(() => f.apply(this, arguments), ms) + setTimeout(() => f.apply(this, arguments), ms); }; } @@ -96,10 +96,10 @@ function sayHi(who) { } let sayHiDeferred = defer(sayHi, 2000); -sayHiDeferred("John"); // 2 秒后打印 Hello, John +sayHiDeferred("John"); // Hello, John after 2 seconds ``` -没有箭头功能的情况如下所示: +The same without an arrow function would look like: ```js function defer(f, ms) { @@ -112,15 +112,15 @@ function defer(f, ms) { } ``` -在这里,我们必须创建额外的变量 `args` 和 `ctx`,以便 `setTimeout` 内部的函数可以接收它们。 +Here we had to create additional variables `args` and `ctx` so that the function inside `setTimeout` could take them. -## 总结 +## Summary -箭头函数: +Arrow functions: -- 没有 `this`。 -- 没有 `arguments`。 -- 不能使用 `new`。 -- (他们也没有 `super`,我们将在这一章 研究)。 +- Do not have `this` +- Do not have `arguments` +- Can't be called with `new` +- They also don't have `super`, but we didn't study it yet. We will on the chapter -所以它们适用于没有自己的“上下文”的短代码片段,比如在当前的代码大放光彩。 +That's because they are meant for short pieces of code that do not have their own "context", but rather work in the current one. And they really shine in that use case. diff --git a/1-js/06-advanced-functions/index.md b/1-js/06-advanced-functions/index.md index 95104abdfb..7800dcc424 100644 --- a/1-js/06-advanced-functions/index.md +++ b/1-js/06-advanced-functions/index.md @@ -1 +1 @@ -# 函数进阶内容 +# Advanced working with functions 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 dbb11c7e9a..bdc6934182 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -1,38 +1,40 @@ -# 属性的标志和描述符 +# Property flags and descriptors -我们知道,对象可以存储属性。 +As we know, objects can store properties. -到目前为止,属性对我们来说是一个简单的“键-值”对。但对象属性实际上是更复杂可变的东西。 +Until now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing. -## 属性的标志 +In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions. -对象属性除 **`value`** 外还有三个特殊属性(所谓的“标志”): +## Property flags -- **`writable`** — 如果为 `true`,则可以修改,否则它是只读的。 -- **`enumerable`** — 如果是 `true`,则可在循环中列出,否则不列出。 -- **`configurable`** — 如果是 `true`,则此属性可以被删除,相应的特性也可以被修改,否则不可以。 +Object properties, besides a **`value`**, have three special attributes (so-called "flags"): -我们还没看到它们,是因为它们通常不会出现当我们用“常用的方式”创建一个属性时,它们都是 `true`。但我们也可以随时更改它们。 +- **`writable`** -- if `true`, the value can be changed, otherwise it's read-only. +- **`enumerable`** -- if `true`, then listed in loops, otherwise not listed. +- **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not. -首先,让我们看看如何获得这些标志。 +We didn't see them yet, because generally they do not show up. When we create a property "the usual way", all of them are `true`. But we also can change them anytime. -[Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) 方法允许查询有关属性的**完整**信息。 +First, let's see how to get those flags. -语法是: +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. + +The syntax is: ```js let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); ``` `obj` -: 需要获取信息的对象。 +: The object to get information from. `propertyName` -: 属性的名称。 +: The name of the property. -返回值是一个所谓的“属性描述符”对象:它包含值和所有的标志。 +The returned value is a so-called "property descriptor" object: it contains the value and all the flags. -例如: +For instance: ```js run let user = { @@ -52,23 +54,23 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -为了修改标志,我们可以使用 [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). -语法是: +The syntax is: ```js Object.defineProperty(obj, propertyName, descriptor) ``` -`obj`,`propertyName` -: 要处理的对象和属性。 +`obj`, `propertyName` +: The object and its property to apply the descriptor. `descriptor` -: 要应用的属性描述符。 +: Property descriptor object to apply. -如果该属性存在,则 `defineProperty` 更新其标志。否则,它会创建具有给定值和标志的属性;在这种情况下,如果没有提供标志,则会假定它是 `false`。 +If the property exists, `defineProperty` updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed `false`. -这里使用所有的伪造标志创建一个属性 `name`: +For instance, here a property `name` is created with all falsy flags: ```js run let user = {}; @@ -94,13 +96,13 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -将它与上面的“以常用方式创建的” `user.name` 进行比较:现在所有标志都是假定的。如果这不是我们想要的,那么我们最好在 `descriptor` 中将它们设置为 `true`。 +Compare it with "normally created" `user.name` above: now all flags are falsy. If that's not what we want then we'd better set them to `true` in `descriptor`. -现在让我们通过示例来看看标志的效果。 +Now let's see effects of the flags by example. -## 只读 +## Non-writable -我们通过修改 `writable` 标志来把 `user.name` 设置为只读: +Let's make `user.name` non-writable (can't be reassigned) by changing `writable` flag: ```js run let user = { @@ -114,36 +116,39 @@ Object.defineProperty(user, "name", { }); *!* -user.name = "Pete"; // 错误:不能设置只读属性'name'... +user.name = "Pete"; // Error: Cannot assign to read only property 'name' */!* ``` -现在没有人可以改变我们的用户名称,除非他用自己的 `defineProperty` 来覆盖我们的用户。 +Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. + +```smart header="Errors appear only in strict mode" +In the 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. +``` -以下是相同的操作,但针对的是属性不存在的情况: +Here's the same example, but the property is created from scratch: ```js run let user = { }; Object.defineProperty(user, "name", { *!* - value: "Pete", - // 对于新的属性,需要明确的列出哪些为 true + value: "John", + // for new properties we need to explicitly list what's true enumerable: true, configurable: true */!* }); -alert(user.name); // Pete -user.name = "Alice"; // Error +alert(user.name); // John +user.name = "Pete"; // Error ``` +## Non-enumerable -## 不可枚举 +Now let's add a custom `toString` to `user`. -现在让我们向 `user` 添加一个自定义的 `toString`。 - -通常,对象的内置 `toString` 是不可枚举的,它不会显示在 `for..in` 中。但是如果我们添加我们自己的 `toString`,那么默认情况下它将显示在 `for..in` 中,如下所示: +Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add a `toString` of our own, then by default it shows up in `for..in`, like this: ```js run let user = { @@ -153,11 +158,11 @@ let user = { } }; -// 默认情况下,我们的两个属性都会列出: +// By default, both our properties are listed: for (let key in user) alert(key); // name, toString ``` -如果我们不喜欢它,那么我们可以设置 `enumerable:false`。然后它不会出现在 `for..in` 循环中,就像内置循环一样: +If we don't like it, then we can set `enumerable:false`. Then it won't appear in a `for..in` loop, just like the built-in one: ```js run let user = { @@ -174,24 +179,24 @@ Object.defineProperty(user, "toString", { }); *!* -// 现在 toString 消失了: +// Now our toString disappears: */!* for (let key in user) alert(key); // name ``` -不可枚举的属性也会从 `Object.keys` 中排除: +Non-enumerable properties are also excluded from `Object.keys`: ```js alert(Object.keys(user)); // name ``` -## 不可配置 +## Non-configurable -不可配置标志(`configurable:false`)有时会预设在内置对象和属性中。 +The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties. -一个不可配置的属性不能被 `defineProperty` 删除或修改。 +A non-configurable property can't be deleted, its attributes can't be modified. -例如,`Math.PI` 是只读的、不可枚举和不可配置的: +For instance, `Math.PI` is non-writable, non-enumerable and non-configurable: ```js run let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI'); @@ -206,46 +211,72 @@ alert( JSON.stringify(descriptor, null, 2 ) ); } */ ``` -因此,开发人员无法改变 `Math.PI` 的值或覆盖它。 +So, a programmer is unable to change the value of `Math.PI` or overwrite it. ```js run -Math.PI = 3; // 错误 +Math.PI = 3; // Error, because it has writable: false + +// delete Math.PI won't work either +``` + +We also can't change `Math.PI` to be `writable` again: -// 删除 Math.PI 也不会起作用 +```js run +// Error, because of configurable: false +Object.defineProperty(Math, "PI", { writable: true }); ``` -使属性不可配置是一条单行道。我们不能把它改回去,因为 `defineProperty` 不适用于不可配置的属性。 +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`. -在这里,我们将 user.name 设置为“永久封闭”的常量: +**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 = { }; +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 }); -*!* -// 不能修改 user.name 或 它的标志 -// 下面的所有操作都不起作用: -// user.name = "Pete" -// delete user.name -// defineProperty(user, "name", ...) -Object.defineProperty(user, "name", {writable: true}); // 错误 -*/!* +// 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="只在使用严格模式时才会出现错误" -在非严格模式下,写入只读属性等时不会发生错误。但操作仍然不会成功。非严格模式下违反标志的行为只是默默地被忽略。 +```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. ``` ## Object.defineProperties -有一个方法 [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. -语法是: +The syntax is: ```js Object.defineProperties(obj, { @@ -255,7 +286,7 @@ Object.defineProperties(obj, { }); ``` -例如: +For instance: ```js Object.defineProperties(user, { @@ -265,19 +296,19 @@ Object.defineProperties(user, { }); ``` -因此,我们可以一次性设置多个属性。 +So, we can set many properties at once. ## Object.getOwnPropertyDescriptors -要一次获取所有属性描述符,我们可以使用 [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). -与 `Object.defineProperties` 一起,它可以用作克隆对象的“标志感知”方式: +Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object: ```js let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj)); ``` -通常,当我们克隆一个对象时,我们使用赋值的方式来复制属性,如下所示: +Normally when we clone an object, we use an assignment to copy properties, like this: ```js for (let key in user) { @@ -285,34 +316,34 @@ for (let key in user) { } ``` -...但是,这并不能复制标志。所以如果我们想要一个“更好”的克隆,那么 `Object.defineProperties` 是首选。 +...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred. -另一个区别是 `for..in` 忽略了 symbolic 属性,但是 `Object.getOwnPropertyDescriptors` 返回包含 symbolic 属性在内的**所有**属性描述符。 +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. -## 设定一个全局的封闭对象 +## Sealing an object globally -属性描述符可以在各个属性的级别上工作。 +Property descriptors work at the level of individual properties. -还有一些限制访问**整个**对象的方法: +There are also methods that limit access to the *whole* object: -[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) -: 禁止向对象添加属性。 +[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)](mdn:js/Object/seal) -: 禁止添加/删除属性,为所有现有的属性设置 `configurable: false`。 +[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)](mdn:js/Object/freeze) -: 禁止添加/删除/更改属性,为所有现有属性设置 `configurable: false, writable: false`。 +[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. -还有对他们的测试: +And also there are tests for them: -[Object.isExtensible(obj)](mdn:js/Object/isExtensible) -: 如果添加属性被禁止,则返回 `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)](mdn:js/Object/isSealed) -: 如果禁止添加/删除属性,则返回 `true`,并且所有现有属性都具有 `configurable: false`。 +[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)](mdn:js/Object/isFrozen) -: 如果禁止添加/删除/更改属性,并且所有当前属性都是 `configurable: false, writable: false`,则返回 `true`。 +[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`. -这些方法在实践中很少使用。 +These methods are rarely used in practice. 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 f43cc586fa..c2aa35d53a 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -1,15 +1,15 @@ -# 属性的 getter 和 setter +# Property getters and setters -有两种类型的属性 +There are two kinds of object properties. -第一种是**数据属性**。我们已经知道如何使用它们。实际上,我们迄今为止使用的所有属性都是数据属性。 +The first kind is *data properties*. We already know how to work with them. All properties that we've been using until now were data properties. -第二种类型的属性是新东西。它是 **访问器属性(accessor properties)**。它们本质上是获取和设置值的函数,但从外部代码来看像常规属性。 +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. -## Getter 和 setter +## Getters and setters -访问器属性由 "getter" 和 "setter" 方法表示。在对象字面量中,它们用 `get` 和 `set` 表示: +Accessor properties are represented by "getter" and "setter" methods. In an object literal they are denoted by `get` and `set`: ```js let obj = { @@ -23,18 +23,18 @@ let obj = { }; ``` -当读取 `obj.propName` 时,使用 getter,当设置值时,使用 setter。 +The getter works when `obj.propName` is read, the setter -- when it is assigned. -例如,我们有一个具有 `name` 和 `surname` 属性的 `user` 对象: +For instance, we have a `user` object with `name` and `surname`: -```js run +```js let user = { name: "John", surname: "Smith" }; ``` -现在我们要添加一个 "fullName" 属性,该属性是 "John Smith"。当然,我们不想复制粘贴现有信息,因此我们可以用访问器来实现: +Now we want to add a `fullName` property, that should be `"John Smith"`. Of course, we don't want to copy-paste existing information, so we can implement it as an accessor: ```js run let user = { @@ -53,11 +53,23 @@ alert(user.fullName); // John Smith */!* ``` -从外表看,访问器属性看起来像一个普通的属性。这是访问器属性的设计思想。我们不以函数的方式**调用** `user.fullName`,我们通常**读取**它:getter 在幕后运行。 +From the outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. + +As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error: + +```js run +let user = { + get fullName() { + return `...`; + } +}; -截至目前,`fullName` 只有一个 getter。如果我们尝试赋值操作 `user.fullName=`,将会出现错误。 +*!* +user.fullName = "Test"; // Error (property has only a getter) +*/!* +``` -我们通过为 `user.fullName` 添加一个 setter 来修复它: +Let's fix it by adding a setter for `user.fullName`: ```js run let user = { @@ -82,31 +94,22 @@ alert(user.name); // Alice alert(user.surname); // Cooper ``` -现在我们有一个“虚拟”属性。它是可读写的,但实际上并不存在。 - -```smart header="访问器属性只能访问 get/set" -属性可以是“数据属性”或“访问器属性”,但不能同时属于两者。 - -一旦使用 `get prop()` 或 `set prop()` 定义了一个属性,它就是一个访问器属性。所以必须有一个getter来读取它,如果我们对它赋值,它必须是一个 setter。 - -有时候只有一个 setter 或者只有一个 getter 是正常的。但在这种情况下,该属性将不可读或可写。 -``` - +As the result, we have a "virtual" property `fullName`. It is readable and writable. -## 访问器描述符 +## Accessor descriptors -访问器属性的描述符与数据属性相比是不同的。 +Descriptors for accessor properties are different from those for data properties. -对于访问器属性,没有 `value` 和 `writable`,但是有 `get` 和 `set` 函数。 +For accessor properties, there is no `value` or `writable`, but instead there are `get` and `set` functions. -所以访问器描述符可能有: +That is, an accessor descriptor may have: -- **`get`** —— 一个没有参数的函数,在读取属性时工作, -- **`set`** —— 带有一个参数的函数,当属性被设置时调用, -- **`enumerable`** —— 与数据属性相同, -- **`configurable`** —— 与数据属性相同。 +- **`get`** -- a function without arguments, that works when a property is read, +- **`set`** -- a function with one argument, that is called when the property is set, +- **`enumerable`** -- same as for data properties, +- **`configurable`** -- same as for data properties. -例如,要使用 `defineProperty` 创建 `fullName` 的访问器,我们可以使用 `get` 和 `set` 来传递描述符: +For instance, to create an accessor `fullName` with `defineProperty`, we can pass a descriptor with `get` and `set`: ```js run let user = { @@ -131,9 +134,9 @@ alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ``` -请再次注意,属性可以要么是访问器,要么是数据属性,而不能两者都是。 +Please note that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. -如果我们试图在同一个描述符中提供 `get` 和 `value`,则会出现错误: +If we try to supply both `get` and `value` in the same descriptor, there will be an error: ```js run *!* @@ -148,11 +151,11 @@ Object.defineProperty({}, 'prop', { }); ``` -## 更聪明的 getter/setters +## Smarter getters/setters -Getter/setter 可以用作“真实”属性值的包装器,以便对它们进行更多的控制。 +Getters/setters can be used as wrappers over "real" property values to gain more control over operations with them. -例如,如果我们想禁止为 `user` 设置太短的名称,我们可以将 `name` 存储在一个特殊的 `_name` 属性中。并在 setter 中过滤赋值操作: +For instance, if we want to forbid too short names for `user`, we can have a setter `name` and keep the value in a separate property `_name`: ```js run let user = { @@ -175,13 +178,16 @@ alert(user.name); // Pete user.name = ""; // Name is too short... ``` -从技术上讲,外部代码仍然可以通过使用 `user._name` 直接访问该名称。但是有一个众所周知的协议,即以下划线 `"_"` 开头的属性是内部的,不应该从对象外部访问。 +So, the name is stored in `_name` property, and the access is done via getter and setter. + +Technically, external code is able to access the name directly by using `user._name`. But there is a widely known convention that properties starting with an underscore `"_"` are internal and should not be touched from outside the object. -## 兼容性 -getter 和 setter 背后的伟大设计思想之一 —— 它们允许控制“正常”数据属性并随时调整它。 +## Using for compatibility -例如,我们开始使用数据属性 `name` 和 `age` 来实现用户对象: +One of the great uses of accessors is that they allow to take control over a "regular" data property at any moment by replacing it with a getter and a setter and tweak its behavior. + +Imagine we started implementing user objects using data properties `name` and `age`: ```js function User(name, age) { @@ -194,7 +200,7 @@ let john = new User("John", 25); alert( john.age ); // 25 ``` -...但迟早,情况可能会发生变化。我们可能决定存储 `birthday`,而不是 `age`,因为它更加精确和方便: +...But sooner or later, things may change. Instead of `age` we may decide to store `birthday`, because it's more precise and convenient: ```js function User(name, birthday) { @@ -205,11 +211,13 @@ function User(name, birthday) { let john = new User("John", new Date(1992, 6, 1)); ``` -现在如何处理仍使用 `age` 属性的旧代码? +Now what to do with the old code that still uses `age` property? + +We can try to find all such places and fix them, but that takes time and can be hard to do if that code is used by many other people. And besides, `age` is a nice thing to have in `user`, right? -我们可以尝试找到所有这些地方并修复它们,但这需要时间,而且如果该代码是由其他人编写的,则很难做到。另外,`age` 放在 `user` 中也是一件好事,对吧?在某些地方,这正是我们想要的。 +Let's keep it. -为 `age` 添加 getter 可缓解问题: +Adding a getter for `age` solves the problem: ```js run no-beautify function User(name, birthday) { @@ -217,7 +225,7 @@ function User(name, birthday) { this.birthday = birthday; *!* - // age 是由当前日期和生日计算出来的 + // age is calculated from the current date and birthday Object.defineProperty(this, "age", { get() { let todayYear = new Date().getFullYear(); @@ -229,8 +237,8 @@ function User(name, birthday) { let john = new User("John", new Date(1992, 6, 1)); -alert( john.birthday ); // birthday 是可访问的 -alert( john.age ); // ...age 也是可访问的 +alert( john.birthday ); // birthday is available +alert( john.age ); // ...as well as the age ``` -现在旧的代码也可以工作,而且我们拥有了一个很好的附加属性。 +Now the old code works too and we've got a nice additional property. diff --git a/1-js/07-object-properties/index.md b/1-js/07-object-properties/index.md index 8d359fb0da..67fcccaffc 100644 --- a/1-js/07-object-properties/index.md +++ b/1-js/07-object-properties/index.md @@ -1,3 +1,3 @@ -# 对象属性配置 +# Object properties configuration -在本节中,我们将回到对象上,并更深入地研究其属性。 +In this section we return to objects and study their properties even more in-depth. diff --git a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md index 25fc65a43a..6d25a462ae 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md @@ -1,4 +1,4 @@ -1. `true`,来自于 `rabbit`。 -2. `null`,来自于 `animal`。 -3. `undefined`,不再有这样的属性存在。 +1. `true`, taken from `rabbit`. +2. `null`, taken from `animal`. +3. `undefined`, there's no such property any more. diff --git a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md index 875da86cd6..f38fb6f97a 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 与原型一起工作 +# Working with prototype -如下创建一对对象的代码,然后对它们进行修改。 +Here's the code that creates a pair of objects, then modifies them. -过程中显示了哪些值? +Which values are shown in the process? ```js let animal = { @@ -28,4 +28,4 @@ delete animal.jumps; alert( rabbit.jumps ); // ? (3) ``` -应该有 3 个答案。 +There should be 3 answers. diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md index 9318599624..a16796f9cc 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md @@ -1,5 +1,5 @@ -1. 让我们添加 `__proto__`: +1. Let's add `__proto__`: ```js run let head = { @@ -27,6 +27,6 @@ alert( table.money ); // undefined ``` -2. 在现代引擎的性能方面,无法是从对象中还是从它的原型中获取一个属性,都是没有区别的。它们会记住在哪里找到该属性的,然后下一次请求到来时,重用它。 +2. In modern engines, performance-wise, there's no difference whether we take a property from an object or its prototype. They remember where the property was found and reuse it in the next request. - 例如,对于 `pockets.glasses` 来说,它们会记得找到 `glasses`(在 `head` 中)的地方,这样下次就会直接在之前的地方搜索。一旦有内容更改,它们也会自动更新内容缓存,因此这样的优化是安全的。 + For instance, for `pockets.glasses` they remember where they found `glasses` (in `head`), and next time will search right there. They are also smart enough to update internal caches if something changes, so that optimization is safe. diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md index 6f5a8eb220..bc2db47fed 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 搜索算法 +# Searching algorithm -任务有两部分。 +The task has two parts. -我们有一个对象: +Given the following objects: ```js let head = { @@ -27,5 +27,5 @@ let pockets = { }; ``` -1. 使用 `__proto__` 来分配原型的方式,任何查找都会遵循路径:`pockets` -> `bed` -> `table` -> `head`。例如,`pockets.pen` 应该是 `3`(在 `table` 中找到), `bed.glasses` 应该是 `1` (在 `head` 中找到)。 -2. 回答问题:如果需要检测的话,将 `glasses` 作为 `pockets.glasses` 更快还是作为 `head.glasses` 更快? +1. Use `__proto__` to assign prototypes in a way that any property lookup will follow the path: `pockets` -> `bed` -> `table` -> `head`. For instance, `pockets.pen` should be `3` (found in `table`), and `bed.glasses` should be `1` (found in `head`). +2. Answer the question: is it faster to get `glasses` as `pockets.glasses` or `head.glasses`? Benchmark if needed. diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md index 675f06c7a7..4d6ea2653c 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md @@ -1,6 +1,7 @@ -**答案:`rabbit`。** +**The answer: `rabbit`.** -这是因为 `this` 是“点”之前对象,因此 `rabbit.eat()` 修改了 `rabbit`。 +That's because `this` is an object before the dot, so `rabbit.eat()` modifies `rabbit`. -属性查找和执行是两件不同的事情。 -`rabbit.eat` 方法在原型中被第一个找到,然后执行 `this=rabbit`。 +Property lookup and execution are two different things. + +The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`. diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md index 4f359f3abb..ed8482c072 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 写在哪里? +# Where does it write? -`rabbit` 继承自 `animal`。 +We have `rabbit` inheriting from `animal`. -如果我们调用 `rabbit.eat()`,哪一个对象会接收到 `full` 属性:`animal` 还是 `rabbit`? +If we call `rabbit.eat()`, which object receives the `full` property: `animal` or `rabbit`? ```js let animal = { diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md index a46bda6e14..c141b2ecdc 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md @@ -1,18 +1,18 @@ -我们仔细研究一下在调用 `speedy.eat("apple")` 的时候,发生了什么。 +Let's look carefully at what's going on in the call `speedy.eat("apple")`. -1. `speedy.eat` 方法在原型(`=hamster`)中被发现,然后执行 `this=speedy`(在点之前的对象)。 +1. The method `speedy.eat` is found in the prototype (`=hamster`), then executed with `this=speedy` (the object before the dot). -2. `this.stomach.push()` 需要查找到 `stomach` 属性,然后调用 `push` 来处理。它在 `this` (`=speedy`) 中查找 `stomach`,但并没有找到。 +2. Then `this.stomach.push()` needs to find `stomach` property and call `push` on it. It looks for `stomach` in `this` (`=speedy`), but nothing found. -3. 然后它顺着原型链,在 `hamster` 中找到 `stomach`。 +3. Then it follows the prototype chain and finds `stomach` in `hamster`. -4. 然后它调用 `push` ,将食物添加到**胃的原型链**中。 +4. Then it calls `push` on it, adding the food into *the stomach of the prototype*. -因此所有的仓鼠都有共享一个胃! +So all hamsters share a single stomach! -每次 `stomach` 从原型中获取,然后 `stomach.push` 修改它的“位置”。 +Both for `lazy.stomach.push(...)` and `speedy.stomach.push()`, the property `stomach` is found in the prototype (as it's not in the object itself), then the new data is pushed into it. -请注意,这种情况在 `this.stomach=` 进行简单的赋值情况下不会发生: +Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`: ```js run let hamster = { @@ -42,9 +42,9 @@ alert( speedy.stomach ); // apple alert( lazy.stomach ); // ``` -现在,所有的都在正常运行,因为 `this.stomach=` 不会在 `stomach` 中执行查找。该值会被直接写入 `this` 对象。 +Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object. -此外,我们还可以通过确保每只仓鼠都有自己的胃来完全回避这个问题: +Also we can totally avoid the problem by making sure that each hamster has their own stomach: ```js run let hamster = { @@ -77,4 +77,4 @@ alert( speedy.stomach ); // apple alert( lazy.stomach ); // ``` -作为一种常见的解决方案,描述特定对象状态的所有属性,如上述的 `stomach`,通常都被写入到该对象中。这防止了类似问题的出现。 +As a common solution, all properties that describe the state of a particular object, like `stomach` above, should be written into that object. That prevents such problems. diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md index 860cdab8a5..50171123d4 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# 为什么两只仓鼠都饱了? +# Why are both hamsters full? -我们有两只仓鼠:`speedy` 和 `lazy` 都继承自普通的 `hamster` 对象。 +We have two hamsters: `speedy` and `lazy` inheriting from the general `hamster` object. -当我们喂一只的同时,另一只也吃饱了。为什么?如何修复这件事? +When we feed one of them, the other one is also full. Why? How can we fix it? ```js run let hamster = { diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 58f55f4b7a..ef6c7ffebd 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -1,22 +1,22 @@ -# 原型继承 +# Prototypal inheritance -在编程中,我们经常想要获取并扩展一些事情。 +In programming, we often want to take something and extend it. -例如,我们有一个 `user` 对象及其属性和方法。而且希望将 `admin` 和 `guest` 作为它稍加修改的变体。我们希望重用 `user` 所拥有的内容,而不是复制/实现它的方法,只需要在其上创建一个新的对象。 +For instance, we have a `user` object with its properties and methods, and want to make `admin` and `guest` as slightly modified variants of it. We'd like to reuse what we have in `user`, not copy/reimplement its methods, just build a new object on top of it. -**原型继承**的语言特性可以帮助我们实现这一需求。 +*Prototypal inheritance* is a language feature that helps in that. ## [[Prototype]] -在 JavaScript 中,对象有一个特殊的隐藏属性 `[[Prototype]]`(如规范中所命名的),其取值为 `null` 或者是另一个对象的引用。该对象称为“原型”: +In JavaScript, objects have a special hidden property `[[Prototype]]` (as named in the specification), that is either `null` or references another object. That object is called "a prototype": ![prototype](object-prototype-empty.svg) -`[[Prototype]]` 有一个“神奇”的意义。当我们想要从 `object` 中读取一个缺失的属性时,JavaScript 会自动从原型中获取它。在编程中,这称为“原型继承”。许多很酷的语言特性和编程技巧都是基于它的。 +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. -属性 `[[Prototype]]` 是内部的而且隐藏的,但是设置它的方法却有很多种。 +The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. -其中之一是使用 `__proto__`,就像这样: +One of them is to use the special name `__proto__`, like this: ```js run let animal = { @@ -27,17 +27,15 @@ let rabbit = { }; *!* -rabbit.__proto__ = animal; +rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal */!* ``` -请注意 `__proto__` 与 `[[Prototype]]` **不一样**。这是一个 getter/setter。我们之后会讨论如何设置它,但是现在 `__proto__` 工作的很好。 +Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`. -如果我们在 `rabbit` 中查找一个缺失的属性,JavaScript 会自动从 `animal` 中获取它。 +For instance: -例如: - -```js run +```js let animal = { eats: true }; @@ -56,17 +54,17 @@ alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true ``` -这里的 `(*)` 行将 `animal` 设置为 `rabbit` 的原型。 +Here the line `(*)` sets `animal` to be the prototype of `rabbit`. -当 `alert` 试图读取 `rabbit.eats` `(**)` 时,因为它不存在于 `rabbit`,JavaScript 会遵循 `[[Prototype]]` 引用,并在 `animal` 中查找(自下而上): +Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up): ![](proto-animal-rabbit.svg) -我们可以说 "`animal` 是 `rabbit`" 的原型或者说 "`rabbit` 的原型继承自 `animal`"。 +Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` prototypically inherits from `animal`". -因此如果 `animal` 有许多有用的属性和方法,那么它们在 `rabbit` 中会自动变为可用的。这种属性行为称为“继承”。 +So if `animal` has a lot of useful properties and methods, then they become automatically available in `rabbit`. Such properties are called "inherited". -如果我们在 `animal` 中有一种方法,它可以在 `rabbit` 中被调用: +If we have a method in `animal`, it can be called on `rabbit`: ```js run let animal = { @@ -89,12 +87,11 @@ rabbit.walk(); // Animal walk */!* ``` -该方法自动从原型中提取,如下所示: +The method is automatically taken from the prototype, like this: ![](proto-animal-rabbit-walk.svg) -原型链可以很长: - +The prototype chain can be longer: ```js run let animal = { @@ -106,13 +103,17 @@ let animal = { let rabbit = { jumps: true, +*!* __proto__: animal +*/!* }; let longEar = { earLength: 10, +*!* __proto__: rabbit -} +*/!* +}; // walk is taken from the prototype chain longEar.walk(); // Animal walk @@ -121,20 +122,34 @@ alert(longEar.jumps); // true (from rabbit) ![](proto-animal-rabbit-chain.svg) -实际上只有两个限制: +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: + +1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle. +2. The value of `__proto__` can be either an object or `null`. Other types are ignored. + +Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others. -1. 引用不能形成闭环。如果我们试图在一个闭环中分配 `__proto__`,JavaScript 会抛出异常。 -2. `__proto__` 的值可以是对象,也可以是 `null`。所有其他的值(例如原语)都会被忽略。 +```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. -这也可能是显而易见的,即:只能有一个 `[[Prototype]]`。对象不能继承自其他两个对象。 +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 -原型仅用于读取属性。 +The prototype is only used for reading properties. -对于数据属性(不是 getters/setters)写/删除操作直接在对象上进行操作。 +Write/delete operations work directly with the object. -在以下示例中,我们将 `walk` 方法分配给 `rabbit`: +In the example below, we assign its own `walk` method to `rabbit`: ```js run let animal = { @@ -146,7 +161,7 @@ let animal = { let rabbit = { __proto__: animal -} +}; *!* rabbit.walk = function() { @@ -157,13 +172,13 @@ rabbit.walk = function() { rabbit.walk(); // Rabbit! Bounce-bounce! ``` -从现在开始,`rabbit.walk()` 调用将立即在对象中找到方法并执行它,而不是使用原型: +From now on, `rabbit.walk()` call finds the method immediately in the object and executes it, without using the prototype: ![](proto-animal-rabbit-walk-2.svg) -对于 getters/setters —— 如果我们读写一个属性,就会在原型中查找并调用它们。 +Accessor properties are an exception, as assignment is handled by a setter function. So writing to such a property is actually the same as calling a function. -例如,在以下代码中检查出 `admin.fullName` 属性: +For that reason `admin.fullName` works correctly in the code below: ```js run let user = { @@ -188,25 +203,28 @@ alert(admin.fullName); // John Smith (*) // setter triggers! admin.fullName = "Alice Cooper"; // (**) + +alert(admin.fullName); // Alice Cooper, state of admin modified +alert(user.fullName); // John Smith, state of user protected ``` -这里的 `(*)` 属性 `admin.fullName` 在原型 `user` 中有一个 getter,因此它会被调用。在 `(**)` 中,属性在原型中有一个 setter,因此它会被调用。 +Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called. -## "this" 的值 +## The value of "this" -在上面的例子中可能会出现一个有趣的现象:在 `set fullName(value)` 中 `this` 的值是什么?属性 `this.name` 和 `this.surname` 写在哪里: `user` 还是 `admin`? +An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where are the properties `this.name` and `this.surname` written: into `user` or `admin`? -答案很简单:`this` 根本不受原型的影响。 +The answer is simple: `this` is not affected by prototypes at all. -**无论在哪里找到方法:在对象或者原型中。调用方法时,`this` 始终是点之前的对象。** +**No matter where the method is found: in an object or its prototype. In a method call, `this` is always the object before the dot.** -因此,实际上 setter 使用 `admin` 作为 `this`,而不是 `user`。 +So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`. -这是一件非常重要的事情,因为我们可能有一个有很多方法而且继承它的大对象。然后我们可以在继承的对象上运行它的方法,它们将修改这些对象的状态,而不是大对象的。 +That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object. -例如,这里的 `animal` 代表“方法存储”,而且 `rabbit` 在使用它。 +For instance, here `animal` represents a "method storage", and `rabbit` makes use of it. -调用 `rabbit.sleep()`,在 `rabbit` 对象上设置 `this.isSleeping`: +The call `rabbit.sleep()` sets `this.isSleeping` on the `rabbit` object: ```js run // animal has methods @@ -233,18 +251,88 @@ alert(rabbit.isSleeping); // true alert(animal.isSleeping); // undefined (no such property in the prototype) ``` -结果: +The resulting picture: ![](proto-animal-rabbit-walk-3.svg) -如果我们从 `animal` 继承像 `bird`、`snake` 等其他对象,他们也将获取 `animal` 的方法。但是每个方法 `this` 都是相应的对象,而不是 `animal`,在调用时(在点之前)确定。因此当我们将数据写入 `this` 时,它会被存储进这些对象中。 +If we had other objects, like `bird`, `snake`, etc., inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects. + +As a result, methods are shared, but the object state is not. + +## for..in loop + +The `for..in` loop iterates over inherited properties too. + +For instance: + +```js run +let animal = { + eats: true +}; + +let rabbit = { + jumps: true, + __proto__: animal +}; + +*!* +// Object.keys only returns own keys +alert(Object.keys(rabbit)); // jumps +*/!* + +*!* +// for..in loops over both own and inherited keys +for(let prop in rabbit) alert(prop); // jumps, then eats +*/!* +``` + +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`. + +So we can filter out inherited properties (or do something else with them): + +```js run +let animal = { + eats: true +}; + +let rabbit = { + jumps: true, + __proto__: animal +}; + +for(let prop in rabbit) { + let isOwn = rabbit.hasOwnProperty(prop); + + if (isOwn) { + alert(`Our: ${prop}`); // Our: jumps + } else { + alert(`Inherited: ${prop}`); // Inherited: eats + } +} +``` + +Here we have the following inheritance chain: `rabbit` inherits from `animal`, that inherits from `Object.prototype` (because `animal` is a literal object `{...}`, so it's by default), and then `null` above it: + +![](rabbit-animal-object.svg) + +Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? We did not define it. Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited. -因此,方法是共享的,但对象状态不是。 +...But why does `hasOwnProperty` not appear in the `for..in` loop like `eats` and `jumps` do, if `for..in` lists inherited properties? + +The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. And `for..in` only lists enumerable properties. That's why it and the rest of the `Object.prototype` properties are not listed. + +```smart header="Almost all other key/value-getting methods ignore inherited properties" +Almost all other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties. + +They only operate on the object itself. Properties from the prototype are *not* taken into account. +``` -## 总结 +## Summary -- JavaScript 中,所有的对象都有一个隐藏的 `[[Prototype]]` 属性,它可以是另一个对象或者 `null`。 -- 我们可以使用 `obj.__proto__` 进行访问(还有其他方法,但很快就会被覆盖)。 -- `[[Prototype]]` 引用的对象称为“原型”。 -- 如果我们想要读取 `obj` 属性或者调用一个方法,而且它不存在,那么 JavaScript 就会尝试在原型中查找它。写/删除直接在对象上进行操作,它们不使用原型(除非属性实际上是一个 setter)。 -- 如果我们调用 `obj.method()`,而且 `method` 是从原型中获取的,`this` 仍然会引用 `obj`。因此方法重视与当前对象一起工作,即使它们是继承的。 +- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`. +- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon). +- The object referenced by `[[Prototype]]` is called a "prototype". +- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. +- Write/delete operations act directly on the object, they don't use the prototype (assuming it's a data property, not a setter). +- If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current object even if they are inherited. +- The `for..in` loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself. diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg index e1efc1cea4..eb79c19ffd 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg @@ -1,22 +1 @@ - - - - object-prototype-empty.svg - Created with sketchtool. - - - - - prototype object - - - - object - - - - [[Prototype]] - - - - \ No newline at end of file +prototype objectobject[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg index 5eda2d9e23..4bf580ae77 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg @@ -1,40 +1 @@ - - - - proto-animal-rabbit-chain.svg - Created with sketchtool. - - - - - eats: true - walk: function - - - animal - - - - jumps: true - - - rabbit - - - - [[Prototype]] - - - - earLength: 10 - - - longEar - - - - [[Prototype]] - - - - \ No newline at end of file +eats: true walk: functionanimaljumps: truerabbit[[Prototype]]earLength: 10longEar[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg index a0a495cb3a..838c78395b 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg @@ -1,29 +1 @@ - - - - proto-animal-rabbit-walk-2.svg - Created with sketchtool. - - - - - eats: true - walk: function - - - animal - - - - walk: function - - - rabbit - - - - [[Prototype]] - - - - \ No newline at end of file +eats: true walk: functionanimalwalk: functionrabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg index 541b92d839..d791e5390d 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg @@ -1,30 +1 @@ - - - - proto-animal-rabbit-walk-3.svg - Created with sketchtool. - - - - - walk: function - sleep: function - - - animal - - - rabbit - - - - [[Prototype]] - - - - name: "White Rabbit" - isSleeping: true - - - - \ No newline at end of file +walk: function sleep: functionanimalrabbit[[Prototype]]name: "White Rabbit" isSleeping: true \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg index db1208eac2..b324710286 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg @@ -1,29 +1 @@ - - - - proto-animal-rabbit-walk.svg - Created with sketchtool. - - - - - eats: true - walk: function - - - animal - - - - jumps: true - - - rabbit - - - - [[Prototype]] - - - - \ No newline at end of file +eats: true walk: functionanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg index 2672739e52..4f3c1bc0ec 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg @@ -1,28 +1 @@ - - - - proto-animal-rabbit.svg - Created with sketchtool. - - - - - eats: true - - - animal - - - - jumps: true - - - rabbit - - - - [[Prototype]] - - - - \ No newline at end of file +eats: trueanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg index 37b318f2b8..bf0baf013a 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg @@ -1,32 +1 @@ - - - - proto-user-admin.svg - Created with sketchtool. - - - - - name: "John" - surname: "Smith" - set fullName: function - - - - isAdmin: true - name: "Alice" - surname: "Cooper" - - - user - - - admin - - - - [[Prototype]] - - - - \ No newline at end of file +name: "John" surname: "Smith" set fullName: functionisAdmin: true name: "Alice" surname: "Cooper"useradmin[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg new file mode 100644 index 0000000000..32a9858f83 --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg @@ -0,0 +1 @@ +toString: function hasOwnProperty: function ...Object.prototypeanimal[[Prototype]][[Prototype]][[Prototype]]nulleats: truerabbitjumps: true \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md index e9ef99cb05..ebbdf3a7c1 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md @@ -1,20 +1,20 @@ Answers: -1. `true`。 +1. `true`. - 赋值操作 `Rabbit.prototype` 为新对象设置了 `[[Prototype]]`,但它不影响现有的对象。 + The assignment to `Rabbit.prototype` sets up `[[Prototype]]` for new objects, but it does not affect the existing ones. -2. `false`。 +2. `false`. - 对象通过引用进行赋值。来自 `Rabbit.prototype` 的对象没有被复制,它仍然是由 `Rabbit.prototype` 和 `rabbit` 的 `[[Prototype]]` 引用的单个对象。 + Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`. - 所以当我们通过一个引用来改变它的上下文时,它对其他引用来说是可见的。 + So when we change its content through one reference, it is visible through the other one. -3. `true`。 +3. `true`. - 所有 `delete` 操作都直接应用于对象。这里 `delete rabbit.eats` 试图从 `rabbit` 中删除 `eats` 属性,但 `rabbit` 对象并没有 `eats` 属性。所以这个操作不会有任何 副作用。 + All `delete` operations are applied directly to the object. Here `delete rabbit.eats` tries to remove `eats` property from `rabbit`, but it doesn't have it. So the operation won't have any effect. -4. `undefined`。 +4. `undefined`. - 属性 `eats` 从原型中删除,它不再存在。 \ No newline at end of file + The property `eats` is deleted from the prototype, it doesn't exist any more. diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md index 0aad32f265..2838c125ad 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md @@ -4,9 +4,9 @@ importance: 5 # Changing "prototype" -在下面的代码中,我们创建了 `new Rabbit`,然后尝试修改其原型。 +In the code below we create `new Rabbit`, and then try to modify its prototype. -一开始,我们有这样的代码: +In the start, we have this code: ```js run function Rabbit() {} @@ -20,7 +20,7 @@ alert( rabbit.eats ); // true ``` -1. 我们增加了一个字符串(强调),`alert` 现在会显示什么? +1. We added one more string (emphasized). What will `alert` show now? ```js function Rabbit() {} @@ -37,7 +37,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -2. ...如果代码是这样的(换了一行)? +2. ...And if the code is like this (replaced one line)? ```js function Rabbit() {} @@ -54,7 +54,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -3. 像这样呢(换了一行)? +3. And like this (replaced one line)? ```js function Rabbit() {} @@ -71,7 +71,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -4. 最后一种情况: +4. The last variant: ```js function Rabbit() {} 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 54969af8a5..372d50dd6d 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 @@ -1,6 +1,6 @@ -如果我们确信 `"constructor"` 属性具有正确的值,我们可以使用这种方法。 +We can use such approach if we are sure that `"constructor"` property has the correct value. -例如,如果我们不访问默认的 `"prototype"`,那么这段代码肯定会起作用: +For instance, if we don't touch the default `"prototype"`, then this code works for sure: ```js run function User(name) { @@ -13,11 +13,11 @@ let user2 = new user.constructor('Pete'); alert( user2.name ); // Pete (worked!) ``` -它起作用了,因为 `User.prototype.constructor == User`。 +It worked, because `User.prototype.constructor == User`. -...但是如果有人说,覆盖 `User.prototype` 并忘记重新创建 `"constructor"`,那么它就会失败。 +..But if someone, so to speak, overwrites `User.prototype` and forgets to recreate `constructor` to reference `User`, then it would fail. -例如: +For instance: ```js run function User(name) { @@ -33,12 +33,17 @@ let user2 = new user.constructor('Pete'); alert( user2.name ); // undefined ``` -为什么 `user2.name` 是 `undefined`? +Why `user2.name` is `undefined`? -`new user.constructor('Pete')` 的工作原理是: +Here's how `new user.constructor('Pete')` works: -1. 首先,它在 `user` 中寻找 `constructor`。什么也没有。 -2. 然后它追溯原型链。`user` 的原型是 `User.prototype`,它也什么都没有。 -3. `User.prototype` 的值是一个普通对象 `{}`,其原型是 `Object.prototype`。还有 `Object.prototype.constructor == Object`。所以就用它了。 +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. -最后,我们有 `let user2 = new Object('Pete')`。内置的 `Object` 构造函数忽略参数,它总是创建一个空对象 —— 这就是我们在 `user2` 中所拥有的东西。 \ No newline at end of file +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). \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md index 1d5ea5150e..934f3470b9 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md @@ -2,14 +2,14 @@ importance: 5 --- -# 使用相同的构造函数创建一个对象 +# Create an object with the same constructor -想象一下,我们有一个任意对象 `obj`,由一个构造函数创建 —— 我们不知道这个构造函数是什么,但是我们想用它创建一个新对象。 +Imagine, we have an arbitrary object `obj`, created by a constructor function -- we don't know which one, but we'd like to create a new object using it. -我们可以这样做吗? +Can we do it like that? ```js let obj2 = new obj.constructor(); ``` -给出一个代码可以正常工作的 `obj` 的构造函数的例子。再给出一个会导致错误的例子。 +Give an example of a constructor function for `obj` which lets such code work right. And an example that makes it work wrong. diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md index 864b20fd5e..b1ef518266 100644 --- a/1-js/08-prototypes/02-function-prototype/article.md +++ b/1-js/08-prototypes/02-function-prototype/article.md @@ -1,18 +1,18 @@ -# 函数原型 +# F.prototype -正如我们已经知道的那样,`new F()` 创建一个新对象。 +Remember, new objects can be created with a constructor function, like `new F()`. -当用 `new F()` 创建一个新对象时,该对象的 `[[Prototype]]` 被设置为 `F.prototype`。 +If `F.prototype` is an object, then the `new` operator uses it to set `[[Prototype]]` for the new object. ```smart -JavaScript 从一开始就有了原型继承。这是该语言的核心特征之一。 +JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language. -但在过去,我们是无法直接对其进行访问的。唯一可靠的设置方法是使用构造函数的“prototype”属性。我们将在本章对其进行讨论。即使现在,还是有很多使用它的脚本。 +But in the old times, there was no direct access to it. The only thing that worked reliably was a `"prototype"` property of the constructor function, described in this chapter. So there are many scripts that still use it. ``` -请注意,`F.prototype` 意味着在 `F` 上有一个名为 `"prototype"` 的常规属性。这听起来与“原型”这个术语很相似,但在这里我们的意思是指有这个名字的常规属性。 +Please note that `F.prototype` here means a regular property named `"prototype"` on `F`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name. -这是一个例子: +Here's the example: ```js run let animal = { @@ -32,27 +32,27 @@ let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true ``` -设置 `Rabbit.prototype = animal` 的这段代码表达的意思是:“当 `new Rabbit` 创建时,把它的 `[[Prototype]]` 赋值为 `animal`” 。 +Setting `Rabbit.prototype = animal` literally states the following: "When a `new Rabbit` is created, assign its `[[Prototype]]` to `animal`". -这是结果图: +That's the resulting picture: ![](proto-constructor-animal-rabbit.svg) -在图片上,`"prototype"` 是一个水平箭头,它是一个常规属性,`[[Prototype]]` 是垂直的,意味着是继承自 `animal` 的 `rabbit`。 +On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. -```smart header="`F.prototype`仅用于`new F`时" -`F.prototype` 属性仅在 `new F` 调用时使用,它为新对象的 `[[Prototype]]` 赋值。在此之后,`F.prototype` 和新对象之间就分道扬镳了。可以将其看为一个“单次赠与”效果。 +```smart header="`F.prototype` only used at `new F` time" +`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. -如果在创建之后 `F.prototype` 属性有了变化(`F.prototype = `),那么 `new F` 创建的新对象也将随之拥有新的 `[[Prototype]]`。但已经存在的对象将保持旧有的值。 +If, after the creation, `F.prototype` property changes (`F.prototype = `), then new objects created by `new F` will have another object as `[[Prototype]]`, but already existing objects keep the old one. ``` -## 默认的函数原型,构造函数属性 +## Default F.prototype, constructor property -每个函数都有 `"prototype"` 属性,即使我们不设置它。 +Every function has the `"prototype"` property even if we don't supply it. -默认的 `"prototype"` 是一个只有属性 `constructor` 的对象,它指向函数本身。 +The default `"prototype"` is an object with the only property `constructor` that points back to the function itself. -像这样: +Like this: ```js function Rabbit() {} @@ -64,7 +64,7 @@ Rabbit.prototype = { constructor: Rabbit }; ![](function-prototype-constructor.svg) -我们可以检查一下: +We can check it: ```js run function Rabbit() {} @@ -74,7 +74,7 @@ function Rabbit() {} alert( Rabbit.prototype.constructor == Rabbit ); // true ``` -当然,如果我们什么都不做,`constructor` 属性可以通过 `[[Prototype]]` 给所有 rabbits 对象使用: +Naturally, if we do nothing, the `constructor` property is available to all rabbits through `[[Prototype]]`: ```js run function Rabbit() {} @@ -88,9 +88,10 @@ alert(rabbit.constructor == Rabbit); // true (from prototype) ![](rabbit-prototype-constructor.svg) -我们可以用 `constructor` 属性使用与现有构造函数相同的构造函数创建一个新对象。 +We can use `constructor` property to create a new object using the same constructor as the existing one. + +Like here: -像这样: ```js run function Rabbit(name) { this.name = name; @@ -104,18 +105,17 @@ let rabbit2 = new rabbit.constructor("Black Rabbit"); */!* ``` -当我们有一个对象,但不知道为它使用哪个构造函数(比如它来自第三方库),而且我们需要创建另一个相似的函数时,用这种方法就很方便。 - +That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create another one of the same kind. -但关于 `"constructor"` 最重要的是...... +But probably the most important thing about `"constructor"` is that... -**...JavaScript 本身并不能确保正确的 `"constructor"` 函数值。** +**...JavaScript itself does not ensure the right `"constructor"` value.** -是的,它存在于函数的默认 `"prototype"` 中,但仅此而已。之后会发生什么 —— 完全取决于我们自己。 +Yes, it exists in the default `"prototype"` for functions, but that's all. What happens with it later -- is totally on us. -特别是,如果我们将整个默认原型替换掉,那么其中就不会有构造函数。 +In particular, if we replace the default prototype as a whole, then there will be no `"constructor"` in it. -例如: +For instance: ```js run function Rabbit() {} @@ -129,7 +129,7 @@ alert(rabbit.constructor === Rabbit); // false */!* ``` -因此,为了确保正确的 `"constructor"`,我们可以选择添加/删除属性到默认 `"prototype"` 而不是将其整个覆盖: +So, to keep the right `"constructor"` we can choose to add/remove properties to the default `"prototype"` instead of overwriting it as a whole: ```js function Rabbit() {} @@ -140,7 +140,7 @@ Rabbit.prototype.jumps = true // the default Rabbit.prototype.constructor is preserved ``` -或者,也可以手动重新创建 `constructor` 属性: +Or, alternatively, recreate the `constructor` property manually: ```js Rabbit.prototype = { @@ -150,26 +150,26 @@ Rabbit.prototype = { */!* }; -// 这样的 constructor 也是正确的,因为我们手动添加了它 +// now constructor is also correct, because we added it ``` -## 总结 +## Summary -在本章中,我们简要介绍了如何为通过构造函数创建的对象设置一个 `[[Prototype]]`。稍后我们将看到更多依赖于它的高级编程模式。 +In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it. -一切都很简单,只需要几条笔记就能说清楚: +Everything is quite simple, just a few notes to make things clear: -- `F.prototype` 属性与 `[[Prototype]]` 不同。`F.prototype` 唯一的作用是:当 `new F()` 被调用时,它设置新对象的 `[[Prototype]]`。 -- `F.prototype` 的值应该是一个对象或 null:其他值将不起作用。 -- `"prototype"` 属性在设置为构造函数时仅具有这种特殊效果,并且用 `new` 调用。 +- The `F.prototype` property (don't mistake it for `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called. +- The value of `F.prototype` should be either an object or `null`: other values won't work. +- The `"prototype"` property only has such a special effect when set on a constructor function, and invoked with `new`. -在常规对象上,`prototype` 没什么特别的: +On regular objects the `prototype` is nothing special: ```js let user = { name: "John", - prototype: "Bla-bla" // 没什么神秘的 + prototype: "Bla-bla" // no magic at all }; ``` -默认情况下,所有函数都有 `F.prototype = {constructor:F}`,所以我们可以通过访问它的 `"constructor"` 属性来获得对象的构造函数。 +By default all functions have `F.prototype = { constructor: F }`, so we can get the constructor of an object by accessing its `"constructor"` property. diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg index 6aa3710cce..59d60b397a 100644 --- a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg +++ b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg @@ -1,26 +1 @@ - - - - function-prototype-constructor.svg - Created with sketchtool. - - - - - - Rabbit - - - - - prototype - - - constructor - - - default "prototype" - - - - \ No newline at end of file +Rabbitprototypeconstructordefault "prototype" \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.svg b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.svg deleted file mode 100644 index 37601da27b..0000000000 --- a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - native-prototypes-array-tostring.svg - Created with sketchtool. - - - - - toString: function - - ... - - - Array.prototype - - - - toString: function - ... - - - Object.prototype - - - - - [[Prototype]] - - - - [[Prototype]] - - - [1, 2, 3] - - - - \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.svg b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.svg deleted file mode 100644 index a6f78e5747..0000000000 --- a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - - native-prototypes-classes.svg - Created with sketchtool. - - - - - toString: function - other object methods - - - Object.prototype - - - - - null - - - - slice: function - other array methods - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - Array.prototype - - - - call: function - other function methods - - - Function.prototype - - - - toFixed: function - other number methods - - - Number.prototype - - - - - - [1, 2, 3] - - - - function f(args) { - ... - } - - - - 5 - - - - - - - \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype-1.svg b/1-js/08-prototypes/02-function-prototype/object-prototype-1.svg deleted file mode 100644 index 8a93d2a864..0000000000 --- a/1-js/08-prototypes/02-function-prototype/object-prototype-1.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - object-prototype-1.svg - Created with sketchtool. - - - - - constructor: Object - toString: function - ... - - - - Object.prototype - - - - Object - - - obj = new Object() - - - - - [[Prototype]] - - - prototype - - - constructor - - - - \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype.svg b/1-js/08-prototypes/02-function-prototype/object-prototype.svg deleted file mode 100644 index 828f61d3be..0000000000 --- a/1-js/08-prototypes/02-function-prototype/object-prototype.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - object-prototype.svg - Created with sketchtool. - - - - - constructor: Object - toString: function - ... - - - Object.prototype - - - - Object - - - - prototype - - - constructor - - - - \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg index 447a3b72f4..ede4e1227e 100644 --- a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg +++ b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg @@ -1,36 +1 @@ - - - - proto-constructor-animal-rabbit.svg - Created with sketchtool. - - - - - eats: true - - - - name: "White Rabbit" - - - animal - - - - Rabbit - - - rabbit - - - - - [[Prototype]] - - - prototype - - - - \ No newline at end of file +eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.svg b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.svg deleted file mode 100644 index 28daef3af8..0000000000 --- a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - rabbit-animal-object.svg - Created with sketchtool. - - - - - toString: function - hasOwnProperty: function - ... - - - Object.prototype - - - - animal - - - - [[Prototype]] - - - [[Prototype]] - - - - [[Prototype]] - - - null - - - eats: true - - - - rabbit - - - - jumps: true - - - - \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg index 322f7b1038..54b3d79804 100644 --- a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg +++ b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg @@ -1,39 +1 @@ - - - - rabbit-prototype-constructor.svg - Created with sketchtool. - - - - - - - - - - default "prototype" - - - - - Rabbit - - - rabbit - - - - - - [[Prototype]] - - - prototype - - - constructor - - - - \ No newline at end of file +default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md index fe21edb946..d3b3a51c2a 100644 --- a/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md +++ b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md @@ -2,16 +2,16 @@ importance: 5 --- -# 给函数添加一个方法 “f.defer(ms)” +# Add method "f.defer(ms)" to functions -为所有函数的原型添加 `defer(ms)` 方法,能够在 `ms` 毫秒后执行函数。 +Add to the prototype of all functions the method `defer(ms)`, that runs the function after `ms` milliseconds. -当你完成添加后,下面的代码应该是可执行的: +After you do it, such code should work: ```js function f() { alert("Hello!"); } -f.defer(1000); // 1 秒后显示 “Hello!” +f.defer(1000); // shows "Hello!" after 1 second ``` diff --git a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md index e3651683fa..99c358c9b0 100644 --- a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md +++ b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md @@ -15,3 +15,27 @@ function f(a, b) { f.defer(1000)(1, 2); // shows 3 after 1 sec ``` + +Please note: we use `this` in `f.apply` to make our decoration work for object methods. + +So if the wrapper function is called as an object method, then `this` is passed to the original method `f`. + +```js run +Function.prototype.defer = function(ms) { + let f = this; + return function(...args) { + setTimeout(() => f.apply(this, args), ms); + } +}; + +let user = { + name: "John", + sayHi() { + alert(this.name); + } +} + +user.sayHi = user.sayHi.defer(1000); + +user.sayHi(); +``` diff --git a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md index 9a5b4ff22d..4d3823bb85 100644 --- a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md +++ b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md @@ -2,18 +2,18 @@ importance: 4 --- -# 添加装饰器方法 “defer()” 到函数 +# Add the decorating "defer()" to functions -添加方法 `defer(ms)` 到所有的函数原型,它返回一个包装函数,延迟 `ms` 毫秒调用函数。 +Add to the prototype of all functions the method `defer(ms)`, that returns a wrapper, delaying the call by `ms` milliseconds. -这里是它应该如何执行的例子: +Here's an example of how it should work: ```js function f(a, b) { alert( a + b ); } -f.defer(1000)(1, 2); // 1 秒钟后显示 3 +f.defer(1000)(1, 2); // shows 3 after 1 second ``` -请注意参数应该被传给原函数。 +Please note that the arguments should be passed to the original function. diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index 426dc4a2e6..bdfc86dd8d 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -1,60 +1,62 @@ -# 原生的原型 +# Native prototypes -`"prototype"` 属性在 JavaScript 自身的核心模块中被广泛应用。所有的内置构造函数都用到了它。 +The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it. -我们将会首先看到原型对于简单对象是什么样的,然后对于更多的复杂对象又是什么样的。 +First we'll look at the details, and then how to use it for adding new capabilities to built-in objects. ## Object.prototype -假设我们输出一个空对象: +Let's say we output an empty object: ```js run let obj = {}; alert( obj ); // "[object Object]" ? ``` -生成字符串 `"[object Object]"` 的代码在哪里?那就是内置的一个 `toString` 方法,但是它在哪里呢?`obj` 是空的! +Where's the code that generates the string `"[object Object]"`? That's a built-in `toString` method, but where is it? The `obj` is empty! -...然而简短的表达式 `obj = {}` 和 `obj = new Object()` 是一个意思,其中 `Object` 是一个内置的对象构造函数。并且这个方法有一个 `Object.prototype` 属性,这个属性引用了一个庞大的对象,这个庞大的对象有 `toString` 方法和其他的一些方法。 +...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` is a built-in object constructor function, with its own `prototype` referencing a huge object with `toString` and other methods. -就像这样(所有的这些都是内置的): +Here's what's going on: ![](object-prototype.svg) -当 `new Object()` 被调用来创建一个对象(或者一个字面量对象 `{...}` 被创建),按照前面章节的讨论规则,这个对象的 `[[Prototype]]` 属性被设置为 `Object.prototype`: +When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` according to the rule that we discussed in the previous chapter: ![](object-prototype-1.svg) -之后当 `obj.toString()` 被调用时,这个方法是在 `Object.prototype` 中被取到的。 +So then when `obj.toString()` is called the method is taken from `Object.prototype`. -我们可以这样验证它: +We can check it like this: ```js run let obj = {}; alert(obj.__proto__ === Object.prototype); // true -// obj.toString === obj.__proto__.toString == Object.prototype.toString + +alert(obj.toString === obj.__proto__.toString); //true +alert(obj.toString === Object.prototype.toString); //true ``` -请注意在 `Object.prototype` 上没有额外的 `[[Prototype]]` 属性: +Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`: ```js run alert(Object.prototype.__proto__); // null ``` -## 其他内置原型 +## Other built-in prototypes -像 `Array`、`Date`、`Function` 和其他的内置对象都在原型对象上挂载方法。 +Other built-in objects such as `Array`, `Date`, `Function` and others also keep methods in prototypes. -例如,当我们创建一个数组 `[1, 2, 3]`,内部使用默认的 `new Array()` 构造函数。因此这个数组数据被写进了这个新的数组对象,并且 `Array.prototype` 成为这个数组对象的原型且为数组对象提供数组的操作方法。这样内存的存储效率是很高的。 +For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient. -按照规范,所有的内置原型顶端都是 `Object.prototype`。有些时候人们说“一切都从对象上继承而来”。 +By specification, all of the built-in prototypes have `Object.prototype` on the top. That's why some people say that "everything inherits from objects". -下面是完整的示意图(3 个内置对象): +Here's the overall picture (for 3 built-ins to fit): ![](native-prototypes-classes.svg) -让我们手动验证原型: +Let's check the prototypes manually: ```js run let arr = [1, 2, 3]; @@ -69,24 +71,24 @@ alert( arr.__proto__.__proto__ === Object.prototype ); // true alert( arr.__proto__.__proto__.__proto__ ); // null ``` -一些方法可能在原型上发生重叠,例如,`Array.prototype` 有自己的 `toString` 方法来列举出来数组的所有元素并用逗号分隔每一个元素。 +Some methods in prototypes may overlap, for instance, `Array.prototype` has its own `toString` that lists comma-delimited elements: ```js run let arr = [1, 2, 3] alert(arr); // 1,2,3 <-- the result of Array.prototype.toString ``` -正如我们之前看到的那样,`Object.prototype` 也有 `toString` 方法,但是 `Array.prototype` 在原型链上是更近的,所以数组对象原型上的方法会被使用。 +As we've seen before, `Object.prototype` has `toString` as well, but `Array.prototype` is closer in the chain, so the array variant is used. ![](native-prototypes-array-tostring.svg) -像 Chrome 开发者控制台这样的浏览器内置工具也显示继承关系的(可能需要对内置对象使用 `console.dir`): +In-browser tools like Chrome developer console also show inheritance (`console.dir` may need to be used for built-in objects): ![](console_dir_array.png) -其他内置对象以同样的方式运行。即使是函数。它们是内置构造函数 `Function` 创建出来的对象,并且他们的方法:`call/apply` 和其他方法都来自 `Function.prototype`。函数也有自己的 `toString` 方法。 +Other built-in objects also work the same way. Even functions -- they are objects of a built-in `Function` constructor, and their methods (`call`/`apply` and others) are taken from `Function.prototype`. Functions have their own `toString` too. ```js run function f() {} @@ -95,21 +97,21 @@ alert(f.__proto__ == Function.prototype); // true alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects ``` -## 基本数据类型 +## Primitives -最复杂的事情发生在字符串、数字和布尔值上。 +The most intricate thing happens with strings, numbers and booleans. -正如我们记忆中的那样,它们并不是对象。但是如果我们试图访问它们的属性,那么临时的包装对象将会被内置的构造函数`String`、 `Number` 或 `Boolean`创建,它们提供给我们操作字符串、数字和布尔值的方法然后藏匿起来。(译者注:这里的“隐匿起来”应该是指我们在打印这些值的时候看不到对象的方法) +As we remember, they are not objects. But if we try to access their properties, temporary wrapper objects are created using built-in constructors `String`, `Number` and `Boolean`. They provide the methods and disappear. -这些对象对我们来说是被无形的创造出来的且大多数引擎优化了它们,而规范精准的描述了这种方式。这些对象的方法也驻留在它们的原型 `String.prototype`、`Number.prototype` 和 `Boolean.prototype` 中。 +These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as `String.prototype`, `Number.prototype` and `Boolean.prototype`. -```warn header="值 `null` 和 `undefined` 没有对象包装" -特殊值 `null` 和 `undefined` 要被区分看待。它们没有对象包装,所以它们没有自己的方法和属性。并且它们没有相应的原型。 +```warn header="Values `null` and `undefined` have no object wrappers" +Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes either. ``` -## 更改原生原型 [#原生-原型-更改] +## Changing native prototypes [#native-prototype-change] -原生的原型是可以被修改的。例如,我们在 `String.prototype` 中添加一个方法,这个方法对所有的字符串都是可用的: +Native prototypes can be modified. For instance, if we add a method to `String.prototype`, it becomes available to all strings: ```js run String.prototype.show = function() { @@ -119,24 +121,32 @@ String.prototype.show = function() { "BOOM!".show(); // BOOM! ``` -在开发的过程中我们可能会想在内置对象上创建一个我们想要的方法。把他们添加到原生对象的原型中可能看起来不错,但那样通常来说是个坏主意。 +During the process of development, we may have ideas for new built-in methods we'd like to have, and we may be tempted to add them to native prototypes. But that is generally a bad idea. + +```warn +Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them will be overwriting the method of the other. + +So, generally, modifying a native prototype is considered a bad idea. +``` + +**In modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.** -原型是全局的,所以很容易造成冲突。如果有两个代码段都添加了 `String.prototype.show` 方法,那么其中一个将覆盖另外一个。 +Polyfilling is a term for making a substitute for a method that exists in the JavaScript specification, but is not yet supported by a particular JavaScript engine. -在现代编程中,只有一种情况下修改原生原型是被允许的。那就是在做 polyfills (译者注:兼容)的时候。换句话说,如果有一个 JavaScript 规范还没有被我们的 JavaScript 引擎支持(或者我们希望被支持的那些规范),那么需要手动实现这些规范并且把这些手动实现填充到内置对象的原型上。 +We may then implement it manually and populate the built-in prototype with it. -例如: +For instance: ```js run -if (!String.prototype.repeat) { //假设没有这个方法 - //把它添加到原型上 +if (!String.prototype.repeat) { // if there's no such method + // add it to the prototype String.prototype.repeat = function(n) { - //重复字符串 n 次 + // repeat the string n times - //实际上代码是比这个更复杂的, - //当 "n" 的值为负数的时候抛出异常 - //完整的算法在规范中 + // actually, the code should be a little bit more complex than that + // (the full algorithm is in the specification) + // but even an imperfect polyfill is often considered good enough return new Array(n + 1).join(this); }; } @@ -144,37 +154,45 @@ if (!String.prototype.repeat) { //假设没有这个方法 alert( "La".repeat(3) ); // LaLaLa ``` -## 从原型中借用 -在 章节中我们讨论的方法借用: +## Borrowing from prototypes + +In the chapter we talked about method borrowing. + +That's when we take a method from one object and copy it into another. + +Some methods of native prototypes are often borrowed. + +For instance, if we're making an array-like object, we may want to copy some `Array` methods to it. + +E.g. ```js run -function showArgs() { +let obj = { + 0: "Hello", + 1: "world!", + length: 2, +}; + *!* - // 从数组借用 join 方法并在 arguments 的上下文中调用 - alert( [].join.call(arguments, " - ") ); +obj.join = Array.prototype.join; */!* -} -showArgs("John", "Pete", "Alice"); // John - Pete - Alice +alert( obj.join(',') ); // Hello,world! ``` -因为 `join` 方法在 `Array.prototype` 对象上,我们可以直接调用它并且重写上面的代码: +It works because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property. It doesn't check if the object is indeed an array. Many built-in methods are like that. -```js -function showArgs() { -*!* - alert( Array.prototype.join.call(arguments, " - ") ); -*/!* -} -``` +Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, so all `Array` methods are automatically available in `obj`. + +But that's impossible if `obj` already inherits from another object. Remember, we only can inherit from one object at a time. -这样是更有效率的,因为它避免了一个额外数组对象 `[]` 的创建。另一方面,这样做,需要更长的时间来编写。 +Borrowing methods is flexible, it allows to mix functionalities from different objects if needed. -## 总结 +## Summary -- 所有的内置对象都遵循一样的模式: - - 方法都存储在原型对象上(`Array.prototype`、`Object.prototype`、`Date.prototype` 等)。 - - 对象本身只存储数据(数组元素、对象属性、日期)。 -- 基本数据类型同样在包装对象的原型上存储方法:`Number.prototype`、`String.prototype` 和 `Boolean.prototype`。只有 `undefined` 和 `null` 没有包装对象。 -- 内置对象的原型可以被修改或者被新的方法填充。但是这样做是不被推荐的。只有当添加一个还没有被 JavaScript 引擎支持的新方法的时候才可能允许这样做。 +- All built-in objects follow the same pattern: + - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype`, etc.) + - The object itself stores only the data (array items, object properties, the date) +- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype` and `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects +- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. The only allowable case is probably when we add-in a new standard, but it's not yet supported by the JavaScript engine diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg index 6aa3710cce..59d60b397a 100644 --- a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg +++ b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg @@ -1,26 +1 @@ - - - - function-prototype-constructor.svg - Created with sketchtool. - - - - - - Rabbit - - - - - prototype - - - constructor - - - default "prototype" - - - - \ No newline at end of file +Rabbitprototypeconstructordefault "prototype" \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg index 37601da27b..ebb4f32051 100644 --- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg @@ -1,39 +1 @@ - - - - native-prototypes-array-tostring.svg - Created with sketchtool. - - - - - toString: function - - ... - - - Array.prototype - - - - toString: function - ... - - - Object.prototype - - - - - [[Prototype]] - - - - [[Prototype]] - - - [1, 2, 3] - - - - \ No newline at end of file +toString: function ...Array.prototypetoString: function ...Object.prototype[[Prototype]][[Prototype]][1, 2, 3] \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg index a6f78e5747..4d6129e0a0 100644 --- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg @@ -1,87 +1 @@ - - - - native-prototypes-classes.svg - Created with sketchtool. - - - - - toString: function - other object methods - - - Object.prototype - - - - - null - - - - slice: function - other array methods - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - [[Prototype]] - - - Array.prototype - - - - call: function - other function methods - - - Function.prototype - - - - toFixed: function - other number methods - - - Number.prototype - - - - - - [1, 2, 3] - - - - function f(args) { - ... - } - - - - 5 - - - - - - - \ No newline at end of file +toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg index 8a93d2a864..9630e68e27 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg @@ -1,38 +1 @@ - - - - object-prototype-1.svg - Created with sketchtool. - - - - - constructor: Object - toString: function - ... - - - - Object.prototype - - - - Object - - - obj = new Object() - - - - - [[Prototype]] - - - prototype - - - constructor - - - - \ No newline at end of file +constructor: Object toString: function ...Object.prototypeObjectobj = new Object()[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg index ba22dc601b..9ccb342299 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg @@ -1,21 +1 @@ - - - - object-prototype-null.svg - Created with sketchtool. - - - - - obj - - - - [[Prototype]] - - - null - - - - \ No newline at end of file +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg index 828f61d3be..024dd30213 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg @@ -1,30 +1 @@ - - - - object-prototype.svg - Created with sketchtool. - - - - - constructor: Object - toString: function - ... - - - Object.prototype - - - - Object - - - - prototype - - - constructor - - - - \ No newline at end of file +constructor: Object toString: function ...Object.prototypeObjectprototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg deleted file mode 100644 index 447a3b72f4..0000000000 --- a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - proto-constructor-animal-rabbit.svg - Created with sketchtool. - - - - - eats: true - - - - name: "White Rabbit" - - - animal - - - - Rabbit - - - rabbit - - - - - [[Prototype]] - - - prototype - - - - \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg index 322f7b1038..54b3d79804 100644 --- a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg +++ b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg @@ -1,39 +1 @@ - - - - rabbit-prototype-constructor.svg - Created with sketchtool. - - - - - - - - - - default "prototype" - - - - - Rabbit - - - rabbit - - - - - - [[Prototype]] - - - prototype - - - constructor - - - - \ No newline at end of file +default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file 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 ca995defcc..f3c9cf0e52 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 @@ -1,13 +1,13 @@ -可以使用 `Object.keys` 列出所有可枚举键值,然后输出。 +The method can take all enumerable keys using `Object.keys` and output their list. -为了使 `toString` 不可枚举,我们使用属性描述器来定义它。`Object.create` 语法允许我们为一个对象提供属性描述器作为第二参数。 +To make `toString` non-enumerable, let's define it using a property descriptor. The syntax of `Object.create` allows us to provide an object with property descriptors as the second argument. ```js run *!* let dictionary = Object.create(null, { -  toString: { // 定义 toString 方法 -    value() { // value 是一个函数 + toString: { // define toString property + value() { // the value is a function return Object.keys(this).join(); } } @@ -17,13 +17,15 @@ let dictionary = Object.create(null, { dictionary.apple = "Apple"; dictionary.__proto__ = "test"; -// apple 和 __proto__ 在循环内 +// apple and __proto__ is in the loop for(let key in dictionary) { -  alert(key); // "apple",然后 "__proto__" + alert(key); // "apple", then "__proto__" } -// 通过 toString 得到逗号分隔的属性值 -alert(dictionary.toString()); // "apple,__proto__" +// comma-separated list of properties by toString +alert(dictionary); // "apple,__proto__" ``` -当我们使用描述器创建一个属性,它的标识默认是 `false`。因此在以上代码中,`dictonary.toString` 是不可枚举的。 +When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable. + +See the chapter [](info:property-descriptors) for review. diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md index dd8b6cc440..0d831f2cc5 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md @@ -2,28 +2,28 @@ importance: 5 --- -# 给字典对象添加 toString 方法 +# Add toString to the dictionary -有一个对象 `dictionary`,通过 `Object.create(null)` 创建,用来存储任意键值对。 +There's an object `dictionary`, created as `Object.create(null)`, to store any `key/value` pairs. -为该对象添加方法 `dictionary.toString()`,返回所有键的列表,用逗号隔开。你的 `toString` 方法不能对该对象使用 `for...in`。 +Add method `dictionary.toString()` into it, that should return a comma-delimited list of keys. Your `toString` should not show up in `for..in` over the object. -以下是它的运行例子: +Here's how it should work: ```js let dictionary = Object.create(null); *!* -// 添加 dictionary.toString 方法的代码 +// your code to add dictionary.toString method */!* -// 添加一些数据 +// add some data dictionary.apple = "Apple"; -dictionary.__proto__ = "test"; // __proto__ 在这里是正常参数 +dictionary.__proto__ = "test"; // __proto__ is a regular property key here -// 只有 apple 和 __proto__ 在循环内 +// only apple and __proto__ are in the loop for(let key in dictionary) { -  alert(key); // "apple",然后 "__proto__" + alert(key); // "apple", then "__proto__" } // your toString in action diff --git a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md index 546ddb722d..90d3118bfd 100644 --- a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md @@ -1,7 +1,7 @@ -第一个调用中 `this == rabbit`,其他的 `this` 等同于 `Rabbit.prototype`,因为它是逗号之前的对象。 +The first call has `this == rabbit`, the other ones have `this` equal to `Rabbit.prototype`, because it's actually the object before the dot. -因此只有第一个调用显示 `Rabbit`,其他的都是 `undefined`: +So only the first call shows `Rabbit`, other ones show `undefined`: ```js run function Rabbit(name) { diff --git a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md index 796952dfa9..09bb7f1ed8 100644 --- a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md +++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 调用方式的差异 +# The difference between calls -让我们创建一个新的 `rabbit` 对象: +Let's create a new `rabbit` object: ```js function Rabbit(name) { @@ -17,7 +17,7 @@ Rabbit.prototype.sayHi = function() { let rabbit = new Rabbit("Rabbit"); ``` -以下调用得到的结果是否相同? +These calls do the same thing or not? ```js rabbit.sayHi(); diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index ff06c39163..71f118e1b9 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -1,37 +1,47 @@ -# 原型方法 +# Prototype methods, objects without __proto__ -本章节我们会讨论原型(prototype)的附加方法。 +In the first chapter of this section, we mentioned that there are modern methods to setup a prototype. -获取/设置原型的方式有很多,我们已知的有: +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). -- [Object.create(proto[, descriptors])](mdn:js/Object/create) —— 利用 `proto` 作为 `[[Prototype]]` 和可选的属性描述来创建一个空对象。 -- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) —— 返回 `obj` 对象的 `[[Prototype]]`。 -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) —— 将 `obj` 对象的 `[[Prototype]]` 设置为 `proto`。 +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. + +For instance: ```js run let animal = { eats: true }; -// 以 animal 为原型创建一个新对象 +// create a new object with animal as a prototype *!* -let rabbit = Object.create(animal); +let rabbit = Object.create(animal); // same as {__proto__: animal} */!* alert(rabbit.eats); // true + *!* -alert(Object.getPrototypeOf(rabbit) === animal); // 获取 rabbit 的原型 +alert(Object.getPrototypeOf(rabbit) === animal); // true */!* *!* -Object.setPrototypeOf(rabbit, {}); // 将 rabbit 的原型更改为 {} +Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {} */!* ``` -`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: ```js run let animal = { @@ -47,40 +57,53 @@ let rabbit = Object.create(animal, { alert(rabbit.jumps); // true ``` -参数的格式同 章节中讨论的相同。 +The descriptors are in the same format as described in the chapter . -我们可以利用 `Object.create` 来实现比 `for..in` 循环赋值属性方式更强大的对象复制功能: +We can use `Object.create` to perform an object cloning more powerful than copying properties in `for..in`: ```js -// obj 对象的浅复制 -let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); +let clone = Object.create( + Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) +); ``` -这样实现了 `obj` 的完整复制,包含了所有属性:可枚举的和不可枚举的,数据属性以及 setters/getters —— 所有属性,以及正确的 `[[Prototype]]`。 +This call makes a truly exact copy of `obj`, including all properties: enumerable and non-enumerable, data properties and setters/getters -- everything, and with the right `[[Prototype]]`. + -# 原型简史 +## Brief history -如果我们计算有多少种方式来管理 `[[Prototype]]`,答案是很多!很多种方式! +There're so many ways to manage `[[Prototype]]`. How did that happen? Why? -为什么会出现这样的情况? +That's for historical reasons. -这里有历史遗留问题。 +The prototypal inheritance was in the language since its dawn, but the ways to manage it evolved over time. -- 在上古时代 `prototype` 作为一个构造函数的属性来运行。 -- 之后在 2012 年: `Object.create` 出现在标准中。它允许利用给定的原型来创建对象,但是不能 get/set 原型。因此许多浏览器厂商实现了非标准属性 `__proto__`,允许任何时候 get/set 原型。 -- 之后在 2015 年: `Object.setPrototypeOf` 和 `Object.getPrototypeOf` 被加入到标准中。 `__proto__` 在几乎所有地方都得到实现,因此它成了标准以外的替代方案 B,在非浏览器环境下,它的支持性是不确定的,可选的。 +- 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`? -从技术上来讲,我们可以在任何时候 get/set `[[Prototype]]`。但是通常我们只在创建对象的时候设置它一次,自那之后不再修改:`rabbit` 继承自 `animal`,之后不再改变。对此 JavaScript 引擎做了高度的优化。运行中利用 `Object.setPrototypeOf` 或者 `obj.__proto__=` 来更改 prototype 是一个非常缓慢的操作。但是,这是可行的。 +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. -...但是如果我们尝试存储**用户提供的**键(比如说:一个用户输入的字典),我们可以发现一个有趣的错误:所有的键都运行正常,除了 `"__proto__"`。 +```warn header="Don't change `[[Prototype]]` on existing objects if speed matters" +Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time and don't modify it anymore: `rabbit` inherits from `animal`, and that is not going to change. -看一下这个例子: +And JavaScript engines are highly optimized for this. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation as it breaks internal optimizations for object property access operations. So avoid it unless you know what you're doing, or JavaScript speed totally doesn't matter for you. +``` + +## "Very plain" objects [#very-plain] + +As we know, objects can be used as associative arrays to store key/value pairs. + +...But if we try to store *user-provided* keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except `"__proto__"`. + +Check out the example: ```js run let obj = {}; @@ -88,38 +111,52 @@ let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; -alert(obj[key]); // [object Object],而不是 "some value"! +alert(obj[key]); // [object Object], not "some value"! ``` -这里如果用户输入 `__proto__`,那么赋值将会被忽略! +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 an assignment a string to `__proto__` is ignored. + +But we didn't *intend* to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug! -我们不应该感到惊讶。`__proto__` 属性很特别:它必须是对象或者 `null` 值,字符串不能成为 prototype。 +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. -但是我们并不想实现这样的行为,对吗?我们想要存储键值对,然而键名为 `"__proto__"` 没有被正确存储。所以这是一个错误。在这里,结果并没有很严重。但是在其他用例中,prototype 可能被改变,因此可能导致完全意想不到的结果。 +What's worse -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side. -最可怕的是 —— 通常开发者完全不会考虑到这一点。这让类似的 bug 很难被发现,甚至使得它们容易遭到攻击,特别是当 JavaScript 被用在服务端的时候。 +Unexpected things also may happen when assigning to `obj.toString`, as it's a built-in object method. -这样的情况只出现在 `__proto__` 上,所有其他的属性都是正常被「赋值」。 +How can we avoid this problem? -怎么样避免这个错误呢? +First, we can just switch to using `Map` for storage instead of plain objects, then everything's fine: -首先,我们可以改用 `Map`,那么一切都迎刃而解。 +```js run +let map = new Map(); + +let key = prompt("What's the key?", "__proto__"); +map.set(key, "some value"); -但是 `Object` 同样可以运行得很好,因为语言制造者很早以前就注意到这一点。 +alert(map.get(key)); // "some value" (as intended) +``` -`__proto__` 根本不是一个对象的属性,只是 `Object.prototype` 的访问属性: +...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`: ![](object-prototype-2.svg) -因此,如果 `obj.__proto__` 被读取或者赋值,那么对应的 getter/setter 从它的原型被调用,它会获取/设置 `[[Prototype]]`。 +So, if `obj.__proto__` is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`. -就像开头所说:`__proto__` 是访问 `[[Prototype]]` 的方式,而不是 `[[prototype]]` 本身。 +As it was said in the beginning of this tutorial section: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself. -现在,我们想要使用一个对象作为关联数组,我们可以用一个小技巧: +Now, if we intend to use an object as an associative array and be free of such problems, we can do it with a little trick: ```js run *!* let obj = Object.create(null); +// or: obj = { __proto__: null } */!* let key = prompt("What's the key?", "__proto__"); @@ -128,126 +165,59 @@ obj[key] = "some value"; alert(obj[key]); // "some value" ``` -`Object.create(null)` 创建了一个空对象,这个对象没有原型(`[[Prototype]]` 是 `null`): +`Object.create(null)` creates an empty object without a prototype (`[[Prototype]]` is `null`): ![](object-prototype-null.svg) -因此,它没有继承 `__proto__` 的 getter/setter 方法。现在它像正常的数据属性一样运行,因此以上的例子运行正确。 +So, there is no inherited getter/setter for `__proto__`. Now it is processed as a regular data property, so the example above works right. -我们可以叫这样的对象「极简」或者「纯字典对象」,因此它们甚至比通常的简单对象 `{...}` 还要简单。 +We can call such objects "very plain" or "pure dictionary" objects, because they are even simpler than the regular plain object `{...}`. -这样的对象有一个缺点是缺少内置的对象方法,比如说 `toString`: +A downside is that such objects lack any built-in object methods, e.g. `toString`: ```js run *!* let obj = Object.create(null); */!* -alert(obj); // Error (没有 toString 方法) +alert(obj); // Error (no toString) ``` -...但是它们通常对关联数组而言还是很友好。 +...But that's usually fine for associative arrays. -请注意,和对象关系最密切的方法是 `Object.something(...)`,比如 `Object.keys(obj)` —— 它们不在 prototype 中,因此在极简对象中它们还是可以继续使用: +Note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects: ```js run let chineseDictionary = Object.create(null); -chineseDictionary.hello = "ni hao"; -chineseDictionary.bye = "zai jian"; +chineseDictionary.hello = "你好"; +chineseDictionary.bye = "再见"; alert(Object.keys(chineseDictionary)); // hello,bye ``` -## 获取所有属性 - -获取一个对象的键/值有很多种方法。 - -我们已知的有: - -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- 返回一个数组,包含所有可枚举字符串属性名称/值/键值对。这些方法只会列出**可枚举**属性,而且它们**键名为字符串形式**。 - -如果我们想要 symbol 属性: - -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) —— 返回包含所有 symbol 属性名称的数组。 - -如果我们想要非可枚举属性: - -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) —— 返回包含所有字符串属性名的数组。 - -如果我们想要**所有**属性: - -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) —— 返回包含所有属性名称的数组。 - -这些方法和它们返回的属性有些不同,但它们都是对对象本身进行操作。prototype 的属性没有包含在内。 - -`for...in` 循环有所不同:它会对继承得来的属性也进行循环。 - -举个例子: - -```js run -let animal = { - eats: true -}; - -let rabbit = { - jumps: true, - __proto__: animal -}; - -*!* -// 这里只有自身的键 -alert(Object.keys(rabbit)); // jumps -*/!* - -*!* -// 这里包含了继承得来的键 -for(let prop in rabbit) alert(prop); // jumps,然后 eats -*/!* -``` - -如果我们想要区分继承属性,有一个内置方法 [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty):如果 `obj` 有名为 `key` 的自身属性(而非继承),返回值为 `true`。 - -因此我们可以找出继承属性(或者对它们进行一些操作): - -```js run -let animal = { - eats: true -}; +## Summary -let rabbit = { - jumps: true, - __proto__: animal -}; - -for(let prop in rabbit) { - let isOwn = rabbit.hasOwnProperty(prop); - alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false -} -``` -这个例子中我们有以下继承链:`rabbit`,然后 `animal`,然后 `Object.prototype` (因为 `animal` 是个字面量对象 `{...}`,因此默认是这样),然后最终到达 `null`: +- To create an object with the given prototype, use: -![](rabbit-animal-object.svg) + - literal syntax: `{ __proto__: ... }`, allows to specify multiple properties + - or [Object.create(proto, [descriptors])](mdn:js/Object/create), allows to specify property descriptors. -请注意:这里有一个有趣的现象。`rabbit.hasOwnProperty` 这个方法来自哪里?观察继承链我们发现这个方法由 `Object.prototype.hasOwnProperty` 提供。换句话说,它是继承得来的。 + The `Object.create` provides an easy way to shallow-copy an object with all descriptors: -...但是如果说 `for...in` 列出了所有继承属性,为什么 `hasOwnProperty` 这个方法没有出现在其中?答案很简单:它是不可枚举的。就像所有其他在 `Object.prototype` 中的属性一样。这是为什么它们没有被列出的原因。 + ```js + let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); + ``` -## 小结 +- Modern methods to get/set the prototype are: -以下是我们在本章节讨论的方法 —— 作为一个总结: + - [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). -- [Object.create(proto[, descriptors])](mdn:js/Object/create) —— 利用给定的 `proto` 作为 `[[Prototype]]` 来创建一个空对象。 -- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) —— 返回 `obj` 的 `[[Prototype]]`(和 `__proto__` getter 相同)。 -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) —— 将 `obj` 的 `[[Prototype]]` 设置为 `proto`(和 `__proto__` setter 相同)。 -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) —— 返回包含自身属性的名称/值/键值对的数组。 -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) —— 返回包含所有自身 symbol 属性名称的数组。 -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) —— 返回包含所有自身字符串属性名称的数组。 -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) —— 返回包含所有自身属性名称的数组。 -- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty):如果 `obj` 拥有名为 `key` 的自身属性(非继承得来),返回 `true`。 +- Getting/setting the prototype using the built-in `__proto__` getter/setter isn't recommended, it's now in the Annex B of the specification. -同时我们还明确了 `__proto__` 是 `[[Prototype]]` 的 getter/setter,位置在 `Object.prototype`,和其他方法相同。 +- We also covered prototype-less objects, created with `Object.create(null)` or `{__proto__: null}`. -我们可以不借助 prototype 创建一个对象,那就是 `Object.create(null)`。这些对象被用作是「纯字典」,对于它们而言 `"__proto__"` 作为键没有问题。 + These objects are used as dictionaries, to store any (possibly user-generated) keys. -所有返回对象属性的方法(如 `Object.keys` 以及其他)—— 都返回「自身」属性。如果我们想继承它们,我们可以使用 `for...in`。 + 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. diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg index d0bf3464ec..cf4d3023f8 100644 --- a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg +++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg @@ -1,38 +1 @@ - - - - object-prototype-2.svg - Created with sketchtool. - - - - - ... - get __proto__: function - set __proto__: function - - - - Object.prototype - - - - Object - - - obj - - - - - [[Prototype]] - - - prototype - - - constructor - - - - \ No newline at end of file +... get __proto__: function set __proto__: functionObject.prototypeObjectobj[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg index ba22dc601b..9ccb342299 100644 --- a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg +++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg @@ -1,21 +1 @@ - - - - object-prototype-null.svg - Created with sketchtool. - - - - - obj - - - - [[Prototype]] - - - null - - - - \ No newline at end of file +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/rabbit-animal-object.svg b/1-js/08-prototypes/04-prototype-methods/rabbit-animal-object.svg deleted file mode 100644 index 28daef3af8..0000000000 --- a/1-js/08-prototypes/04-prototype-methods/rabbit-animal-object.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - rabbit-animal-object.svg - Created with sketchtool. - - - - - toString: function - hasOwnProperty: function - ... - - - Object.prototype - - - - animal - - - - [[Prototype]] - - - [[Prototype]] - - - - [[Prototype]] - - - null - - - eats: true - - - - rabbit - - - - jumps: true - - - - \ No newline at end of file diff --git a/1-js/08-prototypes/index.md b/1-js/08-prototypes/index.md index 29ae2f7168..8554a0e30b 100644 --- a/1-js/08-prototypes/index.md +++ b/1-js/08-prototypes/index.md @@ -1 +1 @@ -# 原型,继承 +# Prototypes, inheritance 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 88a8bcaaf2..4477de6799 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 @@ -2,8 +2,8 @@ importance: 5 --- -# 重写为 class +# Rewrite to class -`Clock` 类是用函数式编写的,请以 “class” 语法重写它。 +The `Clock` class (see the sandbox) is written in functional style. Rewrite it in the "class" syntax. -P.S. 请打开控制台以查看时钟显示。 +P.S. The clock ticks in the console, open it to see. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index ce173985b3..135d24929b 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -1,22 +1,22 @@ -# Class 基本语法 +# Class basic syntax ```quote author="Wikipedia" -在面向对象的编程中,class 是用于创建对象的可扩展的程序代码模版,它为对象提供了状态(成员变量)的初始值和行为(成员函数和方法)的实现。 +In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods). ``` -在日常开发中,我们经常要创建许多相同类型的对象,比如用户(users)、商品(goods)或者其他任何东西。 +In practice, we often need to create many objects of the same kind, like users, or goods or whatever. -在 章节中我们已经知道,`new function` 可以做到这样。 +As we already know from the chapter , `new function` can help with that. -但是在现代 JavaScript 中,有一个更为高级的类(class)构造方式,它引入了对于面向对象编程很有用的功能。 +But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming. -## “class” 语法 +## The "class" syntax -基本语法是: +The basic syntax is: ```js class MyClass { - // class 方法 + // class methods constructor() { ... } method1() { ... } method2() { ... } @@ -25,11 +25,11 @@ class MyClass { } ``` -然后通过 `new MyClass()` 来创建具有上述列出的所有方法的新对象。 +Then use `new MyClass()` to create a new object with all the listed methods. -通过 `new` 关键词创建的对象会自动调用 `constructor()` 方法,因此我们可以在 `constructor()` 里初始化对象。 +The `constructor()` method is called automatically by `new`, so we can initialize the object there. -例如: +For example: ```js run class User { @@ -44,33 +44,33 @@ class User { } -// 使用方法: +// Usage: let user = new User("John"); user.sayHi(); ``` -当 `new User("John")` 被调用: -1. 一个新对象被创建。 -2. `constructor` 构造器使用给定的参数运行,并为其分配 `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`. -...然后我们就可以调用诸如 `user.sayHi` 这样的方法了。 +...Then we can call object methods, such as `user.sayHi()`. -```warn header="类方法之间没有逗号" -经验不足的开发者常犯的语法错误是在类方法之间加一个逗号。 +```warn header="No comma between class methods" +A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error. -这里的符号不要与对象字面量相混淆,在类中,不需要逗号。 +The notation here is not to be confused with object literals. Within the class, no commas are required. ``` -## 什么是 class? +## What is a class? -所以,`class` 到底是什么?正如人们可能认为的那样,这不是一个全新的语言级实体。 +So, what exactly is a `class`? That's not an entirely new language-level entity, as one might think. -让我们揭开其神秘面纱,看看类究竟是什么。这将会有助于你理解许多复杂的方面。 +Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects. -在 JavaScript 中,类是一种函数。 +In JavaScript, a class is a kind of function. -看看下面这段代码: +Here, take a look: ```js run class User { @@ -78,25 +78,24 @@ class User { sayHi() { alert(this.name); } } -// 佐证:User 是一个函数 +// proof: User is a function *!* alert(typeof User); // function */!* ``` -`class User {...}` 构造器内部为我们做了什么: -1. 创建一个以 `User` 为名称的函数,这是类声明的结果。 - - 函数代码来自于 `constructor` 中的方法(如果我们不写这样的方法,那么就假设它为空的)。 -2. 储存所有方法,例如 `User.prototype` 中的 `sayHi`。 +What `class User {...}` construct really does is: -然后,对于新的对象,当我们调用方法时,它取自原型,就像我们在 章节中描述的那样。所以 `new User` 对象可以访问类中的方法。 +1. Creates a function named `User`, that becomes the result of the class declaration. The function code is taken from the `constructor` method (assumed empty if we don't write such method). +2. Stores class methods, such as `sayHi`, in `User.prototype`. -我们可以将 `class User` 声明结果解释为: +After `new User` object is created, when we call its method, it's taken from the prototype, just as described in the chapter . So the object has access to class methods. -![](class-user.svg) +We can illustrate the result of `class User` declaration as: -下面这些代码很好的解释了它们: +![](class-user.svg) +Here's the code to introspect it: ```js run class User { @@ -104,50 +103,50 @@ class User { sayHi() { alert(this.name); } } -// 类是函数 +// class is a function alert(typeof User); // function -// ...或者,更确切地说是构造方法 +// ...or, more precisely, the constructor method alert(User === User.prototype.constructor); // true -// 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 -// 实际上在原型中有两个方法 +// there are exactly two methods in the prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ``` -## 不仅仅是语法糖 +## Not just a syntactic sugar -人们常说 `class` 是 JavaScript 中的语法糖,主要是因为我们可以在没有 `class` 的情况下声明同样的内容: +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: ```js run -// 以纯函数的重写 User 类 +// rewriting class User in pure functions -// 1. 创建构造器函数 +// 1. Create constructor function function User(name) { this.name = name; } -// 任何函数原型默认具有构造器属性, -// 所以,我们不需要创建它 +// a function prototype has "constructor" property by default, +// so we don't need to create it -// 2. 向原型中添加方法 +// 2. Add the method to prototype User.prototype.sayHi = function() { alert(this.name); }; -// 使用方法: +// Usage: let user = new User("John"); user.sayHi(); ``` -这和定义的结果大致相同。因此,这确实是 `class` 被视为一种定义构造函数及其原型方法的语法糖的理由。 +The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntactic sugar to define a constructor together with its prototype methods. -尽管它们存在重大差异: +Still, there are important differences. -1. 首先,通过 `class` 创建的函数是由特殊内部属性标记的 `[[FunctionKind]]:"classConstructor"`。所以,相较于手动创建它还是有点不同的。 +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. - 不像普通函数,调用类构造器时必须要用 `new` 关键词: + The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with `new`: ```js run class User { @@ -155,10 +154,10 @@ user.sayHi(); } alert(typeof User); // function - User(); // Error: 没有 ‘new’ 关键词,类构造器 User 无法调用 + User(); // Error: Class constructor User cannot be invoked without 'new' ``` - 此外,大多数 JavaScript 引擎中的类构造函数的字符串表示形式都以 “class” 开头 + Also, a string representation of a class constructor in most JavaScript engines starts with the "class..." ```js run class User { @@ -167,23 +166,23 @@ user.sayHi(); alert(User); // class User { ... } ``` + There are other differences, we'll see them soon. -2. 类方法不可枚举。 - 对于 `"prototype"` 中的所有方法,类定义将 `enumerable` 标记设为 `false。 - - 这很好,因为如果我们对一个对象调用 `for..in` 方法,我们通常不希望 class 方法出现。 +2. Class methods are non-enumerable. + A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. -3. 类默认使用 `use strict`。 - 在类构造函数中的所有方法自动使用严格模式。 + That's good, because if we `for..in` over an object, we usually don't want its class methods. +3. Classes always `use strict`. + All code inside the class construct is automatically in strict mode. -此外,除了它的基础操作外,`class` 语法也带来许多其他功能,我们稍后将会探索它们。 +Besides, `class` syntax brings many other features that we'll explore later. -## 类表达式(Class Expression) +## Class Expression -正如函数一样,类可以在另外一个表达式中定义,传递(passed around),返回(returned),调用(assigned)等 +Just like functions, classes can be defined inside another expression, passed around, returned, assigned, etc. -这里是类表达式的例子: +Here's an example of a class expression: ```js let User = class { @@ -193,55 +192,54 @@ let User = class { }; ``` -类似于命名函数表达式(Named Function Expressions),类表达式可能也可能没有名称。 +Similar to Named Function Expressions, class expressions may have a name. -如果类表达式有名称,它仅在类内部可见: +If a class expression has a name, it's visible inside the class only: ```js run -// “命名类表达式” -// (规范中没有这样的术语,但是它和命名函数表达式类似) +// "Named Class Expression" +// (no such term in the spec, but that's similar to Named Function Expression) let User = class *!*MyClass*/!* { sayHi() { - alert(MyClass); // MyClass 仅在其内部可见 + alert(MyClass); // MyClass name is visible only inside the class } }; -new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容 +new User().sayHi(); // works, shows MyClass definition -alert(MyClass); // 错误,MyClass 在外部不可见 +alert(MyClass); // error, MyClass name isn't visible outside of the class ``` - -我们甚至可以“按需”动态创建类,就像这样: +We can even make classes dynamically "on-demand", like this: ```js run function makeClass(phrase) { - // 声明并返回类 + // declare a class and return it return class { sayHi() { alert(phrase); - }; + } }; } -// 创建新类 +// Create a new class let User = makeClass("Hello"); new User().sayHi(); // Hello ``` -## Getters/setters 及其他 shorthands +## Getters/setters -就像对象字面量,类可能包括 getters/setters,generators,计算属性(computed properties)等。 +Just like literal objects, classes may include getters/setters, computed properties etc. -这是使用 `get/set` 实现 `user.name` 的示例: +Here's an example for `user.name` implemented using `get/set`: ```js run class User { constructor(name) { - // 调用 setter + // invokes the setter this.name = name; } @@ -266,31 +264,21 @@ class User { let user = new User("John"); alert(user.name); // John -user = new User(""); // Name too short. +user = new User(""); // Name is too short. ``` -类声明在 `User.prototype` 中创建 getters 和 setters,就像这样: +Technically, such class declaration works by creating getters and setters in `User.prototype`. -```js -Object.defineProperties(User.prototype, { - name: { - get() { - return this._name - }, - set(name) { - // ... - } - } -}); -``` +## Computed names [...] -这是计算属性的例子: +Here's an example with a computed method name using brackets `[...]`: ```js run -function f() { return "sayHi"; } - class User { - [f()]() { + +*!* + ['say' + 'Hi']() { +*/!* alert("Hello"); } @@ -299,53 +287,142 @@ class User { new User().sayHi(); ``` -对于 generator 方法,类似的,在它前面添加 `*`。 +Such features are easy to remember, as they resemble that of literal objects. -## Class 属性 +## Class fields -```warn header="旧的浏览器可能需要 polyfill" -类级别的属性是最近才添加到语言中的。 +```warn header="Old browsers may need a polyfill" +Class fields are a recent addition to the language. ``` -上面的例子中,`User` 只有方法。现在我们为其添加属性: +Previously, our classes only had methods. + +"Class fields" is a syntax that allows to add any properties. + +For instance, let's add `name` property to `class User`: ```js run class User { - name = "Anonymous"; +*!* + name = "John"; +*/!* sayHi() { alert(`Hello, ${this.name}!`); } } -new User().sayHi(); +new User().sayHi(); // Hello, John! +``` + +So, we just write " = " in the declaration, and that's it. + +The important difference of class fields is that they are set on individual objects, not `User.prototype`: + +```js run +class User { +*!* + name = "John"; +*/!* +} + +let user = new User(); +alert(user.name); // John +alert(User.prototype.name); // undefined +``` + +We can also assign values using more complex expressions and function calls: + +```js run +class User { +*!* + name = prompt("Name, please?", "John"); +*/!* +} + +let user = new User(); +alert(user.name); // John +``` + + +### Making bound methods with class fields + +As demonstrated in the chapter functions in JavaScript have a dynamic `this`. It depends on the context of the call. + +So if an object method is passed around and called in another context, `this` won't be a reference to its object any more. + +For instance, this code will show `undefined`: + +```js run +class Button { + constructor(value) { + this.value = value; + } + + click() { + alert(this.value); + } +} + +let button = new Button("hello"); + +*!* +setTimeout(button.click, 1000); // undefined +*/!* +``` + +The problem is called "losing `this`". + +There are two approaches to fixing it, as discussed in the chapter : + +1. Pass a wrapper-function, such as `setTimeout(() => button.click(), 1000)`. +2. Bind the method to object, e.g. in the constructor. + +Class fields provide another, quite elegant syntax: + +```js run +class Button { + constructor(value) { + this.value = value; + } +*!* + click = () => { + alert(this.value); + } +*/!* +} + +let button = new Button("hello"); + +setTimeout(button.click, 1000); // hello ``` -属性不在 `User.prototype` 内。相反它是通过 `new` 分别为每个对象创建的。所以,该属性永远不会在同一个类的不同对象之间共享。 +The class field `click = () => {...}` is created on a per-object basis, there's a separate function for each `Button` object, with `this` inside it referencing that object. We can pass `button.click` around anywhere, and the value of `this` will always be correct. +That's especially useful in browser environment, for event listeners. -## 总结 +## Summary -基本的类语法看起来是这样的: +The basic class syntax looks like this: ```js class MyClass { - prop = value; // field + prop = value; // property - constructor(...) { // 构造器 + constructor(...) { // constructor // ... } - method(...) {} // 方法 + method(...) {} // method - get something(...) {} // getter 方法 - set something(...) {} // setter 方法 + get something(...) {} // getter method + set something(...) {} // setter method - [Symbol.iterator]() {} // 计算 name/symbol 名方法 + [Symbol.iterator]() {} // method with computed name (symbol here) // ... } ``` -技术上来说,`MyClass` 是一个函数(我们提供作为 `constructor` 的那个),而 methods,getters 和 settors 都被写入 `MyClass.prototype`。 +`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and setters are written to `MyClass.prototype`. -在下一章,我们将会进一步研究类,包括继承在内的其他功能。 +In the next chapters we'll learn more about classes, including inheritance and other features. diff --git a/1-js/09-classes/01-class/class-user.svg b/1-js/09-classes/01-class/class-user.svg index 788e142292..418d71d187 100644 --- a/1-js/09-classes/01-class/class-user.svg +++ b/1-js/09-classes/01-class/class-user.svg @@ -1,29 +1 @@ - - - - class-user.svg - Created with sketchtool. - - - - - sayHi: function - - - - User - - - User.prototype - - - - prototype - - - constructor: User - - - - - \ No newline at end of file +sayHi: functionUserUser.prototypeprototypeconstructor: User \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md index c843511b30..4711e48271 100644 --- a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md +++ b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md @@ -1,6 +1,6 @@ -这是因为子类的构造函数必须调用 `super()`。 +That's because the child constructor must call `super()`. -这里是正确的代码: +Here's the corrected code: ```js run class Animal { @@ -25,5 +25,3 @@ let rabbit = new Rabbit("White Rabbit"); // ok now */!* alert(rabbit.name); // White Rabbit ``` - - diff --git a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md index 3eb73c1802..380a4720b2 100644 --- a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md +++ b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md @@ -2,12 +2,11 @@ importance: 5 --- -# 创建实例时出错 +# Error creating an instance -这里有一份代码,是 `Rabbit` 继承 `Animal`。 - -不幸的是,`Rabbit` 对象无法被创建,是哪里出错了呢?请解决这个问题。 +Here's the code with `Rabbit` extending `Animal`. +Unfortunately, `Rabbit` objects can't be created. What's wrong? Fix it. ```js run class Animal { @@ -29,5 +28,3 @@ let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined */!* alert(rabbit.name); ``` - - diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md index e69de29bb2..dcb4ffe59a 100644 --- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md @@ -0,0 +1 @@ +[js src="solution.view/extended-clock.js"] diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js index ca613ca5e5..be2053cfcf 100644 --- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js @@ -1,7 +1,7 @@ class ExtendedClock extends Clock { constructor(options) { super(options); - let { precision=1000 } = options; + let { precision = 1000 } = options; this.precision = precision; } diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md index 933637247a..bbc2c6a43c 100644 --- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md @@ -2,12 +2,14 @@ importance: 5 --- -# 继承 clock +# Extended clock -我们有一个 `Clock` 类。到目前为止,它每秒都会打印时间。 +We've got a `Clock` class. As of now, it prints the time every second. -从 `Clock` 继承并创建一个新的类 `ExtendedClock`,同时添加一个参数 `precision` —— 每次 "ticks" 之间间隔的毫秒数,默认是 `1000`(1秒)。 -- 你的代码在 `extended-clock.js` 文件里 -- 不要修改原来的 `clock.js`。只继承它。 +[js src="source.view/clock.js"] +Create a new class `ExtendedClock` that inherits from `Clock` and adds the parameter `precision` -- the number of `ms` between "ticks". Should be `1000` (1 second) by default. + +- Your code should be in the file `extended-clock.js` +- Don't modify the original `clock.js`. Extend it. diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg deleted file mode 100644 index 8ab5682913..0000000000 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - rabbit-extends-object.svg - Created with sketchtool. - - - - - call: function - bind: function - ... - - - - Function.prototype - - - - constructor - - - Object - - - Rabbit - - - - [[Prototype]] - - - - [[Prototype]] - - - constructor - - - - call: function - bind: function - ... - - - - Function.prototype - - - Rabbit - - - - [[Prototype]] - - - constructor - - - class Rabbit - - - class Rabbit extends Object - - - - \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md deleted file mode 100644 index c6aed741a2..0000000000 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md +++ /dev/null @@ -1,85 +0,0 @@ -首先,让我们看看为什么之前的代码无法运行。 - -如果我们尝试运行它,就会发现明显的原因。派生类的构造函数必须调用 `super()`。否则不会定义 `"this"`。 - -这里就是解决问题的代码: - -```js run -class Rabbit extends Object { - constructor(name) { -*!* - super(); // 需要在继承时调用父类的构造函数 -*/!* - this.name = name; - } -} - -let rabbit = new Rabbit("Rab"); - -alert( rabbit.hasOwnProperty('name') ); // true -``` - - -但这还不是全部原因。 - -即便是修复了问题,`"class Rabbit extends Object"` 和 `class Rabbit` 仍然存在着重要差异。 - -我们知道,"extends" 语法会设置两个原型: - -1. 在构造函数的 `"prototype"` 之间设置原型(为了获取实例方法) -2. 在构造函数之间会设置原型(为了获取静态方法) - -在我们的例子里,对于 `class Rabbit extends Object`,它意味着: - -```js run -class Rabbit extends Object {} - -alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true -alert( Rabbit.__proto__ === Object ); // (2) true -``` - -所以现在 `Rabbit` 对象可以通过 `Rabbit` 访问 `Object` 的静态方法,如下所示: - -```js run -class Rabbit extends Object {} - -*!* -// 通常我们调用 Object.getOwnPropertyNames -alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b -*/!* -``` - -但是如果我们没有声明 `extends Object`,那么 `Rabbit.__proto__` 将不会被设置为 `Object`。 - -这里有个示例: - -```js run -class Rabbit {} - -alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true -alert( Rabbit.__proto__ === Object ); // (2) false (!) -alert( Rabbit.__proto__ === Function.prototype ); // 所有函数都是默认如此 - -*!* -// 报错,Rabbit 上没有对应的函数 -alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error -*/!* -``` - -所以在这种情况下,`Rabbit` 无法访问 `Object` 的静态方法。 - -顺便说一下,`Function.prototype` 也有一些函数的通用方法,比如 `call`、`bind` 等等。在上述的两种情况下他们都是可用的,因为对于内置的 `Object` 构造函数来说,`Object.__proto__ === Function.prototype`。 - -这里有一张图来解释: - -![](rabbit-extends-object.svg) - - -所以,简而言之,这里有两点区别: - -| class Rabbit | class Rabbit extends Object | -|--------------|------------------------------| -| -- | needs to call `super()` in constructor | -| `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` | - - diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md deleted file mode 100644 index ce51926d73..0000000000 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md +++ /dev/null @@ -1,45 +0,0 @@ -importance: 5 - ---- - -# 类继承自对象? - -正如我们所知道的那样,所有的对象通常都继承自 `Object.prototype`,并且可以访问像 `hasOwnProperty` 那样的通用方法。 - -举个例子: - -```js run -class Rabbit { - constructor(name) { - this.name = name; - } -} - -let rabbit = new Rabbit("Rab"); - -*!* -// hasOwnProperty 方法来自 Object.prototype -// rabbit.__proto__ === Object.prototype -alert( rabbit.hasOwnProperty('name') ); // true -*/!* -``` - -但是如果我们明确的拼出 `"class Rabbit extends Object"`,那么结果会和简单的 `"class Rabbit"` 有所不同么? - -如果有的话,不同之处又在哪? - -这里是示例代码(它确实无法运行了,原因是什么?请解决它): - -```js -class Rabbit extends Object { - constructor(name) { - this.name = name; - } -} - -let rabbit = new Rabbit("Rab"); - -alert( rabbit.hasOwnProperty('name') ); // true -``` - - diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg index 7a55a5043c..63b5a18a19 100644 --- a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg +++ b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg @@ -1,64 +1 @@ - - - - animal-rabbit-extends.svg - Created with sketchtool. - - - - - constructor: Animal - run: function - stop: function - - - - Animal.prototype - - - - constructor: Rabbit - hide: function - - - Rabbit.prototype - - - - Animal - - - - Rabbit - - - new Rabbit - - - - - [[Prototype]] - - - - [[Prototype]] - - - prototype - - - - prototype - - - name: "White Rabbit" - - - constructor - - - constructor - - - - \ No newline at end of file +constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "White Rabbit"constructorconstructorextends \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 19b5cd7c2a..464042d823 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -1,9 +1,13 @@ -# 类继承 +# Class inheritance -假设我们有两个类: +Class inheritance is a way for one class to extend another class. -`Animal`: +So we can create new functionality on top of the existing. + +## The "extends" keyword + +Let's say we have class `Animal`: ```js class Animal { @@ -12,64 +16,31 @@ class Animal { this.name = name; } run(speed) { - this.speed += speed; + this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; - alert(`${this.name} stopped.`); + alert(`${this.name} stands still.`); } } let animal = new Animal("My animal"); ``` -![](rabbit-animal-independent-animal.svg) - - -...和 `Rabbit`: - -```js -class Rabbit { - constructor(name) { - this.name = name; - } - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbit = new Rabbit("My rabbit"); -``` - -![](rabbit-animal-independent-rabbit.svg) - +Here's how we can represent `animal` object and `Animal` class graphically: -现在,它们是完全独立的。 +![](rabbit-animal-independent-animal.svg) -但是,我们想要 `Rabbit` 继承自 `Animal`。换句话说,rabbits 应该基于 animals,能够访问 `Animal` 中的方法,并使用自己的方法扩展它们。 +...And we would like to create another `class Rabbit`. -要继承自另一个类,我们需要在 `{..}` 前指定 `“extends”` 和父类。 +As rabbits are animals, `Rabbit` class should be based on `Animal`, have access to animal methods, so that rabbits can do what "generic" animals can do. -在这里,`Rabbit` 继承自 `Animal`: +The syntax to extend another class is: `class Child extends Parent`. -```js run -class Animal { - constructor(name) { - this.speed = 0; - this.name = name; - } - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } -} +Let's create `class Rabbit` that inherits from `Animal`: -// 通过指定“extends Animal”让 Rabbit 继承自 Animal +```js *!* class Rabbit extends Animal { */!* @@ -84,26 +55,29 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` -现在 `Rabbit` 代码变简洁了一点,因为它默认以 `Animal` 作为其构造函数,并且它能 `run`,就像 animals 一样。 +Object of `Rabbit` class have access both to `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. -在其内部,`extends` 关键字添加了 `[[Prototype]]` 引用:从 `Rabbit.prototype` 到 `Animal.prototype`: +Internally, `extends` keyword works using the good old prototype mechanics. It sets `Rabbit.prototype.[[Prototype]]` to `Animal.prototype`. So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. ![](animal-rabbit-extends.svg) -因此,如果在 `Rabbit.prototype` 中没有找到某个方法,JavaScript 将会从 `Animal.prototype` 中获取它。 +For instance, to find `rabbit.run` method, the engine checks (bottom-up on the picture): +1. The `rabbit` object (has no `run`). +2. Its prototype, that is `Rabbit.prototype` (has `hide`, but not `run`). +3. Its prototype, that is (due to `extends`) `Animal.prototype`, that finally has the `run` method. -我们可以回忆一下这一章 ,JavaScript 内置对象同样也是基于原型继承的。例如,`Date.prototype.[[Prototype]]` 是 `Object.prototype`,所以 dates 有通用的对象方法。 +As we can recall from the chapter , JavaScript itself uses prototypal inheritance for built-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`. That's why dates have access to generic object methods. -````smart header="`extends` 允许后接任何表达式" -类语法不仅可以指定一个类,还可以指定 `extends` 之后的任何表达式。 +````smart header="Any expression is allowed after `extends`" +Class syntax allows to specify not just a class, but any expression after `extends`. -例如,一个生成父类的函数调用: +For instance, a function call that generates the parent class: ```js run function f(phrase) { return class { - sayHi() { alert(phrase) } - } + sayHi() { alert(phrase); } + }; } *!* @@ -112,34 +86,34 @@ class User extends f("Hello") {} new User().sayHi(); // Hello ``` -这里 `class User` 继承自 `f("Hello")` 的结果。 +Here `class User` inherits from the result of `f("Hello")`. -我们可以根据多种状况使用函数生成类,并继承它们,这对于高级编程模式来说可能很有用。 +That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them. ```` -## 重写方法 +## Overriding a method -现在,让我们继续前行并尝试重写一个方法。到目前为止,`Rabbit` 继承了 `Animal` 中的 `stop` 方法,该方法设置了 `this.speed = 0`。 +Now let's move forward and override a method. By default, all methods that are not specified in `class Rabbit` are taken directly "as is" from `class Animal`. -如果我们在 `Rabbit` 中定义了我们自己的 `stop` 方法,那么它将被用来代替 `Animal` 中的 `stop`: +But if we specify our own method in `Rabbit`, such as `stop()` then it will be used instead: ```js class Rabbit extends Animal { stop() { - // ...这将用于 rabbit.stop() + // ...now this will be used for rabbit.stop() + // instead of stop() from class Animal } } ``` +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. -...但是通常来说,我们不希望完全替换父类的方法,而是希望基于它做一些调整或者功能性的扩展。我们在我们的方法中做一些事情,但是在它之前/之后或在执行过程中调用父类方法。 - -为此,类提供了 `"super"` 关键字。 +Classes provide `"super"` keyword for that. -- 执行 `super.method(...)` 调用父类方法。 -- 执行 `super(...)` 调用父类构造函数(只能在子类的构造函数中运行)。 +- `super.method(...)` to call a parent method. +- `super(...)` to call a parent constructor (inside our constructor only). -例如,让我们的兔子在停下来的时候自动隐藏: +For instance, let our rabbit autohide when stopped: ```js run class Animal { @@ -150,13 +124,13 @@ class Animal { } run(speed) { - this.speed += speed; + this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; - alert(`${this.name} stopped.`); + alert(`${this.name} stands still.`); } } @@ -168,8 +142,8 @@ class Rabbit extends Animal { *!* stop() { - super.stop(); // 调用父类的 stop 函数 - this.hide(); // 然后隐藏 + super.stop(); // call parent stop + this.hide(); // and then hide } */!* } @@ -177,24 +151,25 @@ class Rabbit extends Animal { let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stopped. White rabbit hides! +rabbit.stop(); // White Rabbit stands still. White Rabbit hides! ``` -现在 `Rabbit` 拥有自己的 `stop` 方法,并且在执行中会调用父类的 `super.stop()`。 +Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. + +````smart header="Arrow functions have no `super`" +As was mentioned in the chapter , arrow functions do not have `super`. -````smart header="箭头函数没有 `super`" -正如我们在 章节中所提到的,箭头函数没有 `super`。 +If accessed, it's taken from the outer function. For instance: -如果被访问,它会从外部函数获取。例如: ```js class Rabbit extends Animal { stop() { - setTimeout(() => super.stop(), 1000); // 1 秒后调用父类 stop 方法 + setTimeout(() => super.stop(), 1000); // call parent stop after 1sec } } ``` -箭头函数中的 `super` 与 `stop()` 中的是相同的,所以它能按预期工作。如果我们在这里指定一个“普通”函数,那么将会抛出错误: +The `super` in the arrow function is the same as in `stop()`, so it works as intended. If we specified a "regular" function here, there would be an error: ```js // Unexpected super @@ -202,18 +177,17 @@ setTimeout(function() { super.stop() }, 1000); ``` ```` +## Overriding constructor -## 重写构造函数 +With constructors it gets a little bit tricky. -对于构造函数来说,重写则有点棘手。 +Until now, `Rabbit` did not have its own `constructor`. -到目前为止,`Rabbit` 还没有自己的 `constructor`。 - -根据 [规范](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation),如果一个类继承了另一个类并且没有 `constructor`,那么将生成以下“空” `constructor`: +According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated: ```js class Rabbit extends Animal { - // 为没有构造函数的继承类生成以下的构造函数 + // generated for extending classes without own constructors *!* constructor(...args) { super(...args); @@ -222,9 +196,9 @@ class Rabbit extends Animal { } ``` -我们可以看到,它调用了父类的 `constructor`,并传递了所有的参数。如果我们不写自己的构造函数,就会出现这种情况。 +As we can see, it basically calls the parent `constructor` passing it all the arguments. That happens if we don't write a constructor of our own. -现在,我们给 `Rabbit` 添加一个自定义的构造函数。除了 `name` 它还会定义 `earLength`。 +Now let's add a custom constructor to `Rabbit`. It will specify the `earLength` in addition to `name`: ```js run class Animal { @@ -249,29 +223,31 @@ class Rabbit extends Animal { } *!* -// 不生效! +// Doesn't work! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. */!* ``` -哎呦!我们得到一个报错。现在我们没法新建兔子了。是什么地方出错了? +Whoops! We've got an error. Now we can't create rabbits. What went wrong? + +The short answer is: -简短的解释下原因:继承类的构造函数必须调用 `super(...)`,并且 (!) 一定要在使用 this 之前调用。 +- **Constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`.** -...但这是为什么呢?这里发生了什么?这个要求确实看起来很奇怪。 +...But why? What's going on here? Indeed, the requirement seems strange. -当然,本文会给出一个解释。让我们深入细节,这样你就可以真正的理解发生了什么。 +Of course, there's an explanation. Let's get into details, so you'll really understand what's going on. -在 JavaScript 中,“继承类的构造函数”与所有其他的构造函数之间存在区别。在继承类中,相应的构造函数会被标记为特殊的内部属性 `[[ConstructorKind]]:"derived"`。 +In JavaScript, there's a distinction between a constructor function of an inheriting class (so-called "derived constructor") and other functions. A derived constructor has a special internal property `[[ConstructorKind]]:"derived"`. That's a special internal label. -不同点就在于: +That label affects its behavior with `new`. -- 当一个普通构造函数执行时,它会创建一个空对象作为 `this` 并继续执行。 -- 但是当继承的构造函数执行时,它并不会做这件事。它期望父类的构造函数来完成这项工作。 +- When a regular function is executed with `new`, it creates an empty object and assigns it to `this`. +- But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job. -因此,如果我们构建了我们自己的构造函数,我们必须调用 `super`,因为如果不这样的话 `this` 指向的对象不会被创建。并且我们会收到一个报错。 +So a derived constructor must call `super` in order to execute its parent (base) constructor, otherwise the object for `this` won't be created. And we'll get an error. -为了让 `Rabbit` 可以运行,我们需要在使用 `this` 之前调用 `super()`,就像下面这样: +For the `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here: ```js run class Animal { @@ -297,27 +273,128 @@ class Rabbit extends Animal { } *!* -// 现在可以了 +// now fine let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10 */!* ``` +### Overriding class fields: a tricky note + +```warn header="Advanced note" +This note assumes you have a certain experience with classes, maybe in other programming languages. -## Super 内部探究:[[HomeObject]] +It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often). -让我们再深入的去研究下 `super`。顺便说一句,我们会发现一些有趣的事情。 +If you find it difficult to understand, just go on, continue reading, then return to it some time later. +``` + +We can override not only methods, but also class fields. -首先要说的是,从我们迄今为止学到的知识来看,`super` 是不可能运行的。 +Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages. -的确是这样,让我们问问自己,在技术上它是如何实现的?当一个对象方法运行时,它会将当前对象作为 `this`。如果之后我们调用 `super.method()`,它需要从当前对象的原型中调用 `method`。 +Consider this example: -任务看起来是挺容易的,但其实并不简单。引擎知道当前对象的 `this`,因此它可以获取父 `method` 作为 `this.__proto__.method`。不幸的是,这个“天真”的解决方法是行不通的。 +```js run +class Animal { + name = 'animal'; -让我们来说明一下这个问题。没有类,为简单起见,使用普通对象。 + constructor() { + alert(this.name); // (*) + } +} -在下面的例子中是 `rabbit.__proto__ = animal`。现在让我们尝试一下:在 `rabbit.eat()` 我们将会通过 `this.__proto__` 调用 `animal.eat()`: +class Rabbit extends Animal { + name = 'rabbit'; +} + +new Animal(); // animal +*!* +new Rabbit(); // animal +*/!* +``` + +Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value. + +There's no own constructor in `Rabbit`, so `Animal` constructor is called. + +What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`. + +**In other words, the parent constructor always uses its own field value, not the overridden one.** + +What's odd about it? + +If it's not clear yet, please compare with methods. + +Here's the same code, but instead of `this.name` field we call `this.showName()` method: + +```js run +class Animal { + showName() { // instead of this.name = 'animal' + alert('animal'); + } + + constructor() { + this.showName(); // instead of alert(this.name); + } +} + +class Rabbit extends Animal { + showName() { + alert('rabbit'); + } +} + +new Animal(); // animal +*!* +new Rabbit(); // rabbit +*/!* +``` + +Please note: now the output is different. + +And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method. + +...But for class fields it's not so. As said, the parent constructor always uses the parent field. + +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. + +In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`. + +So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used. + +This subtle difference between fields and methods is specific to JavaScript. + +Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here. + +If it becomes a problem, one can fix it by using methods or getters/setters instead of fields. + +## Super: internals, [[HomeObject]] + +```warn header="Advanced information" +If you're reading the tutorial for the first time - this section may be skipped. + +It's about the internal mechanisms behind inheritance and `super`. +``` + +Let's get a little deeper under the hood of `super`. We'll see some interesting things along the way. + +First to say, from all that we've learned till now, it's impossible for `super` to work at all! + +Yeah, indeed, let's ask ourselves, how it should technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, the engine needs to get the `method` from the prototype of the current object. But how? + +The task may seem simple, but it isn't. The engine knows the current object `this`, so it could get the parent `method` as `this.__proto__.method`. Unfortunately, such a "naive" solution won't work. + +Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity. + +You may skip this part and go below to the `[[HomeObject]]` subsection if you don't want to know the details. That won't harm. Or read on if you're interested in understanding things in-depth. + +In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`: ```js run let animal = { @@ -332,7 +409,7 @@ let rabbit = { name: "Rabbit", eat() { *!* - // 这就是 super.eat() 可能运行的原因 + // that's how super.eat() could presumably work this.__proto__.eat.call(this); // (*) */!* } @@ -341,11 +418,11 @@ let rabbit = { rabbit.eat(); // Rabbit eats. ``` -在 (\*) 这一行,我们从原型(`animal`)上获取 `eat`,并在当前对象的上下文中调用它。请注意,`.call(this)` 在这里非常重要,因为简单的调用 `this.__proto__.eat()` 将在原型的上下文中执行 `eat`,而非当前对象。 +At the line `(*)` we take `eat` from the prototype (`animal`) and call it in the context of the current object. Please note that `.call(this)` is important here, because a simple `this.__proto__.eat()` would execute parent `eat` in the context of the prototype, not the current object. -在上述的代码中,它按照期望运行:我们获得了正确的 `alert`。 +And in the code above it actually works as intended: we have the correct `alert`. -现在让我们在原型链上再添加一个额外的对象。我们将看到这件事是如何被打破的: +Now let's add one more object to the chain. We'll see how things break: ```js run let animal = { @@ -358,7 +435,7 @@ let animal = { let rabbit = { __proto__: animal, eat() { - // ...围绕 rabbit-style 和 调用父类(animal)方法 + // ...bounce around rabbit-style and call parent (animal) method this.__proto__.eat.call(this); // (*) } }; @@ -366,7 +443,7 @@ let rabbit = { let longEar = { __proto__: rabbit, eat() { - // ...用长耳朵做一些事情,并调用父类(rabbit)的方法 + // ...do something with long ears and call parent (rabbit) method this.__proto__.eat.call(this); // (**) } }; @@ -376,49 +453,49 @@ longEar.eat(); // Error: Maximum call stack size exceeded */!* ``` -代码无法再运行了!我们可以看到,在试图调用 `longEar.eat()` 时抛出了错误。 +The code doesn't work anymore! We can see the error trying to call `longEar.eat()`. -原因可能不那么明显,但是如果我们跟踪 `longEar.eat()` 的调用,就可以发现原因。在 (\*) 和 (\*\*) 这两行中,`this` 的值都是当前对象(`longEar`)。这是至关重要的一点:所有的对象方法都将当前对象作为 `this`,而非原型或其他什么东西。 +It may be not that obvious, but if we trace `longEar.eat()` call, then we can see why. In both lines `(*)` and `(**)` the value of `this` is the current object (`longEar`). That's essential: all object methods get the current object as `this`, not a prototype or something. -因此,在 (\*) 和 (\*\*) 这两行中,`this.__proto__` 的值是完全相同的:都是 `rabbit`。在这个无限循环中,他们都调用了 `rabbit.eat`,而不是在原型链上向上寻找方法。 +So, in both lines `(*)` and `(**)` the value of `this.__proto__` is exactly the same: `rabbit`. They both call `rabbit.eat` without going up the chain in the endless loop. -这张图介绍了发生的情况: +Here's the picture of what happens: ![](this-super-loop.svg) -1. 在 `longEar.eat()` 中,(\*\*) 这一行调用 `rabbit.eat` 并且此时 `this=longEar`。 +1. Inside `longEar.eat()`, the line `(**)` calls `rabbit.eat` providing it with `this=longEar`. ```js - // 在 longEar.eat() 中 this 指向 longEar + // inside longEar.eat() we have this = longEar this.__proto__.eat.call(this) // (**) - // 变成了 + // becomes longEar.__proto__.eat.call(this) - // 即等同于 + // that is rabbit.eat.call(this); ``` -2. 之后在 `rabbit.eat` 的 (\*) 行中,我们希望将函数调用在原型链上向更高层传递,但是因为 `this=longEar`,因此 `this.__proto__.eat` 又是 `rabbit.eat`! +2. Then in the line `(*)` of `rabbit.eat`, we'd like to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` is again `rabbit.eat`! ```js - // 在 rabbit.eat() 中 this 依旧等于 longEar + // inside rabbit.eat() we also have this = longEar this.__proto__.eat.call(this) // (*) - // 变成了 + // becomes longEar.__proto__.eat.call(this) - // 再次等同于 + // or (again) rabbit.eat.call(this); ``` -3. ...所以 `rabbit.eat` 不停地循环调用自己,因此它无法进一步地往原型链的更高层调用。 +3. ...So `rabbit.eat` calls itself in the endless loop, because it can't ascend any further. -这个问题没法单独使用 `this` 来解决。 +The problem can't be solved by using `this` alone. ### `[[HomeObject]]` -为了提供解决方法,JavaScript 为函数额外添加了一个特殊的内部属性:`[[HomeObject]]`。 +To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`. -当一个函数被定义为类或者对象方法时,它的 `[[HomeObject]]` 属性就成为那个对象。 +When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object. -然后 `super` 使用它来解析父类原型和它自己的方法。 +Then `super` uses it to resolve the parent prototype and its methods. -让我们看看它是怎么工作的,首先,对于普通对象: +Let's see how it works, first with plain objects: ```js run let animal = { @@ -445,30 +522,31 @@ let longEar = { }; *!* -// 正常运行 +// works correctly longEar.eat(); // Long Ear eats. */!* ``` -它按照预期运行,基于 `[[HomeObject]]` 运行机制。像 `longEar.eat` 这样的方法,知道 `[[HomeObject]]`,并且从它的原型中获取父类方法。并没有使用 `this`。 +It works as intended, due to `[[HomeObject]]` mechanics. A method, such as `longEar.eat`, knows its `[[HomeObject]]` and takes the parent method from its prototype. Without any use of `this`. -### 方法并不是“自由”的 +### Methods are not "free" -在前面我们已经知道,通常函数都是 “自由” 的,并没有绑定到 JavaScript 中的对象。正因如此,它们可以在对象之间复制,并且用另外一个 `this` 调用它。 +As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another `this`. -`[[HomeObject]]` 的存在违反了这个原则,因为方法记住了它们的对象。`[[HomeObject]]` 不能被修改,所以这个绑定是永久的。 +The very existence of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. -在 JavaScript 语言中 `[[HomeObject]]` 仅被用于 `super`。所以,如果一个方法不使用 `super`,那么我们仍然可以视它为自由的并且可在对象之间复制。但是在 `super` 中可能出错。 +The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. But with `super` things may go wrong. -下面是错误的 `super` 调用示例: +Here's the demo of a wrong `super` result after copying: ```js run let animal = { sayHi() { - console.log(`I'm an animal`); + alert(`I'm an animal`); } }; +// rabbit inherits from animal let rabbit = { __proto__: animal, sayHi() { @@ -478,10 +556,11 @@ let rabbit = { let plant = { sayHi() { - console.log("I'm a plant"); + alert("I'm a plant"); } }; +// tree inherits from plant let tree = { __proto__: plant, *!* @@ -494,26 +573,28 @@ tree.sayHi(); // I'm an animal (?!?) */!* ``` -调用 `tree.sayHi()` 显示 “I'm an animal”。绝对是错误的。 +A call to `tree.sayHi()` shows "I'm an animal". Definitely wrong. + +The reason is simple: +- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication? +- Its `[[HomeObject]]` is `rabbit`, as it was created in `rabbit`. There's no way to change `[[HomeObject]]`. +- The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`. -原因很简单: -- 在 (\*) 行,`tree.sayHi` 方法从 `rabbit` 复制而来。也许我们只是想避免重复代码? -- 所以它的 `[[HomeObject]]` 是 `rabbit`,因为他是在 `rabbit` 中创建的。没有办法修改 `[[HomeObject]]`。 -- `tree.sayHi()` 内具有 `super.sayHi()`。它从 `rabbit` 中上溯,然后从 `animal` 中获取方法。 +Here's the diagram of what happens: ![](super-homeobject-wrong.svg) -### 方法,不是函数属性 +### Methods, not function properties -`[[HomeObject]]` 是为类和普通对象中的方法定义的。但是对于对象来说,方法必须确切指定为 `method()`,而不是 `"method: function()"`。 +`[[HomeObject]]` is defined for methods both in classes and in plain objects. But for objects, methods must be specified exactly as `method()`, not as `"method: function()"`. -这个差别对我们来说可能不重要,但是对 JavaScript 来说却是非常重要的。 +The difference may be non-essential for us, but it's important for JavaScript. -下面的例子中,使用非方法(non-method)语句进行比较。`[[HomeObject]]` 属性未设置,并且继承不起作用: +In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work: ```js run let animal = { - eat: function() { // 可以使用简短写法:eat() {...} + eat: function() { // intentionally writing like this instead of eat() {... // ... } }; @@ -526,21 +607,21 @@ let rabbit = { }; *!* -rabbit.eat(); // 错误调用 super(因为这里并没有 [[HomeObject]]) +rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) */!* ``` -## 总结 +## Summary -1. 扩展类:`class Child extends Parent`: - - 这就意味着 `Child.prototype.__proto__` 将是 `Parent.prototype`,所以方法被继承。 -2. 重写构造函数: - - 在使用 `this` 之前,我们必须在 `Child` 构造函数中将父构造函数调用为 `super()`。 -3. 重写方法: - - 我们可以在 `Child` 方法中使用 `super.method()` 来调用 `Parent` 方法。 -4. 内部工作: - - 方法在内部 `[[HomeObject]]` 属性中记住它们的类/对象。这就是 `super` 如何解析父类方法的。 - - 因此,将一个带有 `super` 的方法从一个对象复制到另一个对象是不安全的。 +1. To extend a class: `class Child extends Parent`: + - That means `Child.prototype.__proto__` will be `Parent.prototype`, so methods are inherited. +2. When overriding a constructor: + - We must call parent constructor as `super()` in `Child` constructor before using `this`. +3. When overriding another method: + - We can use `super.method()` in a `Child` method to call `Parent` method. +4. Internals: + - Methods remember their class/object in the internal `[[HomeObject]]` property. That's how `super` resolves parent methods. + - So it's not safe to copy a method with `super` from one object to another. -补充: -- 箭头函数没有自己的 `this` 或 `super`,所以它们能融入到就近的上下文,像透明似的。 +Also: +- Arrow functions don't have their own `this` or `super`, so they transparently fit into the surrounding context. diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg index 6f2124ebbc..5ea9bf29ea 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg @@ -1,41 +1 @@ - - - - class-inheritance-array-object.svg - Created with sketchtool. - - - - - slice: function - ... - - - Array.prototype - - - arr - - - - hasOwnProperty: function - ... - - - Object.prototype - - - - - - [1, 2, 3] - - - [[Prototype]] - - - [[Prototype]] - - - - \ No newline at end of file +slice: function ...Array.prototypearrhasOwnProperty: function ...Object.prototype[1, 2, 3][[Prototype]][[Prototype]] \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg index 9714d67087..72e47e34c2 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg @@ -1,62 +1 @@ - - - - class-inheritance-rabbit-animal-2.svg - Created with sketchtool. - - - - - jump: function - - - Rabbit.prototype - - - rabbit - - - - eat: function - - - Animal.prototype - - - - - - name: "White Rabbit" - - - [[Prototype]] - - - [[Prototype]] - - - Rabbit.prototype.__proto__ = Animal.prototype sets this - - - - toString: function - hasOwnProperty: function - ... - - - Object.prototype - - - - [[Prototype]] - - - - [[Prototype]] - - - null - - - - \ No newline at end of file +jump: functionRabbit.prototyperabbiteat: functionAnimal.prototypename: "White Rabbit"[[Prototype]][[Prototype]]Rabbit.prototype.__proto__ = Animal.prototype sets thistoString: function hasOwnProperty: function ...Object.prototype[[Prototype]][[Prototype]]null \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg index 249bfa4c97..bced3d355e 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg @@ -1,39 +1 @@ - - - - class-inheritance-rabbit-animal.svg - Created with sketchtool. - - - - - methods of Rabbit - - - Rabbit.prototype - - - rabbit - - - - methods of Animal - - - Animal.prototype - - - - - - [[Prototype]] - - - [[Prototype]] - - - properties of rabbit - - - - \ No newline at end of file +methods of RabbitRabbit.prototyperabbitmethods of AnimalAnimal.prototype[[Prototype]][[Prototype]]properties of rabbit \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg index 7a16b78550..f53fc92dee 100644 --- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg @@ -1,42 +1 @@ - - - - rabbit-animal-independent-animal.svg - Created with sketchtool. - - - - - - constructor: Animal - run: function - stop: function - - - Animal.prototype - - - - Animal - - - - new Animal - - - - - [[Prototype]] - - - prototype - - - name: "My animal" - - - constructor - - - - \ No newline at end of file + constructor: Animal run: function stop: functionAnimal.prototypeAnimalnew Animal[[Prototype]]prototypename: "My animal" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg index ab936c1516..2f30a3a901 100644 --- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg @@ -1,41 +1 @@ - - - - rabbit-animal-independent-rabbit.svg - Created with sketchtool. - - - - - - constructor: Rabbit - hide: function - - - Rabbit.prototype - - - - Rabbit - - - - new Rabbit - - - - - [[Prototype]] - - - prototype - - - name: "My rabbit" - - - constructor - - - - \ No newline at end of file + constructor: Rabbit hide: functionRabbit.prototypeRabbitnew Rabbit[[Prototype]]prototypename: "My rabbit" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg index 9a24f0cd9d..f6450ddc49 100644 --- a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg +++ b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg @@ -1,44 +1 @@ - - - - super-homeobject-wrong.svg - Created with sketchtool. - - - - - sayHi - - - plant - - - - sayHi - - - tree - - - - - - sayHi - - - - animal - - - rabbit - - - - [[HomeObject]] - - - sayHi - - - - \ No newline at end of file +sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop.svg b/1-js/09-classes/02-class-inheritance/this-super-loop.svg index 7862b41c01..4f5f45034a 100644 --- a/1-js/09-classes/02-class-inheritance/this-super-loop.svg +++ b/1-js/09-classes/02-class-inheritance/this-super-loop.svg @@ -1,72 +1 @@ - - - - this-super-loop.svg - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rabbit - - - longEar - - - rabbit - - - longEar - - - - - - - - \ No newline at end of file +rabbitlongEarrabbitlongEar \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg new file mode 100644 index 0000000000..915ab9aa64 --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg @@ -0,0 +1 @@ +call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file 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 new file mode 100644 index 0000000000..cb9829ce05 --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md @@ -0,0 +1,81 @@ +First, let's see why the latter code doesn't work. + +The reason becomes obvious if we try to run it. An inheriting class constructor must call `super()`. Otherwise `"this"` won't be "defined". + +So here's the fix: + +```js run +class Rabbit extends Object { + constructor(name) { +*!* + super(); // need to call the parent constructor when inheriting +*/!* + this.name = name; + } +} + +let rabbit = new Rabbit("Rab"); + +alert( rabbit.hasOwnProperty('name') ); // true +``` + +But that's not all yet. + +Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`. + +As we know, the "extends" syntax sets up two prototypes: + +1. Between `"prototype"` of the constructor functions (for methods). +2. Between the constructor functions themselves (for static methods). + +In the case of `class Rabbit extends Object` it means: + +```js run +class Rabbit extends Object {} + +alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true +alert( Rabbit.__proto__ === Object ); // (2) true +``` + +So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this: + +```js run +class Rabbit extends Object {} + +*!* +// normally we call Object.getOwnPropertyNames +alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b +*/!* +``` + +But if we don't have `extends Object`, then `Rabbit.__proto__` is not set to `Object`. + +Here's the demo: + +```js run +class Rabbit {} + +alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true +alert( Rabbit.__proto__ === Object ); // (2) false (!) +alert( Rabbit.__proto__ === Function.prototype ); // as any function by default + +*!* +// error, no such function in Rabbit +alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error +*/!* +``` + +So `Rabbit` doesn't provide access to static methods of `Object` in that case. + +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`. + +Here's the picture: + +![](rabbit-extends-object.svg) + +So, to put it short, there are two differences: + +| class Rabbit | class Rabbit extends Object | +|--------------|------------------------------| +| -- | needs to call `super()` in constructor | +| `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` | diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md new file mode 100644 index 0000000000..1d0f98a74e --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md @@ -0,0 +1,42 @@ +importance: 3 + +--- + +# Class extends Object? + +As we know, all objects normally inherit from `Object.prototype` and get access to "generic" object methods like `hasOwnProperty` etc. + +For instance: + +```js run +class Rabbit { + constructor(name) { + this.name = name; + } +} + +let rabbit = new Rabbit("Rab"); + +*!* +// hasOwnProperty method is from Object.prototype +alert( rabbit.hasOwnProperty('name') ); // true +*/!* +``` + +But if we spell it out explicitly like `"class Rabbit extends Object"`, then the result would be different from a simple `"class Rabbit"`? + +What's the difference? + +Here's an example of such code (it doesn't work -- why? fix it?): + +```js +class Rabbit extends Object { + constructor(name) { + this.name = name; + } +} + +let rabbit = new Rabbit("Rab"); + +alert( rabbit.hasOwnProperty('name') ); // Error +``` diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg index 1e868cce25..3e354b895d 100644 --- a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg +++ b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg @@ -1,64 +1 @@ - - - - animal-rabbit-static.svg - Created with sketchtool. - - - - - constructor: Animal - run: function - - - - Animal.prototype - - - - constructor: Rabbit - hide: function - - - Rabbit.prototype - - - - Animal - - - - Rabbit - - - rabbit - - - - - [[Prototype]] - - - - [[Prototype]] - - - - [[Prototype]] - - - prototype - - - - prototype - - - compare - - - name: "White Rabbit" - - - - \ No newline at end of file +constructor: Animal run: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitrabbit[[Prototype]][[Prototype]][[Prototype]]prototypeprototypecomparename: "White Rabbit" \ No newline at end of file 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 8abfd7ddde..4b493a5e8b 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,9 @@ -# 静态属性和静态方法 +# Static properties and methods -我们可以把一个方法赋值给一个类方法,而不是赋给它的 `"原型对象"`。这样的方法我们称为**静态的**。 +We can also assign a method to the class as a whole. Such methods are called *static*. -在类里面,他们在前面添加 "static" 关键字,就像这样: +In a class declaration, they are prepended by `static` keyword, like this: ```js run class User { @@ -17,21 +17,25 @@ class User { User.staticMethod(); // true ``` -这实际上跟直接作为属性赋值做了同样的事情: +That actually does the same as assigning it as a property directly: -```js -class User() { } +```js run +class User { } User.staticMethod = function() { alert(this === User); }; + +User.staticMethod(); // true ``` -在 `User.staticMethod` 方法内部,`this` 的值是构造函数 `User` 它自己(“点之前对象”[object before dot]规则)。 +The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule). -通常来说,静态方法用来实现一个属于类,但不属于类的某个对象的特定方法。 +Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it. -举个例子,我们有 `Article` 对象,需要一个方法来比较它们。一个自然的解决方案是添加 `Article.compare` 方法,就像这样: +For instance, we have `Article` objects and need a function to compare them. + +A natural solution would be to add `Article.compare` static method: ```js run class Article { @@ -47,7 +51,7 @@ class Article { */!* } -// 用法 +// usage let articles = [ new Article("HTML", new Date(2019, 1, 1)), new Article("CSS", new Date(2019, 0, 1)), @@ -61,17 +65,19 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` -这里 `Article.compare` 代表这些文章,作为一个比较它们的意思。它不是一篇文章的方法,而是所有 class 的方法。 +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: -1. 通过用指定的参数来创建(`title`,`date` 等)。 -2. 用今天的日期来创建一篇空的文章。 -3. ……其它等等。 +1. Create by given parameters (`title`, `date` etc). +2. Create an empty article with today's date. +3. ...or else somehow. -第一种方法我们可以使用构造函数来实现。对于第二种方式,我们可以创建一个类的静态方法来实现。 +The first way can be implemented by the constructor. And for the second one we can make a static method of the class. -就像这里的 `Article.createTodays()`: +Such as `Article.createTodays()` here: ```js run class Article { @@ -82,7 +88,7 @@ class Article { *!* static createTodays() { - // 记住,this = Article + // remember, this = Article return new this("Today's digest", new Date()); } */!* @@ -90,24 +96,35 @@ class Article { let article = Article.createTodays(); -alert( article.title ); // Todays digest +alert( article.title ); // Today's digest ``` -现在,每当我们需要创建一个今天的摘要时,我们可以调用 `Article.createTodays()`。再一次说明,这不是一篇文章的方法,而是整个 class 的方法。 +Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class. -静态方法也用于与数据库相关的公共类,可以用来搜索/保存删除数据库中的条目, 就像这样: +Static methods are also used in database-related classes to search/save/remove entries from the database, like this: ```js -// 假定 Article 是一个用来管理文章的特殊类 -// 静态方法用于移除文章: +// 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 [recent browser=Chrome] -静态的属性也是有可能的,它们看起来像常规的类属性,但前面加有 `static`: +Static properties are also possible, they look like regular class properties, but prepended by `static`: ```js run class Article { @@ -117,20 +134,21 @@ class Article { alert( Article.publisher ); // Ilya Kantor ``` -这等同于直接给 `Article` 赋值: +That is the same as a direct assignment to `Article`: ```js Article.publisher = "Ilya Kantor"; ``` -## 静态方法的继承 +## Inheritance of static properties and methods [#statics-and-inheritance] -静态方法是继承的。 +Static properties and methods are inherited. -举个例子:下面代码里的 `Animal.compare` 被继承,可以通过 `Rabbit.compare` 来访问: +For instance, `Animal.compare` and `Animal.planet` in the code below are inherited and accessible as `Rabbit.compare` and `Rabbit.planet`: ```js run class Animal { + static planet = "Earth"; constructor(name, speed) { this.speed = speed; @@ -150,7 +168,7 @@ class Animal { } -// 继承 Animal 类 +// Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); @@ -167,46 +185,48 @@ rabbits.sort(Rabbit.compare); */!* rabbits[0].run(); // Black Rabbit runs with speed 5. + +alert(Rabbit.planet); // Earth ``` -现在我们调用 `Rabbit.compare` 被断定为继承的 `Animal.compare` 将被调用。 +Now when we call `Rabbit.compare`, the inherited `Animal.compare` will be called. -它的原理是什么? 再次的,使用原型。你可能已经猜到了,`继承` 让 `Rabbit` 的 `[[Prototype]]` 属性指向了 `Animal`。 +How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`. ![](animal-rabbit-static.svg) -因此,`Rabbit extends Animal` 创建了两个 `[[Prototype]]` 的引用: +So, `Rabbit extends Animal` creates two `[[Prototype]]` references: -1. `Rabbit` 方法原型继承自 `Animal` 方法。 -2. `Rabbit.prototype` 原型继承自 `Animal.prototype`。 +1. `Rabbit` function prototypally inherits from `Animal` function. +2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`. -结果就是,继承对于常规的和静态的方法都有效。 +As a result, inheritance works both for regular and static methods. -这里,让我们通过代码来检验一下: +Here, let's check that by code: ```js run class Animal {} class Rabbit extends Animal {} -// 对于静态属性和静态方法 +// for statics alert(Rabbit.__proto__ === Animal); // true -// 对于普通方法 -alert(Rabbit.prototype.__proto__ === Animal.prototype); +// for regular methods +alert(Rabbit.prototype.__proto__ === Animal.prototype); // true ``` - -## 总结 -静态方法被用来实现属于整个类的功能,不涉及到某个具体的类实例的功能。 +## Summary + +Static methods are used for the functionality that belongs to the class "as a whole". It doesn't relate to a concrete class instance. -举个例子, 一个用来比较的方法 `Article.compare(article1, article2)` 或者一个工厂函数 `Article.createTodays()`。 +For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`. -它们在类声明中通过 `static` 来标记。 +They are labeled by the word `static` in class declaration. -当我们想要存储类级别的数据时,我们会使用静态属性,而不是在实例上绑定数据。 +Static properties are used when we'd like to store class-level data, also not bound to an instance. +The syntax is: -语法: ```js class MyClass { static property = ...; @@ -217,13 +237,13 @@ class MyClass { } ``` -技术上来说,静态声明等同于直接给类本身赋值: +Technically, static declaration is the same as assigning to the class itself: ```js MyClass.property = ... MyClass.method = ... ``` -静态属性和方法是被继承的。 +Static properties and methods are inherited. -对于 `class B extends A`,类 `B` 的 prototype 指向了 `A`:`B.[[Prototype]] = A`。因此,如果一个字段在 `B` 中没有找到,会继续在 `A` 中查找。 +For `class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`. 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 749098f4e1..91efb89eeb 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 @@ -1,71 +1,71 @@ -# 私有的和受保护的属性和方法 +# Private and protected properties and methods -面向对象编程最重要的原则之一 —— 划分出外部接口和内部接口。 +One of the most important principles of object oriented programming -- delimiting internal interface from the external one. -在开发比 “hello world” 应用更复杂的东西时,这是“必须”的做法。 +That is "a must" practice in developing anything more complex than a "hello world" app. -为了理解这一点,让我们脱离开发过程,把目光转向现实世界。 +To understand this, let's break away from development and turn our eyes into the real world. -通常,我们使用的设备非常复杂。但是划分出外部接口和内部接口可以让我们使用它们而没有任何问题。 +Usually, devices that we're using are quite complex. But delimiting the internal interface from the external one allows to use them without problems. -## 一个真实的例子 +## A real-life example -例如咖啡机。从外面看很简单:一个按钮,一个显示器,几个洞……当然,结果就是 —— 很棒的咖啡!:) +For instance, a coffee machine. Simple from outside: a button, a display, a few holes...And, surely, the result -- great coffee! :) ![](coffee.jpg) -但在里面……(维修手册中的图片) +But inside... (a picture from the repair manual) ![](coffee-inside.jpg) -有很多细节。但我们可以在不了解的情况下使用它。 +A lot of details. But we can use it without knowing anything. -咖啡机非常可靠,不是吗?我们可以使用好几年,只有在出现问题时 —— 进行维修。 +Coffee machines are quite reliable, aren't they? We can use one for years, and only if something goes wrong -- bring it for repairs. -咖啡机的可靠性和简洁性的秘诀 —— 所有细节都经过精心调整并 **隐藏** 在内部。 +The secret of reliability and simplicity of a coffee machine -- all details are well-tuned and *hidden* inside. -如果我们从咖啡机上取下保护盖,那么使用它将会复杂得多(要按哪里?),并且危险(会触电)。 +If we remove the protective cover from the coffee machine, then using it will be much more complex (where to press?), and dangerous (it can electrocute). -正如我们所看到的,在编程中,对象就像咖啡机。 +As we'll see, in programming objects are like coffee machines. -但是为了隐藏内部细节,我们不会使用保护盖,而是使用语言和惯例中的特殊语法。 +But in order to hide inner details, we'll use not a protective cover, but rather special syntax of the language and conventions. -## 内部接口和外部接口 +## Internal and external interface -在面向对象的编程中,属性和方法分为两组: +In object-oriented programming, properties and methods are split into two groups: -- **内部接口** —— 可以通过类的其他方法访问,但不能从外部访问的方法和属性。 -- **外部接口** —— 也可从类的外部访问的方法和属性。 +- *Internal interface* -- methods and properties, accessible from other methods of the class, but not from the outside. +- *External interface* -- methods and properties, accessible also from outside the class. -如果我们继续用咖啡机进行类比 —— 内部隐藏的内容:锅炉管,加热元件等 —— 是其内部的接口。 +If we continue the analogy with the coffee machine -- what's hidden inside: a boiler tube, heating element, and so on -- is its internal interface. -内部接口用于对象,它的细节相互使用。例如,锅炉管连接到加热元件。 +An internal interface is used for the object to work, its details use each other. For instance, a boiler tube is attached to the heating element. -但是从外面看,咖啡机内部被保护罩遮住,所以没有人可以接触到。细节被隐藏起来并且无法访问。我们可以通过外部接口使用它的功能。 +But from the outside a coffee machine is closed by the protective cover, so that no one can reach those. Details are hidden and inaccessible. We can use its features via the external interface. -所以,我们需要使用一个对象时只需知道它的外部接口。我们可能完全不知道它的内部是如何工作的,这很棒。 +So, all we need to use an object is to know its external interface. We may be completely unaware how it works inside, and that's great. -这是个概括的介绍。 +That was a general introduction. -在 JavaScript 中,有两种类型的对象字段(属性和方法): +In JavaScript, there are two types of object fields (properties and methods): -- 公共的:可从任何地方访问。它们包含外部接口。直到现在我们只使用公共属性和方法。 -- 私有的:只能从类的内部访问。这些用于内部接口。 +- Public: accessible from anywhere. They comprise the external interface. Until now we were only using public properties and methods. +- Private: accessible only from inside the class. These are for the internal interface. -在许多其他语言中,还存在“受保护”的字段:只能从类的内部访问和扩展它们(类似私有的,但是加上了向继承的类的访问)。它们对内部接口也很有用。它们在某种意义上比私有的属性和方法更广泛,因为我们通常希望继承类来获得正确执行扩展的访问权限。 +In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them. -受保护的字段不是在 Javascript 语言级别上实现的,但实际上它们非常方便,因为它们是在 Javascript 中模拟的类定义语法。 +Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated. -现在,我们将使用所有这些类型的属性在 Javascript 中制作咖啡机。咖啡机有很多细节,我们不会对它们进行全面模拟以保持简洁(尽管我们可以)。 +Now we'll make a coffee machine in JavaScript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could). -## 受保护的“waterAmount” +## Protecting "waterAmount" -让我们先做一个简单的咖啡机类: +Let's make a simple coffee machine class first: ```js run class CoffeeMachine { - waterAmount = 0; // 内部的水量 + waterAmount = 0; // the amount of water inside constructor(power) { this.power = power; @@ -74,29 +74,31 @@ class CoffeeMachine { } -// 创建咖啡机 +// create the coffee machine let coffeeMachine = new CoffeeMachine(100); -// 加入水 +// add water coffeeMachine.waterAmount = 200; ``` -现在,属性 `waterAmount` 和 `power` 是公共的。我们可以轻松地从外部读取/设置它们为任何值。 +Right now the properties `waterAmount` and `power` are public. We can easily get/set them from the outside to any value. -让我们将 `waterAmount` 属性更改为受保护的属性以对其进行更多控制。例如,我们不希望任何人将其值设置为小于零的数。 +Let's change `waterAmount` property to protected to have more control over it. For instance, we don't want anyone to set it below zero. -**受保护的属性通常以下划线 `_` 作为前缀。** +**Protected properties are usually prefixed with an underscore `_`.** -这不是在语言层面强制实施的,但是有一个在程序员之间人尽皆知的惯例是不应该从外部访问这些属性和方法。 +That is not enforced on the language level, but there's a well-known convention between programmers that such properties and methods should not be accessed from the outside. -所以我们的属性将被称为 `_waterAmount` : +So our property will be called `_waterAmount`: ```js run class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) { + value = 0; + } this._waterAmount = value; } @@ -110,22 +112,22 @@ class CoffeeMachine { } -// 创建咖啡机 +// create the coffee machine let coffeeMachine = new CoffeeMachine(100); -// 加入水 -coffeeMachine.waterAmount = -10; // Error: Negative water +// 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. -## 只读的“power” +## Read-only "power" -对于 `power` 属性,让我们将它设为只读的。有时候一个属性必须仅在创建时设置,然后不再修改。 +For `power` property, let's make it read-only. It sometimes happens that a property must be set at creation time only, and then never modified. -这就是咖啡机的实际情况:功率永远不会改变。 +That's exactly the case for a coffee machine: power never changes. -要做到这一点,我们只需要设置 getter,而不是 setter: +To do so, we only need to make getter, but not the setter: ```js run class CoffeeMachine { @@ -141,25 +143,25 @@ class CoffeeMachine { } -// 创建咖啡机 +// create the coffee machine let coffeeMachine = new CoffeeMachine(100); -alert(`Power is: ${coffeeMachine.power}W`); // 功率是:100W +alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W coffeeMachine.power = 25; // Error (no setter) ``` -````smart header="Getter/setter 函数" -这里我们使用 getter/setter 语法。 +````smart header="Getter/setter functions" +Here we used getter/setter syntax. -但大多数时候首选 `get.../set...` 函数,像这样: +But most of the time `get.../set...` functions are preferred, like this: ```js class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this._waterAmount = value; } @@ -171,26 +173,26 @@ class CoffeeMachine { new CoffeeMachine().setWaterAmount(100); ``` -这看起来有点长,但函数更灵活。他们可以接受多个参数(即使我们现在不需要它们)。 +That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now). -另一方面,get/set 语法更短,所以最终没有严格的规则,而是由你自己来决定。 +On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide. ```` -```smart header="受保护的字段是继承的" -如果我们继承 `class MegaMachine extends CoffeeMachine`,那么无法阻止我们从新的类中的方法访问 `this._waterAmount` 或 `this._power`。 +```smart header="Protected fields are inherited" +If we inherit `class MegaMachine extends CoffeeMachine`, then nothing prevents us from accessing `this._waterAmount` or `this._power` from the methods of the new class. -所以受保护的字段是自然可继承的。不像我们接下来将看到的私有字段。 +So protected fields are naturally inheritable. Unlike private ones that we'll see below. ``` -## 私有的“#waterLimit” +## Private "#waterLimit" [recent browser=none] -在标准中几乎有个已完成的 Javascript 提案,它为私有属性和方法提供语言级支持。 +There's a finished JavaScript proposal, almost in the standard, that provides language-level support for private properties and methods. -私有属性和方法应该以 `#` 开头。他们只能从类的内部访问。 +Privates should start with `#`. They are only accessible from inside the class. -例如,这有一个私有属性 `#waterLimit`,以及检查水量的私有方法 `#checkWater`: +For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: ```js run class CoffeeMachine { @@ -199,28 +201,32 @@ class CoffeeMachine { */!* *!* - #checkWater(value) { - if (value < 0) throw new Error("Negative water"); - if (value > this.#waterLimit) throw new Error("Too much water"); + #fixWaterAmount(value) { + if (value < 0) return 0; + if (value > this.#waterLimit) return this.#waterLimit; } */!* + setWaterAmount(value) { + this.#waterLimit = this.#fixWaterAmount(value); + } + } let coffeeMachine = new CoffeeMachine(); *!* -// 不能从类的外部访问其私有方法 -coffeeMachine.#checkWater(); // Error +// can't access privates from outside of the class +coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error */!* ``` -在语言层面,`#` 是该字段为私有的特殊标志。我们无法从外部或从继承的类中访问它。 +On the language level, `#` is a special sign that the field is private. We can't access it from outside or from inheriting classes. -私有字段不与公共字段发生冲突。我们可以同时拥有私有属性 `#waterAmount` 和公共属性 `waterAmount`。 +Private fields do not conflict with public ones. We can have both private `#waterAmount` and public `waterAmount` fields at the same time. -例如,让 `waterAmount` 成为 `#waterAmount` 的访问器: +For instance, let's make `waterAmount` an accessor for `#waterAmount`: ```js run class CoffeeMachine { @@ -232,7 +238,7 @@ class CoffeeMachine { } set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this.#waterAmount = value; } } @@ -243,26 +249,26 @@ machine.waterAmount = 100; alert(machine.#waterAmount); // Error ``` -与受保护的字段不同,私有字段由语言本身强制执行。这是好事。 +Unlike protected ones, private fields are enforced by the language itself. That's a good thing. -但是如果我们继承 `CoffeeMachine`,那么我们将无法直接访问 `#waterAmount`。我们需要依赖 `waterAmount` getter / setter: +But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter: ```js -class MegaCoffeeMachine extends CoffeeMachine() { +class MegaCoffeeMachine extends CoffeeMachine { method() { *!* - alert( this.#waterAmount ); // 错误:只能从 CoffeeMachine 中访问 + alert( this.#waterAmount ); // Error: can only access from CoffeeMachine */!* } } ``` -在许多情况下,这种限制太严重了。如果我们扩展一个 `CoffeeMachine`,我们可能有正当理由访问其内部。这就是为什么大多数时候都会使用受保护字段的原因,即使它们不受语言语法的支持。 +In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reasons to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax. -````warn header="私有字段不能通过 this[name] 访问" -私有字段很特别。 +````warn header="Private fields are not available as this[name]" +Private fields are special. -如我们所知,通常我们可以使用 `this[name]` 访问字段: +As we know, usually we can access fields using `this[name]`: ```js class User { @@ -274,43 +280,43 @@ class User { } ``` -私有字段是不可能的: `this['#name']` 不起作用。这是确保私有性的语法限制。 +With private fields that's impossible: `this['#name']` doesn't work. That's a syntax limitation to ensure privacy. ```` -## 总结 +## Summary -就面向对象编程(OOP)而言,内部接口与外部接口的划分称为[封装]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)")。 +In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)). -它具有以下优点: +It gives the following benefits: -保护用户,使他们不会误伤自己 -: 想象一下,有一群开发人员使用咖啡机。它是由“Best CoffeeMachine”公司制造的,工作正常,但保护盖被拿走了。因此内部接口暴露了出来。 +Protection for users, so that they don't shoot themselves in the foot +: Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed. - 所有的开发人员都是文明的 —— 他们按照预期使用咖啡机。但其中一个人,约翰,被认为是最聪明的,并且决定让他在咖啡机内部做一些调整。然而咖啡机两天后就坏了。 + All developers are civilized -- they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later. - 这肯定不是约翰的错,而是那个取下保护套并让约翰执行自己操作的人的错。 + That's surely not John's fault, but rather the person who removed the protective cover and let John do his manipulations. - 编程也一样。如果一个类的使用者想要改变那些本不打算从外部改变的东西 —— 后果是不可预测的。 + The same in programming. If a user of a class will change things not intended to be changed from the outside -- the consequences are unpredictable. -可支持的 -: 编程的情况比现实生活中的咖啡机更复杂,因为我们不只是购买一次。代码不断经历着发展和改进。 +Supportable +: The situation in programming is more complex than with a real-life coffee machine, because we don't just buy it once. The code constantly undergoes development and improvement. - **如果我们严格界定内部接口,那么类的开发人员可以自由地更改其内部属性和方法,即使没有通知用户。** + **If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users.** - 如果你是这样的类的开发者,当知道由于没有外部代码的依赖,私有方法可以安全地重命名,它们的参数可以改变,甚至可以删除是很棒的事。 + If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them. - 对于使用者来说,当新版本出现时,它可能是全面的内部检查,但如果外部接口相同,则仍然很容易升级。 + For users, when a new version comes out, it may be a total overhaul internally, but still simple to upgrade if the external interface is the same. -隐藏复杂性 -: 人们喜欢使用简单的东西。至少从外部来看是这样。内部的东西则是另外一回事了。 +Hiding complexity +: People adore using things that are simple. At least from outside. What's inside is a different thing. - 程序员也不例外。 + Programmers are not an exception. - **隐藏实施细节时总是很方便,并且提供了一个简单的,记录详细的外部接口。** + **It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.** -为了隐藏内部接口,我们使用受保护的或私有的属性: +To hide an internal interface we use either protected or private properties: -- 受保护的字段以 `_` 开头。这是一个众所周知的惯例,没有在语言层面强制执行。程序员只应该通过它的类和它继承的类中访问以 `_` 开头的字段。 -- 私有字段以 `#` 开头。JavaScript 确保我们只能访问类中的内容。 +- Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it. +- Private fields start with `#`. JavaScript makes sure we can only access those from inside the class. -目前,在各浏览器中不支持私有字段,但可以用 polyfill 解决。 +Right now, private fields are not well-supported among browsers, but can be polyfilled. diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md index 9b647d8332..28b4c6eb64 100644 --- a/1-js/09-classes/05-extend-natives/article.md +++ b/1-js/09-classes/05-extend-natives/article.md @@ -1,12 +1,12 @@ -# 继承内置类 -内置的类比如 `Array`,`Map` 等也都是可以继承的。 +# Extending built-in classes -比如,这里有一个 `PowerArray` 继承自原生的 `Array`: +Built-in classes like Array, Map and others are extendable also. +For instance, here `PowerArray` inherits from the native `Array`: ```js run -// 给 PowerArray 新增了一个方法(可以增加更多) +// add one more method to it (can do more) class PowerArray extends Array { isEmpty() { return this.length === 0; @@ -21,22 +21,20 @@ alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false ``` -请注意一个非常有趣的事情。内置的方法比如 `filter`,`map` 等 -- 返回的正是子类 `PowerArray` 的新对象。它们内部通过对象的 `constructor` 属性实现了这一功能。 +Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses the object's `constructor` property for that. -在上面的例子中, +In the example above, ```js arr.constructor === PowerArray ``` +When `arr.filter()` is called, it internally creates the new array of results using exactly `arr.constructor`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result. +Even more, we can customize that behavior. -所以当 `arr.filter()` 被调用的时候,它在内部使用的是 `new PowerArray` 的返回值来创建新数组,而不是原生的 `Array`。这真的很酷,因为我们可以在返回值中继续使用 `PowerArray` 的方法。 +We can add a special static getter `Symbol.species` to the class. If it exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on. -更重要的是,我们可以定制这种行为。 - -我们可以给这个类增加一个特殊的静态 getter `Symbol.species`。如果存在,则应返回 JavaScript 内部用来在 map,filter 等方法中创建新实体的构造函数 (constructor)。 - -如果我们希望类似 `map` 或者 `filter` 这样的内置方法返回常规的数组,我们应该在 `Symbol.species` 中返回 `Array`,就像这样: +If we'd like built-in methods like `map` or `filter` to return regular arrays, we can return `Array` in `Symbol.species`, like here: ```js run class PowerArray extends Array { @@ -45,7 +43,7 @@ class PowerArray extends Array { } *!* - // 内置方法会使用这个作为构造函数 (constructor) + // built-in methods will use this as the constructor static get [Symbol.species]() { return Array; } @@ -55,37 +53,37 @@ class PowerArray extends Array { let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false -// filter 使用 arr.constructor[Symbol.species] 作为构造函数 (constructor) 创建新数组 +// filter creates new array using arr.constructor[Symbol.species] as constructor let filteredArr = arr.filter(item => item >= 10); *!* -// filteredArr 不是 PowerArray, 而是 Array +// filteredArr is not PowerArray, but Array */!* alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function ``` -如你所见,现在 `.filter` 返回 `Array`。所以子类 `PowerArray` 的功能不再传递给 `filteredArr`。 +As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further. -```smart header="其他集合也同样适用" -其他的集合比如 `Map` 和 `Set` 也同样适用,它们也可以使用 `Symbol.species`. +```smart header="Other collections work similarly" +Other collections, such as `Map` and `Set`, work alike. They also use `Symbol.species`. ``` -### 内置类没有静态方法继承 +## No static inheritance in built-ins -内置对象有它们自己的静态方法,比如 `Object.keys`,`Array.isArray` 等。 +Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc. -如我们所知道的,原生的类互相继承。比如,`Array` 继承自 `Object`。 +As we already know, native classes extend each other. For instance, `Array` extends `Object`. -正常来说,当一个类继承自其他类的时候,静态方法和非静态方法都会被继承。这已经在这篇文章 [](info:static-properties-methods#statics-and-inheritance) 中详尽地解释过了。 +Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the article [](info:static-properties-methods#statics-and-inheritance). -但内置类却是一个例外,它们相互间不继承静态属性和方法。 +But built-in classes are an exception. They don't inherit statics from each other. -比如,`Array` 和 `Data` 都是继承自 `Object`,所以它们的实例都有来自 `Object.prototype` 的方法,但是 `Array.[[Prototype]]` 不指向 `Object`,所以它们没有例如 `Array.keys()`(或者 `Data.keys()`)的静态方法。 +For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no, for instance, `Array.keys()` (or `Date.keys()`) static method. -这里有一张 `Date` 和 `Object` 结构关系的图片 +Here's the picture structure for `Date` and `Object`: ![](object-date-inheritance.svg) -如你所见,`Date` 和 `Object` 之间没有连结。`Object` 和 `Date` 都是独立存在的。`Date.prototype` 继承自 `Object.prototype`,但也仅此而已。 +As you can see, there's no link between `Date` and `Object`. They are independent, only `Date.prototype` inherits from `Object.prototype`. -与我们了解的继承(`extends`)相比,这是内置对象之间的继承的一个非常重要的区别。 +That's an important difference of inheritance between built-in objects compared to what we get with `extends`. diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg index f46577f141..be47d7fd96 100644 --- a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg +++ b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg @@ -1,71 +1 @@ - - - - object-date-inheritance.svg - Created with sketchtool. - - - - - constructor: Object - toString: function - hasOwnProperty: function - ... - - - - Object.prototype - - - - constructor: Date - toString: function - getDate: function - ... - - - Date.prototype - - - - Object - - - - Date - - - new Date() - - - - - [[Prototype]] - - - - [[Prototype]] - - - prototype - - - - prototype - - - defineProperty - keys - ... - - - now - parse - ... - - - 1 Jan 2019 - - - - \ No newline at end of file +constructor: Object toString: function hasOwnProperty: function ...Object.prototypeconstructor: Date toString: function getDate: function ...Date.prototypeObjectDatenew Date()[[Prototype]][[Prototype]]prototypeprototypedefineProperty keys ...now parse ...1 Jan 2019 \ No newline at end of file diff --git a/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md index 29ae04130d..d41d90edf4 100644 --- a/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md +++ b/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md @@ -1,7 +1,7 @@ -确实挺诡异的。 +Yeah, looks strange indeed. -`instanceof` 并不关心构造函数,它真正关心的是原型链。 +But `instanceof` does not care about the function, but rather about its `prototype`, that it matches against the prototype chain. -这里有 `a.__proto__ == B.prototype` 成立,所以 `instanceof` 返回了 `true`。 +And here `a.__proto__ == B.prototype`, so `instanceof` returns `true`. -总之,按照 `instanceof` 的逻辑,真正决定类型的是 `prototype`,而不是构造函数。 +So, by the logic of `instanceof`, the `prototype` actually defines the type, not the constructor function. diff --git a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md index 703b1f4d0a..5b8dc7de3c 100644 --- a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md +++ b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# 不按套路出牌的 instanceof +# Strange instanceof -下面代码中,`instanceof` 为什么会返回 `true`?很显然,`a` 并不是通过 `B()` 创建的。 +In the code below, why does `instanceof` return `true`? We can easily see that `a` is not created by `B()`. ```js run function A() {} diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index 77c06b2212..f9db989ca9 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -1,42 +1,42 @@ -# 类型检测:"instanceof" +# Class checking: "instanceof" -`instanceof` 操作符用于检测对象是否属于某个 class,同时,检测过程中也会将继承关系考虑在内。 +The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account. -这种检测在多数情况下还是比较有用的,下面,我们就用它来构建一个具备 **多态** 性的函数,这个函数能识别出参数类型,从而作出不同的处理。 +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. -## instanceof [#ref-instanceof] +## The instanceof operator [#ref-instanceof] -用法: +The syntax is: ```js obj instanceof Class ``` -如果 `obj` 隶属于 `Class` 类(或者是 `Class` 类的衍生类),表达式将返回 `true`。 +It returns `true` if `obj` belongs to the `Class` or a class inheriting from it. -举例说明: +For instance: ```js run class Rabbit {} let rabbit = new Rabbit(); -// rabbit 是 Rabbit 类的实例对象吗? +// is it an object of Rabbit class? *!* alert( rabbit instanceof Rabbit ); // true */!* ``` -使用构造函数结果也是一样的: +It also works with constructor functions: ```js run *!* -// 构造函数而非 class +// instead of class function Rabbit() {} */!* alert( new Rabbit() instanceof Rabbit ); // true ``` -...再来看看内置类型 `Array`: +...And with built-in classes like `Array`: ```js run let arr = [1, 2, 3]; @@ -44,16 +44,19 @@ alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true ``` -有一点需要留意,`arr` 同时还隶属于 `Object` 类。因为从原型上来讲,`Array` 是继承自 `Object` 类的。 +Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypically inherits from `Object`. -`instanceof` 在检测中会将原型链考虑在内,此外,还能借助静态方法 `Symbol.hasInstance` 来改善检测效果。 +Normally, `instanceof` examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`. -`obj instanceof Class` 语句的大致执行过程如下: +The algorithm of `obj instanceof Class` works roughly as follows: -1. 如果提供了静态方法 `Symbol.hasInstance`,那就直接用这个方法进行检测: +1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`, and we're done. That's how we can customize the behavior of `instanceof`. + + For example: ```js run - // 假设具有 canEat 属性的对象为动物类 + // setup instanceOf check that assumes that + // anything with canEat property is an animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; @@ -61,22 +64,25 @@ alert( arr instanceof Object ); // true } let obj = { canEat: true }; - alert(obj instanceof Animal); // 返回 true:调用 Animal[Symbol.hasInstance](obj) + + alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called ``` -2. 大部分的类是没有 `Symbol.hasInstance` 方法的,这时会检查 `Class.prototype` 是否与 `obj` 的原型链中的任何一个原型相等。 +2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` is equal to one of the prototypes in the `obj` prototype chain. - 简而言之,是这么比较的: + In other words, compare one after another: ```js - obj.__proto__ === Class.prototype - obj.__proto__.__proto__ === Class.prototype - obj.__proto__.__proto__.__proto__ === Class.prototype + obj.__proto__ === Class.prototype? + obj.__proto__.__proto__ === Class.prototype? + obj.__proto__.__proto__.__proto__ === Class.prototype? ... + // if any answer is true, return true + // otherwise, if we reached the end of the chain, return false ``` - 在上一个例子中有 `Rabbit.prototype === rabbit.__proto__` 成立,所以结果是显然的。 + In the example above `rabbit.__proto__ === Rabbit.prototype`, so that gives the answer immediately. - 再比如下面一个继承的例子,`rabbit` 对象同时也是父类的一个实例: + In the case of an inheritance, the match will be at the second step: ```js run class Animal {} @@ -86,76 +92,77 @@ 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 (match!) + */!* ``` -下图展示了 `rabbit instanceof Animal` 的执行过程中,`Animal.prototype` 是如何参与比较的: +Here's the illustration of what `rabbit instanceof Animal` compares with `Animal.prototype`: ![](instanceof.svg) -这里还要提到一个方法 [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf),如果 `objA` 处在 `objB` 的原型链中,调用结果为 `true`。所以,`obj instanceof Class` 也可以被视作为是调用 `Class.prototype.isPrototypeOf(obj)`。 +By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), that returns `true` if `objA` is somewhere in the chain of prototypes for `objB`. So the test of `obj instanceof Class` can be rephrased as `Class.prototype.isPrototypeOf(obj)`. -虽然有点奇葩,其实 `Class` 的构造器自身是不参与检测的!检测过程只和原型链以及 `Class.prototype` 有关。 +It's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters. -所以,当 `prototype` 改变时,会产生意想不到的结果。 +That can lead to interesting consequences when a `prototype` property is changed after the object is created. -就像这样: +Like here: ```js run function Rabbit() {} let rabbit = new Rabbit(); -// 修改其 prototype +// changed the prototype Rabbit.prototype = {}; -// ...再也不是只兔子了! +// ...not a rabbit any more! *!* alert( rabbit instanceof Rabbit ); // false */!* ``` -所以,为了谨慎起见,最好避免修改 `prototype`。 - -## 福利:使用 Object 的 toString 方法来揭示类型 +## Bonus: Object.prototype.toString for the type -大家都知道,一个普通对象被转化为字符串时为 `[object Object]`: +We already know that plain objects are converted to string as `[object Object]`: ```js run let obj = {}; alert(obj); // [object Object] -alert(obj.toString()); // 同上 +alert(obj.toString()); // the same ``` -这也是它们的 `toString` 方法的实现如此。但是,`toString` 自有其潜质,可以让它变得更实用一点。甚至可以用来替代 `instanceof`,也可以视作为 `typeof` 的增强版。 +That's their implementation of `toString`. But there's a hidden feature that makes `toString` actually much more powerful than that. We can use it as an extended `typeof` and an alternative for `instanceof`. -听起来挺不可思议?那是自然,精彩马上揭晓。 +Sounds strange? Indeed. Let's demystify. -按照 [规范](https://tc39.github.io/ecma262/#sec-object.prototype.tostring) 上所讲,内置的 `toString` 方法可以从对象中提取出来,以其他值作为上下文(context)对象进行调用,调用结果取决于传入的上下文对象。 +By [specification](https://tc39.github.io/ecma262/#sec-object.prototype.tostring), the built-in `toString` can be extracted from the object and executed in the context of any other value. And its result depends on that value. -- 如果传入的是 number 类型,返回 `[object Number]` -- 如果传入的是 boolean 类型,返回 `[object Boolean]` -- 如果传入 `null`,返回 `[object Null]` -- 传入 `undefined`,返回 `[object Undefined]` -- 传入数组,返回 `[object Array]` -- ...等等(例如一些自定义类型) +- For a number, it will be `[object Number]` +- For a boolean, it will be `[object Boolean]` +- For `null`: `[object Null]` +- For `undefined`: `[object Undefined]` +- For arrays: `[object Array]` +- ...etc (customizable). -下面进行阐述: +Let's demonstrate: ```js run -// 保存 toString 方法的引用,方便后面使用 +// copy toString method into a variable for convenience let objectToString = Object.prototype.toString; -// 猜猜是什么类型? +// what type is this? let arr = []; -alert( objectToString.call(arr) ); // [object Array] +alert( objectToString.call(arr) ); // [object *!*Array*/!*] ``` -这里用到了章节 [](info:call-apply-decorators) 里提到的 [call](mdn:js/function/call) 方法来调用 `this=arr` 上下文的方法 `objectToString`。 +Here we used [call](mdn:js/function/call) as described in the chapter [](info:call-apply-decorators) to execute the function `objectToString` in the context `this=arr`. -`toString` 的内部算法会检查 `this` 对象,返回对应的结果。再来几个例子: +Internally, the `toString` algorithm examines `this` and returns the corresponding result. More examples: ```js run let s = Object.prototype.toString; @@ -167,9 +174,9 @@ alert( s.call(alert) ); // [object Function] ### Symbol.toStringTag -对象的 `toString` 方法可以使用 `Symbol.toStringTag` 这个特殊的对象属性进行自定义输出。 +The behavior of Object `toString` can be customized using a special object property `Symbol.toStringTag`. -举例说明: +For instance: ```js run let user = { @@ -179,33 +186,33 @@ let user = { alert( {}.toString.call(user) ); // [object User] ``` -大部分和环境相关的对象也有这个属性。以下输出可能因浏览器不同而异: +For most environment-specific objects, there is such a property. Here are some browser specific examples: ```js run -// 环境相关对象和类的 toStringTag: -alert( window[Symbol.toStringTag]); // window +// toStringTag for the environment-specific object and class: +alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest] ``` -输出结果和 `Symbol.toStringTag`(前提是这个属性存在)一样,只不过被包裹进了 `[object ...]` 里。 +As you can see, the result is exactly `Symbol.toStringTag` (if exists), wrapped into `[object ...]`. -这样一来,我们手头上就有了个“磕了药似的 typeof”,不仅能检测基本数据类型,就是内置对象类型也不在话下,更可贵的是还支持自定义。 +At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized. -所以,如果希望以字符串的形式获取内置对象类型信息,而不仅仅只是检测类型的话,可以用这个方法来替代 `instanceof`。 +We can use `{}.toString.call` instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check. -## 总结 +## Summary -下面,来总结下大家学到的类型检测方式: +Let's summarize the type-checking methods that we know: -| | 用于 | 返回 | +| | works for | returns | |---------------|-------------|---------------| -| `typeof` | 基本数据类型 | string | -| `{}.toString` | 基本数据类型、内置对象以及包含 `Symbol.toStringTag` 属性的对象 | string | -| `instanceof` | 任意对象 | true/false | +| `typeof` | primitives | string | +| `{}.toString` | primitives, built-in objects, objects with `Symbol.toStringTag` | string | +| `instanceof` | objects | true/false | -看样子,`{}.toString` 基本就是一增强版 `typeof`。 +As we can see, `{}.toString` is technically a "more advanced" `typeof`. -`instanceof` 在涉及多层类结构的场合中比较实用,这种情况下需要将类的继承关系考虑在内。 +And `instanceof` operator really shines when we are working with a class hierarchy and want to check for the class taking into account inheritance. diff --git a/1-js/09-classes/06-instanceof/instanceof.svg b/1-js/09-classes/06-instanceof/instanceof.svg index b291384f2f..d63b03a8a2 100644 --- a/1-js/09-classes/06-instanceof/instanceof.svg +++ b/1-js/09-classes/06-instanceof/instanceof.svg @@ -1,51 +1 @@ - - - - instanceof.svg - Created with sketchtool. - - - - - Animal.prototype - - - - Object.prototype - - - - - Rabbit.prototype - - - - [[Prototype]] - - - - rabbit - - - - [[Prototype]] - - - [[Prototype]] - - - - null - - - [[Prototype]] - - - = Animal.prototype? - - - - - - - \ No newline at end of file +Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 70de648bae..06001d900e 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -1,22 +1,22 @@ -# JavaScript 中的 Mixin 模式 +# Mixins -在 JavaScript 中,我们只能继承单个对象。每个对象只能有一个 `[[Prototype]]` 原型。并且每个类只可以扩展另外一个类。 +In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class. -但是有些时候这种设定(译者注:单继承)会让人感到受限制。比如说我有一个 `StreetSweeper` 类和一个 `Bicycle` 类,现在我想要一个 `StreetSweepingBicycle` 类(译者注:实现两个父类的功能)。 +But sometimes that feels limiting. For instance, we have a class `StreetSweeper` and a class `Bicycle`, and want to make their mix: a `StreetSweepingBicycle`. -或者,在谈论编程的时候,我们有一个实现模板的 `Renderer` 类和一个实现事件处理的 `EventEmitter` 类,现在想要把这两个功能合并到一个 `Page` 类上以使得一个页面可以同时使用模板和触发事件。 +Or we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events. -有一个概念可以帮助我们,叫做“mixins”。 +There's a concept that can help here, called "mixins". -根据维基百科的定义,[mixin](https://en.wikipedia.org/wiki/Mixin) 是一个包含许多供其它类使用的方法的类,而且这个类不必是其它类的父类。 +As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class containing methods that can be used by other classes without a need to inherit from it. -换句话说,一个 *mixin* 提供了许多实现具体行为的方法,但是我们不单独使用它,我们用它来将这些行为添加到其它类中。 +In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes. -## 一个 Mixin 实例 +## A mixin example -在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有许多实用方法的对象,通过这个对象我们可以轻易地将这些实用方法合并到任何类的原型中。 +The simplest way to implement a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class. -例如,这个叫做 `sayHiMixin` 的 mixin 用于给 `User` 添加一些“言语”。 +For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`: ```js run *!* @@ -32,7 +32,7 @@ let sayHiMixin = { }; *!* -// 用法: +// usage: */!* class User { constructor(name) { @@ -40,14 +40,14 @@ class User { } } -// 拷贝方法 +// copy the methods Object.assign(User.prototype, sayHiMixin); -// 现在 User 可以说 hi 了 +// now User can say hi new User("Dude").sayHi(); // Hello Dude! ``` -没有继承,只有一个简单的方法拷贝。因此 `User` 可以扩展其它类并且同样包含 mixin 来“mix-in”其它方法,就像这样: +There's no inheritance, but a simple method copying. So `User` may inherit from another class and also include the mixin to "mix-in" the additional methods, like this: ```js class User extends Person { @@ -57,9 +57,9 @@ class User extends Person { Object.assign(User.prototype, sayHiMixin); ``` -Mixin 可以在自己内部使用继承。 +Mixins can make use of inheritance inside themselves. -比如,这里的 `sayHiMixin` 继承于 `sayMixin`: +For instance, here `sayHiMixin` inherits from `sayMixin`: ```js run let sayMixin = { @@ -69,16 +69,16 @@ let sayMixin = { }; let sayHiMixin = { - __proto__: sayMixin, // (或者,我们可以在这里通过 Object.create 来设置原型。) + __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here) sayHi() { *!* - // 调用父类中的方法 + // call parent method */!* - super.say(`Hello ${this.name}`); + super.say(`Hello ${this.name}`); // (*) }, sayBye() { - super.say(`Bye ${this.name}`); + super.say(`Bye ${this.name}`); // (*) } }; @@ -88,39 +88,43 @@ class User { } } -// 拷贝方法 +// copy the methods Object.assign(User.prototype, sayHiMixin); -// 现在 User 可以说 hi 了 +// now User can say hi new User("Dude").sayHi(); // Hello Dude! ``` -请注意在 `sayHiMixin` 内部对于父类方法 `super.say()` 的调用会在 mixin 的原型上查找方法而不是在 class 自身查找。 +Please note that the call to the parent method `super.say()` from `sayHiMixin` (at lines labelled with `(*)`) looks for the method in the prototype of that mixin, not the class. + +Here's the diagram (see the right part): ![](mixin-inheritance.svg) -那是因为 `sayHiMixin` 内部的方法设置了 `[[HomeObject]]` 属性。因此 `super` 实际上就是 `sayHiMixin.__proto__` ,而不是 `User.__proto__`。 +That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. -## EventMixin +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. -现在让我们为了实际运用构造一个 mixin。 +## EventMixin -许多对象的重要特征是与事件一起工作。 +Now let's make a mixin for real life. -也就是说:对象应该有一个方法在发生重要事件时“生成事件”,其它对象应该能够“监听”这样的事件。 +An important feature of many browser objects (for instance) is that they can generate events. Events are a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows us to easily add event-related functions to any class/object. -一个事件必须有一个名称,并可以选择性的捆绑一些额外的数据。 +- The mixin will provide a method `.trigger(name, [...data])` to "generate an event" when something important happens to it. The `name` argument is a name of the event, optionally followed by additional arguments with event data. +- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from the `.trigger` call. +- ...And the method `.off(name, handler)` that removes the `handler` listener. -比如说,一个 `user` 对象能够在访问者登录时产生`“login”`事件。另一个 `calendar` 对象可能在等待着接受一个这样的事件以便为登录后的用户加载日历。 +After adding the mixin, an object `user` will be able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen for such events to load the calendar for the logged-in person. -或者,`menu` 在菜单选项被选择之后会产生 `"select"` 事件,并且其它对象可能在等待着接受事件的信息并且对事件做出反应。 +Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may assign handlers to react on that event. And so on. -事件是一种与任何想要得到信息的人分享信息的方式。它在任何类中都可以使用,因此现在为它构造一个 mixin。 +Here's the code: ```js run let eventMixin = { /** - * 订阅事件,用法: + * Subscribe to event, usage: * menu.on('select', function(item) { ... } */ on(eventName, handler) { @@ -132,11 +136,11 @@ let eventMixin = { }, /** - * 取消订阅,用法: + * Cancel the subscription, usage: * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for (let i = 0; i < handlers.length; i++) { if (handlers[i] === handler) { @@ -146,59 +150,59 @@ let eventMixin = { }, /** - * 触发事件并传递参数 + * Generate an event with the given name and data * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { - if (!this._eventHandlers || !this._eventHandlers[eventName]) { - return; // 对应事件名没有事件处理函数。 + if (!this._eventHandlers?.[eventName]) { + return; // no handlers for that event name } - // 调用事件处理函数 + // call the handlers this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; ``` -有三个方法: -1. `.on(eventName, handler)` — 指定函数 `handler` 在具有对应事件名的事件发生时运行。这些事件处理函数存储在 `_eventHandlers` 属性中。 -2. `.off(eventName, handler)` — 在事件处理函数列表中移除指定的函数。 -3. `.trigger(eventName, ...args)` — 触发事件:所有被指定到对应事件的事件处理函数都会被调用并且 `args` 会被作为参数传递给它们。 +- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name occurs. Technically, there's an `_eventHandlers` property that stores an array of handlers for each event name, and it just adds it to the list. +- `.off(eventName, handler)` -- removes the function from the handlers list. +- `.trigger(eventName, ...args)` -- generates the event: all handlers from `_eventHandlers[eventName]` are called, with a list of arguments `...args`. -用法: +Usage: ```js run -// 新建一个 class +// Make a class class Menu { choose(value) { this.trigger("select", value); } } -// 添加 mixin +// Add the mixin with event-related methods Object.assign(Menu.prototype, eventMixin); let menu = new Menu(); -// 被选中时调用事件处理函数: +// add a handler, to be called on selection: *!* menu.on("select", value => alert(`Value selected: ${value}`)); */!* -// 触发事件 => 展示被选中的值:123 -menu.choose("123"); // 被选中的值 +// triggers the event => the handler above runs and shows: +// Value selected: 123 +menu.choose("123"); ``` -现在如果我们已经有了针对用户选择事件做出具体反应的代码,可以将代码使用 `menu.on(...)` 进行绑定。 +Now, if we'd like any code to react to a menu selection, we can listen for it with `menu.on(...)`. -只要我们喜欢,就可以通过 `eventMixin` 将这些行为添加到任意个数的类中而不影响继承链。 +And `eventMixin` mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain. -## 总结 +## Summary -*Mixin* — 是一个通用的面向对象编程术语:一个包含其他类的方法的类。 +*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes. -一些其它语言比如 python 允许通过多继承实现 mixin。JavaScript 不支持多继承,但是可以通过拷贝多个类中的方法到某个类的原型中实现 mixin。 +Some other languages allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype. -我们可以使用 mixin 作为一种通过多种行为来增强类的方式,就像我们上面看到的事件处理一样。 +We can use mixins as a way to augment a class by adding multiple behaviors, like event-handling as we have seen above. -如果 Mixins 偶尔会重写原生类中的方法,那么 Mixins 可能会成为一个冲突点。因此通常情况下应该好好考虑 mixin 的命名,以减少这种冲突的可能性。 +Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that happening. diff --git a/1-js/09-classes/07-mixins/head.html b/1-js/09-classes/07-mixins/head.html index 77ea38b204..20e3a63547 100644 --- a/1-js/09-classes/07-mixins/head.html +++ b/1-js/09-classes/07-mixins/head.html @@ -18,7 +18,7 @@ * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for(let i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.svg b/1-js/09-classes/07-mixins/mixin-inheritance.svg index c1ce62f91d..1fdc223936 100644 --- a/1-js/09-classes/07-mixins/mixin-inheritance.svg +++ b/1-js/09-classes/07-mixins/mixin-inheritance.svg @@ -1,54 +1 @@ - - - - mixin-inheritance.svg - Created with sketchtool. - - - - - sayHi: function - sayBye: function - - - sayHiMixin - - - - say: function - - - sayMixin - - - - [[Prototype]] - - - - constructor: User - sayHi: function - sayBye: function - - - User.prototype - - - - - - [[Prototype]] - - - - name: ... - - - user - - - [[HomeObject] - - - - \ No newline at end of file +sayHi: function sayBye: functionsayHiMixinsay: functionsayMixin[[Prototype]]constructor: User sayHi: function sayBye: functionUser.prototype[[Prototype]]name: ...user[[HomeObject] \ No newline at end of file diff --git a/1-js/09-classes/index.md b/1-js/09-classes/index.md index 9b19312829..87846ef6ba 100644 --- a/1-js/09-classes/index.md +++ b/1-js/09-classes/index.md @@ -1 +1 @@ -# 类 +# Classes 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 13f335d082..ec0dabc9a6 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,8 @@ -当我们看下面这个函数里面的代码,差异就很明显了。 +The difference becomes obvious when we look at the code inside a function. -如果能够跳出 `try..catch`,表现就不同了。 +The behavior is different if there's a "jump out" of `try...catch`. -例如,当 `try..catch` 里有 `return` 的时候,`finally` 代码块会在退出 `try..catch` 后继续执行,即使是通过 `return` 语句退出的。它会在再后面的代码执行之前,在 `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. ```js run function f() { @@ -11,7 +11,7 @@ function f() { *!* return "result"; */!* - } catch (e) { + } catch (err) { /// ... } finally { alert('cleanup!'); @@ -21,18 +21,18 @@ function f() { f(); // cleanup! ``` -...或者当有 `throw` 的时候,如下: +...Or when there's a `throw`, like here: ```js run function f() { try { alert('start'); throw new Error("an error"); - } catch (e) { + } catch (err) { // ... if("can't handle the error") { *!* - throw e; + throw err; */!* } @@ -44,4 +44,4 @@ function f() { f(); // cleanup! ``` -正是这里的 `finally` 代码块保证了 `cleanup` 的显示,但是如果把 `finally` 里面的这行代码放在函数 `f` 的最后,那么,它不会执行。 +It's `finally` that guarantees the cleanup here. If we just put the code at the end of `f`, it wouldn't run in these situations. 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 03750a1763..b6dc813261 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 @@ -2,16 +2,16 @@ importance: 5 --- -# 在 `finally` 中执行,还是在直接放在代码后面 +# Finally or just the code? -比较下面两个代码片段。 +Compare the two code fragments. -1. 用 `finally` 执行 `try..catch` 之后的代码: +1. The first one uses `finally` to execute the code after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } finally { *!* @@ -19,12 +19,12 @@ importance: 5 */!* } ``` -2. 第二个代码片段,把清空工作空间的代码放在 `try..catch` 之后,但并不包裹在 `finally` 里面。 +2. The second fragment puts the cleaning right after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } @@ -33,6 +33,6 @@ importance: 5 */!* ``` -我们只是需要在代码执行完之后,清除工作空间,而不管是不是在执行的过程中遇到异常。 +We definitely need the cleanup after the work, doesn't matter if there was an error or not. -那么,是用 `finally` 好呢还是两种方式都一样?如果哪种更好,请举例说明在什么情况下它会更好? +Is there an advantage here in using `finally` or both code fragments are equal? If there is such an advantage, then give an example when it matters. 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 abdc206bc5..a928da2896 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,59 +1,57 @@ -# 异常处理,"try..catch" +# Error handling, "try...catch" -不管你多么的精通编程,有时我们的脚本总还是会有一些错误。可能是因为我们的编写出错,或是与预期不同的用户输入,或是错误的的服务端返回或者是其他总总不同的原因。 +No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons. -通常,一段代码会在出错的时候“死掉”(停止执行)并在控制台将异常打印出来。 +Usually, a script "dies" (immediately stops) in case of an error, printing it to console. -但是有一种更为合理的语法结构 `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. -## "try..catch" 语法 +## The "try...catch" syntax -`try..catch` 结构由两部分组成:`try` 和 `catch`: +The `try...catch` construct has two main blocks: `try`, and then `catch`: ```js try { - // 代码... + // code... } catch (err) { - // 异常处理 + // error handling } ``` -它按照以下步骤执行: +It works like this: -1. 首先,执行 `try {...}` 里面的代码。 -2. 如果执行过程中没有异常,那么忽略 `catch(err)` 里面的代码,`try` 里面的代码执行完之后跳出该代码块。 -3. 如果执行过程中发生异常,控制流就到了 `catch(err)` 的开头。变量 `err`(可以取其他任何的名称)是一个包含了异常信息的对象。 +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) -所以,发生在 `try {…}` 代码块的异常不会使代码停止执行:我们可以在 `catch` 里面处理异常。 +So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`. -让我们来看更多的例子。 +Let's look at some examples. -- 没有异常的例子:显示下面(1)和(2)中 `alert` 的内容: +- An errorless example: shows `alert` `(1)` and `(2)`: ```js run try { alert('Start of try runs'); // *!*(1) <--*/!* - // ...这里没有异常 + // ...no errors here alert('End of try runs'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) } - - alert("...Then the execution continues"); ``` -- 包含异常的例子:显示下面(1)和(3)中 `alert` 的内容: +- An example with an error: shows `(1)` and `(3)`: ```js run try { @@ -61,142 +59,153 @@ try { alert('Start of try runs'); // *!*(1) <--*/!* *!* - lalala; // 异常,变量未定义! + lalala; // error, variable is not defined! */!* alert('End of try (never reached)'); // (2) - } catch(err) { + } catch (err) { - alert(`Error has occured!`); // *!*(3) <--*/!* + alert(`Error has occurred!`); // *!*(3) <--*/!* } - - alert("...Then the execution continues"); ``` -````warn header="`try..catch` only works for runtime errors" -要使得 `try..catch` 能工作,代码必须是可执行的,换句话说,它必须是有效的 JavaScript 代码。 +````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. -如果代码包含语法错误,那么 `try..catch` 不能正常工作,例如含有未闭合的花括号: +It won't work if the code is syntactically wrong, for instance it has unmatched curly braces: ```js run try { {{{{{{{{{{{{ -} catch(e) { +} catch (err) { alert("The engine can't understand this code, it's invalid"); } ``` -JavaScript 引擎读取然后执行代码。发生在读取代码阶段的异常被称为 "parse-time" 异常,它们不会被 `try..catch` 覆盖到(包括那之间的代码)。这是因为引擎读不懂这段代码。 +The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. -所以,`try..catch` 只能处理有效代码之中的异常。这类异常被称为 "runtime errors",有时候也称为 "exceptions"。 +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" -如果一个异常是发生在计划中将要执行的代码中,例如在 `setTimeout` 中,那么 `try..catch` 不能捕捉到: +````warn header="`try...catch` works synchronously" +If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it: ```js run try { setTimeout(function() { - noSuchVariable; // 代码在这里停止执行 + noSuchVariable; // script will die here }, 1000); -} catch (e) { +} catch (err) { alert( "won't work" ); } ``` -因为 `try..catch` 包裹了计划要执行的 `setTimeout` 函数。但是函数本身要稍后才能执行,这时引擎已经离开了 `try..catch` 结构。 +That's because the function itself is executed later, when the engine has already left the `try...catch` construct. -要捕捉到计划中将要执行的函数中的异常,那么 `try..catch` 必须在这个函数之中: +To catch an exception inside a scheduled function, `try...catch` must be inside that function: ```js run setTimeout(function() { - try { - noSuchVariable; // try..catch 处理异常! - } catch (e) { + try { + noSuchVariable; // try...catch handles the error! + } catch { alert( "error is caught here!" ); } }, 1000); ``` ```` -## Error 对象 +## Error object -当一个异常发生之后,JavaScript 生成一个包含异常细节的对象。这个对象会作为一个参数传递给 `catch`: +When an error occurs, JavaScript generates an object containing the details about it. The object is then passed as an argument to `catch`: ```js try { // ... -} catch(err) { // <-- “异常对象”,可以用其他参数名代替 err +} catch (err) { // <-- the "error object", could use another word instead of err // ... } ``` -对于所有内置的异常,`catch` 代码块捕捉到的相应的异常的对象都有两个属性: +For all built-in errors, the error object has two main properties: `name` -: 异常名称,对于一个未定义的变量,名称是 `"ReferenceError"` +: Error name. For instance, for an undefined variable that's `"ReferenceError"`. `message` -: 异常详情的文字描述。 +: Textual message about error details. -还有很多非标准的属性在绝大多数环境中可用。其中使用最广泛并且被广泛支持的是: +There are other non-standard properties available in most environments. One of most widely used and supported is: `stack` -: 当前的调用栈:用于调试的,一个包含引发异常的嵌套调用序列的字符串。 +: Current call stack: a string with information about the sequence of nested calls that led to the error. Used for debugging purposes. -例如: +For instance: ```js run untrusted try { *!* - lalala; // 异常,变量未定义! + lalala; // error, variable is not defined! */!* -} catch(err) { +} catch (err) { alert(err.name); // ReferenceError - alert(err.message); // lalala 未定义 - alert(err.stack); // ReferenceError: lalala 在... 中未定义 + alert(err.message); // lalala is not defined + alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) - // 可以完整的显示一个异常 - // 可以转化成 "name: message" 形式的字符串 - alert(err); // ReferenceError: lalala 未定义 + // Can also show an error as a whole + // The error is converted to string as "name: message" + alert(err); // ReferenceError: lalala is not defined } ``` +## Optional "catch" binding + +[recent browser=new] + +If we don't need error details, `catch` may omit it: + +```js +try { + // ... +} catch { // <-- without (err) + // ... +} +``` -## 使用 "try..catch" +## Using "try...catch" -让我们一起探究一下真实使用场景中 `try..catch` 的使用。 +Let's explore a real-life use case of `try...catch`. -正如我们所知,JavaScript 支持 [JSON.parse(str)](mdn:js/JSON/parse) 方法来解析 JSON 编码的值。 +As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values. -通常,它被用来解析从网络,从服务器或是从其他来源收到的数据。 +Usually it's used to decode data received over the network, from the server or another source. -我们收到数据后,像下面这样调用 `JSON.parse`: +We receive it and call `JSON.parse` like this: ```js run -let json = '{"name":"John", "age": 30}'; // 来自服务器的数据 +let json = '{"name":"John", "age": 30}'; // data from the server *!* -let user = JSON.parse(json); // 将文本表示转化成 JS 对象 +let user = JSON.parse(json); // convert the text representation to JS object */!* -// 现在 user 是一个解析自 json 字符串的有自己属性的对象 +// now user is an object with properties from the string alert( user.name ); // John alert( user.age ); // 30 ``` -你可以在 这章找到更多的关于 JSON 的详细信息。 +You can find more detailed information about JSON in the chapter. -**如果 `json` 格式错误,`JSON.parse` 就会报错,代码就会停止执行。** +**If `json` is malformed, `JSON.parse` generates an error, so the script "dies".** -得到报错之后我们就应该满意了吗?当然不! +Should we be satisfied with that? Of course not! -如果这样做,当拿到的数据出错,用户就不会知道(除非他们打开开发者控制台)。代码执行失败却没有提示信息会导致糟糕的用户体验。 +This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message. -让我们来用 `try..catch` 来处理这个错误: +Let's use `try...catch` to handle the error: ```js run let json = "{ bad json }"; @@ -204,74 +213,74 @@ let json = "{ bad json }"; try { *!* - let user = JSON.parse(json); // <-- 当这里抛出异常... + let user = JSON.parse(json); // <-- when an error occurs... */!* - alert( user.name ); // 不工作 + alert( user.name ); // doesn't work -} catch (e) { +} catch (err) { *!* - // ...跳到这里继续执行 + // ...the execution jumps here alert( "Our apologies, the data has errors, we'll try to request it one more time." ); - alert( e.name ); - alert( e.message ); + alert( err.name ); + alert( err.message ); */!* } ``` -我们用 `catch` 代码块来展示信息,但是我们可以做的更多:发送一个新的网络请求,给用户提供另外的选择,把异常信息发送给记录日志的工具,... 。所有这些都比让代码直接停止执行好的多。 +Here we use the `catch` block only to show the message, but we can do much more: send a new network request, suggest an alternative to the visitor, send information about the error to a logging facility, ... . All much better than just dying. -## 抛出自定义的异常 +## Throwing our own errors -如果这个 `json` 数据语法正确,但是少了我们需要的 `name` 属性呢? +What if `json` is syntactically correct, but doesn't have a required `name` property? -像这样: +Like this: ```js run -let json = '{ "age": 30 }'; // 不完整的数据 +let json = '{ "age": 30 }'; // incomplete data try { - let user = JSON.parse(json); // <-- 不抛出异常 + let user = JSON.parse(json); // <-- no errors *!* - alert( user.name ); // 没有 name! + alert( user.name ); // no name! */!* -} catch (e) { +} catch (err) { alert( "doesn't execute" ); } ``` -这里 `JSON.parse` 正常执行,但是缺少 `name` 属性对我们来说确实是个异常。 +Here `JSON.parse` runs normally, but the absence of `name` is actually an error for us. -为了统一的异常处理,我们会使用 `throw` 运算符。 +To unify error handling, we'll use the `throw` operator. -### "Throw" 运算符 +### "Throw" operator -`throw` 运算符生成异常对象。 +The `throw` operator generates an error. -语法如下: +The syntax is: ```js throw ``` -技术上讲,我们可以使用任何东西来作为一个异常对象。甚至可以是基础类型,比如数字或者字符串。但是更好的方式是用对象,尤其是有 `name` 和 `message` 属性的对象(某种程度上和内置的异常有可比性)。 +Technically, we can use anything as an error object. That may be even a primitive, like a number or a string, but it's better to use objects, preferably with `name` and `message` properties (to stay somewhat compatible with built-in errors). -JavaScript 有很多标准异常的内置的构造器:`Error`、 `SyntaxError`、`ReferenceError`、`TypeError` 和其他的。我们也可以用他们来创建异常对象。 +JavaScript has many built-in constructors for standard errors: `Error`, `SyntaxError`, `ReferenceError`, `TypeError` and others. We can use them to create error objects as well. -他们的语法是: +Their syntax is: ```js let error = new Error(message); -// 或者 +// or let error = new SyntaxError(message); let error = new ReferenceError(message); // ... ``` -对于内置的异常对象(不是对于其他的对象,而是对于异常对象),`name` 属性刚好是构造器的名字。`message` 则来自于参数。 +For built-in errors (not for any objects, just for errors), the `name` property is exactly the name of the constructor. And `message` is taken from the argument. -例如: +For instance: ```js run let error = new Error("Things happen o_O"); @@ -280,31 +289,31 @@ alert(error.name); // Error alert(error.message); // Things happen o_O ``` -让我们来看看 `JSON.parse` 会生成什么样的错误: +Let's see what kind of error `JSON.parse` generates: ```js run try { JSON.parse("{ bad json o_O }"); -} catch(e) { +} catch (err) { *!* - alert(e.name); // SyntaxError + alert(err.name); // SyntaxError */!* - alert(e.message); // Unexpected token o in JSON at position 0 + alert(err.message); // Unexpected token b in JSON at position 2 } ``` -如我们所见, 那是一个 `SyntaxError`。 +As we can see, that's a `SyntaxError`. -假定用户必须有一个 `name` 属性,在我们看来,该属性的缺失也可以看作语法问题。 +And in our case, the absence of `name` is an error, as users must have a `name`. -所以,让我们抛出这个异常。 +So let's throw it: ```js run -let json = '{ "age": 30 }'; // 不完整的数据 +let json = '{ "age": 30 }'; // incomplete data try { - let user = JSON.parse(json); // <-- 没有异常 + let user = JSON.parse(json); // <-- no errors if (!user.name) { *!* @@ -314,64 +323,68 @@ try { alert( user.name ); -} catch(e) { - alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name +} catch (err) { + alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name } ``` -在 `(*)` 标记的这一行,`throw` 操作符生成了包含着我们所给的 `message` 的 `SyntaxError`,就如同 JavaScript 自己生成的一样。`try` 里面的代码执行停止,控制权转交到 `catch` 代码块。 +In the line `(*)`, the `throw` operator generates a `SyntaxError` with the given `message`, the same way as JavaScript would generate it itself. The execution of `try` immediately stops and the control flow jumps into `catch`. -现在 `catch` 代码块成为了处理包括 `JSON.parse` 在内和其他所有异常的地方。 +Now `catch` became a single place for all error handling: both for `JSON.parse` and other cases. -## 再次抛出异常 +## Rethrowing -上面的例子中,我们用 `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. -例如: +For example: ```js run -let json = '{ "age": 30 }'; // 不完整的数据 +let json = '{ "age": 30 }'; // incomplete data try { - user = JSON.parse(json); // <-- 忘了在 user 前加 "let" + user = JSON.parse(json); // <-- forgot to put "let" before user // ... -} catch(err) { +} catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined - // ( 实际上并没有 JSON Error) + // (no JSON Error actually) } ``` -当然,一切皆有可能。程序员也是会犯错的。即使是一些开源的被数百万人用了几十年的项目 —— 一个严重的 bug 因为他引发的严重的黑客事件被发现(比如发生在 `ssh` 工具上的黑客事件)。 +Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks. + +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. + +To avoid such problems, we can employ the "rethrowing" technique. The rule is simple: + +**Catch should only process errors that it knows and "rethrow" all others.** -对我们来说,`try..catch` 是用来捕捉“数据错误”的异常,但是 catch 本身会捕捉到**所有**来自于 `try` 的异常。这里,我们遇到了预料之外的错误,但是仍然抛出了 `"JSON Error"` 的信息,这是不正确的,同时也会让我们的代码变得更难调试。 +The "rethrowing" technique can be explained in more detail as: -幸运的是,我们可以通过其他方式找出这个异常,例如通过它的 `name` 属性: +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`. + +Usually, we can check the error type using the `instanceof` operator: ```js run try { user = { /*...*/ }; -} catch(e) { +} catch (err) { *!* - alert(e.name); // "ReferenceError" for accessing an undefined variable + if (err instanceof ReferenceError) { */!* + alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable + } } ``` -规则很简单: - -**`catch` 应该只捕获已知的异常,而重新抛出其他的异常。** +We can also get the error class name from `err.name` property. All native errors have it. Another option is to read `err.constructor.name`. -"rethrowing" 技术可以被更详细的理解为: - -1. 捕获全部异常。 -2. 在 `catch(err) {...}` 代码块,我们分析异常对象 `err`。 -3. 如果我们不知道如何处理它,那我们就做 `throw err` 操作。 - -在下面的代码中,为了达到只在 `catch` 代码块处理 `SyntaxError` 的目的,我们使用重新抛出的方法: +In the code below, we use rethrowing so that `catch` only handles `SyntaxError`: ```js run -let json = '{ "age": 30 }'; // 不完整的数据 +let json = '{ "age": 30 }'; // incomplete data try { let user = JSON.parse(json); @@ -381,29 +394,29 @@ try { } *!* - blabla(); // 预料之外的异常 + blabla(); // unexpected error */!* alert( user.name ); -} catch(e) { +} catch (err) { *!* - if (e.name == "SyntaxError") { - alert( "JSON Error: " + e.message ); + if (err instanceof SyntaxError) { + alert( "JSON Error: " + err.message ); } else { - throw e; // rethrow (*) + throw err; // rethrow (*) } */!* } ``` -`(*)` 标记的这行从 `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. -所以,`catch` 代码块只处理已知如何处理的异常,并且跳过其他的异常。 +So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others. -下面这段代码将演示,这种类型的异常如何被另外一层 `try..catch` 代码捕获。 +The example below demonstrates how such errors can be caught by one more level of `try...catch`: ```js run function readData() { @@ -412,13 +425,13 @@ function readData() { try { // ... *!* - blabla(); // 异常! + blabla(); // error! */!* - } catch (e) { + } catch (err) { // ... - if (e.name != 'SyntaxError') { + if (!(err instanceof SyntaxError)) { *!* - throw e; // 重新抛出(不知道如何处理它) + throw err; // rethrow (don't know how to deal with it) */!* } } @@ -426,63 +439,63 @@ function readData() { try { readData(); -} catch (e) { +} catch (err) { *!* - alert( "External catch got: " + e ); // 捕获到! + alert( "External catch got: " + err ); // caught it! */!* } ``` -例子中的 `readData` 只能处理 `SyntaxError`,而外层的 `try..catch` 能够处理所有的异常。 +Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything. -## try..catch..finally +## try...catch...finally -然而,这并不是全部。 +Wait, that's not all. -`try..catch` 还有另外的语法:`finally`。 +The `try...catch` construct may have one more code clause: `finally`. -如果它有被使用,那么,所有条件下都会执行: +If it exists, it runs in all cases: -- `try` 之后,如果没有异常。 -- `catch` 之后,如果没有异常。 +- after `try`, if there were no errors, +- after `catch`, if there were errors. -该扩展语法如下所示: +The extended syntax looks like this: ```js *!*try*/!* { - ... 尝试执行的代码 ... -} *!*catch*/!*(e) { - ... 异常处理 ... + ... try to execute the code ... +} *!*catch*/!* (err) { + ... handle errors ... } *!*finally*/!* { - ... 最终会执行的代码 ... + ... execute always ... } ``` -试试运行这段代码: +Try running this code: ```js run try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); -} catch (e) { +} catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); } ``` -这段代码有两种执行方式: +The code has two ways of execution: -1. 如果对于 "Make an error?" 你的回答是 "Yes",那么执行 `try -> catch -> finally`。 -2. 如果你的回答是 "No",那么执行 `try -> finally`。 +1. If you answer "Yes" to "Make an error?", then `try -> catch -> finally`. +2. If you say "No", then `try -> finally`. -`finally` 的语法通常用在:我们在 `try..catch` 之前开始一个操作,不管在该代码块中执行的结果怎样,我们都想结束的时候执行某个操作。 +The `finally` clause is often used when we start doing something and want to finalize it in any case of outcome. -比如,生成斐波那契数的函数 `fib(n)` 的执行时间,通常,我们在开始和结束的时候测量。但是,如果该函数在被调用的过程中发生异常,就如执行下面的代码就会返回负数或者非整数的异常。 +For instance, we want to measure the time that a Fibonacci numbers function `fib(n)` takes. Naturally, we can start measuring before it runs and finish afterwards. But what if there's an error during the function call? In particular, the implementation of `fib(n)` in the code below returns an error for negative or non-integer numbers. -任何情况下,`finally` 代码块就是一个很好的结束测量的地方。 +The `finally` clause is a great place to finish the measurements no matter what. -这里,不管前面的代码正确执行,或者抛出异常,`finally` 都保证了正确的时间测量。 +Here `finally` guarantees that the time will be measured correctly in both situations -- in case of a successful execution of `fib` and in case of an error in it: ```js run let num = +prompt("Enter a positive integer number?", 35) @@ -500,7 +513,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -508,26 +521,26 @@ try { } */!* -alert(result || "error occured"); +alert(result || "error occurred"); alert( `execution took ${diff}ms` ); ``` -你可以通过后面的不同的输入来检验上面代码的执行:先在 `prompt` 弹框中先输入 `35` —— 它会正常执行,`try` 代码执行后执行 `finally` 里面的代码。然后再输入 `-1`,会立即捕获一个异常,执行时间将会是 `0ms`。两次的测量结果都是正确的。 +You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, and the execution will take `0ms`. Both measurements are done correctly. -换句话说,有两种方式退出这个函数的执行:`return` 或是 `throw`,`finally` 语法都能处理。 +In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases. -```smart header="Variables are local inside `try..catch..finally`" -请注意:上面代码中的 `result` 和 `diff` 变量,都需要在 `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`. -否则,如果用 `let` 在 `{...}` 代码块里声明,那么只能在该代码块访问到。 +Otherwise, if we declared `let` in `try` block, it would only be visible inside of it. ``` ````smart header="`finally` and `return`" -`finally` 语法支持**任何**的结束 `try..catch` 执行的方式,包括明确的 `return`。 +The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`. -下面就是 `try` 代码块包含 `return` 的例子。在代码执行的控制权转移到外部代码之前,`finally` 代码块会被执行。 +In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code. ```js run function func() { @@ -537,7 +550,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -546,40 +559,40 @@ function func() { } } -alert( func() ); // 先 alert "finally" 里面的内容,再执行这里 +alert( func() ); // first works alert from finally, and then this one ``` ```` -````smart header="`try..finally`" +````smart header="`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. ```js function func() { - // 开始做需要被完成的操作(比如测量) + // start doing something that needs completion (like measurements) try { // ... } finally { - // 完成前面要做的事情,即使 try 里面执行失败 + // complete that thing even if all dies } } ``` -上面的代码中,由于没有 `catch`,`try` 代码块中的异常会跳出这块代码的执行。但是,在跳出之前 `finally` 里面的代码会被执行。 +In the code above, an error inside `try` always falls out, because there's no `catch`. But `finally` works before the execution flow leaves the function. ```` -## 全局 catch +## Global catch ```warn header="Environment-specific" -这个部分的内容并不是 JavaScript 核心的一部分。 +The information from this section is not a part of the core JavaScript. ``` -设想一下,`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. -那么,有没办法来应对这种情况呢?我们希望记录这个异常,给用户一些提示信息(通常,用户是看不到提示信息的),或者做一些其他操作。 +Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc. -虽然没有这方面的规范,但是代码的执行环境一般会提供这种机制,因为这真的很有用。例如,Node.JS 有 [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) 。对于浏览器环境,我们可以绑定一个函数到 [window.onerror](mdn:api/GlobalEventHandlers/onerror),当遇到未知异常的时候,它就会执行。 +There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to the special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property, that will run in case of an uncaught error. -语法如下: +The syntax: ```js window.onerror = function(message, url, line, col, error) { @@ -588,18 +601,18 @@ window.onerror = function(message, url, line, col, error) { ``` `message` -: 异常信息。 +: Error message. `url` -: 发生异常的代码的 URL。 +: URL of the script where error happened. `line`, `col` -: 错误发生的代码的行号和列号。 +: Line and column numbers where error happened. `error` -: 异常对象。 +: Error object. -例如: +For instance: ```html run untrusted refresh height=1 ``` -`window.onerror` 的目的不是去处理整个代码的执行中的所有异常 —— 这几乎是不可能的,这只是为了给开发者提供异常信息。 +The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. -也有针对这种情况提供异常日志的 web 服务,比如 或者 。 +There are also web-services that provide error-logging for such cases, like or . -它们会这样运行: +They work like this: -1. 我们注册这个服务,拿到一段 JS 代码(或者代码的 URL),然后插入到页面中。 -2. 这段 JS 代码会有一个客户端的 `window.onerror` 函数。 -3. 发生异常时,它会发送一个异常相关的网络请求到服务提供方。 -4. 我们只要登录服务方提供方的网络接口就可以看到这些异常。 +1. We register at the service and get a piece of JS (or a script URL) from them to insert on pages. +2. That JS script sets a custom `window.onerror` function. +3. When an error occurs, it sends a network request about it to the service. +4. We can log in to the service web interface and see errors. -## 总结 +## Summary -`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. -语法如下: +The syntax is: ```js try { - // 执行此处代码 -} catch(err) { - // 如果发生异常,跳到这里 - // err 是一个异常对象 + // run this code +} catch (err) { + // if an error happened, then jump here + // err is the error object } finally { - // 不管 try/catch 怎样都会执行 + // do in any case after try/catch } ``` -可能会没有 `catch` 代码块,或者没有 `finally` 代码块。所以 `try..catch` 或者 `try..finally` 都是可用的。 +There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid. + +Error objects have following properties: -异常对象包含下列属性: +- `message` -- the human-readable error message. +- `name` -- the string with error name (error constructor name). +- `stack` (non-standard, but well-supported) -- the stack at the moment of error creation. -- `message` —— 我们能阅读的异常提示信息。 -- `name` —— 异常名称(异常对象的构造函数的名称)。 -- `stack`(没有标准) —— 异常发生时的调用栈。 +If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`. -我们也可以通过使用 `throw` 运算符来生成自定义的异常。技术上来讲,`throw` 的参数没有限制,但是通常它是一个继承自内置的 `Error` 类的异常对象。更多关于异常的扩展,请看下个章节。 +We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter. -重新抛出异常,是一种异常处理的基本模式:`catch` 代码块通常处理某种已知的特定类型的异常,所以它应该抛出其他未知类型的异常。 +*Rethrowing* is a very important pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know. -即使我们没有使用 `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`. diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg index 1db85eec7a..2c0d71348c 100644 --- a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg +++ b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg @@ -1,58 +1 @@ - - - - try-catch-flow.svg - Created with sketchtool. - - - - - - Begin - - - - - - - - - - - - - - No Errors - - - - - - An error occured in the code - - - - - - - Ignore catch block - - - Ignore the rest of try - - - Execute catch block - - - try { - - - - } - - - // code... - - - - \ No newline at end of file +BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry { }// code... \ No newline at end of file diff --git a/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md index bb6b74cfaf..754e68f9a4 100644 --- a/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md +++ b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md @@ -2,7 +2,7 @@ class FormatError extends SyntaxError { constructor(message) { super(message); - this.name = "FormatError"; + this.name = this.constructor.name; } } diff --git a/1-js/10-error-handling/2-custom-errors/1-format-error/task.md b/1-js/10-error-handling/2-custom-errors/1-format-error/task.md index 36fd27a41b..2c8e910fc0 100644 --- a/1-js/10-error-handling/2-custom-errors/1-format-error/task.md +++ b/1-js/10-error-handling/2-custom-errors/1-format-error/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# 继承 SyntaxError +# Inherit from SyntaxError -创造一个继承自内置类 `SyntaxError` 的 `FormatError` 类。 +Create a class `FormatError` that inherits from the built-in `SyntaxError` class. -它应该支持 `message`,`name` 和 `stack` 属性。 +It should support `message`, `name` and `stack` properties. -用例: +Usage example: ```js let err = new FormatError("formatting error"); @@ -18,5 +18,5 @@ alert( err.name ); // FormatError alert( err.stack ); // stack alert( err instanceof FormatError ); // true -alert( err instanceof SyntaxError ); // true(因为它继承自 SyntaxError) +alert( err instanceof SyntaxError ); // true (because inherits from SyntaxError) ``` 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 e4e458f753..918289319d 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -1,46 +1,42 @@ -# 自定义错误及扩展错误 +# Custom errors, extending Error -当我们在进行开发的时候,通常需要属于我们自己的错误类来反映任务中可能出现的特殊情况。对于网络操作错误,我们需要 `HttpError`,对于数据库操作错误,我们需要 `DbError`,对于搜索操作错误,我们需要 `NotFoundError`,等等。 +When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks. For errors in network operations we may need `HttpError`, for database operations `DbError`, for searching operations `NotFoundError` and so on. -我们自定义的错误应该具有基本的错误属性,例如 `message`,`name` 以及更加详细的 `stack`。但是它们也会有属于自己的属性。举个例子,`HttpError` 对象会有一个 `statusCode` 属性,取值可能为 `404`、`403` 或 `500` 等。 +Our errors should support basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have a `statusCode` property with a value like `404` or `403` or `500`. -JavaScript 允许我们在使用 `throw` 时带任何参数,所以从技术层面上说,我们自定义的错误不需要继承 `Error` 类,但如果我们继承了这个类,就能使用 `obj instanceof Error` 来鉴别错误对象,所以我们最好继承它。 +JavaScript allows to use `throw` with any argument, so technically our custom error classes don't need to inherit from `Error`. But if we inherit, then it becomes possible to use `obj instanceof Error` to identify error objects. So it's better to inherit from it. -在我们进行开发时,我们自己的异常类通常是有层次结构的,例如 `HttpTimeoutError` 可能继承自 `HttpError` 等。 +As the application grows, our own errors naturally form a hierarchy. For instance, `HttpTimeoutError` may inherit from `HttpError`, and so on. -## 扩展错误 +## Extending Error -让我们用一个能够读取用户数据的函数 `readUser(json)` 来作为例子。 +As an example, let's consider a function `readUser(json)` that should read JSON with user data. -这里是一个可用的 `json` 的例子: +Here's an example of how a valid `json` may look: ```js let json = `{ "name": "John", "age": 30 }`; ``` -在这里面,我们使用 `JSON.parse`。如果它接收到错误的 `json`,就会抛出 `SyntaxError`。 +Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it throws `SyntaxError`. But even if `json` is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, it may not have `name` and `age` properties that are essential for our users. -但即使是格式正确的 `json`,也并不表示它就是可用的,对吧?它有可能会遗漏一些必要的数据。例如,缺失了对用户所必需的 `name` 和 `age` 属性。 +Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field. -函数 `readUser(json)` 不仅会读取 JSON,也会检查(验证)数据。如果没有所需要的字段,或者格式不正确,那也是错误。而这不是 `SyntaxError`,因为数据在语法上是正确的,但是有其他的错误。我们称之为 `ValidationError` 并且为之创建一个类。这种类型的错误也应该承载缺少的字段的信息。 +Our `ValidationError` class should inherit from the `Error` class. -我们的 `ValidationError` 类应该继承自内置的 `Error` 类。 - -`Error` 类是内置的,但是我们需要看一下大致的代码,来理解我们需要扩展什么。 - -代码如下: +The `Error` class is built-in, but here's its approximate code so we can understand what we're extending: ```js -// 由JavaScript本身定义的内置错误类“伪代码” +// The "pseudocode" for the built-in Error class defined by JavaScript itself class Error { constructor(message) { this.message = message; - this.name = "Error"; //(不同内置错误类别的名称) - this.stack = ; // 非标准,但大多数环境支持它 + this.name = "Error"; // (different names for different built-in error classes) + this.stack = ; // non-standard, but most environments support it } } ``` -现在让我们开始用 `ValidationError` 来进行继承: +Now let's inherit `ValidationError` from it and try it in action: ```js run untrusted *!* @@ -60,17 +56,16 @@ try { test(); } catch(err) { alert(err.message); // Whoops! - alert(err.name); // 验证错误 - alert(err.stack); // 每个行编号的嵌套调用列表 + alert(err.name); // ValidationError + alert(err.stack); // a list of nested calls with line numbers for each } ``` -来看看构造器: +Please note: in the line `(1)` we call the parent constructor. JavaScript requires us to call `super` in the child constructor, so that's obligatory. The parent constructor sets the `message` property. -1. 行 `(1)` 被称为父类构造器。JavaScript 需要我们在子类构造器中调用 `super`,这是强制性的。父类构造器设定 `message` 属性。 -2. 父类构造器也设定 `name` 的值为 `"Error"`,所以在行 `(2)` 我们将其重置为正确的值 +The parent constructor also sets the `name` property to `"Error"`, so in the line `(2)` we reset it to the right value. -让我们用 `readUser(json)` 来试试: +Let's try to use it in `readUser(json)`: ```js run class ValidationError extends Error { @@ -94,43 +89,43 @@ function readUser(json) { return user; } -// try..catch 实例 +// Working example with try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* - alert("Invalid data: " + err.message); // 无效的数据:缺失字段:name + alert("Invalid data: " + err.message); // Invalid data: No field: name */!* } else if (err instanceof SyntaxError) { // (*) alert("JSON Syntax Error: " + err.message); } else { - throw err; // 未知错误,再次抛出(**) + throw err; // unknown error, rethrow it (**) } } ``` -上面的 `try..catch` 代码块同时处理我们的 `ValidationError` 和来自 `JSON.parse` 的内置 `SyntaxError`。 +The `try..catch` block in the code above handles both our `ValidationError` and the built-in `SyntaxError` from `JSON.parse`. -接下来看看我们是如何使用 `instanceof` 来检测行 `(*)` 中的特定错误类型。 +Please take a look at how we use `instanceof` to check for the specific error type in the line `(*)`. -也看看 `err.name`,就像这样: +We could also look at `err.name`, like this: ```js // ... // instead of (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ... -``` +``` -使用 `instanceof` 的做法会好很多,因为我们在以后会扩展 `ValidationError`,创造一个它的子类型,例如 `PropertyRequiredError`。而 `instanceof` 对于新的继承类也适用。所以这是个长远的保证。 +The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof. -还有一点很重要,在 `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. -## 更进一步的继承 +## Further inheritance -`ValidationError` 类是十分通用的。因此可能会在某些方面出错。属性可能缺失,格式可能发生错误(例如 `age` 属性的值为一个字符串)。让我们来创造一个更加具体的类 `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. ```js run class ValidationError extends Error { @@ -164,32 +159,32 @@ function readUser(json) { return user; } -// try..catch 实例 +// Working example with try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* - alert("Invalid data: " + err.message); // 无效的数据:缺失属性:name + alert("Invalid data: " + err.message); // Invalid data: No property: name alert(err.name); // PropertyRequiredError alert(err.property); // name */!* } else if (err instanceof SyntaxError) { alert("JSON Syntax Error: " + err.message); } else { - throw err; // 未知错误,再次抛出 + throw err; // unknown error, rethrow it } } ``` -这个 `PropertyRequiredError` 十分容易上手:我们只需要传递属性名:`new PropertyRequiredError(property)`。易懂的 `message` 属性将会由构造器提供。 +The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor. -需要注意的是,在 `PropertyRequiredError` 构造器中的 `this.name` 是再次进行手动赋值的。这可能会造成冗余 —— 在创建每个自定义错误的时候都要进行赋值 `this.name = `。但这并不是唯一的办法。我们可以创建自己的“基础异常”类,通过将 `this.constructor.name` 赋值给 `this.name` 来卸下我们肩上的负担,然后再进行继承。 +Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all our custom errors from it. -我们称其为 `MyError`。 +Let's call it `MyError`. -这是 `MyError` 以及其他自定义错误类的代码: +Here's the code with `MyError` and other custom error classes, simplified: ```js run class MyError extends Error { @@ -214,19 +209,47 @@ class PropertyRequiredError extends ValidationError { alert( new PropertyRequiredError("field").name ); // PropertyRequiredError ``` -现在的自定义错误更加的简洁,特别是 `ValidationError`,我们在其构造器中删除了 `"this.name = ..."` 这一行。 +Now custom errors are much shorter, especially `ValidationError`, as we got rid of the `"this.name = ..."` line in the constructor. + +## Wrapping exceptions + +The purpose of the function `readUser` in the code above is "to read the user data". There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow and probably generate other kinds of errors. + +The code which calls `readUser` should handle these errors. Right now it uses multiple `if`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. + +The scheme is like this: + +```js +try { + ... + readUser() // the potential error source + ... +} catch (err) { + if (err instanceof ValidationError) { + // handle validation errors + } else if (err instanceof SyntaxError) { + // handle syntax errors + } else { + throw err; // unknown error, rethrow it + } +} +``` + +In the code above we can see two types of errors, but there can be more. -## 包装异常 +If the `readUser` function generates several kinds of errors, then we should ask ourselves: do we really want to check for all error types one-by-one every time? -上述代码中的函数 `readUser` 的目的就是“读取用户数据”,对吧?在此过程中可能会出现多个不同类型的异常,目前我们有 `SyntaxError` 和 `ValidationError`,但在将来,函数 `readUser` 将会不断壮大,新添加的代码或许会导致其他类型的异常。 +Often the answer is "No": we'd like to be "one level above all that". We just want to know if there was a "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, we'd like to have a way to get the error details, but only if we need to. -调用函数 `readUser` 的代码要能够处理这些异常。现在它在 `catch` 语句块中使用多个 `if` 语句来检测不同类型的异常以及抛出未知异常。但如果函数 `readUser` 抛出了多种异常 —— 我们扪心自问:我们真的需要一个接一个地处理它抛出的异常吗? +The technique that we describe here is called "wrapping exceptions". -通常答案是 “No”:外部代码想要比其他代码更高一级。它想要一些类似于“数据读取异常“的东西。它为什么发生 —— (其错误描述信息)通常是不相关的。或者,如果能有一种获取异常细节的办法就更好了,但这仅限于我们需要的时候。 +1. We'll make a new class `ReadError` to represent a generic "data reading" error. +2. The function `readUser` will catch data reading errors that occur inside it, such as `ValidationError` and `SyntaxError`, and generate a `ReadError` instead. +3. The `ReadError` object will keep the reference to the original error in its `cause` property. -所以,我们创建一个 `ReadError` 类来表现上述的异常。如果在函数 `readUser` 中发生了异常,我们会将其捕获,并生成 `ReadError`。我们同时也会在其 `cause` 属性中保留对原始异常的引用。那么外部的代码就只需要检测 `ReadError`。 +Then the code that calls `readUser` will only have to check for `ReadError`, not for every kind of data reading errors. And if it needs more details of an error, it can check its `cause` property. -下面的代码定义了 `ReadError` ,并演示了如何在 `readUser` 和 `try..catch` 中使用它: +Here's the code that defines `ReadError` and demonstrates its use in `readUser` and `try..catch`: ```js run class ReadError extends Error { @@ -285,7 +308,7 @@ try { if (e instanceof ReadError) { *!* alert(e); - // 原错误:语法错误:在位置 1 处不应有 b + // Original error: SyntaxError: Unexpected token b in JSON at position 1 alert("Original error: " + e.cause); */!* } else { @@ -294,14 +317,14 @@ try { } ``` -上述代码中,`readUser` 正如描述的一样正常工作 —— 捕获语法以及验证的异常并且抛出 `ReadError` 异常用来代替之前的行为(未知的异常依旧重新抛出)。 +In the code above, `readUser` works exactly as described -- catches syntax and validation errors and throws `ReadError` errors instead (unknown errors are rethrown as usual). -所以外部代码负责检测 `instanceof ReadError`,不必列出所有可能的异常类型。 +So the outer code checks `instanceof ReadError` and that's it. No need to list all possible error types. -这种途径称为“包装异常”,因为我们将“低级别的异常”包装为 `ReadError`,使得调用代码更加抽象和方便。它在面向对象编程中被广泛使用。 +The approach is called "wrapping exceptions", because we take "low level" exceptions and "wrap" them into `ReadError` that is more abstract. It is widely used in object-oriented programming. -## 总结 +## Summary -- 我们能够正常地继承 `Error` 以及其他内置的错误类,只需要注意 `name` 属性以及不要忘了调用 `super`。 -- 大多数时候,我们应该使用 `instanceof` 来检测一些特定的异常。它也能够在继承中使用。但有时我们会发现来自第三方库的异常,并且不容易得到它的类。那么 `name` 属性就可用于这一类的检测。 -- 包装异常是一种广泛应用的技术,当一个函数处理低级别的异常时,用一个高级别的对象来报告错误。低级别的异常有时会变成这个对象的属性,就像上面例子中的 `err.cause`,但这并不严格要求。 +- We can inherit from `Error` and other built-in error classes normally. We just need to take care of the `name` property and don't forget to call `super`. +- We can use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from a 3rd-party library and there's no easy way to get its class. Then `name` property can be used for such checks. +- Wrapping exceptions is a widespread technique: a function handles low-level exceptions and creates higher-level errors instead of various low-level ones. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required. diff --git a/1-js/10-error-handling/index.md b/1-js/10-error-handling/index.md index a27f18e2b9..face61c6e1 100644 --- a/1-js/10-error-handling/index.md +++ b/1-js/10-error-handling/index.md @@ -1 +1 @@ -# 错误处理 +# Error handling diff --git a/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md b/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md deleted file mode 100644 index f940d1c42b..0000000000 --- a/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md +++ /dev/null @@ -1,25 +0,0 @@ - -# 使用回调的圆形动画 - -在 任务中,显示了一个逐渐变大的圆形动画。 - -假设我们现在不只是需要一个圆形,还要在其中显示信息。信息应该在动画完整出现**之后**出现(圆形已经完全长大了),否则圆形会看起来很难看。 - -在此任务的解决方案中,`showCircle(cx, cy, radius)` 函数画了一个圆形,但却没有给出如何跟踪圆形是否已经准备好。 - -添加一个回调参数:当动画完成时,可以调用 `showCircle(cx, cy, radius, callback)`。`callback` 应该将圆形的 `

` 作为参数。 - -这是示例: - -```js -showCircle(150, 150, 100, div => { - div.classList.add('message-ball'); - div.append("Hello, world!"); -}); -``` - -案例: - -[iframe src="solution" height=260] - -以 任务作为解决问题的基础。 diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index 47f0a1604f..57115a9098 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -1,53 +1,68 @@ -# 简介:回调 +# Introduction: callbacks -JavaScipt 中的许多动作都是**异步**的。 +```warn header="We use browser methods in examples here" +To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations. -比如,这个 `loadScript(src)` 函数: +If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the [next part](/document) of the tutorial. + +Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise. +``` + +Many functions are provided by JavaScript host environments that allow you to schedule *asynchronous* actions. In other words, actions that we initiate now, but they finish later. + +For instance, one such function is the `setTimeout` function. + +There are other real-world examples of asynchronous actions, e.g. loading scripts and modules (we'll cover them in later chapters). + +Take a look at the function `loadScript(src)`, that loads a script with the given `src`: ```js function loadScript(src) { + // creates a ``` -在这里我们可以在浏览器里看到它,但是对于任何模块来说都是一样的。 +### Module-level scope -### 模块级作用域(Module-level scope) +Each module has its own top-level scope. In other words, top-level variables and functions from a module are not seen in other scripts. -每个模块都有自己的顶级作用域(top-level scope)。换句话说,一个模块中的顶级作用域变量和函数在其他脚本中是不可见的。 - -在下面的这个例子中,我们导入了两个脚本,`hello.js` 尝试使用从 `user.js` 中导入的 `user` 变量。 +In the example below, two scripts are imported, and `hello.js` tries to use `user` variable declared in `user.js`. It fails, because it's a separate module (you'll see the error in the console): [codetabs src="scopes" height="140" current="index.html"] -模块可以导出 `export` 想要从外部访问的内容,也可以导入 `import` 想要的内容。 +Modules should `export` what they want to be accessible from outside and `import` what they need. + +- `user.js` should export the `user` variable. +- `hello.js` should import it from `user.js` module. -所以,我们应该在 `hello.js` 中直接导入 `user.js`,而不是在 `index.html` 中导入。 +In other words, with modules we use import/export instead of relying on global variables. -这是正确导入的方法: +This is the correct variant: [codetabs src="scopes-working" height="140" current="hello.js"] -在浏览器中,每个 ` @@ -103,15 +113,23 @@ sayHi('John'); // Hello, John! ``` -如果我们真的需要创建一个窗口级别(window-level)的全局变量,我们可以显式地将它分配给 `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. +``` -这具有很重要的后果。我们来看一下下面的例子: +### A module code is evaluated only the first time when imported -首先,如果执行一个模块中的代码带来一些副作用,比如显示一个消息,然后多次导入它但是只会显示一次,即第一次: +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. + +First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time: ```js // 📁 alert.js @@ -119,20 +137,22 @@ alert("Module is evaluated!"); ``` ```js -// 从不同的文件导入相同模块 +// Import the same module from different files // 📁 1.js import `./alert.js`; // Module is evaluated! // 📁 2.js -import `./alert.js`; // (nothing) +import `./alert.js`; // (shows nothing) ``` -在日常开发中,顶级模块主要是用于初始化使用的。我们创建数据结构,预填充它们,如果我们想要可重用某些东西,只要导出即可。 +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. -我们假设一个模块导出了一个对象: +Let's say, a module exports an object: ```js // 📁 admin.js @@ -141,9 +161,9 @@ export let admin = { }; ``` -如果这个模块被导入到多个文件中,模块仅仅在第一次导入的时候解析创建 `admin` 对象。然后将其传入所有导入的位置。 +If this module is imported from multiple files, the module is only evaluated the first time, `admin` object is created, and then passed to all further importers. -所有导入位置都得到了唯一的 `admin` 对象。 +All importers get exactly the one and only `admin` object: ```js // 📁 1.js @@ -155,60 +175,77 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* -// 1.js 和 2.js 导入相同的对象 -// 1.js 中对对象的修改,在 2.js 中是可访问的 +// Both 1.js and 2.js reference the same admin object +// Changes made in 1.js are visible in 2.js */!* ``` -所以,让我们重申一下:模块只执行一次。生成导出,然后在导入的位置共享同一个导出,当在某个位置修改了 `admin` 对象,在其他模块中是可以看到修改的。 +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. -例如,下面的 `admin.js` 模块可能提供特定的功能,但是希望在外部可访问 `admin` 对象: +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: ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` -现在,在 `init.js`——我们 app 的第一个脚本中,设置了 `admin.name`。现在每个位置都能看到它了,包括来自 `admin.js` 本身的调用。 +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 {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +...Now the module `admin.js` is configured. -alert(admin.name); // *!*Pete*/!* +Further importers can call it, and it correctly shows the current user: + +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta -`import.meta` 对象包含当前模块的一些信息。 +The object `import.meta` contains the information about the current module. -它的内容取决于其所在环境,比如说在浏览器环境中,它包含脚本的链接,如果是在 HTML 中的话就是当前页面的链接。 +Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML: ```html run height=0 ``` -### 顶级 "this" 是 未定义(undefined)的 +### In a module, "this" is undefined + +That's kind of a minor feature, but for completeness we should mention it. -这是一个小功能,但为了完整性,我们应该提到它。 +In a module, top-level `this` is undefined. -在一个模块中,顶级 `this` 是未定义的,而不是像非模块脚本中的全局变量。 +Compare it to non-module scripts, where `this` is a global object: ```html run height=0 ``` -## 特定于浏览器的功能 +## Browser-specific features -与常规脚本相比,拥有 `type="module"` 标识的脚本有几个特定于浏览器的差异。 +There are also several browser-specific differences of scripts with `type="module"` compared to regular ones. -如果你是第一次阅读或者你不在浏览器中使用 JavaScript,你可能需要暂时略过这些内容。 +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. -### 模块脚本是延迟解析的 +### Module scripts are deferred -对于外部和内联模块脚本来说,它 **总是** 延迟解析的,就和 `defer` 属性一样(参见 [script-async-defer](info:script-async-defer))。 +Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:script-async-defer)), for both external and inline scripts. -也就是说: - - 外部模块脚本 ` -相较于普通脚本: +Compare to regular script below: ``` -注意:上面的第二个脚本要先于前一个脚本执行,所以我们先会看到 `undefined`,然后才是 `object`。 +Please note: the second script actually runs before the first! So we'll see `undefined` first, and then `object`. -这是因为模块脚本被延迟执行了,所以要等到页面加载结束才执行。而普通脚本就没有这个限制了,它会马上执行,所以我们先看到它的输出。 +That's because modules are deferred, so we wait for the document to be processed. The regular script runs immediately, so we see its output first. -当使用模块脚本的时候,我们应该知道当 HTML 页面加载完毕的时候会显示出来,然后 JavaScript 在其后开始执行,所以用户会先于 JavaScript 脚本加载完成是看到页面内容。某些依赖于 JavaScript 的功能可能还不能正常工作。我们应该使用透明层或者 “加载指示”,或者其他方法以确保用户不会感到莫名其妙。 +When using modules, we should be aware that the HTML page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that. -### 内联脚本是异步的 +### Async works on inline scripts -内联脚本和外部脚本都允许使用 ` ``` -### 外部脚本 +### External scripts -外部脚本相较于其他脚本有两个显著的差异: +External scripts that have `type="module"` are different in two aspects: -1. 具有相同 `src` 属性值的外部脚本仅运行一次: +1. External scripts with the same `src` run only once: ```html - + ``` -2. 从其他域名获取的外部脚本需要加上 [CORS](mdn:Web/HTTP/CORS) 头。换句话说,如果一个模块脚本是从其他域名获取的,那么它所在的远端服务器必须提供 `Access-Control-Allow-Origin: *`(可能使用加载的域名代替 `*`)响应头以指明当前请求是被允许的。 +2. External scripts that are fetched from another origin (e.g. another site) require [CORS](mdn:Web/HTTP/CORS) headers, as described in the chapter . In other words, if a module script is fetched from another origin, the remote server must supply a header `Access-Control-Allow-Origin` allowing the fetch. ```html - - + + ``` - 这可以保证最基本的安全问题。 + That ensures better security by default. -### 不允许裸模块("bare" modules) +### No "bare" modules allowed -在浏览器中,必须给与 `import` 一个相对或者绝对的 URL。没有给定路径的模块被称作“裸”模块。`import` 中不允许使用这些模块。 +In the browser, `import` must get either a relative or absolute URL. Modules without any path are called "bare" modules. Such modules are not allowed in `import`. -例如,下面这个 `import` 是不允许的: +For instance, this `import` is invalid: ```js -import {sayHi} from 'sayHi'; // Error,“裸”模块 -// 模块必须提供路径,例如 './sayHi.js' +import {sayHi} from 'sayHi'; // Error, "bare" module +// the module must have a path, e.g. './sayHi.js' or wherever the module is ``` -在具体环境有所不同,比如 Node.js 或者打包工具中是可以使用裸模块的,因为它们有自己的查找模块和钩子的方法。但是目前浏览器还不支持裸模块。 +Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have their own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet. -### 兼容性,"nomodule" +### Compatibility, "nomodule" -旧时的浏览器不理解 `type="module"` 值。对于位置类型的脚本会被忽略掉。对于它们来说可以使用 `nomodule` 属性来提供后备: +Old browsers do not understand `type="module"`. Scripts of an unknown type are just ignored. For them, it's possible to provide a fallback using the `nomodule` attribute: ```html run ``` -如果我们使用打包工具,当脚本被打包进一个单一文件(或者几个文件),在这些脚本中,`import/export` 语句被特殊的打包函数处理后替代。因此最终打包好的脚本不包含任何 `import/export` 语句,它也不需要 `type="module"` 属性,我们仅像普通脚本一样使用就好了: +## Build tools -```html - - -``` +In real-life, browser modules are rarely used in their "raw" form. Usually, we bundle them together with a special tool such as [Webpack](https://webpack.js.org/) and deploy to the production server. -## 构建工具 +One of the benefits of using bundlers -- they give more control over how modules are resolved, allowing bare modules and much more, like CSS/HTML modules. -在日常开发中,浏览器模块很少以原始形式使用,通常,我们用一些特殊工具,像 [Webpack](https://webpack.js.org/),将他们打包在一起,然后部署到服务器。 +Build tools do the following: -使用打包工具的一个好处是——它们对于如何解析模块给与了足够多的控制,比如允许使用裸模块,以及 CSS/HTML 模块等等。 +1. Take a "main" module, the one intended to be put in ` +``` -也就是说,原生模块也是可以使用的。所以我们在这里不会使用 Webpack,你可以稍后再配置它。 +That said, native modules are also usable. So we won't be using Webpack here: you can configure it later. -## 总结 +## Summary -下面总结一下模块的核心概念: +To summarize, the core concepts are: -1. 模块就是文件。浏览器需要使用 ` ```online -在上面的图片中,你可以点击元素节点,他们的子节点会打开/折叠。 +On the picture above, you can click on element nodes and their children will open/collapse. ``` -标签被称为**元素节点**(或者仅仅是元素)。嵌套标签称为闭合标签的子标签。因此我们有这样一个元素树:`` 在根目录下,然后 `` 和 `` 是它的子项,等等。 +Every tree node is an object. + +Tags are *element nodes* (or just elements) and form the tree structure: `` is at the root, then `` and `` are its children, etc. -元素内的文本形成**文本节点**,标记为 `#text`。文本节点只包含一个字符串。它没有子项,永远是树的一片叶子。 +The text inside elements forms *text nodes*, labelled as `#text`. A text node contains only a string. It may not have children and is always a leaf of the tree. -例如,`` 标签里面有文本 `"About elks"`。 +For instance, the `<title>` tag has the text `"About elk"`. -请注意文本节点中的特殊字符: +Please note the special characters in text nodes: -- 换行符:`↵`(在 JavaScript 中称为`\n`) -- 一个空格:`␣` +- a newline: `↵` (in JavaScript known as `\n`) +- a space: `␣` -空格和换行符是完全有效的字符,它们形成文本节点并成为 DOM 的一部分。因此,在上面的例子中,`<head>` 标签在 `<title>` 之前包含了一些空格,并且该文本变成了一个 `#text` 节点(它只包含换行符和一些空格)。 +Spaces and newlines are totally valid characters, like letters and digits. They form text nodes and become a part of the DOM. So, for instance, in the example above the `<head>` tag contains some spaces before `<title>`, and that text becomes a `#text` node (it contains a newline and some spaces only). -只有两个顶级排除项目: -1. 由于历史原因,`<head>` 之前的空格和换行符被忽略, -2. 如果我们在 `</body>` 之后放置了一些东西,那么它会自动移动到 `body` 内部,因为 HTML 规范要求所有内容必须位于 `<body>` 内。所以 `</body>` 后面可能没有空格。 +There are only two top-level exclusions: +1. Spaces and newlines before `<head>` are ignored for historical reasons. +2. If we put something after `</body>`, then that is automatically moved inside the `body`, at the end, as the HTML spec requires that all content must be inside `<body>`. So there can't be any spaces after `</body>`. -在其他情况下,一切都很简单 —— 如果文档中有空格(就像任何字符一样),那么它们将成为 DOM 中的文本节点,如果我们删除它们,则不会有任何内容。 +In other cases everything's straightforward -- if there are spaces (just like any character) in the document, then they become text nodes in the DOM, and if we remove them, then there won't be any. -这里是没有空格的文本节点: +Here are no space-only text nodes: ```html no-beautify <!DOCTYPE HTML> -<html><head><title>About elksThe truth about elks. +About elkThe truth about elk. ```
-```smart header="Edge spaces and in-between empty text are usually hidden in tools" -与 DOM 协同工作的浏览器工具(不久将会覆盖)通常不会在文本的开始/结尾处显示空格,并且在标记之间不会显示空文本节点(换行符)。 +```smart header="Spaces at string start/end and space-only text nodes are usually hidden in tools" +Browser tools (to be covered soon) that work with DOM usually do not show spaces at the start/end of the text and empty text nodes (line-breaks) between tags. -这是因为它们主要用于装饰 HTML,并且不会影响其显示方式(在大多数情况下)。 +Developer tools save screen space this way. -在进一步的 DOM 图片上,我们有时候会忽略它们,因为它们是无关紧要的,所以要保持简短。 +On further DOM pictures we'll sometimes omit them when they are irrelevant. Such spaces usually do not affect how the document is displayed. ``` +## Autocorrection -## 自动修正 +If the browser encounters malformed HTML, it automatically corrects it when making the DOM. -如果浏览器遇到格式不正确的 HTML,它会在形成 DOM 时自动修正它。 +For instance, the top tag is always ``. Even if it doesn't exist in the document, it will exist in the DOM, because the browser will create it. The same goes for ``. -例如,顶部标签总是 ``。即使它不在于文档中 —— 它将在 DOM 中出现,因为浏览器会创建它。`` 也是一样。 - -例如,如果 HTML 文件是单个单词“Hello”,浏览器将把它包装到 `` 和 `` 中,添加所需的 ``,DOM 将会变成: +As an example, if the HTML file is the single word `"Hello"`, the browser will wrap it into `` and ``, and add the required ``, and the DOM will be:
@@ -104,9 +121,9 @@ let node3 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1, drawHtmlTree(node3, 'div.domtree', 690, 150); -在生成 DOM 时,浏览器会自动处理文档中的错误,关闭标签等等。 +While generating the DOM, browsers automatically process errors in the document, close tags and so on. -这样的“无效”文档: +A document with unclosed tags: ```html no-beautify

Hello @@ -115,7 +132,7 @@ drawHtmlTree(node3, 'div.domtree', 690, 150);

  • Dad ``` -...将成为一个正常的 DOM,因为浏览器会读取标签并恢复丢失的部分: +...will become a normal DOM as the browser reads tags and restores the missing parts:
    @@ -126,15 +143,15 @@ drawHtmlTree(node4, 'div.domtree', 690, 360); ````warn header="Tables always have ``" -表格是一个有趣的“特例”。按照 DOM 规范,它们必须具有 ``,但 HTML 文本可能(官方的)忽略它。然后浏览器自动在 DOM 中创建 ``。 +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. -对于 HTML: +For the HTML: ```html no-beautify
    1
    ``` -DOM 结构会变成: +DOM-structure will be:
    -看到了吗?`` 出现了。在使用表格时,应该牢记这一点以避免意外。 +You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. +```` +## Other node types -## 其他节点类型 +There are some other node types besides elements and text nodes. -让我们在页面中添加更多标签和注释: +For example, comments: ```html - The truth about elks. + The truth about elk.
    1. An elk is a smart
    2. *!* @@ -169,93 +188,95 @@ drawHtmlTree(node5, 'div.domtree', 600, 200);
      -在这里我们看到一个新的树节点类型 —— *comment node*,标记为 `#comment`。 +We can see here a new tree node type -- *comment node*, labeled as `#comment`, between two text nodes. + +We may think -- why is a comment added to the DOM? It doesn't affect the visual representation in any way. But there's a rule -- if something's in HTML, then it also must be in the DOM tree. -我们可能会想 —— 为什么要将注释添加到 DOM 中?它不会以任何方式影响视觉表示。但是有一条规则 —— 如果 HTML 中有东西,那么它也必须在 DOM 树中。 +**Everything in HTML, even comments, becomes a part of the DOM.** -**HTML 中的所有内容甚至注释都成为 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. -甚至 HTML 开头的 `` 指令也是一个 DOM 节点。它在 `` 之前的 DOM 树中。我们不会触及那个节点,我们甚至不会因为那个原因在图表上绘制它,但它就在那里。 +The `document` object that represents the whole document is, formally, a DOM node as well. -表示整个文档的 `document` 对象在形式上也是一个 DOM 节点。 +There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usually work with 4 of them: -有 [12 种节点类型](https://dom.spec.whatwg.org/#node)。实际上,我们通常用到的是其中的 4 个: +1. `document` -- the "entry point" into DOM. +2. element nodes -- HTML-tags, the tree building blocks. +3. text nodes -- contain text. +4. comments -- sometimes we can put information there, it won't be shown, but JS can read it from the DOM. -1. `document`—— DOM 中的“入口点”。 -2. 元素节点 —— HTML 标签,树构建块。 -3. 文本节点 —— 包含文本。 -4. 注释 —— 有时我们可以将内容放入其中,它不会显示,但 JS 可以从 DOM 中读取它。 +## See it for yourself -## 自行查看 +To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. -要实时查看 DOM 结构,请尝试 [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/)。只需输入文档,它就会立即显示 DOM。 +Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. -## 在浏览器中检查 +To do so, open the web page [elk.html](elk.html), turn on the browser developer tools and switch to the Elements tab. -研究 DOM 的另一种方式是使用浏览器开发工具。事实上,这正是我们开发时所使用的工具。 +It should look like this: -请打开网页 [elks.html](elks.html),打开浏览器开发工具并切换到元素选项卡。 +![](elk.svg) -它是这样的: +You can see the DOM, click on elements, see their details and so on. -![](elks.png) +Please note that the DOM structure in developer tools is simplified. Text nodes are shown just as text. And there are no "blank" (space only) text nodes at all. That's fine, because most of the time we are interested in element nodes. -你可以看到 DOM,点击元素,查看其中的细节等等。 +Clicking the button in the left-upper corner allows us to choose a node from the webpage using a mouse (or other pointer devices) and "inspect" it (scroll to it in the Elements tab). This works great when we have a huge HTML page (and corresponding huge DOM) and would like to see the place of a particular element in it. -请注意,开发者工具中的 DOM 结构已经过简化。文本节点仅以文本形式显。根本没有“空白”(只有空格)的文本节点。这其实很好,因为大部分时间我们都对元素节点感兴趣。 +Another way to do it would be just right-clicking on a webpage and selecting "Inspect" in the context menu. -点击左上角的 按钮可以使用鼠标(或其他指针设备)从网页中选择一个节点并“检查”它(在“元素”选项卡中滚动到该节点)。当我们有一个巨大的 HTML 页面(和相应的巨大 DOM),并希望看到其中的一个特定元素的位置时,这很有用。 +![](inspect.svg) -另一种方法是在网页上右键单击并在上下文菜单中选择“检查”。 +At the right part of the tools there are the following subtabs: +- **Styles** -- we can see CSS applied to the current element rule by rule, including built-in rules (gray). Almost everything can be edited in-place, including the dimensions/margins/paddings of the box below. +- **Computed** -- to see CSS applied to the element by property: for each property we can see a rule that gives it (including CSS inheritance and such). +- **Event Listeners** -- to see event listeners attached to DOM elements (we'll cover them in the next part of the tutorial). +- ...and so on. -![](inspect.png) +The best way to study them is to click around. Most values are editable in-place. -在工具的右侧部分有以下子表单: -- **Styles** —— 我们可以看到应用于当前元素的 CSS 中的一条条规则,包括内置规则(灰色)。几乎所有东西都可以在原地进行编辑,包括下面框中的尺寸/边距/填充。 -- **Computed** —— 按属性查看应用于元素的 CSS:对于每个属性,我们可以看到一条规则(包括 CSS 继承等)。 -- **Event Listeners** —— 查看附加到 DOM 元素的事件侦听器(我们将在本教程的下一部分介绍它们)。 -- ...等等。 +## Interaction with console -研究它们的最佳方式就是多多点击。大多数值都是可以原地编辑的。 +As we work the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see the result. Here are few tips to travel between the Elements tab and the console. -## 与控制台交互 +For the start: -在我们研究 DOM 时,我们也可能想要使用 JavaScript。就比如:获取一个节点并运行一些代码来修改它,看看它长什么样。这里有一些在元素选项卡和控制台之间传输数据的提示。 +1. Select the first `
    3. ` in the Elements tab. +2. Press `key:Esc` -- it will open console right below the Elements tab. -- 在元素标签中选择第一个 `
    4. `。 -- 按下 `key:Esc` —— 它将在元素标签下方打开控制台。 +Now the last selected element is available as `$0`, the previously selected is `$1` etc. -现在最后选中的元素可以用 `$0` 来进行操作,以前选择的是 `$1`,如此等等。 +We can run commands on them. For instance, `$0.style.background = 'red'` makes the selected list item red, like this: -我们可以在它们之上运行命令。例如,`$0.style.background = 'red'` 使选定的列表项变成红色,如下所示: +![](domconsole0.svg) -![](domconsole0.png) +That's how to get a node from Elements in Console. -另一方面,如果我们处在控制台中,并且有一个引用 DOM 节点的变量,那么我们可以使用命令 `inspect(node)` 在元素窗格中查看它。 +There's also a road back. If there's a variable referencing a DOM node, then we can use the command `inspect(node)` in Console to see it in the Elements pane. -或者我们可以在控制台中输出它并“就地”测试它,如下面的 `document.body`: +Or we can just output the DOM node in the console and explore "in-place", like `document.body` below: -![](domconsole1.png) +![](domconsole1.svg) -这当然是用于调试的目的。从下一章我们将使用 JavaScript 访问和修改 DOM。 +That's for debugging purposes of course. From the next chapter on we'll access and modify DOM using JavaScript. -浏览器开发者工具对开发有很大的帮助:我们可以研究 DOM,做一些测试并查看出了什么问题。 +The browser developer tools are a great help in development: we can explore the DOM, try things and see what goes wrong. -## 总结 +## Summary -HTML/XML 文档在浏览器内表示为 DOM 树。 +An HTML/XML document is represented inside the browser as the DOM tree. -- 标签成为元素节点并形成文档结构。 -- 文本成为文本节点。 -- ...如此等等,HTML 中的所有东西在 DOM 中都有它的位置,甚至是注释。 +- Tags become element nodes and form the structure. +- Text becomes text nodes. +- ...etc, everything in HTML has its place in DOM, even comments. -我们可以使用开发者工具来检查 DOM 并手动修改它。 +We can use developer tools to inspect DOM and modify it manually. -在这里,我们介绍了基本知识,入门最常用和最重要的操作。在 上有大量有关 Chrome 开发者工具的文档。学习这些工具的最佳方式是四处点击,阅读菜单:大多数选项都很明显。而后,当你差不多了解它们时,阅读文档并学习其余的部分。 +Here we covered the basics, the most used and important actions to start with. There's an extensive documentation about Chrome Developer Tools at . The best way to learn the tools is to click here and there, read menus: most options are obvious. Later, when you know them in general, read the docs and pick up the rest. -DOM 节点具有在它们之间传递数据,修改、移动页面等功能的属性和方法。我们将在接下来的章节中讨论他们。 +DOM nodes have properties and methods that allow us to travel between them, modify them, move around the page, and more. We'll get down to them in the next chapters. diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.png b/2-ui/1-document/02-dom-nodes/domconsole0.png deleted file mode 100644 index 121c11d75a..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole0.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg new file mode 100644 index 0000000000..eb99f193fe --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole0@2x.png b/2-ui/1-document/02-dom-nodes/domconsole0@2x.png deleted file mode 100644 index a8953395c5..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole0@2x.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.png b/2-ui/1-document/02-dom-nodes/domconsole1.png deleted file mode 100644 index c04f015cf5..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole1.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg new file mode 100644 index 0000000000..02ef5f0a65 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole1@2x.png b/2-ui/1-document/02-dom-nodes/domconsole1@2x.png deleted file mode 100644 index ce0fa0fffa..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole1@2x.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/elks.html b/2-ui/1-document/02-dom-nodes/elk.html similarity index 86% rename from 2-ui/1-document/02-dom-nodes/elks.html rename to 2-ui/1-document/02-dom-nodes/elk.html index 7d29f3d4e3..dc5d65f541 100644 --- a/2-ui/1-document/02-dom-nodes/elks.html +++ b/2-ui/1-document/02-dom-nodes/elk.html @@ -1,7 +1,7 @@ - The truth about elks. + The truth about elk.
      1. An elk is a smart
      2. diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg new file mode 100644 index 0000000000..448eea9d13 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/elk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/elks.png b/2-ui/1-document/02-dom-nodes/elks.png deleted file mode 100644 index 03177c40e8..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/elks.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/elks@2x.png b/2-ui/1-document/02-dom-nodes/elks@2x.png deleted file mode 100644 index e8a15bd5b9..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/elks@2x.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/inspect.png b/2-ui/1-document/02-dom-nodes/inspect.png deleted file mode 100644 index 075cf93080..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/inspect.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg new file mode 100644 index 0000000000..60696ec0d5 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/inspect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/inspect@2x.png b/2-ui/1-document/02-dom-nodes/inspect@2x.png deleted file mode 100644 index 8743dd297d..0000000000 Binary files a/2-ui/1-document/02-dom-nodes/inspect@2x.png and /dev/null differ diff --git a/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md b/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md index 3e3922c3cd..decfa62c7d 100644 --- a/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md +++ b/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md @@ -1,27 +1,27 @@ -这里有很多方法,比方说: +There are many ways, for instance: -获取 `
        ` DOM 节点: +The `
        ` DOM node: ```js document.body.firstElementChild -// 或者 +// or document.body.children[0] -// 或者(第一个节点是空格,所有我们拿第二个) +// or (the first node is space, so we take 2nd) document.body.childNodes[1] ``` -获取 `